diff --git a/api/src/certification/enrolment/application/api/candidates-api.js b/api/src/certification/enrolment/application/api/candidates-api.js new file mode 100644 index 00000000000..c5c5dbe850b --- /dev/null +++ b/api/src/certification/enrolment/application/api/candidates-api.js @@ -0,0 +1,17 @@ +import { usecases } from '../../../enrolment/domain/usecases/index.js'; + +/** + * Checks if a user has been candidate to a certification + * + * @function + * @param {Object} params + * @param {number} params.userId user id to search for candidates + * @returns {Promise} + * @throws {TypeError} preconditions failed + */ +export const hasBeenCandidate = async ({ userId }) => { + if (!userId) { + throw new TypeError('user identifier is required'); + } + return usecases.hasBeenCandidate({ userId }); +}; diff --git a/api/src/certification/enrolment/domain/usecases/has-been-candidate.js b/api/src/certification/enrolment/domain/usecases/has-been-candidate.js new file mode 100644 index 00000000000..23f08af82bd --- /dev/null +++ b/api/src/certification/enrolment/domain/usecases/has-been-candidate.js @@ -0,0 +1,13 @@ +/** + * @typedef {import('./index.js').CandidateRepository} CandidateRepository + */ + +/** + * @param {Object} params + * @param {number} params.userId + * @param {CandidateRepository} params.candidateRepository + */ +export async function hasBeenCandidate({ userId, candidateRepository }) { + const candidates = await candidateRepository.findByUserId({ userId }); + return candidates.some((candidate) => candidate.isReconciled()); +} diff --git a/api/src/certification/enrolment/infrastructure/repositories/candidate-repository.js b/api/src/certification/enrolment/infrastructure/repositories/candidate-repository.js index d5e1bb6dc21..50a1633bdb2 100644 --- a/api/src/certification/enrolment/infrastructure/repositories/candidate-repository.js +++ b/api/src/certification/enrolment/infrastructure/repositories/candidate-repository.js @@ -35,6 +35,19 @@ export async function findBySessionId({ sessionId }) { return candidatesData.map(toDomain); } +/** + * @function + * @param {Object} params + * @param {number} params.userId + * + * @return {Promise>} + */ +export async function findByUserId({ userId }) { + const knexTransaction = DomainTransaction.getConnection(); + const candidatesData = await buildBaseReadQuery(knexTransaction).where({ 'certification-candidates.userId': userId }); + return candidatesData.map(toDomain); +} + /** * @function * @param {Object} candidate diff --git a/api/tests/certification/enrolment/integration/infrastructure/repositories/candidate-repository_test.js b/api/tests/certification/enrolment/integration/infrastructure/repositories/candidate-repository_test.js index ecd8489984b..19138906ffd 100644 --- a/api/tests/certification/enrolment/integration/infrastructure/repositories/candidate-repository_test.js +++ b/api/tests/certification/enrolment/integration/infrastructure/repositories/candidate-repository_test.js @@ -4,7 +4,7 @@ import * as candidateRepository from '../../../../../../src/certification/enrolm import { SUBSCRIPTION_TYPES } from '../../../../../../src/certification/shared/domain/constants.js'; import { catchErr, databaseBuilder, domainBuilder, expect, knex } from '../../../../../test-helper.js'; -describe('Integration | Certification | Session | Repository | Candidate', function () { +describe('Integration | Certification | Enrolment | Repository | Candidate', function () { describe('#get', function () { context('when the candidate exists', function () { it('should return the candidate', async function () { @@ -128,6 +128,49 @@ describe('Integration | Certification | Session | Repository | Candidate', funct }); }); + describe('#findByUserId', function () { + context('when there are candidates', function () { + it('should return the candidates', async function () { + // given + const candidate1 = databaseBuilder.factory.buildCertificationCandidate(); + databaseBuilder.factory.buildCoreSubscription({ certificationCandidateId: candidate1.id }); + const userId = candidate1.userId; + + const candidate2 = databaseBuilder.factory.buildCertificationCandidate({ userId }); + databaseBuilder.factory.buildCoreSubscription({ certificationCandidateId: candidate2.id }); + + const candidate3 = databaseBuilder.factory.buildCertificationCandidate(); + databaseBuilder.factory.buildCoreSubscription({ certificationCandidateId: candidate3.id }); + await databaseBuilder.commit(); + + // when + const result = await candidateRepository.findByUserId({ userId }); + + // then + expect(result).to.deepEqualArray([ + domainBuilder.certification.enrolment.buildCandidate({ + ...candidate1, + subscriptions: [domainBuilder.buildCoreSubscription({ certificationCandidateId: candidate1.id })], + }), + domainBuilder.certification.enrolment.buildCandidate({ + ...candidate2, + subscriptions: [domainBuilder.buildCoreSubscription({ certificationCandidateId: candidate2.id })], + }), + ]); + }); + }); + + context('when there are no candidates', function () { + it('returns an empty array', async function () { + //when + const result = await candidateRepository.findByUserId({ userId: 123 }); + + // then + expect(result).to.be.empty; + }); + }); + }); + describe('#update', function () { context('when the candidate exists', function () { it('should update the candidate', async function () { diff --git a/api/tests/certification/enrolment/unit/application/api/candidates-api_test.js b/api/tests/certification/enrolment/unit/application/api/candidates-api_test.js new file mode 100644 index 00000000000..1675d21ae2c --- /dev/null +++ b/api/tests/certification/enrolment/unit/application/api/candidates-api_test.js @@ -0,0 +1,30 @@ +import { hasBeenCandidate } from '../../../../../../src/certification/enrolment/application/api/candidates-api.js'; +import { usecases } from '../../../../../../src/certification/enrolment/domain/usecases/index.js'; +import { catchErr, expect, sinon } from '../../../../../test-helper.js'; + +describe('Unit | Certification | Enrolment | API | candidates-api', function () { + describe('hasBeenCandidate', function () { + it('should check if a user has been candidate', async function () { + // given + sinon.stub(usecases, 'hasBeenCandidate').resolves(); + + // when + await hasBeenCandidate({ userId: 12 }); + + // then + expect(usecases.hasBeenCandidate).to.have.been.calledOnceWithExactly({ userId: 12 }); + }); + + it('should reject calls without a userId', async function () { + // given + sinon.stub(usecases, 'hasBeenCandidate').resolves(); + + // when + const error = await catchErr(() => hasBeenCandidate({ userId: null }))(); + + // then + expect(error).to.be.instanceOf(TypeError); + expect(error.message).to.equals('user identifier is required'); + }); + }); +}); diff --git a/api/tests/certification/enrolment/unit/domain/usecases/has-been-candidate_test.js b/api/tests/certification/enrolment/unit/domain/usecases/has-been-candidate_test.js new file mode 100644 index 00000000000..13f3243f304 --- /dev/null +++ b/api/tests/certification/enrolment/unit/domain/usecases/has-been-candidate_test.js @@ -0,0 +1,58 @@ +import { hasBeenCandidate } from '../../../../../../src/certification/enrolment/domain/usecases/has-been-candidate.js'; +import { domainBuilder, expect, sinon } from '../../../../../test-helper.js'; + +describe('Certification | Enrolment | Unit | UseCase | has-been-candidate', function () { + let candidateRepository; + + beforeEach(function () { + candidateRepository = { findByUserId: sinon.stub() }; + }); + + context('when at least one candidate is reconciled', function () { + it('should return true', async function () { + // given + const candidate1 = domainBuilder.certification.enrolment.buildCandidate({ + userId: 4321, + reconciledAt: new Date(), + }); + const candidate2 = domainBuilder.certification.enrolment.buildCandidate({ userId: 4321 }); + + candidateRepository.findByUserId.withArgs({ userId: 4321 }).resolves([candidate1, candidate2]); + + // when + const result = await hasBeenCandidate({ userId: 4321, candidateRepository }); + + // then + expect(result).to.be.true; + }); + }); + + context('when no candidates are reconciled', function () { + it('should return false', async function () { + // given + const candidate1 = domainBuilder.certification.enrolment.buildCandidate({ userId: 4321 }); + const candidate2 = domainBuilder.certification.enrolment.buildCandidate({ userId: 4321 }); + + candidateRepository.findByUserId.withArgs({ userId: 4321 }).resolves([candidate1, candidate2]); + + // when + const result = await hasBeenCandidate({ userId: 4321, candidateRepository }); + + // then + expect(result).to.be.false; + }); + }); + + context('when no candidates are returned', function () { + it('should return false', async function () { + // given + candidateRepository.findByUserId.withArgs({ userId: 4321 }).resolves([]); + + // when + const result = await hasBeenCandidate({ userId: 4321, candidateRepository }); + + // then + expect(result).to.be.false; + }); + }); +});