From baa28173b0af73fe5bc522553fcb409cc5f9abbb Mon Sep 17 00:00:00 2001 From: Steph0 Date: Fri, 15 Nov 2024 18:25:59 +0100 Subject: [PATCH] :sparkles: api: V2 scoring must find the competences used during certification challenges selection --- .../domain/services/scoring/scoring-v2.js | 14 ++- .../services/scoring/scoring-v2_test.js | 114 ++++++++++++++++-- .../certification-course-route_test.js | 2 + .../application/finalize-route_test.js | 22 +++- .../shared/fixtures/certification-course.js | 5 +- 5 files changed, 140 insertions(+), 17 deletions(-) diff --git a/api/src/certification/evaluation/domain/services/scoring/scoring-v2.js b/api/src/certification/evaluation/domain/services/scoring/scoring-v2.js index f98220761ed..28a17cf98ba 100644 --- a/api/src/certification/evaluation/domain/services/scoring/scoring-v2.js +++ b/api/src/certification/evaluation/domain/services/scoring/scoring-v2.js @@ -2,6 +2,7 @@ * @typedef {import('../index.js').AssessmentResultRepository} AssessmentResultRepository * @typedef {import('../index.js').CertificationCourseRepository} CertificationCourseRepository * @typedef {import('../index.js').CompetenceMarkRepository} CompetenceMarkRepository + * @typedef {import('../index.js').CertificationCandidateRepository} CertificationCandidateRepository * @typedef {import('../index.js').ScoringDegradationService} ScoringDegradationService * @typedef {import('../index.js').ScoringCertificationService} ScoringCertificationService * @typedef {import('../index.js').ScoringService} ScoringService @@ -36,6 +37,7 @@ import { AlgorithmEngineVersion } from '../../../../shared/domain/models/Algorit * @param {AreaRepository} params.areaRepository * @param {PlacementProfileService} params.placementProfileService * @param {ScoringService} params.scoringService + * @param {CertificationCandidateRepository} params.certificationCandidateRepository * @param {Object} params.dependencies * @param {calculateCertificationAssessmentScore} params.dependencies.calculateCertificationAssessmentScore */ @@ -50,6 +52,7 @@ export const handleV2CertificationScoring = async ({ areaRepository, placementProfileService, scoringService, + certificationCandidateRepository, dependencies = { calculateCertificationAssessmentScore }, }) => { const certificationAssessmentScore = await dependencies.calculateCertificationAssessmentScore({ @@ -57,6 +60,7 @@ export const handleV2CertificationScoring = async ({ areaRepository, placementProfileService, scoringService, + certificationCandidateRepository, }); const certificationCourse = await certificationCourseRepository.get({ id: certificationAssessment.certificationCourseId, @@ -85,17 +89,23 @@ export const handleV2CertificationScoring = async ({ /** * @param {Object} params * @param {CertificationAssessment} params.certificationAssessment - * @param {ScoringService} params.dependencies.scoringService + * @param {ScoringService} params.scoringService + * @param {CertificationCandidateRepository} params.certificationCandidateRepository */ export const calculateCertificationAssessmentScore = async function ({ certificationAssessment, areaRepository, placementProfileService, scoringService, + certificationCandidateRepository, }) { + const candidate = await certificationCandidateRepository.findByAssessmentId({ + assessmentId: certificationAssessment.id, + }); + const testedCompetences = await _getTestedCompetences({ userId: certificationAssessment.userId, - limitDate: certificationAssessment.createdAt, + limitDate: candidate.reconciledAt, version: AlgorithmEngineVersion.V2, placementProfileService, }); diff --git a/api/tests/certification/evaluation/unit/domain/services/scoring/scoring-v2_test.js b/api/tests/certification/evaluation/unit/domain/services/scoring/scoring-v2_test.js index 8114e6c1364..e3d7bd40d49 100644 --- a/api/tests/certification/evaluation/unit/domain/services/scoring/scoring-v2_test.js +++ b/api/tests/certification/evaluation/unit/domain/services/scoring/scoring-v2_test.js @@ -886,12 +886,15 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct context('#calculateCertificationAssessmentScore', function () { let certificationAssessment, certificationAssessmentData, expectedCertifiedCompetences; let competenceWithMarks_1_1, competenceWithMarks_2_2, competenceWithMarks_3_3, competenceWithMarks_4_4; - let areaRepository; + let areaRepository, certificationCandidateRepository; beforeEach(function () { areaRepository = { list: sinon.stub(), }; + certificationCandidateRepository = { + findByAssessmentId: sinon.stub().throws('bad arguments'), + }; areaRepository.list.resolves(['1', '2', '3', '4', '5', '6'].map((code) => ({ id: `area${code}`, code }))); certificationAssessmentData = { id: 1, @@ -964,6 +967,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct certificationAnswersByDate, certificationChallenges: challenges, }; + const candidate = domainBuilder.certification.evaluation.buildCandidate(); const placementProfileService = { getPlacementProfile: sinon.stub(), @@ -971,10 +975,15 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct placementProfileService.getPlacementProfile .withArgs({ userId: certificationAssessment.userId, - limitDate: certificationAssessment.createdAt, + limitDate: candidate.reconciledAt, version: AlgorithmEngineVersion.V2, }) .resolves({ userCompetences }); + certificationCandidateRepository.findByAssessmentId + .withArgs({ + assessmentId: certificationAssessment.id, + }) + .resolves(candidate); // when const error = await catchErr(calculateCertificationAssessmentScore)({ @@ -982,6 +991,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct areaRepository, placementProfileService, scoringService, + certificationCandidateRepository, }); // then @@ -1022,10 +1032,15 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct placementProfileService.getPlacementProfile .withArgs({ userId: certificationAssessment.userId, - limitDate: certificationAssessment.createdAt, + limitDate: candidate.reconciledAt, version: AlgorithmEngineVersion.V2, }) .resolves({ userCompetences }); + certificationCandidateRepository.findByAssessmentId + .withArgs({ + assessmentId: certificationAssessment.id, + }) + .resolves(candidate); // when const error = await catchErr(calculateCertificationAssessmentScore)({ @@ -1034,6 +1049,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct areaRepository, placementProfileService, scoringService, + certificationCandidateRepository, }); // then @@ -1136,10 +1152,15 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct placementProfileService.getPlacementProfile .withArgs({ userId: certificationAssessment.userId, - limitDate: certificationAssessment.createdAt, + limitDate: candidate.reconciledAt, version: AlgorithmEngineVersion.V2, }) .resolves({ userCompetences }); + certificationCandidateRepository.findByAssessmentId + .withArgs({ + assessmentId: certificationAssessment.id, + }) + .resolves(candidate); // when const error = await catchErr(calculateCertificationAssessmentScore)({ @@ -1148,6 +1169,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct areaRepository, placementProfileService, scoringService, + certificationCandidateRepository, }); // then @@ -1164,6 +1186,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct certificationAnswersByDate: wrongAnswersForAllChallenges(), certificationChallenges: challenges, }); + const candidate = domainBuilder.certification.evaluation.buildCandidate(); placementProfileService = { getPlacementProfile: sinon.stub(), @@ -1171,10 +1194,15 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct placementProfileService.getPlacementProfile .withArgs({ userId: certificationAssessment.userId, - limitDate: certificationAssessment.createdAt, + limitDate: candidate.reconciledAt, version: AlgorithmEngineVersion.V2, }) .resolves({ userCompetences }); + certificationCandidateRepository.findByAssessmentId + .withArgs({ + assessmentId: certificationAssessment.id, + }) + .resolves(candidate); }); it('should get user profile', async function () { @@ -1184,6 +1212,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct areaRepository, placementProfileService, scoringService, + certificationCandidateRepository, }); // then @@ -1208,6 +1237,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct areaRepository, placementProfileService, scoringService, + certificationCandidateRepository, }); // then @@ -1223,6 +1253,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct areaRepository, placementProfileService, scoringService, + certificationCandidateRepository, }); // then @@ -1266,6 +1297,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct areaRepository, placementProfileService, scoringService, + certificationCandidateRepository, }); // then @@ -1302,6 +1334,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct areaRepository, placementProfileService, scoringService, + certificationCandidateRepository, }); // then @@ -1342,6 +1375,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct areaRepository, placementProfileService, scoringService, + certificationCandidateRepository, }); // then @@ -1359,6 +1393,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct areaRepository, placementProfileService, scoringService, + certificationCandidateRepository, }); // then @@ -1410,10 +1445,16 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct placementProfileService = { getPlacementProfile: sinon.stub(), }; + const candidate = domainBuilder.certification.evaluation.buildCandidate(); + certificationCandidateRepository.findByAssessmentId + .withArgs({ + assessmentId: certificationAssessment.id, + }) + .resolves(candidate); placementProfileService.getPlacementProfile .withArgs({ userId: certificationAssessment.userId, - limitDate: certificationAssessment.createdAt, + limitDate: candidate.reconciledAt, version: AlgorithmEngineVersion.V2, }) .resolves({ userCompetences }); @@ -1424,6 +1465,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct areaRepository, placementProfileService, scoringService, + certificationCandidateRepository, }); // Then @@ -1443,13 +1485,22 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct certificationAnswersByDate: wrongAnswersForAllChallenges(), certificationChallenges: challenges, }); + const candidate = domainBuilder.certification.evaluation.buildCandidate(); placementProfileService = { getPlacementProfile: sinon.stub(), }; + certificationCandidateRepository = { + findByAssessmentId: sinon + .stub() + .withArgs({ + assessmentId: certificationAssessment.id, + }) + .resolves(candidate), + }; placementProfileService.getPlacementProfile .withArgs({ userId: certificationAssessment.userId, - limitDate: certificationAssessment.createdAt, + limitDate: candidate.reconciledAt, version: AlgorithmEngineVersion.V2, }) .resolves({ userCompetences }); @@ -1463,6 +1514,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct areaRepository, placementProfileService, scoringService, + certificationCandidateRepository, }); // then @@ -1506,6 +1558,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct areaRepository, placementProfileService, scoringService, + certificationCandidateRepository, }); // then @@ -1540,6 +1593,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct areaRepository, placementProfileService, scoringService, + certificationCandidateRepository, }); // then @@ -1580,6 +1634,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct areaRepository, placementProfileService, scoringService, + certificationCandidateRepository, }); // then @@ -1588,7 +1643,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct }); context('when only one challenge is asked for a competence', function () { - let placementProfileService; + let placementProfileService, certificationCandidateRepository; it('certifies a level below the estimated one if reproducibility rate is < 70%', async function () { // given const userCompetences = [ @@ -1624,10 +1679,19 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct placementProfileService = { getPlacementProfile: sinon.stub(), }; + const candidate = domainBuilder.certification.evaluation.buildCandidate(); + certificationCandidateRepository = { + findByAssessmentId: sinon + .stub() + .withArgs({ + assessmentId: certificationAssessment.id, + }) + .resolves(candidate), + }; placementProfileService.getPlacementProfile .withArgs({ userId: certificationAssessment.userId, - limitDate: certificationAssessment.createdAt, + limitDate: candidate.reconciledAt, version: AlgorithmEngineVersion.V2, }) .resolves({ userCompetences }); @@ -1664,6 +1728,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct areaRepository, placementProfileService, scoringService, + certificationCandidateRepository, }); // then @@ -1672,7 +1737,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct }); context('when challenges contains one QROCM-dep challenge to validate two skills', function () { - let placementProfileService; + let placementProfileService, certificationCandidateRepository; beforeEach(function () { const userCompetences = [ _buildUserCompetence(competence_5, 50, 5), @@ -1715,13 +1780,23 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct domainBuilder.buildCertificationChallengeWithType, ); + const candidate = domainBuilder.certification.evaluation.buildCandidate(); + certificationCandidateRepository = { + findByAssessmentId: sinon + .stub() + .withArgs({ + assessmentId: certificationAssessment.id, + }) + .resolves(candidate), + }; + placementProfileService = { getPlacementProfile: sinon.stub(), }; placementProfileService.getPlacementProfile .withArgs({ userId: certificationAssessment.userId, - limitDate: certificationAssessment.createdAt, + limitDate: candidate.reconciledAt, version: AlgorithmEngineVersion.V2, }) .resolves({ userCompetences }); @@ -1763,6 +1838,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct areaRepository, placementProfileService, scoringService, + certificationCandidateRepository, }); // then @@ -1814,6 +1890,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct areaRepository, placementProfileService, scoringService, + certificationCandidateRepository, }); // then @@ -1823,7 +1900,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct }); context('non neutralization rate trustability', function () { - let placementProfileService; + let placementProfileService, certificationCandidateRepository; beforeEach(function () { certificationAssessment = domainBuilder.buildCertificationAssessment({ @@ -1832,13 +1909,22 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct certificationChallenges: challenges, }); certificationAssessment.certificationAnswersByDate = correctAnswersForAllChallenges(); + const candidate = domainBuilder.certification.evaluation.buildCandidate(); placementProfileService = { getPlacementProfile: sinon.stub(), }; + certificationCandidateRepository = { + findByAssessmentId: sinon + .stub() + .withArgs({ + assessmentId: certificationAssessment.id, + }) + .resolves(candidate), + }; placementProfileService.getPlacementProfile .withArgs({ userId: certificationAssessment.userId, - limitDate: certificationAssessment.createdAt, + limitDate: candidate.reconciledAt, version: AlgorithmEngineVersion.V2, }) .resolves({ userCompetences }); @@ -1859,6 +1945,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct areaRepository, placementProfileService, scoringService, + certificationCandidateRepository, }); // then @@ -1883,6 +1970,7 @@ describe('Certification | Shared | Unit | Domain | Services | Scoring V2', funct areaRepository, placementProfileService, scoringService, + certificationCandidateRepository, }); // then diff --git a/api/tests/certification/session-management/acceptance/application/certification-course-route_test.js b/api/tests/certification/session-management/acceptance/application/certification-course-route_test.js index cf97a61e2a2..8ef2ede5781 100644 --- a/api/tests/certification/session-management/acceptance/application/certification-course-route_test.js +++ b/api/tests/certification/session-management/acceptance/application/certification-course-route_test.js @@ -138,6 +138,7 @@ describe('Certification | Session Management | Acceptance | Application | Routes }); const { assessment, assessmentResult } = await createSuccessfulCertificationCourse({ + sessionId: session.id, userId, certificationCourse, }); @@ -276,6 +277,7 @@ describe('Certification | Session Management | Acceptance | Application | Routes }); const { assessment, assessmentResult } = await createSuccessfulCertificationCourse({ + sessionId: session.id, userId, certificationCourse, }); diff --git a/api/tests/certification/session-management/acceptance/application/finalize-route_test.js b/api/tests/certification/session-management/acceptance/application/finalize-route_test.js index e8dd9d314f8..cbdecc8f90b 100644 --- a/api/tests/certification/session-management/acceptance/application/finalize-route_test.js +++ b/api/tests/certification/session-management/acceptance/application/finalize-route_test.js @@ -86,7 +86,15 @@ describe('Certification | Session Management | Acceptance | Application | Route // given const userId = databaseBuilder.factory.buildUser().id; const session = databaseBuilder.factory.buildSession(); - const certificationCourseId = databaseBuilder.factory.buildCertificationCourse({ sessionId: session.id }).id; + const certificationCourseId = databaseBuilder.factory.buildCertificationCourse({ + userId, + sessionId: session.id, + }).id; + databaseBuilder.factory.buildCertificationCandidate({ + sessionId: session.id, + userId, + reconciledAt: new Date('2020-01-01'), + }); databaseBuilder.factory.buildCertificationCenterMembership({ userId, certificationCenterId: session.certificationCenterId, @@ -212,9 +220,15 @@ describe('Certification | Session Management | Acceptance | Application | Route const userId = databaseBuilder.factory.buildUser().id; const session = databaseBuilder.factory.buildSession(); const certificationCourseId = databaseBuilder.factory.buildCertificationCourse({ + userId, sessionId: session.id, completedAt: new Date(), }).id; + databaseBuilder.factory.buildCertificationCandidate({ + sessionId: session.id, + userId, + reconciledAt: new Date('2020-01-01'), + }); databaseBuilder.factory.buildCertificationCenterMembership({ userId, certificationCenterId: session.certificationCenterId, @@ -326,6 +340,11 @@ describe('Certification | Session Management | Acceptance | Application | Route userId, createdAt: new Date(), }).id; + databaseBuilder.factory.buildCertificationCandidate({ + sessionId: session.id, + userId, + reconciledAt: new Date('2020-01-01'), + }); databaseBuilder.factory.buildCertificationCenterMembership({ userId, certificationCenterId: session.certificationCenterId, @@ -477,6 +496,7 @@ describe('Certification | Session Management | Acceptance | Application | Route const userId = databaseBuilder.factory.buildUser().id; const session = databaseBuilder.factory.buildSession(); const certificationCourseId = databaseBuilder.factory.buildCertificationCourse({ + userId, sessionId: session.id, completedAt: new Date(), }).id; diff --git a/api/tests/certification/shared/fixtures/certification-course.js b/api/tests/certification/shared/fixtures/certification-course.js index d8229a2536a..5f7a35f8bc2 100644 --- a/api/tests/certification/shared/fixtures/certification-course.js +++ b/api/tests/certification/shared/fixtures/certification-course.js @@ -127,15 +127,18 @@ const buildKnowledgeElementsFromAnswers = ({ answers, challenges, userId }) => { }); }; -export const createSuccessfulCertificationCourse = async ({ userId, certificationCourse }) => { +export const createSuccessfulCertificationCourse = async ({ sessionId, userId, certificationCourse }) => { const { challenges } = createLearningContent(); + databaseBuilder.factory.buildCertificationCandidate({ sessionId, userId, reconciledAt: new Date('2020-01-01') }); + const assessment = databaseBuilder.factory.buildAssessment({ userId, certificationCourseId: certificationCourse.id, type: Assessment.types.CERTIFICATION, state: 'completed', }); + const assessmentResult = databaseBuilder.factory.buildAssessmentResult.last({ certificationCourseId: certificationCourse.id, assessmentId: assessment.id,