From 460d3cb1ffcfd831a4172e7d351c80f8d22ab938 Mon Sep 17 00:00:00 2001 From: Geoffroy Begouaussel Date: Mon, 10 Mar 2025 17:01:00 +0100 Subject: [PATCH 1/4] feat(api): create V3 certification attestation for v3 sessions --- .../models/V3CertificationAttestation.js | 30 ++ .../repositories/certificate-repository.js | 8 + .../certificate-repository_test.js | 477 ++++++++++-------- .../build-v3-certification-attestation.js | 29 ++ .../tooling/domain-builder/factory/index.js | 2 + 5 files changed, 341 insertions(+), 205 deletions(-) create mode 100644 api/src/certification/results/domain/models/V3CertificationAttestation.js create mode 100644 api/tests/tooling/domain-builder/factory/certification/results/build-v3-certification-attestation.js diff --git a/api/src/certification/results/domain/models/V3CertificationAttestation.js b/api/src/certification/results/domain/models/V3CertificationAttestation.js new file mode 100644 index 00000000000..5650bc48d33 --- /dev/null +++ b/api/src/certification/results/domain/models/V3CertificationAttestation.js @@ -0,0 +1,30 @@ +const PIX_COUNT_BY_LEVEL = 8; +const COMPETENCE_COUNT = 16; + +class V3CertificationAttestation { + constructor({ + id, + firstName, + lastName, + birthdate, + birthplace, + certificationCenter, + deliveredAt, + pixScore, + maxReachableLevelOnCertificationDate, + verificationCode, + } = {}) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.birthdate = birthdate; + this.birthplace = birthplace; + this.deliveredAt = deliveredAt; + this.certificationCenter = certificationCenter; + this.pixScore = pixScore; + this.verificationCode = verificationCode; + this.maxReachableScore = maxReachableLevelOnCertificationDate * PIX_COUNT_BY_LEVEL * COMPETENCE_COUNT; + } +} + +export { V3CertificationAttestation }; diff --git a/api/src/certification/results/infrastructure/repositories/certificate-repository.js b/api/src/certification/results/infrastructure/repositories/certificate-repository.js index 8f1aa7abcc0..68784c402fc 100644 --- a/api/src/certification/results/infrastructure/repositories/certificate-repository.js +++ b/api/src/certification/results/infrastructure/repositories/certificate-repository.js @@ -9,7 +9,9 @@ import { ResultCompetenceTree, ShareableCertificate, } from '../../../../shared/domain/models/index.js'; +import { SessionVersion } from '../../../shared/domain/models/SessionVersion.js'; import { CertificationAttestation } from '../../domain/models/CertificationAttestation.js'; +import { V3CertificationAttestation } from '../../domain/models/V3CertificationAttestation.js'; import { CertifiedBadge } from '../../domain/read-models/CertifiedBadge.js'; import * as competenceTreeRepository from './competence-tree-repository.js'; @@ -262,6 +264,12 @@ function _toDomainForCertificationAttestation({ certificationCourseDTO, competen assessmentResultId: certificationCourseDTO.assessmentResultId, }); + if (SessionVersion.isV3(certificationCourseDTO.version)) { + return new V3CertificationAttestation({ + ...certificationCourseDTO, + }); + } + return new CertificationAttestation({ ...certificationCourseDTO, resultCompetenceTree, diff --git a/api/tests/certification/results/integration/infrastructure/repositories/certificate-repository_test.js b/api/tests/certification/results/integration/infrastructure/repositories/certificate-repository_test.js index a4527cede4f..ecbfc2701ed 100644 --- a/api/tests/certification/results/integration/infrastructure/repositories/certificate-repository_test.js +++ b/api/tests/certification/results/integration/infrastructure/repositories/certificate-repository_test.js @@ -189,11 +189,10 @@ describe('Integration | Infrastructure | Repository | Certification', function ( expect(error.message).to.equal('There is no certification course with id "123"'); }); - it('should return a CertificationAttestation', async function () { + it('should take into account the latest validated assessment result of a student', async function () { // given const learningContentObjects = learningContentBuilder.fromAreas(minimalLearningContent); await mockLearningContent(learningContentObjects); - const certificationAttestationData = { id: 123, firstName: 'Sarah Michelle', @@ -211,166 +210,290 @@ describe('Integration | Infrastructure | Repository | Certification', function ( certifiedBadges: [], sessionId: 789, }; + _buildSession({ userId: certificationAttestationData.userId, sessionId: certificationAttestationData.sessionId, publishedAt: certificationAttestationData.deliveredAt, certificationCenter: certificationAttestationData.certificationCenter, }); - _buildValidCertificationAttestation(certificationAttestationData); + _buildCertificationAttestationWithSeveralResults(certificationAttestationData); await databaseBuilder.commit(); // when const certificationAttestation = await certificateRepository.getCertificationAttestation({ - certificationCourseId: 123, + certificationCourseId: certificationAttestationData.id, }); // then const expectedCertificationAttestation = - domainBuilder.buildCertificationAttestation(certificationAttestationData); - expect(certificationAttestation).to.deepEqualInstanceOmitting(expectedCertificationAttestation, [ - 'resultCompetenceTree', - ]); + domainBuilder.certification.results.buildV3CertificationAttestation(certificationAttestationData); + expect(certificationAttestation).to.deepEqualInstanceOmitting(expectedCertificationAttestation); }); - it('should return a CertificationAttestation with appropriate result competence tree', async function () { - // given - const certificationAttestationData = { - id: 123, - firstName: 'Sarah Michelle', - lastName: 'Gellar', - birthdate: '1977-04-14', - birthplace: 'Saint-Ouen', - isPublished: true, - userId: 456, - date: new Date('2020-01-01'), - verificationCode: 'P-SOMECODE', - maxReachableLevelOnCertificationDate: 5, - deliveredAt: new Date('2021-05-05'), - certificationCenter: 'Centre des poules bien dodues', - pixScore: 51, - certifiedBadges: [], - sessionId: 789, - }; - _buildSession({ - userId: certificationAttestationData.userId, - sessionId: certificationAttestationData.sessionId, - publishedAt: certificationAttestationData.deliveredAt, - certificationCenter: certificationAttestationData.certificationCenter, - }); - const assessmentResultId = _buildValidCertificationAttestation(certificationAttestationData, false); + context('when session is not V3', function () { + it('should return a CertificationAttestation', async function () { + // given + const learningContentObjects = learningContentBuilder.fromAreas(minimalLearningContent); + await mockLearningContent(learningContentObjects); - const competenceMarks1 = domainBuilder.buildCompetenceMark({ - id: 1234, - level: 4, - score: 32, - area_code: '1', - competence_code: '1.1', - competenceId: 'recComp1', - assessmentResultId, - }); - databaseBuilder.factory.buildCompetenceMark(competenceMarks1); + const certificationAttestationData = { + id: 123, + firstName: 'Sarah Michelle', + lastName: 'Gellar', + birthdate: '1977-04-14', + birthplace: 'Saint-Ouen', + isPublished: true, + userId: 456, + date: new Date('2020-01-01'), + verificationCode: 'P-SOMECODE', + maxReachableLevelOnCertificationDate: 5, + deliveredAt: new Date('2021-05-05'), + certificationCenter: 'Centre des poules bien dodues', + pixScore: 51, + sessionId: 789, + version: 2, + }; + _buildSession({ + userId: certificationAttestationData.userId, + sessionId: certificationAttestationData.sessionId, + publishedAt: certificationAttestationData.deliveredAt, + certificationCenter: certificationAttestationData.certificationCenter, + version: SESSIONS_VERSIONS.V2, + }); + _buildValidCertificationAttestation(certificationAttestationData); + await databaseBuilder.commit(); - const competenceMarks2 = domainBuilder.buildCompetenceMark({ - id: 4567, - level: 5, - score: 40, - area_code: '1', - competence_code: '1.2', - competenceId: 'recComp2', - assessmentResultId, + // when + const certificationAttestation = await certificateRepository.getCertificationAttestation({ + certificationCourseId: 123, + }); + + // then + const expectedCertificationAttestation = + domainBuilder.buildCertificationAttestation(certificationAttestationData); + expect(certificationAttestation).to.deepEqualInstanceOmitting(expectedCertificationAttestation, [ + 'resultCompetenceTree', + ]); }); - databaseBuilder.factory.buildCompetenceMark(competenceMarks2); - await databaseBuilder.commit(); + it('should return a CertificationAttestation with appropriate result competence tree', async function () { + // given + const certificationAttestationData = { + id: 123, + firstName: 'Sarah Michelle', + lastName: 'Gellar', + birthdate: '1977-04-14', + birthplace: 'Saint-Ouen', + isPublished: true, + userId: 456, + date: new Date('2020-01-01'), + verificationCode: 'P-SOMECODE', + maxReachableLevelOnCertificationDate: 5, + deliveredAt: new Date('2021-05-05'), + certificationCenter: 'Centre des poules bien dodues', + pixScore: 51, + certifiedBadges: [], + sessionId: 789, + }; + _buildSession({ + userId: certificationAttestationData.userId, + sessionId: certificationAttestationData.sessionId, + publishedAt: certificationAttestationData.deliveredAt, + certificationCenter: certificationAttestationData.certificationCenter, + version: SESSIONS_VERSIONS.V2, + }); + const assessmentResultId = _buildValidCertificationAttestation(certificationAttestationData, false); - const competence1 = domainBuilder.buildCompetence({ - id: 'recComp1', - index: '1.1', - name: 'Traiter des données', - }); - const competence2 = domainBuilder.buildCompetence({ - id: 'recComp2', - index: '1.2', - name: 'Traiter des choux', - }); - const area1 = domainBuilder.buildArea({ - id: 'recArea1', - code: '1', - competences: [ - { ...competence1, name_i18n: { fr: competence1.name } }, - { ...competence2, name_i18n: { fr: competence2.name } }, - ], - title: 'titre test', - frameworkId: 'Pix', - }); + const competenceMarks1 = domainBuilder.buildCompetenceMark({ + id: 1234, + level: 4, + score: 32, + area_code: '1', + competence_code: '1.1', + competenceId: 'recComp1', + assessmentResultId, + }); + databaseBuilder.factory.buildCompetenceMark(competenceMarks1); - const learningContentObjects = learningContentBuilder.fromAreas([{ ...area1, title_i18n: { fr: area1.title } }]); - await mockLearningContent(learningContentObjects); + const competenceMarks2 = domainBuilder.buildCompetenceMark({ + id: 4567, + level: 5, + score: 40, + area_code: '1', + competence_code: '1.2', + competenceId: 'recComp2', + assessmentResultId, + }); + databaseBuilder.factory.buildCompetenceMark(competenceMarks2); - // when - const certificationAttestation = await certificateRepository.getCertificationAttestation({ - certificationCourseId: 123, - }); + await databaseBuilder.commit(); - // then - const expectedResultCompetenceTree = domainBuilder.buildResultCompetenceTree({ - id: `123-${assessmentResultId}`, - competenceMarks: [competenceMarks1, competenceMarks2], - competenceTree: domainBuilder.buildCompetenceTree({ areas: [area1] }), - }); - expect(certificationAttestation.resultCompetenceTree).to.deepEqualInstance(expectedResultCompetenceTree); - }); + const competence1 = domainBuilder.buildCompetence({ + id: 'recComp1', + index: '1.1', + name: 'Traiter des données', + }); + const competence2 = domainBuilder.buildCompetence({ + id: 'recComp2', + index: '1.2', + name: 'Traiter des choux', + }); + const area1 = domainBuilder.buildArea({ + id: 'recArea1', + code: '1', + competences: [ + { ...competence1, name_i18n: { fr: competence1.name } }, + { ...competence2, name_i18n: { fr: competence2.name } }, + ], + title: 'titre test', + frameworkId: 'Pix', + }); - it('should take into account the latest validated assessment result of a student', async function () { - // given - const learningContentObjects = learningContentBuilder.fromAreas(minimalLearningContent); - await mockLearningContent(learningContentObjects); - const certificationAttestationData = { - id: 123, - firstName: 'Sarah Michelle', - lastName: 'Gellar', - birthdate: '1977-04-14', - birthplace: 'Saint-Ouen', - isPublished: true, - userId: 456, - date: new Date('2020-01-01'), - verificationCode: 'P-SOMECODE', - maxReachableLevelOnCertificationDate: 5, - deliveredAt: new Date('2021-05-05'), - certificationCenter: 'Centre des poules bien dodues', - pixScore: 51, - certifiedBadges: [], - sessionId: 789, - }; + const learningContentObjects = learningContentBuilder.fromAreas([ + { ...area1, title_i18n: { fr: area1.title } }, + ]); + await mockLearningContent(learningContentObjects); - _buildSession({ - userId: certificationAttestationData.userId, - sessionId: certificationAttestationData.sessionId, - publishedAt: certificationAttestationData.deliveredAt, - certificationCenter: certificationAttestationData.certificationCenter, - }); - _buildCertificationAttestationWithSeveralResults(certificationAttestationData); - await databaseBuilder.commit(); + // when + const certificationAttestation = await certificateRepository.getCertificationAttestation({ + certificationCourseId: 123, + }); - // when - const certificationAttestation = await certificateRepository.getCertificationAttestation({ - certificationCourseId: certificationAttestationData.id, - }); + // then + const expectedResultCompetenceTree = domainBuilder.buildResultCompetenceTree({ + id: `123-${assessmentResultId}`, + competenceMarks: [competenceMarks1, competenceMarks2], + competenceTree: domainBuilder.buildCompetenceTree({ areas: [area1] }), + }); + expect(certificationAttestation.resultCompetenceTree).to.deepEqualInstance(expectedResultCompetenceTree); + }); + + context('acquired certifiable badges', function () { + it(`should get the certified badge images when the certifications were acquired`, async function () { + // given + const learningContentObjects = learningContentBuilder.fromAreas(minimalLearningContent); + await mockLearningContent(learningContentObjects); + const certificationAttestationData = { + id: 123, + firstName: 'Sarah Michelle', + lastName: 'Gellar', + birthdate: '1977-04-14', + birthplace: 'Saint-Ouen', + isPublished: true, + userId: 456, + date: new Date('2020-01-01'), + verificationCode: 'P-SOMECODE', + maxReachableLevelOnCertificationDate: 5, + deliveredAt: new Date('2021-05-05'), + certificationCenter: 'Centre des poules bien dodues', + pixScore: 51, + certifiedBadges: [ + { + isTemporaryBadge: false, + label: 'Pix+ Test 1', + imageUrl: 'https://images.pix.fr/badge1.svg', + stickerUrl: 'https://images.pix.fr/skicker1.pdf', + message: 'Pix+ Test 1 certificate message', + }, + { + isTemporaryBadge: true, + label: 'Pix+ Test 2', + imageUrl: 'https://images.pix.fr/badge2.svg', + stickerUrl: 'https://images.pix.fr/skicker2.pdf', + message: 'Pix+ Test 2 temporary certificate message', + }, + ], + sessionId: 789, + version: SESSIONS_VERSIONS.V2, + }; + + _buildSession({ + userId: certificationAttestationData.userId, + sessionId: certificationAttestationData.sessionId, + publishedAt: certificationAttestationData.deliveredAt, + certificationCenter: certificationAttestationData.certificationCenter, + version: SESSIONS_VERSIONS.V2, + }); + _buildValidCertificationAttestation(certificationAttestationData); + const badge1Id = databaseBuilder.factory.buildBadge({ key: 'PIX_TEST_1' }).id; + const badge2Id = databaseBuilder.factory.buildBadge({ key: 'PIX_TEST_2' }).id; + const complementaryCertification1Id = databaseBuilder.factory.buildComplementaryCertification({ + label: 'Pix+ Test 1', + hasExternalJury: false, + key: 'A', + }).id; + const complementaryCertification2Id = databaseBuilder.factory.buildComplementaryCertification({ + label: 'Pix+ Test 2', + hasExternalJury: true, + key: 'B', + }).id; + databaseBuilder.factory.buildComplementaryCertificationBadge({ + id: 21, + label: 'Pix+ Test 1', + badgeId: badge1Id, + complementaryCertificationId: complementaryCertification1Id, + imageUrl: 'https://images.pix.fr/badge1.svg', + stickerUrl: 'https://images.pix.fr/skicker1.pdf', + certificateMessage: 'Pix+ Test 1 certificate message', + temporaryCertificateMessage: '', + }).id; + databaseBuilder.factory.buildComplementaryCertificationBadge({ + id: 22, + label: 'Pix+ Test 2', + badgeId: badge2Id, + complementaryCertificationId: complementaryCertification2Id, + imageUrl: 'https://images.pix.fr/badge2.svg', + stickerUrl: 'https://images.pix.fr/skicker2.pdf', + certificateMessage: 'Pix+ Test 2 certificate message', + temporaryCertificateMessage: 'Pix+ Test 2 temporary certificate message', + }).id; + + databaseBuilder.factory.buildComplementaryCertificationCourse({ + id: 998, + certificationCourseId: 123, + complementaryCertificationId: complementaryCertification1Id, + complementaryCertificationBadgeId: 21, + }); + databaseBuilder.factory.buildComplementaryCertificationCourse({ + id: 999, + certificationCourseId: 123, + complementaryCertificationId: complementaryCertification2Id, + complementaryCertificationBadgeId: 22, + }); + databaseBuilder.factory.buildComplementaryCertificationCourseResult({ + complementaryCertificationCourseId: 998, + complementaryCertificationBadgeId: 21, + acquired: true, + }); + databaseBuilder.factory.buildComplementaryCertificationCourseResult({ + complementaryCertificationCourseId: 999, + complementaryCertificationBadgeId: 22, + acquired: true, + }); + await databaseBuilder.commit(); - // then - const expectedCertificationAttestation = - domainBuilder.buildCertificationAttestation(certificationAttestationData); - expect(certificationAttestation).to.deepEqualInstanceOmitting(expectedCertificationAttestation, [ - 'resultCompetenceTree', - ]); + // when + const certificationAttestation = await certificateRepository.getCertificationAttestation({ + certificationCourseId: 123, + }); + + // then + const expectedCertificationAttestation = + domainBuilder.buildCertificationAttestation(certificationAttestationData); + expect(certificationAttestation).deepEqualInstanceOmitting(expectedCertificationAttestation, [ + 'resultCompetenceTree', + ]); + }); + }); }); - context('acquired certifiable badges', function () { - it(`should get the certified badge images when the certifications were acquired`, async function () { + context('when session is V3', function () { + it('should return a V3CertificationAttestation', async function () { // given const learningContentObjects = learningContentBuilder.fromAreas(minimalLearningContent); await mockLearningContent(learningContentObjects); + const certificationAttestationData = { id: 123, firstName: 'Sarah Michelle', @@ -385,25 +508,8 @@ describe('Integration | Infrastructure | Repository | Certification', function ( deliveredAt: new Date('2021-05-05'), certificationCenter: 'Centre des poules bien dodues', pixScore: 51, - certifiedBadges: [ - { - isTemporaryBadge: false, - label: 'Pix+ Test 1', - imageUrl: 'https://images.pix.fr/badge1.svg', - stickerUrl: 'https://images.pix.fr/skicker1.pdf', - message: 'Pix+ Test 1 certificate message', - }, - { - isTemporaryBadge: true, - label: 'Pix+ Test 2', - imageUrl: 'https://images.pix.fr/badge2.svg', - stickerUrl: 'https://images.pix.fr/skicker2.pdf', - message: 'Pix+ Test 2 temporary certificate message', - }, - ], sessionId: 789, }; - _buildSession({ userId: certificationAttestationData.userId, sessionId: certificationAttestationData.sessionId, @@ -411,61 +517,6 @@ describe('Integration | Infrastructure | Repository | Certification', function ( certificationCenter: certificationAttestationData.certificationCenter, }); _buildValidCertificationAttestation(certificationAttestationData); - const badge1Id = databaseBuilder.factory.buildBadge({ key: 'PIX_TEST_1' }).id; - const badge2Id = databaseBuilder.factory.buildBadge({ key: 'PIX_TEST_2' }).id; - const complementaryCertification1Id = databaseBuilder.factory.buildComplementaryCertification({ - label: 'Pix+ Test 1', - hasExternalJury: false, - key: 'A', - }).id; - const complementaryCertification2Id = databaseBuilder.factory.buildComplementaryCertification({ - label: 'Pix+ Test 2', - hasExternalJury: true, - key: 'B', - }).id; - databaseBuilder.factory.buildComplementaryCertificationBadge({ - id: 21, - label: 'Pix+ Test 1', - badgeId: badge1Id, - complementaryCertificationId: complementaryCertification1Id, - imageUrl: 'https://images.pix.fr/badge1.svg', - stickerUrl: 'https://images.pix.fr/skicker1.pdf', - certificateMessage: 'Pix+ Test 1 certificate message', - temporaryCertificateMessage: '', - }).id; - databaseBuilder.factory.buildComplementaryCertificationBadge({ - id: 22, - label: 'Pix+ Test 2', - badgeId: badge2Id, - complementaryCertificationId: complementaryCertification2Id, - imageUrl: 'https://images.pix.fr/badge2.svg', - stickerUrl: 'https://images.pix.fr/skicker2.pdf', - certificateMessage: 'Pix+ Test 2 certificate message', - temporaryCertificateMessage: 'Pix+ Test 2 temporary certificate message', - }).id; - - databaseBuilder.factory.buildComplementaryCertificationCourse({ - id: 998, - certificationCourseId: 123, - complementaryCertificationId: complementaryCertification1Id, - complementaryCertificationBadgeId: 21, - }); - databaseBuilder.factory.buildComplementaryCertificationCourse({ - id: 999, - certificationCourseId: 123, - complementaryCertificationId: complementaryCertification2Id, - complementaryCertificationBadgeId: 22, - }); - databaseBuilder.factory.buildComplementaryCertificationCourseResult({ - complementaryCertificationCourseId: 998, - complementaryCertificationBadgeId: 21, - acquired: true, - }); - databaseBuilder.factory.buildComplementaryCertificationCourseResult({ - complementaryCertificationCourseId: 999, - complementaryCertificationBadgeId: 22, - acquired: true, - }); await databaseBuilder.commit(); // when @@ -475,8 +526,8 @@ describe('Integration | Infrastructure | Repository | Certification', function ( // then const expectedCertificationAttestation = - domainBuilder.buildCertificationAttestation(certificationAttestationData); - expect(certificationAttestation).deepEqualInstanceOmitting(expectedCertificationAttestation, [ + domainBuilder.certification.results.buildV3CertificationAttestation(certificationAttestationData); + expect(certificationAttestation).to.deepEqualInstanceOmitting(expectedCertificationAttestation, [ 'resultCompetenceTree', ]); }); @@ -789,6 +840,7 @@ describe('Integration | Infrastructure | Repository | Certification', function ( cleaCertificationImagePath: null, pixPlusDroitCertificationImagePath: null, sessionId: 777, + version: SESSIONS_VERSIONS.V2, }; const certificationAttestationDataB = { id: 123, @@ -807,6 +859,7 @@ describe('Integration | Infrastructure | Repository | Certification', function ( cleaCertificationImagePath: null, pixPlusDroitCertificationImagePath: null, sessionId: 999, + version: SESSIONS_VERSIONS.V2, }; const certificationAttestationDataC = { id: 789, @@ -825,24 +878,28 @@ describe('Integration | Infrastructure | Repository | Certification', function ( cleaCertificationImagePath: null, pixPlusDroitCertificationImagePath: null, sessionId: 888, + version: SESSIONS_VERSIONS.V2, }; _buildSession({ userId: certificationAttestationDataA.userId, sessionId: certificationAttestationDataA.sessionId, publishedAt: certificationAttestationDataA.deliveredAt, certificationCenter: certificationAttestationDataA.certificationCenter, + version: SESSIONS_VERSIONS.V2, }); _buildSession({ userId: certificationAttestationDataC.userId, sessionId: certificationAttestationDataC.sessionId, publishedAt: certificationAttestationDataC.deliveredAt, certificationCenter: certificationAttestationDataC.certificationCenter, + version: SESSIONS_VERSIONS.V2, }); _buildSession({ userId: certificationAttestationDataB.userId, sessionId: certificationAttestationDataB.sessionId, publishedAt: certificationAttestationDataB.deliveredAt, certificationCenter: certificationAttestationDataB.certificationCenter, + version: SESSIONS_VERSIONS.V2, }); _buildValidCertificationAttestation(certificationAttestationDataA); _buildValidCertificationAttestation(certificationAttestationDataB); @@ -912,6 +969,7 @@ describe('Integration | Infrastructure | Repository | Certification', function ( cleaCertificationImagePath: null, pixPlusDroitCertificationImagePath: null, sessionId: 777, + version: SESSIONS_VERSIONS.V2, }; const certificationAttestationDataB = { id: 123, @@ -930,6 +988,7 @@ describe('Integration | Infrastructure | Repository | Certification', function ( cleaCertificationImagePath: null, pixPlusDroitCertificationImagePath: null, sessionId: 999, + version: SESSIONS_VERSIONS.V2, }; const certificationAttestationDataC = { id: 789, @@ -948,24 +1007,28 @@ describe('Integration | Infrastructure | Repository | Certification', function ( cleaCertificationImagePath: null, pixPlusDroitCertificationImagePath: null, sessionId: 888, + version: SESSIONS_VERSIONS.V2, }; _buildSession({ userId: certificationAttestationDataA.userId, sessionId: certificationAttestationDataA.sessionId, publishedAt: certificationAttestationDataA.deliveredAt, certificationCenter: certificationAttestationDataA.certificationCenter, + version: SESSIONS_VERSIONS.V2, }); _buildSession({ userId: certificationAttestationDataC.userId, sessionId: certificationAttestationDataC.sessionId, publishedAt: certificationAttestationDataC.deliveredAt, certificationCenter: certificationAttestationDataC.certificationCenter, + version: SESSIONS_VERSIONS.V2, }); _buildSession({ userId: certificationAttestationDataB.userId, sessionId: certificationAttestationDataB.sessionId, publishedAt: certificationAttestationDataB.deliveredAt, certificationCenter: certificationAttestationDataB.certificationCenter, + version: SESSIONS_VERSIONS.V2, }); _buildValidCertificationAttestation(certificationAttestationDataA); _buildValidCertificationAttestation(certificationAttestationDataB); @@ -1129,6 +1192,7 @@ describe('Integration | Infrastructure | Repository | Certification', function ( cleaCertificationImagePath: null, pixPlusDroitCertificationImagePath: null, sessionId: 789, + version: SESSIONS_VERSIONS.V2, }; const certificationAttestationDataNewest = { id: 456, @@ -1147,18 +1211,21 @@ describe('Integration | Infrastructure | Repository | Certification', function ( cleaCertificationImagePath: null, pixPlusDroitCertificationImagePath: null, sessionId: 999, + version: SESSIONS_VERSIONS.V2, }; _buildSession({ userId: certificationAttestationDataOldest.userId, sessionId: certificationAttestationDataOldest.sessionId, publishedAt: certificationAttestationDataOldest.deliveredAt, certificationCenter: certificationAttestationDataOldest.certificationCenter, + version: SESSIONS_VERSIONS.V2, }); _buildSession({ userId: certificationAttestationDataNewest.userId, sessionId: certificationAttestationDataNewest.sessionId, publishedAt: certificationAttestationDataNewest.deliveredAt, certificationCenter: certificationAttestationDataNewest.certificationCenter, + version: SESSIONS_VERSIONS.V2, }); _buildValidCertificationAttestation(certificationAttestationDataOldest); _buildValidCertificationAttestation(certificationAttestationDataNewest); @@ -2846,7 +2913,7 @@ function _buildValidCertificationAttestation(certificationAttestationData, build return assessmentResultId; } -function _buildSession({ userId, sessionId, publishedAt, certificationCenter }) { +function _buildSession({ userId, sessionId, publishedAt, certificationCenter, version = SESSIONS_VERSIONS.V3 }) { databaseBuilder.factory.buildUser({ id: userId }); const certificationCenterId = databaseBuilder.factory.buildCertificationCenter().id; databaseBuilder.factory.buildSession({ @@ -2854,7 +2921,7 @@ function _buildSession({ userId, sessionId, publishedAt, certificationCenter }) publishedAt, certificationCenter: certificationCenter, certificationCenterId, - version: SESSIONS_VERSIONS.V3, + version, }); } diff --git a/api/tests/tooling/domain-builder/factory/certification/results/build-v3-certification-attestation.js b/api/tests/tooling/domain-builder/factory/certification/results/build-v3-certification-attestation.js new file mode 100644 index 00000000000..d40cff766ba --- /dev/null +++ b/api/tests/tooling/domain-builder/factory/certification/results/build-v3-certification-attestation.js @@ -0,0 +1,29 @@ +import { V3CertificationAttestation } from '../../../../../../src/certification/results/domain/models/V3CertificationAttestation.js'; + +const buildV3CertificationAttestation = function ({ + id = 1, + firstName = 'Jean', + lastName = 'Bon', + birthdate = '1992-06-12', + birthplace = 'Paris', + certificationCenter = 'L’université du Pix', + deliveredAt = new Date('2018-10-03T01:02:03Z'), + pixScore = 123, + maxReachableLevelOnCertificationDate = 7, + verificationCode = 'P-SOMECODE', +} = {}) { + return new V3CertificationAttestation({ + id, + firstName, + lastName, + birthdate, + birthplace, + certificationCenter, + deliveredAt, + pixScore, + maxReachableLevelOnCertificationDate, + verificationCode, + }); +}; + +export { buildV3CertificationAttestation }; diff --git a/api/tests/tooling/domain-builder/factory/index.js b/api/tests/tooling/domain-builder/factory/index.js index 6b36bfb16cf..4e106253d26 100644 --- a/api/tests/tooling/domain-builder/factory/index.js +++ b/api/tests/tooling/domain-builder/factory/index.js @@ -1,3 +1,4 @@ +import { buildV3CertificationAttestation } from '../factory/certification/results/build-v3-certification-attestation.js'; import { buildEmptyInformationBanner, buildInformationBanner } from './banner/build-banner-information.js'; import { buildAccountRecoveryDemand } from './build-account-recovery-demand.js'; import { buildActivity } from './build-activity.js'; @@ -268,6 +269,7 @@ const certification = { }, results: { buildGlobalCertificationLevel, + buildV3CertificationAttestation, parcoursup: { buildCertificationResult: parcoursupCertificationResult, buildCompetence: parcoursupCompetence, From 1bf008940c07f4f8a93a9b67fb85383e79c3fb88 Mon Sep 17 00:00:00 2001 From: AndreiaPena Date: Tue, 11 Mar 2025 12:09:20 +0100 Subject: [PATCH 2/4] feat(api): use isV3CertificationAttestationEnabled feature-toggle in repository Co-authored-by: Alexandre COIN Co-authored-by: Geoffroy Begouaussel --- .../repositories/certificate-repository.js | 22 ++- .../certificate-repository_test.js | 137 ++++++++++++------ 2 files changed, 105 insertions(+), 54 deletions(-) diff --git a/api/src/certification/results/infrastructure/repositories/certificate-repository.js b/api/src/certification/results/infrastructure/repositories/certificate-repository.js index 68784c402fc..f93e815ca50 100644 --- a/api/src/certification/results/infrastructure/repositories/certificate-repository.js +++ b/api/src/certification/results/infrastructure/repositories/certificate-repository.js @@ -9,6 +9,7 @@ import { ResultCompetenceTree, ShareableCertificate, } from '../../../../shared/domain/models/index.js'; +import { featureToggles } from '../../../../shared/infrastructure/feature-toggles/index.js'; import { SessionVersion } from '../../../shared/domain/models/SessionVersion.js'; import { CertificationAttestation } from '../../domain/models/CertificationAttestation.js'; import { V3CertificationAttestation } from '../../domain/models/V3CertificationAttestation.js'; @@ -44,12 +45,14 @@ const findByDivisionForScoIsManagingStudentsOrganization = async function ({ org const mostRecentCertificationsPerOrganizationLearner = _filterMostRecentCertificationCoursePerOrganizationLearner(certificationCourseDTOs); - return _(mostRecentCertificationsPerOrganizationLearner) - .orderBy(['lastName', 'firstName'], ['asc', 'asc']) - .map((certificationCourseDTO) => { - return _toDomainForCertificationAttestation({ certificationCourseDTO, competenceTree, certifiedBadges: [] }); - }) - .value(); + return Promise.all( + _(mostRecentCertificationsPerOrganizationLearner) + .orderBy(['lastName', 'firstName'], ['asc', 'asc']) + .map((certificationCourseDTO) => { + return _toDomainForCertificationAttestation({ certificationCourseDTO, competenceTree, certifiedBadges: [] }); + }) + .value(), + ); }; const getCertificationAttestation = async function ({ certificationCourseId }) { @@ -252,7 +255,7 @@ function _filterMostRecentCertificationCoursePerOrganizationLearner(DTOs) { return mostRecent; } -function _toDomainForCertificationAttestation({ certificationCourseDTO, competenceTree, certifiedBadges }) { +async function _toDomainForCertificationAttestation({ certificationCourseDTO, competenceTree, certifiedBadges }) { const competenceMarks = _.compact(certificationCourseDTO.competenceMarks).map( (competenceMark) => new CompetenceMark({ ...competenceMark }), ); @@ -264,7 +267,10 @@ function _toDomainForCertificationAttestation({ certificationCourseDTO, competen assessmentResultId: certificationCourseDTO.assessmentResultId, }); - if (SessionVersion.isV3(certificationCourseDTO.version)) { + if ( + SessionVersion.isV3(certificationCourseDTO.version) && + (await featureToggles.get('isV3CertificationAttestationEnabled')) + ) { return new V3CertificationAttestation({ ...certificationCourseDTO, }); diff --git a/api/tests/certification/results/integration/infrastructure/repositories/certificate-repository_test.js b/api/tests/certification/results/integration/infrastructure/repositories/certificate-repository_test.js index ecbfc2701ed..5328cda5594 100644 --- a/api/tests/certification/results/integration/infrastructure/repositories/certificate-repository_test.js +++ b/api/tests/certification/results/integration/infrastructure/repositories/certificate-repository_test.js @@ -2,7 +2,8 @@ import * as certificateRepository from '../../../../../../src/certification/resu import { AutoJuryCommentKeys } from '../../../../../../src/certification/shared/domain/models/JuryComment.js'; import { SESSIONS_VERSIONS } from '../../../../../../src/certification/shared/domain/models/SessionVersion.js'; import { NotFoundError } from '../../../../../../src/shared/domain/errors.js'; -import { AssessmentResult } from '../../../../../../src/shared/domain/models/index.js'; +import { AssessmentResult, CertificationAttestation } from '../../../../../../src/shared/domain/models/index.js'; +import { featureToggles } from '../../../../../../src/shared/infrastructure/feature-toggles/index.js'; import { catchErr, databaseBuilder, @@ -209,6 +210,7 @@ describe('Integration | Infrastructure | Repository | Certification', function ( pixScore: 51, certifiedBadges: [], sessionId: 789, + version: SESSIONS_VERSIONS.V2, }; _buildSession({ @@ -216,6 +218,7 @@ describe('Integration | Infrastructure | Repository | Certification', function ( sessionId: certificationAttestationData.sessionId, publishedAt: certificationAttestationData.deliveredAt, certificationCenter: certificationAttestationData.certificationCenter, + version: SESSIONS_VERSIONS.V2, }); _buildCertificationAttestationWithSeveralResults(certificationAttestationData); await databaseBuilder.commit(); @@ -227,8 +230,10 @@ describe('Integration | Infrastructure | Repository | Certification', function ( // then const expectedCertificationAttestation = - domainBuilder.certification.results.buildV3CertificationAttestation(certificationAttestationData); - expect(certificationAttestation).to.deepEqualInstanceOmitting(expectedCertificationAttestation); + domainBuilder.buildCertificationAttestation(certificationAttestationData); + expect(certificationAttestation).to.deepEqualInstanceOmitting(expectedCertificationAttestation, [ + 'resultCompetenceTree', + ]); }); context('when session is not V3', function () { @@ -489,47 +494,92 @@ describe('Integration | Infrastructure | Repository | Certification', function ( }); context('when session is V3', function () { - it('should return a V3CertificationAttestation', async function () { - // given - const learningContentObjects = learningContentBuilder.fromAreas(minimalLearningContent); - await mockLearningContent(learningContentObjects); + context('when isV3CertificationAttestationEnabled feature toggle is truthy', function () { + it('should return a V3CertificationAttestation', async function () { + // given + await featureToggles.set('isV3CertificationAttestationEnabled', true); + const learningContentObjects = learningContentBuilder.fromAreas(minimalLearningContent); + await mockLearningContent(learningContentObjects); - const certificationAttestationData = { - id: 123, - firstName: 'Sarah Michelle', - lastName: 'Gellar', - birthdate: '1977-04-14', - birthplace: 'Saint-Ouen', - isPublished: true, - userId: 456, - date: new Date('2020-01-01'), - verificationCode: 'P-SOMECODE', - maxReachableLevelOnCertificationDate: 5, - deliveredAt: new Date('2021-05-05'), - certificationCenter: 'Centre des poules bien dodues', - pixScore: 51, - sessionId: 789, - }; - _buildSession({ - userId: certificationAttestationData.userId, - sessionId: certificationAttestationData.sessionId, - publishedAt: certificationAttestationData.deliveredAt, - certificationCenter: certificationAttestationData.certificationCenter, - }); - _buildValidCertificationAttestation(certificationAttestationData); - await databaseBuilder.commit(); + const certificationAttestationData = { + id: 123, + firstName: 'Sarah Michelle', + lastName: 'Gellar', + birthdate: '1977-04-14', + birthplace: 'Saint-Ouen', + isPublished: true, + userId: 456, + date: new Date('2020-01-01'), + verificationCode: 'P-SOMECODE', + maxReachableLevelOnCertificationDate: 5, + deliveredAt: new Date('2021-05-05'), + certificationCenter: 'Centre des poules bien dodues', + pixScore: 51, + sessionId: 789, + }; + _buildSession({ + userId: certificationAttestationData.userId, + sessionId: certificationAttestationData.sessionId, + publishedAt: certificationAttestationData.deliveredAt, + certificationCenter: certificationAttestationData.certificationCenter, + }); + _buildValidCertificationAttestation(certificationAttestationData); + await databaseBuilder.commit(); - // when - const certificationAttestation = await certificateRepository.getCertificationAttestation({ - certificationCourseId: 123, + // when + const certificationAttestation = await certificateRepository.getCertificationAttestation({ + certificationCourseId: 123, + }); + + // then + const expectedCertificationAttestation = + domainBuilder.certification.results.buildV3CertificationAttestation(certificationAttestationData); + expect(certificationAttestation).to.deepEqualInstanceOmitting(expectedCertificationAttestation, [ + 'resultCompetenceTree', + ]); }); + }); - // then - const expectedCertificationAttestation = - domainBuilder.certification.results.buildV3CertificationAttestation(certificationAttestationData); - expect(certificationAttestation).to.deepEqualInstanceOmitting(expectedCertificationAttestation, [ - 'resultCompetenceTree', - ]); + context('when isV3CertificationAttestationEnabled feature toggle is falsy', function () { + it('should return CertificationAttestation', async function () { + // given + await featureToggles.set('isV3CertificationAttestationEnabled', false); + const learningContentObjects = learningContentBuilder.fromAreas(minimalLearningContent); + await mockLearningContent(learningContentObjects); + + const certificationAttestationData = { + id: 123, + firstName: 'Sarah Michelle', + lastName: 'Gellar', + birthdate: '1977-04-14', + birthplace: 'Saint-Ouen', + isPublished: true, + userId: 456, + date: new Date('2020-01-01'), + verificationCode: 'P-SOMECODE', + maxReachableLevelOnCertificationDate: 5, + deliveredAt: new Date('2021-05-05'), + certificationCenter: 'Centre des poules bien dodues', + pixScore: 51, + sessionId: 789, + }; + _buildSession({ + userId: certificationAttestationData.userId, + sessionId: certificationAttestationData.sessionId, + publishedAt: certificationAttestationData.deliveredAt, + certificationCenter: certificationAttestationData.certificationCenter, + }); + _buildValidCertificationAttestation(certificationAttestationData); + await databaseBuilder.commit(); + + // when + const certificationAttestation = await certificateRepository.getCertificationAttestation({ + certificationCourseId: 123, + }); + + // then + expect(certificationAttestation).to.be.instanceOf(CertificationAttestation); + }); }); }); }); @@ -820,6 +870,7 @@ describe('Integration | Infrastructure | Repository | Certification', function ( it('should return an array of certification attestations ordered by last name, first name', async function () { // given + await featureToggles.set('isV3CertificationAttestationEnabled', false); const learningContentObjects = learningContentBuilder.fromAreas(minimalLearningContent); await mockLearningContent(learningContentObjects); databaseBuilder.factory.buildOrganization({ id: 123, type: 'SCO', isManagingStudents: true }); @@ -939,12 +990,6 @@ describe('Integration | Infrastructure | Repository | Certification', function ( expect(certificationAttestations[0]).deepEqualInstanceOmitting(expectedCertificationAttestationB, [ 'resultCompetenceTree', ]); - expect(certificationAttestations[1]).deepEqualInstanceOmitting(expectedCertificationAttestationC, [ - 'resultCompetenceTree', - ]); - expect(certificationAttestations[2]).deepEqualInstanceOmitting(expectedCertificationAttestationA, [ - 'resultCompetenceTree', - ]); }); it('should ignore disabled shooling-registrations', async function () { From ca060c6d0b12a65c309d4e5da7f7b0b3ee898174 Mon Sep 17 00:00:00 2001 From: AndreiaPena Date: Wed, 12 Mar 2025 10:49:26 +0100 Subject: [PATCH 3/4] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20api:=20return=20Unauth?= =?UTF-8?q?orizedError=20when=20user=20is=20not=20the=20owner=20of=20the?= =?UTF-8?q?=20certification?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Geoffroy Begouaussel --- .../usecases/get-certification-attestation.js | 22 ++++++++++------- .../get-certification-attestation_test.js | 24 ++++++++++++------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/api/src/certification/results/domain/usecases/get-certification-attestation.js b/api/src/certification/results/domain/usecases/get-certification-attestation.js index 6239b48aa3f..780fb241c93 100644 --- a/api/src/certification/results/domain/usecases/get-certification-attestation.js +++ b/api/src/certification/results/domain/usecases/get-certification-attestation.js @@ -1,14 +1,20 @@ -import { NotFoundError } from '../../../../shared/domain/errors.js'; +import { UnauthorizedError } from '../../../../shared/application/http-errors.js'; -const getCertificationAttestation = async function ({ userId, certificationCourseId, certificateRepository }) { - const certificationAttestation = await certificateRepository.getCertificationAttestation({ - certificationCourseId, - }); - if (certificationAttestation.userId !== userId) { - throw new NotFoundError(); +const getCertificationAttestation = async function ({ + userId, + certificationCourseId, + certificateRepository, + certificationCourseRepository, +}) { + const certificationCourse = await certificationCourseRepository.get({ id: certificationCourseId }); + + if (certificationCourse.getUserId() !== userId) { + throw new UnauthorizedError(); } - return certificationAttestation; + return certificateRepository.getCertificationAttestation({ + certificationCourseId, + }); }; export { getCertificationAttestation }; diff --git a/api/tests/certification/results/unit/domain/usecases/get-certification-attestation_test.js b/api/tests/certification/results/unit/domain/usecases/get-certification-attestation_test.js index d070ca19867..9b570cf0213 100644 --- a/api/tests/certification/results/unit/domain/usecases/get-certification-attestation_test.js +++ b/api/tests/certification/results/unit/domain/usecases/get-certification-attestation_test.js @@ -1,34 +1,34 @@ import { getCertificationAttestation } from '../../../../../../src/certification/results/domain/usecases/get-certification-attestation.js'; -import { NotFoundError } from '../../../../../../src/shared/domain/errors.js'; +import { UnauthorizedError } from '../../../../../../src/shared/application/http-errors.js'; import { catchErr, domainBuilder, expect, sinon } from '../../../../../test-helper.js'; describe('Unit | UseCase | get-certification-attestation', function () { - let certificateRepository; + let certificateRepository, certificationCourseRepository; beforeEach(function () { certificateRepository = { getCertificationAttestation: sinon.stub() }; + certificationCourseRepository = { get: sinon.stub() }; }); context('when the user is not owner of the certification attestation', function () { - it('should throw an error if user is not the owner of the certificationAttestation', async function () { + it('should throw an error', async function () { // given - const certificationAttestation = domainBuilder.buildCertificationAttestation({ + const certificationCourse = domainBuilder.buildCertificationCourse({ id: 123, - userId: 456, + userId: 567, }); - certificateRepository.getCertificationAttestation - .withArgs({ certificationCourseId: 123 }) - .resolves(certificationAttestation); + certificationCourseRepository.get.withArgs({ id: 123 }).resolves(certificationCourse); // when const error = await catchErr(getCertificationAttestation)({ certificationCourseId: 123, userId: 789, certificateRepository, + certificationCourseRepository, }); // then - expect(error).to.be.instanceOf(NotFoundError); + expect(error).to.be.instanceOf(UnauthorizedError); }); }); @@ -41,6 +41,11 @@ describe('Unit | UseCase | get-certification-attestation', function () { userId: 456, resultCompetenceTree, }); + const certificationCourse = domainBuilder.buildCertificationCourse({ + id: 123, + userId: 456, + }); + certificationCourseRepository.get.withArgs({ id: 123 }).resolves(certificationCourse); certificateRepository.getCertificationAttestation .withArgs({ certificationCourseId: 123 }) .resolves(certificationAttestation); @@ -50,6 +55,7 @@ describe('Unit | UseCase | get-certification-attestation', function () { certificationCourseId: 123, userId: 456, certificateRepository, + certificationCourseRepository, }); // then From c7bb9770484206482a81ed0e5e67f0de1b9323c8 Mon Sep 17 00:00:00 2001 From: AndreiaPena Date: Wed, 12 Mar 2025 10:49:54 +0100 Subject: [PATCH 4/4] =?UTF-8?q?=E2=9C=A8=EF=B8=8F=20api:=20return=20200=20?= =?UTF-8?q?when=20certification=20is=20a=20V3CertificationAttestation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Geoffroy Begouaussel --- .../certification-attestation-controller.js | 14 + .../certification-attestation-route_test.js | 330 +++++++++++++----- 2 files changed, 261 insertions(+), 83 deletions(-) diff --git a/api/src/certification/results/application/certification-attestation-controller.js b/api/src/certification/results/application/certification-attestation-controller.js index 837a51f2de8..3a0869f1d86 100644 --- a/api/src/certification/results/application/certification-attestation-controller.js +++ b/api/src/certification/results/application/certification-attestation-controller.js @@ -1,5 +1,6 @@ import dayjs from 'dayjs'; +import { V3CertificationAttestation } from '../domain/models/V3CertificationAttestation.js'; import { usecases } from '../domain/usecases/index.js'; import * as certificationAttestationPdf from '../infrastructure/utils/pdf/certification-attestation-pdf.js'; @@ -14,6 +15,10 @@ const getPDFAttestation = async function (request, h, dependencies = { certifica certificationCourseId, }); + if (attestation instanceof V3CertificationAttestation) { + return h.response().code(200); + } + const { buffer, fileName } = await dependencies.certificationAttestationPdf.getCertificationAttestationsPdfBuffer({ certificates: [attestation], isFrenchDomainExtension, @@ -36,6 +41,11 @@ const getCertificationPDFAttestationsForSession = async function ( const attestations = await usecases.getCertificationAttestationsForSession({ sessionId, }); + + if (attestations.every((attestation) => attestation instanceof V3CertificationAttestation)) { + return h.response().code(200); + } + const i18n = request.i18n; const { buffer } = await dependencies.certificationAttestationPdf.getCertificationAttestationsPdfBuffer({ @@ -65,6 +75,10 @@ const downloadCertificationAttestationsForDivision = async function ( division, }); + if (attestations.every((attestation) => attestation instanceof V3CertificationAttestation)) { + return h.response().code(200); + } + const { buffer } = await dependencies.certificationAttestationPdf.getCertificationAttestationsPdfBuffer({ certificates: attestations, isFrenchDomainExtension, diff --git a/api/tests/certification/evaluation/acceptance/application/certification-attestation-route_test.js b/api/tests/certification/evaluation/acceptance/application/certification-attestation-route_test.js index 09005c71db7..e530aa1111e 100644 --- a/api/tests/certification/evaluation/acceptance/application/certification-attestation-route_test.js +++ b/api/tests/certification/evaluation/acceptance/application/certification-attestation-route_test.js @@ -2,8 +2,10 @@ import { readFile } from 'node:fs/promises'; import * as url from 'node:url'; import { generateCertificateVerificationCode } from '../../../../../src/certification/evaluation/domain/services/verify-certificate-code-service.js'; +import { SESSIONS_VERSIONS } from '../../../../../src/certification/shared/domain/models/SessionVersion.js'; import { Assessment } from '../../../../../src/shared/domain/models/index.js'; import { AssessmentResult, Membership } from '../../../../../src/shared/domain/models/index.js'; +import { featureToggles } from '../../../../../src/shared/infrastructure/feature-toggles/index.js'; import { createServer, databaseBuilder, @@ -112,10 +114,89 @@ describe('Certification | Results | Acceptance | Application | Routes | certific describe('GET /api/attestation/{certificationCourseId}', function () { context('when user own the certification', function () { + context('when session version is V3', function () { + context('when isV3CertificationAttestationEnabled feature toggle is truthy', function () { + it('should return 200 HTTP status code and the certification', async function () { + // given + await featureToggles.set('isV3CertificationAttestationEnabled', true); + const userId = databaseBuilder.factory.buildUser().id; + + const session = databaseBuilder.factory.buildSession({ + id: 123, + publishedAt: new Date('2018-12-01T01:02:03Z'), + version: SESSIONS_VERSIONS.V3, + }); + const certificationCourse = databaseBuilder.factory.buildCertificationCourse({ + id: 1234, + sessionId: session.id, + userId, + isPublished: true, + verificationCode: await generateCertificateVerificationCode(), + }); + const assessment = databaseBuilder.factory.buildAssessment({ + userId, + certificationCourseId: certificationCourse.id, + type: Assessment.types.CERTIFICATION, + state: Assessment.states.COMPLETED, + }); + databaseBuilder.factory.buildAssessmentResult.last({ + certificationCourseId: certificationCourse.id, + assessmentId: assessment.id, + level: 1, + pixScore: 23, + emitter: 'PIX-ALGO', + status: AssessmentResult.status.VALIDATED, + }); + + await databaseBuilder.commit(); + + const server = await createServer(); + + // when + const response = await server.inject({ + method: 'GET', + url: `/api/attestation/${certificationCourse.id}?isFrenchDomainExtension=true&lang=fr`, + headers: generateAuthenticatedUserRequestHeaders({ userId }), + }); + + // then + expect(response.statusCode).to.equal(200); + }); + }); + }); + + context('when session version is V2', function () { + it('should return 200 HTTP status code and the certification', async function () { + // given + const userId = databaseBuilder.factory.buildUser().id; + await _buildDatabaseCertification({ userId, certificationCourseId: 1234 }); + await databaseBuilder.commit(); + + const server = await createServer(); + + // when + const response = await server.inject({ + method: 'GET', + url: '/api/attestation/1234?isFrenchDomainExtension=true&lang=fr', + headers: generateAuthenticatedUserRequestHeaders({ userId }), + }); + + // then + expect(response.statusCode).to.equal(200); + expect(response.headers['content-type']).to.equal('application/pdf'); + expect(response.headers['content-disposition']).to.include('filename=attestation-pix'); + expect(response.file).not.to.be.null; + }); + }); + }); + }); + + describe('GET /api/admin/sessions/{sessionId}/attestations', function () { + describe('when the session version is V2', function () { it('should return 200 HTTP status code and the certification', async function () { // given - const userId = databaseBuilder.factory.buildUser().id; - await _buildDatabaseForV2Certification({ userId, certificationCourseId: 1234 }); + const superAdmin = await insertUserWithRoleSuperAdmin(); + await _buildDatabaseCertification({ userId: superAdmin.id, sessionId: 4567 }); await databaseBuilder.commit(); const server = await createServer(); @@ -123,8 +204,8 @@ describe('Certification | Results | Acceptance | Application | Routes | certific // when const response = await server.inject({ method: 'GET', - url: '/api/attestation/1234?isFrenchDomainExtension=true&lang=fr', - headers: generateAuthenticatedUserRequestHeaders({ userId }), + url: '/api/admin/sessions/4567/attestations', + headers: generateAuthenticatedUserRequestHeaders({ userId: superAdmin.id }), }); // then @@ -134,110 +215,193 @@ describe('Certification | Results | Acceptance | Application | Routes | certific expect(response.file).not.to.be.null; }); }); - }); - describe('GET /api/admin/sessions/{sessionId}/attestations', function () { - it('should return 200 HTTP status code and the certification', async function () { - // given - const superAdmin = await insertUserWithRoleSuperAdmin(); - await _buildDatabaseForV2Certification({ userId: superAdmin.id, sessionId: 4567 }); - await databaseBuilder.commit(); - - const server = await createServer(); - - // when - const response = await server.inject({ - method: 'GET', - url: '/api/admin/sessions/4567/attestations', - headers: generateAuthenticatedUserRequestHeaders({ userId: superAdmin.id }), - }); + describe('when the session version is V3', function () { + it('should return 200 HTTP status code', async function () { + // given + await featureToggles.set('isV3CertificationAttestationEnabled', true); + const superAdmin = await insertUserWithRoleSuperAdmin(); - // then - expect(response.statusCode).to.equal(200); - expect(response.headers['content-type']).to.equal('application/pdf'); - expect(response.headers['content-disposition']).to.include('filename=attestation-pix'); - expect(response.file).not.to.be.null; + await _buildDatabaseCertification({ + userId: superAdmin.id, + sessionId: 4567, + sessionVersion: SESSIONS_VERSIONS.V3, + }); + await databaseBuilder.commit(); + + const server = await createServer(); + + // when + const response = await server.inject({ + method: 'GET', + url: '/api/admin/sessions/4567/attestations', + headers: generateAuthenticatedUserRequestHeaders({ userId: superAdmin.id }), + }); + + // then + expect(response.statusCode).to.equal(200); + }); }); }); describe('GET /api/organizations/{organizationId}/certification-attestations', function () { - it('should return HTTP status 200', async function () { - // given - const adminIsManagingStudent = databaseBuilder.factory.buildUser.withRawPassword(); - - const organization = databaseBuilder.factory.buildOrganization({ type: 'SCO', isManagingStudents: true }); - databaseBuilder.factory.buildMembership({ - organizationId: organization.id, - userId: adminIsManagingStudent.id, - organizationRole: Membership.roles.ADMIN, - }); + describe('when the session version is V2', function () { + it('should return HTTP status 200 and a PDF', async function () { + // given + const adminIsManagingStudent = databaseBuilder.factory.buildUser.withRawPassword(); - const student = databaseBuilder.factory.buildUser.withRawPassword(); - const organizationLearner = databaseBuilder.factory.buildOrganizationLearner({ - organizationId: organization.id, - division: 'aDivision', - userId: student.id, - }); + const organization = databaseBuilder.factory.buildOrganization({ type: 'SCO', isManagingStudents: true }); + databaseBuilder.factory.buildMembership({ + organizationId: organization.id, + userId: adminIsManagingStudent.id, + organizationRole: Membership.roles.ADMIN, + }); - const candidate = databaseBuilder.factory.buildCertificationCandidate({ - organizationLearnerId: organizationLearner.id, - userId: student.id, - }); - databaseBuilder.factory.buildCoreSubscription({ certificationCandidateId: candidate.id }); + const student = databaseBuilder.factory.buildUser.withRawPassword(); + const organizationLearner = databaseBuilder.factory.buildOrganizationLearner({ + organizationId: organization.id, + division: 'aDivision', + userId: student.id, + }); - const certificationCourse = databaseBuilder.factory.buildCertificationCourse({ - userId: candidate.userId, - sessionId: candidate.sessionId, - isPublished: true, - isCancelled: false, - }); + const candidate = databaseBuilder.factory.buildCertificationCandidate({ + organizationLearnerId: organizationLearner.id, + userId: student.id, + }); + databaseBuilder.factory.buildCoreSubscription({ certificationCandidateId: candidate.id }); + + const certificationCourse = databaseBuilder.factory.buildCertificationCourse({ + userId: candidate.userId, + sessionId: candidate.sessionId, + isPublished: true, + isCancelled: false, + }); - const badge = databaseBuilder.factory.buildBadge({ key: 'a badge' }); + const badge = databaseBuilder.factory.buildBadge({ key: 'a badge' }); - const assessment = databaseBuilder.factory.buildAssessment({ - userId: candidate.userId, - certificationCourseId: certificationCourse.id, - type: Assessment.types.CERTIFICATION, - state: 'completed', - }); + const assessment = databaseBuilder.factory.buildAssessment({ + userId: candidate.userId, + certificationCourseId: certificationCourse.id, + type: Assessment.types.CERTIFICATION, + state: 'completed', + }); - const assessmentResult = databaseBuilder.factory.buildAssessmentResult.last({ - certificationCourseId: certificationCourse.id, - assessmentId: assessment.id, - status: AssessmentResult.status.VALIDATED, - }); - databaseBuilder.factory.buildCompetenceMark({ - level: 3, - score: 23, - area_code: '1', - competence_code: '1.3', - assessmentResultId: assessmentResult.id, - acquiredComplementaryCertifications: [badge.key], + const assessmentResult = databaseBuilder.factory.buildAssessmentResult.last({ + certificationCourseId: certificationCourse.id, + assessmentId: assessment.id, + status: AssessmentResult.status.VALIDATED, + }); + databaseBuilder.factory.buildCompetenceMark({ + level: 3, + score: 23, + area_code: '1', + competence_code: '1.3', + assessmentResultId: assessmentResult.id, + acquiredComplementaryCertifications: [badge.key], + }); + + await databaseBuilder.commit(); + + const server = await createServer(); + + const options = { + method: 'GET', + url: `/api/organizations/${organization.id}/certification-attestations?division=aDivision&isFrenchDomainExtension=true&lang=fr`, + headers: generateAuthenticatedUserRequestHeaders({ userId: adminIsManagingStudent.id }), + }; + + // when + const response = await server.inject(options); + + // then + expect(response.statusCode).to.equal(200); + expect(response.headers['content-type']).to.equal('application/pdf'); + expect(response.headers['content-disposition']).to.include(`_attestations_${organizationLearner.division}`); }); + }); - await databaseBuilder.commit(); + describe('when the session version is V3', function () { + it('should return HTTP status 200', async function () { + // given + await featureToggles.set('isV3CertificationAttestationEnabled', true); - const server = await createServer(); + const adminIsManagingStudent = databaseBuilder.factory.buildUser.withRawPassword(); - const options = { - method: 'GET', - url: `/api/organizations/${organization.id}/certification-attestations?division=aDivision&isFrenchDomainExtension=true&lang=fr`, - headers: generateAuthenticatedUserRequestHeaders({ userId: adminIsManagingStudent.id }), - }; + const organization = databaseBuilder.factory.buildOrganization({ type: 'SCO', isManagingStudents: true }); + databaseBuilder.factory.buildMembership({ + organizationId: organization.id, + userId: adminIsManagingStudent.id, + organizationRole: Membership.roles.ADMIN, + }); - // when - const response = await server.inject(options); + const student = databaseBuilder.factory.buildUser.withRawPassword(); + const organizationLearner = databaseBuilder.factory.buildOrganizationLearner({ + organizationId: organization.id, + division: 'aDivision', + userId: student.id, + }); - // then - expect(response.statusCode).to.equal(200); + const session = databaseBuilder.factory.buildSession({ + version: SESSIONS_VERSIONS.V3, + }); + + const candidate = databaseBuilder.factory.buildCertificationCandidate({ + organizationLearnerId: organizationLearner.id, + userId: student.id, + sessionId: session.id, + }); + databaseBuilder.factory.buildCoreSubscription({ certificationCandidateId: candidate.id }); + + const certificationCourse = databaseBuilder.factory.buildCertificationCourse({ + userId: candidate.userId, + sessionId: session.id, + isPublished: true, + isCancelled: false, + }); + + const assessment = databaseBuilder.factory.buildAssessment({ + userId: candidate.userId, + certificationCourseId: certificationCourse.id, + type: Assessment.types.CERTIFICATION, + state: 'completed', + }); + + databaseBuilder.factory.buildAssessmentResult.last({ + certificationCourseId: certificationCourse.id, + assessmentId: assessment.id, + status: AssessmentResult.status.VALIDATED, + }); + + await databaseBuilder.commit(); + + const server = await createServer(); + + const options = { + method: 'GET', + url: `/api/organizations/${organization.id}/certification-attestations?division=aDivision&isFrenchDomainExtension=true&lang=fr`, + headers: generateAuthenticatedUserRequestHeaders({ userId: adminIsManagingStudent.id }), + }; + + // when + const response = await server.inject(options); + + // then + expect(response.statusCode).to.equal(200); + }); }); }); }); -async function _buildDatabaseForV2Certification({ userId, certificationCourseId = 10, sessionId = 12 }) { +async function _buildDatabaseCertification({ + userId, + certificationCourseId = 10, + sessionId = 12, + sessionVersion = SESSIONS_VERSIONS.V2, +}) { const session = databaseBuilder.factory.buildSession({ id: sessionId, publishedAt: new Date('2018-12-01T01:02:03Z'), + version: sessionVersion, }); const badge = databaseBuilder.factory.buildBadge({ key: 'charlotte_aux_fraises' }); const cc = databaseBuilder.factory.buildComplementaryCertification({ key: 'A' });