diff --git a/api/db/database-builder/factory/build-assessment-result.js b/api/db/database-builder/factory/build-assessment-result.js index 3479a874d0e..7b5f63cd08b 100644 --- a/api/db/database-builder/factory/build-assessment-result.js +++ b/api/db/database-builder/factory/build-assessment-result.js @@ -12,7 +12,7 @@ function buildAssessmentResult({ level = null, status = AssessmentResult.status.VALIDATED, emitter = 'PIX-ALGO', - commentByAutoJury = 'Some comment by auto jury', + commentByAutoJury, commentByJury, commentForCandidate = 'Some comment for candidate', commentForOrganization = 'Some comment for organization', diff --git a/api/lib/application/certification-courses/certification-course-controller.js b/api/lib/application/certification-courses/certification-course-controller.js index 34f4d54d451..913003e8a75 100644 --- a/api/lib/application/certification-courses/certification-course-controller.js +++ b/api/lib/application/certification-courses/certification-course-controller.js @@ -18,8 +18,10 @@ const getCertificationDetails = async function (request, h, dependencies = { cer const getJuryCertification = async function (request, h, dependencies = { juryCertificationSerializer }) { const certificationCourseId = request.params.id; + const translate = request.i18n.__; const juryCertification = await usecases.getJuryCertification({ certificationCourseId }); - return dependencies.juryCertificationSerializer.serialize(juryCertification); + + return dependencies.juryCertificationSerializer.serialize(juryCertification, { translate }); }; const update = async function (request, h, dependencies = { certificationSerializer }) { diff --git a/api/lib/application/certifications/certification-controller.js b/api/lib/application/certifications/certification-controller.js index 8444092995f..2f0894be882 100644 --- a/api/lib/application/certifications/certification-controller.js +++ b/api/lib/application/certifications/certification-controller.js @@ -6,14 +6,16 @@ import * as requestResponseUtils from '../../infrastructure/utils/request-respon const findUserCertifications = async function (request) { const userId = request.auth.credentials.userId; + const translate = request.i18n.__; const privateCertificates = await usecases.findUserPrivateCertificates({ userId }); - return privateCertificateSerializer.serialize(privateCertificates); + return privateCertificateSerializer.serialize(privateCertificates, { translate }); }; const getCertification = async function (request, h, dependencies = { requestResponseUtils }) { const userId = request.auth.credentials.userId; const certificationId = request.params.id; + const translate = request.i18n.__; const locale = dependencies.requestResponseUtils.extractLocaleFromRequest(request); const privateCertificate = await usecases.getPrivateCertificate({ @@ -21,7 +23,7 @@ const getCertification = async function (request, h, dependencies = { requestRes certificationId, locale, }); - return privateCertificateSerializer.serialize(privateCertificate); + return privateCertificateSerializer.serialize(privateCertificate, { translate }); }; const getCertificationByVerificationCode = async function (request, h, dependencies = { requestResponseUtils }) { diff --git a/api/lib/domain/models/CertificationResult.js b/api/lib/domain/models/CertificationResult.js index 1cfbdf8556b..bd8f118cc0f 100644 --- a/api/lib/domain/models/CertificationResult.js +++ b/api/lib/domain/models/CertificationResult.js @@ -1,7 +1,12 @@ import _ from 'lodash'; import { CompetenceMark } from './CompetenceMark.js'; import { ComplementaryCertificationCourseResult } from './ComplementaryCertificationCourseResult.js'; +import { JuryComment, JuryCommentContexts } from '../../../src/certification/shared/domain/models/JuryComment.js'; +/** + * @readonly + * @enum {string} + */ const status = { REJECTED: 'rejected', VALIDATED: 'validated', @@ -10,6 +15,10 @@ const status = { STARTED: 'started', }; +/** + * @readonly + * @enum {string} + */ const emitters = { PIX_ALGO: 'PIX-ALGO', PIX_ALGO_AUTO_JURY: 'PIX-ALGO-AUTO-JURY', @@ -18,6 +27,22 @@ const emitters = { }; class CertificationResult { + /** + * @param {Object} props + * @param {number} props.id + * @param {string} props.firstName + * @param {string} props.lastName + * @param {string} props.birthplace + * @param {number} props.externalId + * @param {Date} props.createdAt + * @param {number} props.sessionId + * @param {status} props.status + * @param {number} props.pixScore + * @param {string} props.emitter + * @param {JuryComment} props.commentForOrganization + * @param {Array} props.competencesWithMark + * @param {Array} props.complementaryCertificationCourseResults + */ constructor({ id, firstName, @@ -73,6 +98,12 @@ class CertificationResult { (complementaryCertifCourseResult) => new ComplementaryCertificationCourseResult(complementaryCertifCourseResult), ); + const commentForOrganization = new JuryComment({ + fallbackComment: certificationResultDTO.commentForOrganization, + context: JuryCommentContexts.ORGANIZATION, + commentByAutoJury: certificationResultDTO.commentByAutoJury, + }); + return new CertificationResult({ id: certificationResultDTO.id, firstName: certificationResultDTO.firstName, @@ -85,7 +116,7 @@ class CertificationResult { status: certificationStatus, pixScore: certificationResultDTO.pixScore, emitter: certificationResultDTO.emitter, - commentForOrganization: certificationResultDTO.commentForOrganization, + commentForOrganization, competencesWithMark, complementaryCertificationCourseResults, }); diff --git a/api/lib/domain/models/JuryCertification.js b/api/lib/domain/models/JuryCertification.js index ca4a37221c1..28e366802b4 100644 --- a/api/lib/domain/models/JuryCertification.js +++ b/api/lib/domain/models/JuryCertification.js @@ -1,6 +1,36 @@ import { CompetenceMark } from './CompetenceMark.js'; +import { JuryComment, JuryCommentContexts } from '../../../src/certification/shared/domain/models/JuryComment.js'; class JuryCertification { + /** + * @param {Object} props + * @param {number} props.certificationCourseId + * @param {number} props.sessionId + * @param {number} props.userId + * @param {number} props.assessmentId + * @param {string} props.firstName + * @param {string} props.lastName + * @param {string} props.birthplace + * @param {string} props.birthINSEECode + * @param {string} props.birthCountry + * @param {string} props.birthPostalCode + * @param {Date} props.createdAt + * @param {Date} props.completedAt + * @param {string} props.status + * @param {boolean} props.isCancelled + * @param {boolean} props.isPublished + * @param {boolean} props.isRejectedForFraud + * @param {number} props.juryId + * @param {number} props.pixScore + * @param {Array} props.competenceMarks + * @param {JuryComment} props.commentForCandidate + * @param {JuryComment} props.commentForOrganization + * @param {string} props.commentByJury + * @param {Array} props.certificationIssueReports + * @param {Object} props.complementaryCertificationCourseResultWithExternal + * @param {Object} props.commonComplementaryCertificationCourseResult + * @param {string} props.version + */ constructor({ certificationCourseId, sessionId, @@ -75,6 +105,22 @@ class JuryCertification { }), ); + const { + commentByAutoJury, + commentForCandidate: manualCommentForCandidate, + commentForOrganization: manualCommentForOrganization, + } = juryCertificationDTO; + const commentForCandidate = new JuryComment({ + commentByAutoJury, + fallbackComment: manualCommentForCandidate, + context: JuryCommentContexts.CANDIDATE, + }); + const commentForOrganization = new JuryComment({ + commentByAutoJury, + fallbackComment: manualCommentForOrganization, + context: JuryCommentContexts.ORGANIZATION, + }); + return new JuryCertification({ certificationCourseId: juryCertificationDTO.certificationCourseId, sessionId: juryCertificationDTO.sessionId, @@ -97,8 +143,8 @@ class JuryCertification { juryId: juryCertificationDTO.juryId, pixScore: juryCertificationDTO.pixScore, competenceMarks, - commentForCandidate: juryCertificationDTO.commentForCandidate, - commentForOrganization: juryCertificationDTO.commentForOrganization, + commentForCandidate, + commentForOrganization, commentByJury: juryCertificationDTO.commentByJury, certificationIssueReports, complementaryCertificationCourseResultWithExternal, diff --git a/api/lib/domain/models/PrivateCertificate.js b/api/lib/domain/models/PrivateCertificate.js index 247d3079d4d..cddc6d68fc2 100644 --- a/api/lib/domain/models/PrivateCertificate.js +++ b/api/lib/domain/models/PrivateCertificate.js @@ -1,14 +1,38 @@ import { status as assessmentResultStatuses } from '../../../src/shared/domain/models/AssessmentResult.js'; +import { JuryComment, JuryCommentContexts } from '../../../src/certification/shared/domain/models/JuryComment.js'; -const status = { +/** + * @readonly + * @enum {string} + */ +const status = Object.freeze({ REJECTED: 'rejected', VALIDATED: 'validated', ERROR: 'error', CANCELLED: 'cancelled', STARTED: 'started', -}; +}); class PrivateCertificate { + /** + * @param {Object} props + * @param {number} props.id + * @param {string} props.firstName + * @param {string} props.lastName + * @param {string} props.birthplace + * @param {boolean} props.isPublished + * @param {number} props.userId + * @param {Date} props.date + * @param {Date} props.deliveredAt + * @param {string} props.certificationCenter + * @param {number} props.pixScore + * @param {status} props.status + * @param {JuryComment} props.commentForCandidate + * @param {Array} props.certifiedBadgeImages + * @param {Object} props.resultCompetenceTree + * @param {string} props.verificationCode + * @param {Date} props.maxReachableLevelOnCertificationDate + */ constructor({ id, firstName, @@ -60,6 +84,7 @@ class PrivateCertificate { certificationCenter, pixScore, commentForCandidate, + commentByAutoJury, certifiedBadgeImages, resultCompetenceTree = null, verificationCode, @@ -68,6 +93,11 @@ class PrivateCertificate { isCancelled, }) { const status = _computeStatus(assessmentResultStatus, isCancelled); + const juryComment = new JuryComment({ + commentByAutoJury, + fallbackComment: commentForCandidate, + context: JuryCommentContexts.CANDIDATE, + }); return new PrivateCertificate({ id, firstName, @@ -80,7 +110,7 @@ class PrivateCertificate { deliveredAt, certificationCenter, pixScore, - commentForCandidate, + commentForCandidate: juryComment, certifiedBadgeImages, resultCompetenceTree, verificationCode, diff --git a/api/lib/infrastructure/repositories/certificate-repository.js b/api/lib/infrastructure/repositories/certificate-repository.js index 0a26e9e18d1..571638c472f 100644 --- a/api/lib/infrastructure/repositories/certificate-repository.js +++ b/api/lib/infrastructure/repositories/certificate-repository.js @@ -122,6 +122,7 @@ function _selectPrivateCertificates() { maxReachableLevelOnCertificationDate: 'certification-courses.maxReachableLevelOnCertificationDate', pixScore: 'assessment-results.pixScore', commentForCandidate: 'assessment-results.commentForCandidate', + commentByAutoJury: 'assessment-results.commentByAutoJury', assessmentResultStatus: 'assessment-results.status', assessmentResultId: 'assessment-results.id', competenceMarks: knex.raw(` diff --git a/api/lib/infrastructure/repositories/certification-result-repository.js b/api/lib/infrastructure/repositories/certification-result-repository.js index a79ca930bd4..a65ec1fd90d 100644 --- a/api/lib/infrastructure/repositories/certification-result-repository.js +++ b/api/lib/infrastructure/repositories/certification-result-repository.js @@ -59,6 +59,7 @@ function _selectCertificationResults() { sessionId: 'certification-courses.sessionId', pixScore: 'assessment-results.pixScore', assessmentResultStatus: 'assessment-results.status', + commentByAutoJury: 'assessment-results.commentByAutoJury', commentForOrganization: 'assessment-results.commentForOrganization', emitter: 'assessment-results.emitter', }) diff --git a/api/lib/infrastructure/repositories/jury-certification-repository.js b/api/lib/infrastructure/repositories/jury-certification-repository.js index 78ef2f5362c..fc768385d4b 100644 --- a/api/lib/infrastructure/repositories/jury-certification-repository.js +++ b/api/lib/infrastructure/repositories/jury-certification-repository.js @@ -97,6 +97,7 @@ function _selectJuryCertifications() { commentForCandidate: 'assessment-results.commentForCandidate', commentForOrganization: 'assessment-results.commentForOrganization', commentByJury: 'assessment-results.commentByJury', + commentByAutoJury: 'assessment-results.commentByAutoJury', }) .from('certification-courses') .join('assessments', 'assessments.certificationCourseId', 'certification-courses.id') diff --git a/api/lib/infrastructure/serializers/jsonapi/jury-certification-serializer.js b/api/lib/infrastructure/serializers/jsonapi/jury-certification-serializer.js index b2cf0b72368..17f175f62ec 100644 --- a/api/lib/infrastructure/serializers/jsonapi/jury-certification-serializer.js +++ b/api/lib/infrastructure/serializers/jsonapi/jury-certification-serializer.js @@ -2,13 +2,15 @@ import jsonapiSerializer from 'jsonapi-serializer'; const { Serializer } = jsonapiSerializer; -const serialize = function (juryCertification) { +const serialize = function (juryCertification, { translate }) { return new Serializer('certifications', { transform(juryCertification) { return { id: juryCertification.certificationCourseId, ...juryCertification, competencesWithMark: juryCertification.competenceMarks, + commentForOrganization: juryCertification.commentForOrganization.getComment(translate), + commentForCandidate: juryCertification.commentForCandidate.getComment(translate), }; }, attributes: [ diff --git a/api/lib/infrastructure/serializers/jsonapi/private-certificate-serializer.js b/api/lib/infrastructure/serializers/jsonapi/private-certificate-serializer.js index e4c87b42268..aaa520f3fcf 100644 --- a/api/lib/infrastructure/serializers/jsonapi/private-certificate-serializer.js +++ b/api/lib/infrastructure/serializers/jsonapi/private-certificate-serializer.js @@ -50,8 +50,14 @@ const attributes = [ 'maxReachableLevelOnCertificationDate', ]; -const serialize = function (certificate) { +const serialize = function (certificate, { translate }) { return new Serializer('certifications', { + transform(privateCertificate) { + return { + ...privateCertificate, + commentForCandidate: privateCertificate.commentForCandidate.getComment(translate), + }; + }, typeForAttribute, attributes, resultCompetenceTree, diff --git a/api/lib/infrastructure/utils/csv/certification-results/CertificationResultsCsvValues.js b/api/lib/infrastructure/utils/csv/certification-results/CertificationResultsCsvValues.js index 87a65be4c57..3ab761c05a0 100644 --- a/api/lib/infrastructure/utils/csv/certification-results/CertificationResultsCsvValues.js +++ b/api/lib/infrastructure/utils/csv/certification-results/CertificationResultsCsvValues.js @@ -88,7 +88,7 @@ class CertificationResultsCsvValues { return this.getTranslation(CertificationResultsCsvValues.VALUES.REJECTED_AUTOMATICALLY_COMMENT); } - return certificationResult.commentForOrganization; + return certificationResult.commentForOrganization.getComment(this.translate); } getComplementaryCertificationStatus({ certificationResult, sessionComplementaryCertificationsLabel }) { diff --git a/api/src/certification/session/domain/models/Center.js b/api/src/certification/session/domain/models/Center.js index 307e7c60c3d..c50b1d59343 100644 --- a/api/src/certification/session/domain/models/Center.js +++ b/api/src/certification/session/domain/models/Center.js @@ -1,3 +1,4 @@ +import { assertEnumValue } from '../../../../shared/domain/models/asserts.js'; import { CenterTypes } from './CenterTypes.js'; export class Center { @@ -8,7 +9,7 @@ export class Center { * @param {Array} props.habilitations List of complementary certification id */ constructor({ id, type, habilitations = [] } = {}) { - Center.#assertType(type); + assertEnumValue(CenterTypes, type); this.id = id; this.type = type; @@ -18,10 +19,4 @@ export class Center { get hasBillingMode() { return this.type !== CenterTypes.SCO; } - - static #assertType(type) { - if (!CenterTypes.contains(type)) { - throw new TypeError('Illegal argument provided'); - } - } } diff --git a/api/src/certification/session/domain/models/CenterTypes.js b/api/src/certification/session/domain/models/CenterTypes.js index 89cf2ae5507..bb5472c9a4c 100644 --- a/api/src/certification/session/domain/models/CenterTypes.js +++ b/api/src/certification/session/domain/models/CenterTypes.js @@ -3,16 +3,8 @@ * @readonly * @enum {string} */ -const CENTER_TYPES = { +export const CenterTypes = Object.freeze({ SUP: 'SUP', SCO: 'SCO', PRO: 'PRO', -}; - -export const CenterTypes = Object.freeze({ - ...CENTER_TYPES, - - contains: (value) => { - return Object.keys(CENTER_TYPES).some((key) => CENTER_TYPES[key] === value); - }, }); diff --git a/api/src/certification/shared/domain/models/JuryComment.js b/api/src/certification/shared/domain/models/JuryComment.js new file mode 100644 index 00000000000..8795dd44477 --- /dev/null +++ b/api/src/certification/shared/domain/models/JuryComment.js @@ -0,0 +1,52 @@ +import { assertEnumValue } from '../../../../shared/domain/models/asserts.js'; + +/** + * @readonly + * @enum {string} + */ +const JuryCommentContexts = Object.freeze({ + CANDIDATE: 'candidate', + ORGANIZATION: 'organization', +}); + +/** + * @readonly + * @enum {string} + */ +const AutoJuryCommentKeys = Object.freeze({ + FRAUD: 'FRAUD', + CANCELLED_DUE_TO_NEUTRALIZATION: 'CANCELLED_DUE_TO_NEUTRALIZATION', +}); + +class JuryComment { + /** + * @param {Object} props + * @param {AutoJuryCommentKeys} props.commentByAutoJury + * @param {string} props.fallbackComment + * @param {JuryCommentContexts} props.context mandatory if AutoJuryCommentKeys given + */ + constructor({ commentByAutoJury, fallbackComment, context }) { + this.commentByAutoJury = AutoJuryCommentKeys[commentByAutoJury]; + + if (this.commentByAutoJury) { + assertEnumValue(JuryCommentContexts, context); + this.context = context; + } + + this.fallbackComment = fallbackComment; + } + + getComment(translate) { + return this.#shouldBeTranslated() ? translate(this.#getKeyToTranslate()) : this.fallbackComment; + } + + #getKeyToTranslate() { + return `jury.comment.${this.commentByAutoJury}.${this.context}`; + } + + #shouldBeTranslated() { + return !!this.commentByAutoJury; + } +} + +export { JuryComment, JuryCommentContexts, AutoJuryCommentKeys }; diff --git a/api/src/shared/domain/models/asserts.js b/api/src/shared/domain/models/asserts.js index 0c996c9f124..a71ed69e9a5 100644 --- a/api/src/shared/domain/models/asserts.js +++ b/api/src/shared/domain/models/asserts.js @@ -3,3 +3,10 @@ export function assertNotNullOrUndefined(value, errorMessage = 'Ne doit pas êtr throw new Error(errorMessage); } } + +export function assertEnumValue(enumObject, value) { + const isValidEnumValue = Object.keys(enumObject).some((key) => enumObject[key] === value); + if (!isValidEnumValue) { + throw new TypeError('Illegal enum value provided'); + } +} diff --git a/api/tests/acceptance/application/certification-courses/certification-course-controller_test.js b/api/tests/acceptance/application/certification-courses/certification-course-controller_test.js index 1d82db0c89a..d2ad62b93dd 100644 --- a/api/tests/acceptance/application/certification-courses/certification-course-controller_test.js +++ b/api/tests/acceptance/application/certification-courses/certification-course-controller_test.js @@ -15,6 +15,7 @@ import { KnowledgeElement } from '../../../../lib/domain/models/KnowledgeElement import { ComplementaryCertificationCourseResult } from '../../../../lib/domain/models/ComplementaryCertificationCourseResult.js'; import { CertificationVersion } from '../../../../src/shared/domain/models/CertificationVersion.js'; import { config } from '../../../../lib/config.js'; +import { AutoJuryCommentKeys } from '../../../../src/certification/shared/domain/models/JuryComment.js'; describe('Acceptance | API | Certification Course', function () { let server; @@ -265,8 +266,7 @@ describe('Acceptance | API | Certification Course', function () { assessmentId: 159, pixScore: 55, juryId: 66, - commentForCandidate: 'comment candidate', - commentForOrganization: 'comment organization', + commentByAutoJury: AutoJuryCommentKeys.FRAUD, commentByJury: 'comment jury', status: 'rejected', }); @@ -317,9 +317,11 @@ describe('Acceptance | API | Certification Course', function () { 'completed-at': new Date('2020-02-01'), 'pix-score': 55, 'jury-id': 66, - 'comment-for-candidate': 'comment candidate', + 'comment-for-candidate': + "Les conditions de passation du test de certification n'ayant pas été respectées et ayant fait l'objet d'un signalement pour fraude, votre certification a été invalidée en conséquence.", 'comment-by-jury': 'comment jury', - 'comment-for-organization': 'comment organization', + 'comment-for-organization': + 'Une situation de fraude a été détectée : après analyse, nous avons statué sur un rejet de la certification.', version: 2, 'competences-with-mark': [ { diff --git a/api/tests/acceptance/application/certifications/index_test.js b/api/tests/acceptance/application/certifications/index_test.js index dbb5377e7a9..b2452f442c6 100644 --- a/api/tests/acceptance/application/certifications/index_test.js +++ b/api/tests/acceptance/application/certifications/index_test.js @@ -11,6 +11,7 @@ import { import { Assessment } from '../../../../src/shared/domain/models/Assessment.js'; import { generateCertificateVerificationCode } from '../../../../lib/domain/services/verify-certificate-code-service.js'; import { CertificationCenterInvitation } from '../../../../lib/domain/models/CertificationCenterInvitation.js'; +import { AutoJuryCommentKeys } from '../../../../src/certification/shared/domain/models/JuryComment.js'; describe('Acceptance | API | Certifications', function () { let server, options; @@ -136,7 +137,8 @@ describe('Acceptance | API | Certifications', function () { birthdate: certificationCourse.birthdate, birthplace: certificationCourse.birthplace, 'certification-center': session.certificationCenter, - 'comment-for-candidate': assessmentResult.commentForCandidate, + 'comment-for-candidate': + "Les conditions de passation du test de certification n'ayant pas été respectées et ayant fait l'objet d'un signalement pour fraude, votre certification a été invalidée en conséquence.", date: certificationCourse.createdAt, 'first-name': certificationCourse.firstName, 'delivered-at': session.publishedAt, @@ -281,7 +283,8 @@ describe('Acceptance | API | Certifications', function () { birthdate: certificationCourse.birthdate, birthplace: certificationCourse.birthplace, 'certification-center': session.certificationCenter, - 'comment-for-candidate': assessmentResult.commentForCandidate, + 'comment-for-candidate': + "Les conditions de passation du test de certification n'ayant pas été respectées et ayant fait l'objet d'un signalement pour fraude, votre certification a été invalidée en conséquence.", date: certificationCourse.createdAt, 'first-name': certificationCourse.firstName, 'delivered-at': session.publishedAt, @@ -686,6 +689,7 @@ async function _buildDatabaseForV2Certification() { pixScore: 23, emitter: 'PIX-ALGO', status: 'validated', + commentByAutoJury: AutoJuryCommentKeys.FRAUD, }); const { id } = databaseBuilder.factory.buildComplementaryCertificationCourse({ certificationCourseId: certificationCourse.id, diff --git a/api/tests/acceptance/application/session/session-controller-get-session-results-to-download_test.js b/api/tests/acceptance/application/session/session-controller-get-session-results-to-download_test.js index a8736f83545..000b829bb64 100644 --- a/api/tests/acceptance/application/session/session-controller-get-session-results-to-download_test.js +++ b/api/tests/acceptance/application/session/session-controller-get-session-results-to-download_test.js @@ -1,6 +1,7 @@ import { createServer, databaseBuilder, expect } from '../../../test-helper.js'; import jsonwebtoken from 'jsonwebtoken'; import { config as settings } from '../../../../lib/config.js'; +import { AutoJuryCommentKeys } from '../../../../src/certification/shared/domain/models/JuryComment.js'; describe('Acceptance | Controller | session-controller-get-session-results-to-download', function () { describe('GET /api/sessions/download-all-results/{token}', function () { @@ -39,7 +40,10 @@ describe('Acceptance | Controller | session-controller-get-session-results-to-do }); const assessmentId1 = dbf.buildAssessment({ certificationCourseId: certif1.id }).id; - dbf.buildAssessment({ certificationCourseId: certif2.id }); + dbf.buildAssessment({ + certificationCourseId: certif2.id, + commentByAutoJury: AutoJuryCommentKeys.CANCELLED_DUE_TO_NEUTRALIZATION, + }); dbf.buildAssessmentResult({ assessmentId: assessmentId1, createdAt: new Date('2018-04-15T00:00:00Z') }); diff --git a/api/tests/certification/course/acceptance/application/organization-controller_test.js b/api/tests/certification/course/acceptance/application/organization-controller_test.js index ac1af845e46..ccb2a96431c 100644 --- a/api/tests/certification/course/acceptance/application/organization-controller_test.js +++ b/api/tests/certification/course/acceptance/application/organization-controller_test.js @@ -7,6 +7,7 @@ import { import { createServer } from '../../../../../server.js'; import { Membership } from '../../../../../lib/domain/models/Membership.js'; +import { AutoJuryCommentKeys } from '../../../../../src/certification/shared/domain/models/JuryComment.js'; describe('Certification | Course | Acceptance | Application | organization-controller', function () { let server; @@ -43,6 +44,7 @@ describe('Certification | Course | Acceptance | Application | organization-contr databaseBuilder.factory.buildAssessmentResult.last({ certificationCourseId: certificationCourse.id, + commentByAutoJury: AutoJuryCommentKeys.CANCELLED_DUE_TO_NEUTRALIZATION, }); await databaseBuilder.commit(); diff --git a/api/tests/certification/session/unit/domain/models/Center_test.js b/api/tests/certification/session/unit/domain/models/Center_test.js index 46b7ef00cdc..f03302654f2 100644 --- a/api/tests/certification/session/unit/domain/models/Center_test.js +++ b/api/tests/certification/session/unit/domain/models/Center_test.js @@ -32,7 +32,7 @@ describe('Unit | Certification | Session | Domain | Models | Center', function ( // then expect(error).to.be.an.instanceOf(TypeError); - expect(error.message).to.equal('Illegal argument provided'); + expect(error.message).to.equal('Illegal enum value provided'); }); }); }); diff --git a/api/tests/certification/shared/unit/domain/models/JuryComment_test.js b/api/tests/certification/shared/unit/domain/models/JuryComment_test.js new file mode 100644 index 00000000000..940c5415f11 --- /dev/null +++ b/api/tests/certification/shared/unit/domain/models/JuryComment_test.js @@ -0,0 +1,60 @@ +import { + JuryComment, + AutoJuryCommentKeys, + JuryCommentContexts, +} from '../../../../../../src/certification/shared/domain/models/JuryComment.js'; +import { expect } from '../../../../../test-helper.js'; +import { getI18n } from '../../../../../tooling/i18n/i18n.js'; + +describe('Unit | Domain | Models | JuryComment', function () { + let translate; + + beforeEach(function () { + translate = getI18n().__; + }); + + describe('#getComment', function () { + context('when there is an automatic jury comment', function () { + it('expects a context', function () { + // given + const juryCommentBadParameters = { + fallbackComment: 'Le petit Max a copié la réponse sur Lily!!', + commentByAutoJury: AutoJuryCommentKeys.FRAUD, + context: 'NOT A VALID CONTEXT', + }; + + // when, then + expect(() => new JuryComment(juryCommentBadParameters)).to.throw(); + }); + + it('should return the translated comment matching the key', function () { + // given + const juryComment = new JuryComment({ + fallbackComment: 'Le petit Max a copié la réponse sur Lily!!', + commentByAutoJury: AutoJuryCommentKeys.FRAUD, + context: JuryCommentContexts.CANDIDATE, + }); + + // when + const translatedComment = juryComment.getComment(translate); + + // then + expect(translatedComment).to.equal(translate('jury.comment.FRAUD.candidate')); + }); + }); + + it('should return the fallback comment', function () { + // given + const juryComment = new JuryComment({ + fallbackComment: 'Belle perf!', + context: JuryCommentContexts.CANDIDATE, + }); + + // when + const translatedComment = juryComment.getComment(translate); + + // then + expect(translatedComment).to.equal('Belle perf!'); + }); + }); +}); diff --git a/api/tests/integration/infrastructure/repositories/certificate-repository_private_test.js b/api/tests/integration/infrastructure/repositories/certificate-repository_private_test.js index f36ab5b32ad..f28ec2a9b9c 100644 --- a/api/tests/integration/infrastructure/repositories/certificate-repository_private_test.js +++ b/api/tests/integration/infrastructure/repositories/certificate-repository_private_test.js @@ -9,6 +9,7 @@ import { import { NotFoundError } from '../../../../lib/domain/errors.js'; import * as certificateRepository from '../../../../lib/infrastructure/repositories/certificate-repository.js'; +import { AutoJuryCommentKeys } from '../../../../src/certification/shared/domain/models/JuryComment.js'; describe('Integration | Infrastructure | Repository | Certificate_private', function () { const minimalLearningContent = [ @@ -282,6 +283,44 @@ describe('Integration | Infrastructure | Repository | Certificate_private', func expect(privateCertificate).to.deepEqualInstanceOmitting(expectedPrivateCertificate, ['resultCompetenceTree']); }); + context('when the comment for candidate is automatically set', function () { + it('should return a PrivateCertificate with matching key', async function () { + // given + const learningContentObjects = learningContentBuilder.fromAreas(minimalLearningContent); + mockLearningContent(learningContentObjects); + + const userId = databaseBuilder.factory.buildUser().id; + const privateCertificateData = { + firstName: 'Sarah Michelle', + lastName: 'Gellar', + birthdate: '1977-04-14', + birthplace: 'Saint-Ouen', + isPublished: true, + isCancelled: false, + userId, + date: new Date('2020-01-01'), + verificationCode: 'ABCDE-F', + maxReachableLevelOnCertificationDate: 5, + deliveredAt: new Date('2021-05-05'), + certificationCenter: 'Centre des poules bien dodues', + pixScore: 51, + commentForCandidate: 'Il aime beaucoup les mangues, mais il a fraudé :( !', + commentByAutoJury: AutoJuryCommentKeys.FRAUD, + }; + + const { certificationCourseId } = await _buildValidPrivateCertificate(privateCertificateData); + + // when + const privateCertificate = await certificateRepository.getPrivateCertificate(certificationCourseId); + + // then + const expectedPrivateCertificate = domainBuilder.buildPrivateCertificate.validated({ + id: certificationCourseId, + ...privateCertificateData, + }); + expect(privateCertificate).to.deepEqualInstanceOmitting(expectedPrivateCertificate, ['resultCompetenceTree']); + }); + }); describe('when "locale" is french', function () { it('should return a PrivateCertificate with french resultCompetenceTree', async function () { // given @@ -930,6 +969,7 @@ async function _buildValidPrivateCertificate(privateCertificateData, buildCompet pixScore: privateCertificateData.pixScore, status: 'validated', commentForCandidate: privateCertificateData.commentForCandidate, + commentByAutoJury: privateCertificateData.commentByAutoJury, createdAt: new Date('2021-01-01'), }).id; diff --git a/api/tests/integration/infrastructure/repositories/certification-result-repository_test.js b/api/tests/integration/infrastructure/repositories/certification-result-repository_test.js index 319db8fe576..94e1927a7bf 100644 --- a/api/tests/integration/infrastructure/repositories/certification-result-repository_test.js +++ b/api/tests/integration/infrastructure/repositories/certification-result-repository_test.js @@ -2,6 +2,7 @@ import { expect, databaseBuilder, domainBuilder } from '../../../test-helper.js' import * as certificationResultRepository from '../../../../lib/infrastructure/repositories/certification-result-repository.js'; import { CertificationResult } from '../../../../lib/domain/models/CertificationResult.js'; import { ComplementaryCertificationCourseResult } from '../../../../lib/domain/models/ComplementaryCertificationCourseResult.js'; +import { AutoJuryCommentKeys } from '../../../../src/certification/shared/domain/models/JuryComment.js'; describe('Integration | Infrastructure | Repository | Certification Result', function () { describe('#findBySessionId', function () { @@ -525,6 +526,7 @@ describe('Integration | Infrastructure | Repository | Certification Result', fun pixScore: 123, status: CertificationResult.status.VALIDATED, commentForOrganization: 'Un commentaire orga 1', + commentByAutoJury: AutoJuryCommentKeys.CANCELLED_DUE_TO_NEUTRALIZATION, }).id; databaseBuilder.factory.buildAssessmentResult.last({ certificationCourseId: certificationCourseId2, @@ -615,6 +617,7 @@ describe('Integration | Infrastructure | Repository | Certification Result', fun status: CertificationResult.status.VALIDATED, pixScore: 123, commentForOrganization: 'Un commentaire orga 1', + commentByAutoJury: AutoJuryCommentKeys.CANCELLED_DUE_TO_NEUTRALIZATION, competencesWithMark: [ domainBuilder.buildCompetenceMark({ id: 123, diff --git a/api/tests/integration/infrastructure/repositories/jury-certification-repository_test.js b/api/tests/integration/infrastructure/repositories/jury-certification-repository_test.js index ee4f70ca92f..99258b1b35d 100644 --- a/api/tests/integration/infrastructure/repositories/jury-certification-repository_test.js +++ b/api/tests/integration/infrastructure/repositories/jury-certification-repository_test.js @@ -2,6 +2,7 @@ import { expect, databaseBuilder, domainBuilder, catchErr } from '../../../test- import { NotFoundError } from '../../../../lib/domain/errors.js'; import * as juryCertificationRepository from '../../../../lib/infrastructure/repositories/jury-certification-repository.js'; import { ComplementaryCertificationCourseResult } from '../../../../lib/domain/models/ComplementaryCertificationCourseResult.js'; +import { AutoJuryCommentKeys } from '../../../../src/certification/shared/domain/models/JuryComment.js'; describe('Integration | Infrastructure | Repository | Jury Certification', function () { describe('#get', function () { @@ -84,6 +85,7 @@ describe('Integration | Infrastructure | Repository | Jury Certification', funct commentForOrganization: 'Un commentaire orga', commentForCandidate: 'Un commentaire candidat', commentByJury: 'Un commentaire jury', + commentByAutoJury: AutoJuryCommentKeys.FRAUD, juryId: 22, }).id; databaseBuilder.factory.buildCompetenceMark({ @@ -133,6 +135,7 @@ describe('Integration | Infrastructure | Repository | Jury Certification', funct commentForOrganization: 'Un commentaire orga', commentForCandidate: 'Un commentaire candidat', commentByJury: 'Un commentaire jury', + commentByAutoJury: AutoJuryCommentKeys.FRAUD, competenceMarks: [expectedCompetenceMark], certificationIssueReports: [], version: 2, diff --git a/api/tests/shared/unit/domain/models/asserts_test.js b/api/tests/shared/unit/domain/models/asserts_test.js index 59449798657..b857370a7f2 100644 --- a/api/tests/shared/unit/domain/models/asserts_test.js +++ b/api/tests/shared/unit/domain/models/asserts_test.js @@ -1,4 +1,4 @@ -import { assertNotNullOrUndefined } from '../../../../../src/shared/domain/models/asserts.js'; +import { assertNotNullOrUndefined, assertEnumValue } from '../../../../../src/shared/domain/models/asserts.js'; import { expect } from '../../../../test-helper.js'; describe('Unit | Shared | Models | asserts', function () { @@ -23,4 +23,26 @@ describe('Unit | Shared | Models | asserts', function () { }); }); }); + + describe('#assertEnumValue', function () { + describe('given invalid value', function () { + it('should throw', function () { + // given + const anEnum = { X: 'y' }; + + // When, Then + expect(() => assertEnumValue(anEnum, 'z')).to.throw(); + }); + }); + + describe('given valid values', function () { + it('should not throw', function () { + // given + const anEnum = { X: 'y' }; + + // When, Then + expect(() => assertEnumValue(anEnum, 'y')).not.to.throw(); + }); + }); + }); }); diff --git a/api/tests/tooling/domain-builder/factory/build-certification-result.js b/api/tests/tooling/domain-builder/factory/build-certification-result.js index 9ee60295615..54fd8d3f191 100644 --- a/api/tests/tooling/domain-builder/factory/build-certification-result.js +++ b/api/tests/tooling/domain-builder/factory/build-certification-result.js @@ -1,4 +1,5 @@ import { CertificationResult } from '../../../../lib/domain/models/CertificationResult.js'; +import { JuryComment, JuryCommentContexts } from '../../../../src/certification/shared/domain/models/JuryComment.js'; const buildCertificationResult = function ({ id = 123, @@ -13,9 +14,16 @@ const buildCertificationResult = function ({ pixScore = 0, emitter = 'PIX-ALGO', commentForOrganization = 'comment organization', + commentByAutoJury, competencesWithMark = [], complementaryCertificationCourseResults = [], } = {}) { + const juryCommentForOrganization = new JuryComment({ + fallbackComment: commentForOrganization, + commentByAutoJury, + context: JuryCommentContexts.ORGANIZATION, + }); + return new CertificationResult({ id, firstName, @@ -28,7 +36,7 @@ const buildCertificationResult = function ({ status, pixScore, emitter, - commentForOrganization, + commentForOrganization: juryCommentForOrganization, competencesWithMark, complementaryCertificationCourseResults, }); @@ -111,6 +119,7 @@ buildCertificationResult.cancelled = function ({ sessionId, pixScore, emitter, + commentByAutoJury, commentForOrganization, competencesWithMark, complementaryCertificationCourseResults, @@ -127,6 +136,7 @@ buildCertificationResult.cancelled = function ({ status: CertificationResult.status.CANCELLED, pixScore, emitter, + commentByAutoJury, commentForOrganization, competencesWithMark, complementaryCertificationCourseResults, diff --git a/api/tests/tooling/domain-builder/factory/build-jury-certification.js b/api/tests/tooling/domain-builder/factory/build-jury-certification.js index 81a23e98633..e38eb058fd3 100644 --- a/api/tests/tooling/domain-builder/factory/build-jury-certification.js +++ b/api/tests/tooling/domain-builder/factory/build-jury-certification.js @@ -2,6 +2,7 @@ import { CertificationVersion } from '../../../../src/shared/domain/models/Certi import { JuryCertification } from '../../../../lib/domain/models/JuryCertification.js'; import { buildCertificationIssueReport } from './build-certification-issue-report.js'; import { buildCompetenceMark } from './build-competence-mark.js'; +import { JuryComment, JuryCommentContexts } from '../../../../src/certification/shared/domain/models/JuryComment.js'; const buildJuryCertification = function ({ certificationCourseId = 123, @@ -27,6 +28,7 @@ const buildJuryCertification = function ({ commentForCandidate = 'comment candidate', commentForOrganization = 'comment organization', commentByJury, + commentByAutoJury, competenceMarks = [buildCompetenceMark()], certificationIssueReports = [buildCertificationIssueReport()], commonComplementaryCertificationCourseResult, @@ -54,8 +56,16 @@ const buildJuryCertification = function ({ completedAt, pixScore, juryId, - commentForCandidate, - commentForOrganization, + commentForCandidate: new JuryComment({ + commentByAutoJury, + fallbackComment: commentForCandidate, + context: JuryCommentContexts.CANDIDATE, + }), + commentForOrganization: new JuryComment({ + commentByAutoJury, + fallbackComment: commentForOrganization, + context: JuryCommentContexts.ORGANIZATION, + }), commentByJury, competenceMarks, certificationIssueReports, diff --git a/api/tests/tooling/domain-builder/factory/build-private-certificate.js b/api/tests/tooling/domain-builder/factory/build-private-certificate.js index dc78a33f750..e5a2631ea60 100644 --- a/api/tests/tooling/domain-builder/factory/build-private-certificate.js +++ b/api/tests/tooling/domain-builder/factory/build-private-certificate.js @@ -1,4 +1,5 @@ import { PrivateCertificate } from '../../../../lib/domain/models/PrivateCertificate.js'; +import { JuryComment, JuryCommentContexts } from '../../../../src/certification/shared/domain/models/JuryComment.js'; const buildPrivateCertificate = function ({ id = 1, @@ -12,6 +13,7 @@ const buildPrivateCertificate = function ({ date = new Date('2018-12-01T01:02:03Z'), deliveredAt = new Date('2018-10-03T01:02:03Z'), commentForCandidate = null, + commentByAutoJury = null, pixScore = 156, status = PrivateCertificate.status.VALIDATED, certifiedBadgeImages = [], @@ -19,6 +21,11 @@ const buildPrivateCertificate = function ({ verificationCode = 'P-BBBCCCDD', maxReachableLevelOnCertificationDate = 5, } = {}) { + const juryComment = new JuryComment({ + commentByAutoJury, + fallbackComment: commentForCandidate, + context: JuryCommentContexts.CANDIDATE, + }); return new PrivateCertificate({ id, firstName, @@ -30,7 +37,7 @@ const buildPrivateCertificate = function ({ certificationCenter, date, deliveredAt, - commentForCandidate, + commentForCandidate: juryComment, pixScore, status, resultCompetenceTree, @@ -52,6 +59,7 @@ buildPrivateCertificate.cancelled = function ({ date, deliveredAt, commentForCandidate, + commentByAutoJury, pixScore, certifiedBadgeImages, resultCompetenceTree, @@ -70,6 +78,7 @@ buildPrivateCertificate.cancelled = function ({ date, deliveredAt, commentForCandidate, + commentByAutoJury, pixScore, certifiedBadgeImages, resultCompetenceTree, @@ -90,6 +99,7 @@ buildPrivateCertificate.validated = function ({ certificationCenter, date, deliveredAt, + commentByAutoJury, commentForCandidate, pixScore, certifiedBadgeImages, @@ -109,6 +119,7 @@ buildPrivateCertificate.validated = function ({ date, deliveredAt, commentForCandidate, + commentByAutoJury, pixScore, certifiedBadgeImages, resultCompetenceTree, @@ -130,6 +141,7 @@ buildPrivateCertificate.rejected = function ({ date, deliveredAt, commentForCandidate, + commentByAutoJury, pixScore, certifiedBadgeImages, resultCompetenceTree, @@ -147,6 +159,7 @@ buildPrivateCertificate.rejected = function ({ certificationCenter, date, deliveredAt, + commentByAutoJury, commentForCandidate, pixScore, certifiedBadgeImages, @@ -169,6 +182,7 @@ buildPrivateCertificate.error = function ({ date, deliveredAt, commentForCandidate, + commentByAutoJury, pixScore, certifiedBadgeImages, resultCompetenceTree, @@ -187,6 +201,7 @@ buildPrivateCertificate.error = function ({ date, deliveredAt, commentForCandidate, + commentByAutoJury, pixScore, certifiedBadgeImages, resultCompetenceTree, @@ -208,6 +223,7 @@ buildPrivateCertificate.started = function ({ date, deliveredAt, commentForCandidate, + commentByAutoJury, pixScore, certifiedBadgeImages, resultCompetenceTree, @@ -226,6 +242,7 @@ buildPrivateCertificate.started = function ({ date, deliveredAt, commentForCandidate, + commentByAutoJury, pixScore, certifiedBadgeImages, resultCompetenceTree, diff --git a/api/tests/tooling/domain-builder/factory/certification/shared/build-jury-comment.js b/api/tests/tooling/domain-builder/factory/certification/shared/build-jury-comment.js new file mode 100644 index 00000000000..10d95a50302 --- /dev/null +++ b/api/tests/tooling/domain-builder/factory/certification/shared/build-jury-comment.js @@ -0,0 +1,18 @@ +import { + JuryComment, + JuryCommentContexts, +} from '../../../../../../src/certification/shared/domain/models/JuryComment.js'; + +const buildJuryComment = ({ commentByAutoJury, context, fallbackComment } = {}) => { + return new JuryComment({ commentByAutoJury, context, fallbackComment }); +}; + +buildJuryComment.candidate = ({ commentByAutoJury, fallbackComment } = {}) => { + return buildJuryComment({ commentByAutoJury, fallbackComment, context: JuryCommentContexts.CANDIDATE }); +}; + +buildJuryComment.organization = ({ commentByAutoJury, fallbackComment } = {}) => { + return buildJuryComment({ commentByAutoJury, fallbackComment, context: JuryCommentContexts.ORGANIZATION }); +}; + +export { buildJuryComment }; diff --git a/api/tests/unit/application/certification-courses/certification-course-controller_test.js b/api/tests/unit/application/certification-courses/certification-course-controller_test.js index bdc583ec447..2a41bcba131 100644 --- a/api/tests/unit/application/certification-courses/certification-course-controller_test.js +++ b/api/tests/unit/application/certification-courses/certification-course-controller_test.js @@ -4,6 +4,7 @@ import { certificationCourseController } from '../../../../lib/application/certi import { usecases } from '../../../../lib/domain/usecases/index.js'; import { DomainTransaction } from '../../../../lib/infrastructure/DomainTransaction.js'; import { CertificationCourse } from '../../../../lib/domain/models/index.js'; +import { getI18n } from '../../../tooling/i18n/i18n.js'; describe('Unit | Controller | certification-course-controller', function () { let certificationDetailsSerializer; @@ -103,6 +104,7 @@ describe('Unit | Controller | certification-course-controller', function () { params: { id: certificationCourseId, }, + i18n: getI18n(), }; const juryCertification = domainBuilder.buildJuryCertification({ diff --git a/api/tests/unit/application/certifications/certification-controller_test.js b/api/tests/unit/application/certifications/certification-controller_test.js index a04bf52f138..f868721bbcd 100644 --- a/api/tests/unit/application/certifications/certification-controller_test.js +++ b/api/tests/unit/application/certifications/certification-controller_test.js @@ -3,13 +3,14 @@ import { certificationController } from '../../../../lib/application/certificati import { usecases } from '../../../../lib/domain/usecases/index.js'; import { ChallengeNeutralized } from '../../../../lib/domain/events/ChallengeNeutralized.js'; import { ChallengeDeneutralized } from '../../../../lib/domain/events/ChallengeDeneutralized.js'; +import { getI18n } from '../../../tooling/i18n/i18n.js'; describe('Unit | Controller | certifications-controller', function () { describe('#findUserCertifications', function () { it('should return the serialized private certificates of the user', async function () { // given const userId = 1; - const request = { auth: { credentials: { userId } } }; + const request = { auth: { credentials: { userId } }, i18n: getI18n() }; const privateCertificate1 = domainBuilder.buildPrivateCertificate.validated({ id: 123, firstName: 'Dorothé', @@ -73,6 +74,7 @@ describe('Unit | Controller | certifications-controller', function () { const request = { auth: { credentials: { userId } }, params: { id: certificationId }, + i18n: getI18n(), }; const locale = 'fr-fr'; const requestResponseUtilsStub = { extractLocaleFromRequest: sinon.stub() }; diff --git a/api/tests/unit/domain/models/CertificationResult_test.js b/api/tests/unit/domain/models/CertificationResult_test.js index bc5937bbc00..6bf43ce3a7d 100644 --- a/api/tests/unit/domain/models/CertificationResult_test.js +++ b/api/tests/unit/domain/models/CertificationResult_test.js @@ -1,5 +1,6 @@ import { CertificationResult } from '../../../../lib/domain/models/index.js'; import { expect, domainBuilder } from '../../../test-helper.js'; +import { AutoJuryCommentKeys } from '../../../../src/certification/shared/domain/models/JuryComment.js'; const CERTIFICATION_RESULT_STATUS_CANCELLED = CertificationResult.status.CANCELLED; const CERTIFICATION_RESULT_STATUS_ERROR = CertificationResult.status.ERROR; @@ -99,6 +100,26 @@ describe('Unit | Domain | Models | CertificationResult', function () { expect(certificationResult).to.deepEqualInstance(expectedCertificationResult); }); + context('when there is an automatic jury comment', function () { + it('should build a CertificationResult from certification', function () { + // given + const certificationResultDTO = { + ...certificationResultData, + commentByAutoJury: AutoJuryCommentKeys.CANCELLED_DUE_TO_NEUTRALIZATION, + }; + + // when + const certificationResult = CertificationResult.from({ + certificationResultDTO, + }); + + // then + const { commentForOrganization } = domainBuilder.buildCertificationResult(certificationResultDTO); + + expect(certificationResult.commentForOrganization).to.deepEqualInstance(commentForOrganization); + }); + }); + context('status', function () { // Rule disabled to allow dynamic generated tests. See https://github.com/lo1tuma/eslint-plugin-mocha/blob/master/docs/rules/no-setup-in-describe.md#disallow-setup-in-describe-blocks-mochano-setup-in-describe // eslint-disable-next-line mocha/no-setup-in-describe diff --git a/api/tests/unit/domain/models/JuryCertification_test.js b/api/tests/unit/domain/models/JuryCertification_test.js index 98fd2554745..2e5914884ae 100644 --- a/api/tests/unit/domain/models/JuryCertification_test.js +++ b/api/tests/unit/domain/models/JuryCertification_test.js @@ -1,12 +1,14 @@ import { expect, domainBuilder } from '../../../test-helper.js'; import { JuryCertification } from '../../../../lib/domain/models/JuryCertification.js'; +import { AutoJuryCommentKeys } from '../../../../src/certification/shared/domain/models/JuryComment.js'; describe('Unit | Domain | Models | JuryCertification', function () { describe('#from', function () { - let juryCertificationBaseDTO; + let juryCertificationDTO; + let competenceMarkDTOs; beforeEach(function () { - juryCertificationBaseDTO = { + juryCertificationDTO = { certificationCourseId: 123, sessionId: 456, userId: 789, @@ -30,23 +32,10 @@ describe('Unit | Domain | Models | JuryCertification', function () { commentForCandidate: 'coucou', commentForOrganization: 'comment', commentByJury: 'ça va', + commentByAutoJury: null, version: 2, }; - }); - - it('should return an instance of JuryCertification', function () { - // given - const certificationIssueReport = domainBuilder.buildCertificationIssueReport({ id: 555 }); - const juryCertificationDTO = { - ...juryCertificationBaseDTO, - }; - const certificationIssueReports = [certificationIssueReport]; - const commonComplementaryCertificationCourseResult = - domainBuilder.buildComplementaryCertificationCourseResultForJuryCertification({ - acquired: true, - }); - - const competenceMarkDTOs = [ + competenceMarkDTOs = [ { id: 123, score: 10, @@ -57,6 +46,16 @@ describe('Unit | Domain | Models | JuryCertification', function () { competenceId: 'recComp23', }, ]; + }); + + it('should return an instance of JuryCertification', function () { + // given + const certificationIssueReport = domainBuilder.buildCertificationIssueReport({ id: 555 }); + const certificationIssueReports = [certificationIssueReport]; + const commonComplementaryCertificationCourseResult = + domainBuilder.buildComplementaryCertificationCourseResultForJuryCertification({ + acquired: true, + }); const complementaryCertificationCourseResultWithExternal = domainBuilder.buildComplementaryCertificationCourseResultForJuryCertificationWithExternal({ @@ -77,15 +76,72 @@ describe('Unit | Domain | Models | JuryCertification', function () { }); // then - const expectedCompetenceMark = domainBuilder.buildCompetenceMark({ - id: 123, - level: 4, - score: 10, - area_code: '2', - competence_code: '2.3', - competenceId: 'recComp23', - assessmentResultId: 753, + const expectedCompetenceMarks = competenceMarkDTOs.map(domainBuilder.buildCompetenceMark); + const expectedJuryCertification = domainBuilder.buildJuryCertification({ + certificationCourseId: 123, + sessionId: 456, + userId: 789, + assessmentId: 159, + firstName: 'James', + lastName: 'Watt', + birthdate: '1990-01-04', + birthplace: 'Somewhere', + sex: 'M', + birthCountry: 'ENGLAND', + birthINSEECode: '99124', + birthPostalCode: null, + createdAt: new Date('2020-02-20T10:30:00Z'), + completedAt: new Date('2020-02-20T11:00:00Z'), + isCancelled: true, + isPublished: true, + isRejectedForFraud: false, + status: 'rejected', + juryId: 1, + pixScore: 555, + commentForCandidate: 'coucou', + commentForOrganization: 'comment', + commentByJury: 'ça va', + commentByAutoJury: null, + version: 2, + competenceMarks: expectedCompetenceMarks, + certificationIssueReports, + commonComplementaryCertificationCourseResult, + complementaryCertificationCourseResultWithExternal, + }); + expect(juryCertification).to.deepEqualInstance(expectedJuryCertification); + }); + + it('should return an instance of JuryCertification with comment by auto jury', function () { + // given + juryCertificationDTO.commentByAutoJury = 'FRAUD'; + + const certificationIssueReport = domainBuilder.buildCertificationIssueReport({ id: 555 }); + const certificationIssueReports = [certificationIssueReport]; + const commonComplementaryCertificationCourseResult = + domainBuilder.buildComplementaryCertificationCourseResultForJuryCertification({ + acquired: true, + }); + + const complementaryCertificationCourseResultWithExternal = + domainBuilder.buildComplementaryCertificationCourseResultForJuryCertificationWithExternal({ + complementaryCertificationCourseId: 123, + pixComplementaryCertificationBadgeId: 98, + pixAcquired: true, + externalComplementaryCertificationBadgeId: 99, + externalAcquired: true, + }); + + // when + const juryCertification = JuryCertification.from({ + juryCertificationDTO, + certificationIssueReports, + competenceMarkDTOs, + commonComplementaryCertificationCourseResult, + complementaryCertificationCourseResultWithExternal, }); + + // then + const expectedCompetenceMarks = competenceMarkDTOs.map(domainBuilder.buildCompetenceMark); const expectedJuryCertification = domainBuilder.buildJuryCertification({ certificationCourseId: 123, sessionId: 456, @@ -110,8 +166,9 @@ describe('Unit | Domain | Models | JuryCertification', function () { commentForCandidate: 'coucou', commentForOrganization: 'comment', commentByJury: 'ça va', + commentByAutoJury: AutoJuryCommentKeys.FRAUD, version: 2, - competenceMarks: [expectedCompetenceMark], + competenceMarks: expectedCompetenceMarks, certificationIssueReports, commonComplementaryCertificationCourseResult, complementaryCertificationCourseResultWithExternal, diff --git a/api/tests/unit/domain/models/PrivateCertificate_test.js b/api/tests/unit/domain/models/PrivateCertificate_test.js index 1ca16fdde4b..28ce69869e6 100644 --- a/api/tests/unit/domain/models/PrivateCertificate_test.js +++ b/api/tests/unit/domain/models/PrivateCertificate_test.js @@ -1,6 +1,7 @@ import { expect, domainBuilder } from '../../../test-helper.js'; import { PrivateCertificate } from '../../../../lib/domain/models/PrivateCertificate.js'; import { status as assessmentResultStatuses } from '../../../../src/shared/domain/models/AssessmentResult.js'; +import { AutoJuryCommentKeys } from '../../../../src/certification/shared/domain/models/JuryComment.js'; describe('Unit | Domain | Models | PrivateCertificate', function () { context('#static buildFrom', function () { @@ -17,6 +18,7 @@ describe('Unit | Domain | Models | PrivateCertificate', function () { certificationCenter: 'Centre des fruits et légumes', pixScore: 250, commentForCandidate: 'Bravo !', + commentByAutoJury: null, certifiedBadgeImages: [], resultCompetenceTree: null, verificationCode: 'someVerifCode', @@ -88,6 +90,20 @@ describe('Unit | Domain | Models | PrivateCertificate', function () { expect(privateCertificate).to.be.instanceOf(PrivateCertificate); expect(privateCertificate).to.deep.equal(expectedPrivateCertificate); }); + + it('builds PrivateCertificate with an auto jury comment', async function () { + // given + const certificateWithAutoJuryCommentData = { ...commonData, commentByAutoJury: AutoJuryCommentKeys.FRAUD }; + + // when + const privateCertificate = PrivateCertificate.buildFrom(certificateWithAutoJuryCommentData); + + // then + const expectedPrivateCertificate = domainBuilder.buildPrivateCertificate.started( + certificateWithAutoJuryCommentData, + ); + expect(privateCertificate).to.deepEqualInstance(expectedPrivateCertificate); + }); }); context('#setResultCompetenceTree', function () { diff --git a/api/tests/unit/infrastructure/serializers/jsonapi/jury-certification-serializer_test.js b/api/tests/unit/infrastructure/serializers/jsonapi/jury-certification-serializer_test.js index 32781fc94cc..9f7cd0aa7ce 100644 --- a/api/tests/unit/infrastructure/serializers/jsonapi/jury-certification-serializer_test.js +++ b/api/tests/unit/infrastructure/serializers/jsonapi/jury-certification-serializer_test.js @@ -1,147 +1,250 @@ import { expect, domainBuilder } from '../../../../test-helper.js'; import * as serializer from '../../../../../lib/infrastructure/serializers/jsonapi/jury-certification-serializer.js'; +import { getI18n } from '../../../../tooling/i18n/i18n.js'; +import { AutoJuryCommentKeys } from '../../../../../src/certification/shared/domain/models/JuryComment.js'; describe('Unit | Serializer | JSONAPI | jury-certification-serializer', function () { - describe('#serialize', function () { - it('should serialize a JuryCertification', function () { - // given - const certificationCourseId = 123; - const certificationIssueReport = domainBuilder.buildCertificationIssueReport.impactful({ - certificationCourseId, - resolvedAt: new Date(), - resolution: 'le challenge est neutralisé', - hasBeenAutomaticallyResolved: true, - }); - const certificationIssueReports = [certificationIssueReport]; - const competenceMarks = [domainBuilder.buildCompetenceMark()]; - const juryCertification = domainBuilder.buildJuryCertification({ - certificationCourseId, - sessionId: 11, - userId: 867, - assessmentId: 44, - firstName: 'James', - lastName: 'Watt', - birthdate: '1990-01-04', - birthplace: 'Somewhere', - sex: 'M', - birthCountry: 'ENGLAND', - birthINSEECode: '99124', - birthPostalCode: null, - status: 'validated', - isCancelled: false, - createdAt: new Date('2020-02-20T10:30:00Z'), - completedAt: new Date('2020-02-20T11:00:00Z'), - isPublished: true, - isRejectedForFraud: false, - juryId: 1, - pixScore: 555, - commentForCandidate: 'coucou', - commentForOrganization: 'comment', - commentByJury: 'ça va', - competenceMarks, - version: 2, - certificationIssueReports, - commonComplementaryCertificationCourseResult: - domainBuilder.buildComplementaryCertificationCourseResultForJuryCertification({ - id: 12, - acquired: true, - label: 'Badge Key 1', - }), - complementaryCertificationCourseResultWithExternal: - domainBuilder.buildComplementaryCertificationCourseResultForJuryCertificationWithExternal({ - complementaryCertificationCourseId: 1234, - pixComplementaryCertificationBadgeId: 98, - pixLabel: 'Badge Key 3', - pixAcquired: true, - pixLevel: 2, - externalComplementaryCertificationBadgeId: 99, - externalLabel: 'Badge Key 4', - externalAcquired: true, - externalLevel: 4, - allowedExternalLevels: [ - { - label: 'Badge Key 3', - value: 98, - }, - { - label: 'Badge Key 4', - value: 99, - }, - ], - }), - }); - - // when - const serializedJuryCertification = serializer.serialize(juryCertification); + let translate; + beforeEach(function () { + translate = getI18n().__; + }); - // then - const expectedSerializedCertification = { - data: { - id: certificationCourseId.toString(), - type: 'certifications', - attributes: { - 'session-id': 11, - 'user-id': 867, - 'assessment-id': 44, - 'first-name': 'James', - 'last-name': 'Watt', - birthdate: '1990-01-04', - birthplace: 'Somewhere', - sex: 'M', - 'birth-country': 'ENGLAND', - 'birth-insee-code': '99124', - 'birth-postal-code': null, - 'created-at': new Date('2020-02-20T10:30:00Z'), - 'completed-at': new Date('2020-02-20T11:00:00Z'), - status: 'validated', - 'is-cancelled': false, - 'is-published': true, - 'is-rejected-for-fraud': false, - 'jury-id': 1, - 'pix-score': 555, - 'competences-with-mark': juryCertification.competenceMarks, - 'comment-for-candidate': 'coucou', - 'comment-by-jury': 'ça va', - 'comment-for-organization': 'comment', - version: 2, - }, - relationships: { - 'certification-issue-reports': { - data: [ + describe('#serialize', function () { + context('when there is no automatic jury comment', function () { + it('should serialize a JuryCertification', function () { + // given + const certificationCourseId = 123; + const certificationIssueReport = domainBuilder.buildCertificationIssueReport.impactful({ + certificationCourseId, + resolvedAt: new Date(), + resolution: 'le challenge est neutralisé', + hasBeenAutomaticallyResolved: true, + }); + const certificationIssueReports = [certificationIssueReport]; + const competenceMarks = [domainBuilder.buildCompetenceMark()]; + const juryCertification = domainBuilder.buildJuryCertification({ + certificationCourseId, + sessionId: 11, + userId: 867, + assessmentId: 44, + firstName: 'James', + lastName: 'Watt', + birthdate: '1990-01-04', + birthplace: 'Somewhere', + sex: 'M', + birthCountry: 'ENGLAND', + birthINSEECode: '99124', + birthPostalCode: null, + status: 'validated', + isCancelled: false, + createdAt: new Date('2020-02-20T10:30:00Z'), + completedAt: new Date('2020-02-20T11:00:00Z'), + isPublished: true, + isRejectedForFraud: false, + juryId: 1, + pixScore: 555, + commentForCandidate: 'coucou', + commentForOrganization: 'comment', + commentByJury: 'ça va', + competenceMarks, + version: 2, + certificationIssueReports, + commonComplementaryCertificationCourseResult: + domainBuilder.buildComplementaryCertificationCourseResultForJuryCertification({ + id: 12, + acquired: true, + label: 'Badge Key 1', + }), + complementaryCertificationCourseResultWithExternal: + domainBuilder.buildComplementaryCertificationCourseResultForJuryCertificationWithExternal({ + complementaryCertificationCourseId: 1234, + pixComplementaryCertificationBadgeId: 98, + pixLabel: 'Badge Key 3', + pixAcquired: true, + pixLevel: 2, + externalComplementaryCertificationBadgeId: 99, + externalLabel: 'Badge Key 4', + externalAcquired: true, + externalLevel: 4, + allowedExternalLevels: [ { - type: 'certificationIssueReports', - id: certificationIssueReport.id.toString(), + label: 'Badge Key 3', + value: 98, + }, + { + label: 'Badge Key 4', + value: 99, }, ], + }), + }); + + // when + const serializedJuryCertification = serializer.serialize(juryCertification, { translate }); + + // then + const expectedSerializedCertification = { + data: { + id: certificationCourseId.toString(), + type: 'certifications', + attributes: { + 'session-id': 11, + 'user-id': 867, + 'assessment-id': 44, + 'first-name': 'James', + 'last-name': 'Watt', + birthdate: '1990-01-04', + birthplace: 'Somewhere', + sex: 'M', + 'birth-country': 'ENGLAND', + 'birth-insee-code': '99124', + 'birth-postal-code': null, + 'created-at': new Date('2020-02-20T10:30:00Z'), + 'completed-at': new Date('2020-02-20T11:00:00Z'), + status: 'validated', + 'is-cancelled': false, + 'is-published': true, + 'is-rejected-for-fraud': false, + 'jury-id': 1, + 'pix-score': 555, + 'competences-with-mark': juryCertification.competenceMarks, + 'comment-for-candidate': 'coucou', + 'comment-by-jury': 'ça va', + 'comment-for-organization': 'comment', + version: 2, }, - 'common-complementary-certification-course-result': { - data: { - id: '12', - type: 'commonComplementaryCertificationCourseResults', + relationships: { + 'certification-issue-reports': { + data: [ + { + type: 'certificationIssueReports', + id: certificationIssueReport.id.toString(), + }, + ], }, - }, - 'complementary-certification-course-result-with-external': { - data: { - id: '1234', - type: 'complementaryCertificationCourseResultWithExternals', + 'common-complementary-certification-course-result': { + data: { + id: '12', + type: 'commonComplementaryCertificationCourseResults', + }, + }, + 'complementary-certification-course-result-with-external': { + data: { + id: '1234', + type: 'complementaryCertificationCourseResultWithExternals', + }, }, }, }, - }, - included: [ - { - type: 'commonComplementaryCertificationCourseResults', - id: '12', - attributes: { - label: 'Badge Key 1', - status: 'Validée', + included: [ + { + type: 'commonComplementaryCertificationCourseResults', + id: '12', + attributes: { + label: 'Badge Key 1', + status: 'Validée', + }, }, - }, - { - type: 'complementaryCertificationCourseResultWithExternals', - id: '1234', - attributes: { - 'allowed-external-levels': [ + { + type: 'complementaryCertificationCourseResultWithExternals', + id: '1234', + attributes: { + 'allowed-external-levels': [ + { + label: 'Badge Key 3', + value: 98, + }, + { + label: 'Badge Key 4', + value: 99, + }, + ], + 'default-jury-options': ['REJECTED', 'UNSET'], + 'complementary-certification-course-id': 1234, + 'pix-result': 'Badge Key 3', + 'external-result': 'Badge Key 4', + 'final-result': 'Badge Key 3', + }, + }, + { + type: 'certificationIssueReports', + id: certificationIssueReport.id.toString(), + attributes: { + category: certificationIssueReport.category, + description: certificationIssueReport.description, + 'is-impactful': true, + 'resolved-at': certificationIssueReport.resolvedAt, + resolution: certificationIssueReport.resolution, + 'question-number': certificationIssueReport.questionNumber, + subcategory: certificationIssueReport.subcategory, + 'has-been-automatically-resolved': certificationIssueReport.hasBeenAutomaticallyResolved, + }, + }, + ], + }; + + expect(serializedJuryCertification).to.deep.equal(expectedSerializedCertification); + }); + }); + + context('when there is an automatic jury comment', function () { + it('should serialize a JuryCertification', function () { + // given + const certificationCourseId = 123; + const certificationIssueReport = domainBuilder.buildCertificationIssueReport.impactful({ + certificationCourseId, + resolvedAt: new Date(), + resolution: 'le challenge est neutralisé', + hasBeenAutomaticallyResolved: true, + }); + const certificationIssueReports = [certificationIssueReport]; + const competenceMarks = [domainBuilder.buildCompetenceMark()]; + const juryCertification = domainBuilder.buildJuryCertification({ + certificationCourseId, + sessionId: 11, + userId: 867, + assessmentId: 44, + firstName: 'James', + lastName: 'Watt', + birthdate: '1990-01-04', + birthplace: 'Somewhere', + sex: 'M', + birthCountry: 'ENGLAND', + birthINSEECode: '99124', + birthPostalCode: null, + status: 'validated', + isCancelled: false, + createdAt: new Date('2020-02-20T10:30:00Z'), + completedAt: new Date('2020-02-20T11:00:00Z'), + isPublished: true, + isRejectedForFraud: false, + juryId: 1, + pixScore: 555, + commentForCandidate: 'coucou', + commentForOrganization: 'comment', + commentByJury: 'ça va', + commentByAutoJury: AutoJuryCommentKeys.FRAUD, + competenceMarks, + version: 2, + certificationIssueReports, + commonComplementaryCertificationCourseResult: + domainBuilder.buildComplementaryCertificationCourseResultForJuryCertification({ + id: 12, + acquired: true, + label: 'Badge Key 1', + }), + complementaryCertificationCourseResultWithExternal: + domainBuilder.buildComplementaryCertificationCourseResultForJuryCertificationWithExternal({ + complementaryCertificationCourseId: 1234, + pixComplementaryCertificationBadgeId: 98, + pixLabel: 'Badge Key 3', + pixAcquired: true, + pixLevel: 2, + externalComplementaryCertificationBadgeId: 99, + externalLabel: 'Badge Key 4', + externalAcquired: true, + externalLevel: 4, + allowedExternalLevels: [ { label: 'Badge Key 3', value: 98, @@ -151,31 +254,115 @@ describe('Unit | Serializer | JSONAPI | jury-certification-serializer', function value: 99, }, ], - 'default-jury-options': ['REJECTED', 'UNSET'], - 'complementary-certification-course-id': 1234, - 'pix-result': 'Badge Key 3', - 'external-result': 'Badge Key 4', - 'final-result': 'Badge Key 3', - }, - }, - { - type: 'certificationIssueReports', - id: certificationIssueReport.id.toString(), + }), + }); + + // when + const serializedJuryCertification = serializer.serialize(juryCertification, { translate }); + + // then + const expectedSerializedCertification = { + data: { + id: certificationCourseId.toString(), + type: 'certifications', attributes: { - category: certificationIssueReport.category, - description: certificationIssueReport.description, - 'is-impactful': true, - 'resolved-at': certificationIssueReport.resolvedAt, - resolution: certificationIssueReport.resolution, - 'question-number': certificationIssueReport.questionNumber, - subcategory: certificationIssueReport.subcategory, - 'has-been-automatically-resolved': certificationIssueReport.hasBeenAutomaticallyResolved, + 'session-id': 11, + 'user-id': 867, + 'assessment-id': 44, + 'first-name': 'James', + 'last-name': 'Watt', + birthdate: '1990-01-04', + birthplace: 'Somewhere', + sex: 'M', + 'birth-country': 'ENGLAND', + 'birth-insee-code': '99124', + 'birth-postal-code': null, + 'created-at': new Date('2020-02-20T10:30:00Z'), + 'completed-at': new Date('2020-02-20T11:00:00Z'), + status: 'validated', + 'is-cancelled': false, + 'is-published': true, + 'is-rejected-for-fraud': false, + 'jury-id': 1, + 'pix-score': 555, + 'competences-with-mark': juryCertification.competenceMarks, + 'comment-for-candidate': translate('jury.comment.FRAUD.candidate'), + 'comment-by-jury': 'ça va', + 'comment-for-organization': translate('jury.comment.FRAUD.organization'), + version: 2, + }, + relationships: { + 'certification-issue-reports': { + data: [ + { + type: 'certificationIssueReports', + id: certificationIssueReport.id.toString(), + }, + ], + }, + 'common-complementary-certification-course-result': { + data: { + id: '12', + type: 'commonComplementaryCertificationCourseResults', + }, + }, + 'complementary-certification-course-result-with-external': { + data: { + id: '1234', + type: 'complementaryCertificationCourseResultWithExternals', + }, + }, }, }, - ], - }; + included: [ + { + type: 'commonComplementaryCertificationCourseResults', + id: '12', + attributes: { + label: 'Badge Key 1', + status: 'Validée', + }, + }, + { + type: 'complementaryCertificationCourseResultWithExternals', + id: '1234', + attributes: { + 'allowed-external-levels': [ + { + label: 'Badge Key 3', + value: 98, + }, + { + label: 'Badge Key 4', + value: 99, + }, + ], + 'default-jury-options': ['REJECTED', 'UNSET'], + 'complementary-certification-course-id': 1234, + 'pix-result': 'Badge Key 3', + 'external-result': 'Badge Key 4', + 'final-result': 'Badge Key 3', + }, + }, + { + type: 'certificationIssueReports', + id: certificationIssueReport.id.toString(), + attributes: { + category: certificationIssueReport.category, + description: certificationIssueReport.description, + 'is-impactful': true, + 'resolved-at': certificationIssueReport.resolvedAt, + resolution: certificationIssueReport.resolution, + 'question-number': certificationIssueReport.questionNumber, + subcategory: certificationIssueReport.subcategory, + 'has-been-automatically-resolved': certificationIssueReport.hasBeenAutomaticallyResolved, + }, + }, + ], + }; - expect(serializedJuryCertification).to.deep.equal(expectedSerializedCertification); + expect(serializedJuryCertification).to.deep.equal(expectedSerializedCertification); + }); }); }); }); diff --git a/api/tests/unit/infrastructure/serializers/jsonapi/private-certificate-serializer_test.js b/api/tests/unit/infrastructure/serializers/jsonapi/private-certificate-serializer_test.js index 01cafcdabce..b3d5e497ac2 100644 --- a/api/tests/unit/infrastructure/serializers/jsonapi/private-certificate-serializer_test.js +++ b/api/tests/unit/infrastructure/serializers/jsonapi/private-certificate-serializer_test.js @@ -2,8 +2,33 @@ import { expect, domainBuilder } from '../../../../test-helper.js'; import * as serializer from '../../../../../lib/infrastructure/serializers/jsonapi/private-certificate-serializer.js'; import { ResultCompetenceTree } from '../../../../../lib/domain/models/ResultCompetenceTree.js'; import { ResultCompetence } from '../../../../../lib/domain/models/ResultCompetence.js'; +import { getI18n } from '../../../../tooling/i18n/i18n.js'; +import { AutoJuryCommentKeys } from '../../../../../src/certification/shared/domain/models/JuryComment.js'; describe('Unit | Serializer | JSONAPI | private-certificate-serializer', function () { + let translate; + let privateCertificateBase; + + beforeEach(function () { + translate = getI18n().__; + privateCertificateBase = { + id: 123, + firstName: 'Dorothé', + lastName: '2Pac', + birthdate: '2000-01-01', + birthplace: 'Sin City', + isPublished: true, + date: new Date('2020-01-01T00:00:00Z'), + deliveredAt: new Date('2021-01-01T00:00:00Z'), + certificationCenter: 'Centre des choux de Bruxelles', + pixScore: 456, + commentForCandidate: 'Cette personne est impolie !', + certifiedBadgeImages: ['/img/1', '/img/2'], + verificationCode: 'P-SUPERCODE', + maxReachableLevelOnCertificationDate: 6, + }; + }); + describe('#serialize', function () { it('should serialize to JSON with included relationships', function () { // given @@ -34,25 +59,12 @@ describe('Unit | Serializer | JSONAPI | private-certificate-serializer', functio areas: [area1], }); const privateCertificate = domainBuilder.buildPrivateCertificate.rejected({ - id: 123, - firstName: 'Dorothé', - lastName: '2Pac', - birthdate: '2000-01-01', - birthplace: 'Sin City', - isPublished: true, - date: new Date('2020-01-01T00:00:00Z'), - deliveredAt: new Date('2021-01-01T00:00:00Z'), - certificationCenter: 'Centre des choux de Bruxelles', - pixScore: 456, - commentForCandidate: 'Cette personne est impolie !', - certifiedBadgeImages: ['/img/1', '/img/2'], + ...privateCertificateBase, resultCompetenceTree, - verificationCode: 'P-SUPERCODE', - maxReachableLevelOnCertificationDate: 6, }); // when - const serializedCertifications = serializer.serialize(privateCertificate); + const serializedCertifications = serializer.serialize(privateCertificate, { translate }); // then expect(serializedCertifications.data).to.deep.equal({ @@ -147,5 +159,21 @@ describe('Unit | Serializer | JSONAPI | private-certificate-serializer', functio }, ]); }); + + it('should translate a commentForCandidate set by auto jury', function () { + // given + const privateCertificate = domainBuilder.buildPrivateCertificate({ + ...privateCertificateBase, + commentByAutoJury: AutoJuryCommentKeys.FRAUD, + }); + + // when + const serializedCertifications = serializer.serialize(privateCertificate, { translate }); + + // then + expect(serializedCertifications.data.attributes['comment-for-candidate']).to.equal( + translate('jury.comment.FRAUD.candidate'), + ); + }); }); }); diff --git a/api/tests/unit/infrastructure/utils/csv/certification-results/CertificationResultsCsvValues_test.js b/api/tests/unit/infrastructure/utils/csv/certification-results/CertificationResultsCsvValues_test.js index b26e6c43e85..c395f34e336 100644 --- a/api/tests/unit/infrastructure/utils/csv/certification-results/CertificationResultsCsvValues_test.js +++ b/api/tests/unit/infrastructure/utils/csv/certification-results/CertificationResultsCsvValues_test.js @@ -2,11 +2,13 @@ import { expect, domainBuilder } from '../../../../../test-helper.js'; import { getI18n } from '../../../../../tooling/i18n/i18n.js'; import { CertificationResult } from '../../../../../../lib/domain/models/CertificationResult.js'; import { CertificationResultsCsvValues } from '../../../../../../lib/infrastructure/utils/csv/certification-results/CertificationResultsCsvValues.js'; +import { AutoJuryCommentKeys } from '../../../../../../src/certification/shared/domain/models/JuryComment.js'; describe('Unit | Infrastructure | Utils | Csv | CertificationResultsCsvValues', function () { - let i18n; + let i18n, translate; beforeEach(function () { i18n = getI18n(); + translate = i18n.__; }); describe('#formatPixScore', function () { @@ -302,6 +304,40 @@ describe('Unit | Infrastructure | Utils | Csv | CertificationResultsCsvValues', complementaryCertificationCourseResults: [], }; + context('when there is an automatic jury comment', function () { + it('should return the automatic jury comment', function () { + // given + const certificationResult = domainBuilder.buildCertificationResult.cancelled({ + ...aCertificationResultData, + emitter: CertificationResult.emitters.PIX_ALGO, + commentByAutoJury: AutoJuryCommentKeys.CANCELLED_DUE_TO_NEUTRALIZATION, + }); + + // when + const result = new CertificationResultsCsvValues(i18n).getCommentForOrganization(certificationResult); + + // then + expect(result).to.equal(translate('jury.comment.CANCELLED_DUE_TO_NEUTRALIZATION.organization')); + }); + }); + + context('when there is no automatic jury comment', function () { + it('should return the manual jury comment', function () { + // given + const certificationResult = domainBuilder.buildCertificationResult.validated({ + ...aCertificationResultData, + emitter: CertificationResult.emitters.PIX_ALGO, + commentForOrganization: 'MANUAL COMMENT', + }); + + // when + const result = new CertificationResultsCsvValues(i18n).getCommentForOrganization(certificationResult); + + // then + expect(result).to.equal('MANUAL COMMENT'); + }); + }); + context('when complementary certification is rejected', function () { it('should return that the certification has been automatically invalidated', function () { // given diff --git a/api/translations/en.json b/api/translations/en.json index 8a58e78672d..7a49415deca 100644 --- a/api/translations/en.json +++ b/api/translations/en.json @@ -302,6 +302,18 @@ "STAGE_TITLE_IS_REQUIRED": "The title is required", "WRONG_EMAIL_FORMAT": "The email address format is invalid." }, + "jury": { + "comment": { + "CANCELLED_DUE_TO_NEUTRALIZATION": { + "candidate": "At least one technical issue, reported to your invigilator during the certification session, has affected the quality of the certification exam. Due to the number of questions to which you have not been able to answer, we are not able to deliver a certification. It has been cancelled, the specifier of your certification exam (if applicable), has been informed.", + "organization": "At least one technical issue, has affected the quality of the certification exam. We are not able to deliver a certification. It has thus been cancelled. This information may lead you to proposing another certification session for this candidate." + }, + "FRAUD": { + "candidate": "The Pix certification exam conditions have not been respected. Your certification exam has been reported for fraud and has consequently been invalidated.", + "organization": "A fraud has been detected : after analysis, we have decided on rejecting this certification exam." + } + } + }, "organization-invitation-email": { "params": { "title": "You are invited to join Pix Orga", diff --git a/api/translations/fr.json b/api/translations/fr.json index 26cfed2f4bf..c06fe97c535 100644 --- a/api/translations/fr.json +++ b/api/translations/fr.json @@ -316,6 +316,18 @@ "STAGE_TITLE_IS_REQUIRED": "Le titre du palier est obligatoire", "WRONG_EMAIL_FORMAT": "Le format de l'adresse e-mail est incorrect." }, + "jury": { + "comment": { + "CANCELLED_DUE_TO_NEUTRALIZATION": { + "candidate": "Un ou plusieurs problème(s) technique(s), signalé(s) à votre surveillant pendant la session de certification, a/ont affecté la qualité du test de certification. En raison du trop grand nombre de questions auxquelles vous n'avez pas pu répondre, nous ne sommes pas en mesure de délivrer une certification. Celle-ci est annulée, le prescripteur de votre certification (le cas échéant), en est informé.", + "organization": "Un ou plusieurs problème(s) technique(s), a/ont affecté le bon déroulement du test de certification. Nous ne sommes pas en mesure de délivrer la certification, celle-ci est donc annulée. Cette information peut vous conduire à proposer une nouvelle session de certification pour ce(cette) candidat(e)." + }, + "FRAUD": { + "candidate": "Les conditions de passation du test de certification n'ayant pas été respectées et ayant fait l'objet d'un signalement pour fraude, votre certification a été invalidée en conséquence.", + "organization": "Une situation de fraude a été détectée : après analyse, nous avons statué sur un rejet de la certification." + } + } + }, "organization-invitation-email": { "params": { "title": "Vous êtes invité(e) à rejoindre Pix Orga", diff --git a/api/translations/nl.json b/api/translations/nl.json index 7c8d112e074..fc8a8ca055a 100644 --- a/api/translations/nl.json +++ b/api/translations/nl.json @@ -316,6 +316,18 @@ "STAGE_TITLE_IS_REQUIRED": "De titel van het niveau is verplicht", "WRONG_EMAIL_FORMAT": "De indeling van het e-mailadres is onjuist." }, + "jury": { + "comment": { + "CANCELLED_DUE_TO_NEUTRALIZATION": { + "candidate": "At least one technical issue, reported to your invigilator during the certification session, has affected the quality of the certification exam. Due to the number of questions to which you have not been able to answer, we are not able to deliver a certification. It has been cancelled, the specifier of your certification exam (if applicable), has been informed.", + "organization": "At least one technical issue, has affected the quality of the certification exam. We are not able to deliver a certification. It has thus been cancelled. This information may lead you to proposing another certification session for this candidate." + }, + "FRAUD": { + "candidate": "The Pix certification exam conditions have not been respected. Your certification exam has been reported for fraud and has consequently been invalidated.", + "organization": "A fraud has been detected : after analysis, we have decided on rejecting this certification exam." + } + } + }, "organization-invitation-email": { "params": { "title": "Je bent uitgenodigd om lid te worden van Pix Orga",