Skip to content

Commit f2fdb86

Browse files
[FEATURE] Enregistrer comme correcte une réponse focused out si le candidat a un besoin d'aménagement (PIX-14242). (#10211)
1 parent 65acc35 commit f2fdb86

File tree

11 files changed

+449
-104
lines changed

11 files changed

+449
-104
lines changed

api/lib/domain/usecases/correct-answer-then-update-assessment.js

+30-8
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ import { Examiner } from '../../../src/shared/domain/models/Examiner.js';
99
import { KnowledgeElement } from '../../../src/shared/domain/models/index.js';
1010
import { logger } from '../../../src/shared/infrastructure/utils/logger.js';
1111

12-
const evaluateAnswer = function ({ challenge, answer, assessment, examiner: injectedExaminer }) {
12+
const evaluateAnswer = function ({
13+
challenge,
14+
answer,
15+
assessment,
16+
examiner: injectedExaminer,
17+
accessibilityAdjustmentNeeded,
18+
}) {
1319
const examiner = injectedExaminer ?? new Examiner({ validator: challenge.validator });
1420
try {
1521
return examiner.evaluate({
@@ -18,6 +24,7 @@ const evaluateAnswer = function ({ challenge, answer, assessment, examiner: inje
1824
isFocusedChallenge: challenge.focused,
1925
hasLastQuestionBeenFocusedOut: assessment.hasLastQuestionBeenFocusedOut,
2026
isCertificationEvaluation: assessment.isCertification(),
27+
accessibilityAdjustmentNeeded,
2128
});
2229
} catch (error) {
2330
throw new AnswerEvaluationError(challenge);
@@ -126,6 +133,7 @@ const correctAnswerThenUpdateAssessment = async function ({
126133
skillRepository,
127134
campaignRepository,
128135
knowledgeElementRepository,
136+
certificationEvaluationCandidateRepository,
129137
flashAssessmentResultRepository,
130138
certificationChallengeLiveAlertRepository,
131139
flashAlgorithmService,
@@ -152,17 +160,31 @@ const correctAnswerThenUpdateAssessment = async function ({
152160

153161
const challenge = await challengeRepository.get(answer.challengeId);
154162

155-
const onGoingCertificationChallengeLiveAlert =
156-
await certificationChallengeLiveAlertRepository.getOngoingByChallengeIdAndAssessmentId({
157-
challengeId: challenge.id,
163+
let certificationCandidate;
164+
165+
if (assessment.isCertification()) {
166+
const onGoingCertificationChallengeLiveAlert =
167+
await certificationChallengeLiveAlertRepository.getOngoingByChallengeIdAndAssessmentId({
168+
challengeId: challenge.id,
169+
assessmentId: assessment.id,
170+
});
171+
172+
if (onGoingCertificationChallengeLiveAlert) {
173+
throw new ForbiddenAccess('An alert has been set.');
174+
}
175+
176+
certificationCandidate = await certificationEvaluationCandidateRepository.findByAssessmentId({
158177
assessmentId: assessment.id,
159178
});
160-
161-
if (onGoingCertificationChallengeLiveAlert) {
162-
throw new ForbiddenAccess('An alert has been set.');
163179
}
164180

165-
const correctedAnswer = evaluateAnswer({ challenge, answer, assessment, examiner });
181+
const correctedAnswer = evaluateAnswer({
182+
challenge,
183+
answer,
184+
assessment,
185+
examiner,
186+
accessibilityAdjustmentNeeded: certificationCandidate?.accessibilityAdjustmentNeeded,
187+
});
166188
const now = dateUtils.getNowDate();
167189
const lastQuestionDate = assessment.lastQuestionDate || now;
168190
correctedAnswer.setTimeSpentFrom({ now, lastQuestionDate });

api/lib/domain/usecases/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import * as centerRepository from '../../../src/certification/enrolment/infrastr
1010
import * as certificationCandidateRepository from '../../../src/certification/enrolment/infrastructure/repositories/certification-candidate-repository.js';
1111
import * as certificationCpfCityRepository from '../../../src/certification/enrolment/infrastructure/repositories/certification-cpf-city-repository.js';
1212
import * as sessionEnrolmentRepository from '../../../src/certification/enrolment/infrastructure/repositories/session-repository.js';
13+
import * as certificationEvaluationCandidateRepository from '../../../src/certification/evaluation/infrastructure/repositories/certification-candidate-repository.js';
1314
import * as flashAlgorithmService from '../../../src/certification/flash-certification/domain/services/algorithm-methods/flash.js';
1415
import * as certificationOfficerRepository from '../../../src/certification/session-management/infrastructure/repositories/certification-officer-repository.js';
1516
import * as finalizedSessionRepository from '../../../src/certification/session-management/infrastructure/repositories/finalized-session-repository.js';
@@ -175,6 +176,7 @@ function requirePoleEmploiNotifier() {
175176
* @typedef {certificationBadgesService} CertificationBadgesService
176177
* @typedef {certificationCenterRepository} CertificationCenterRepository
177178
* @typedef {certificationRepository} CertificationRepository
179+
* @typedef {certificationEvaluationCandidateRepository} CertificationEvaluationCandidateRepository
178180
* @typedef {complementaryCertificationRepository} ComplementaryCertificationRepository
179181
* @typedef {complementaryCertificationCourseRepository} ComplementaryCertificationCourseRepository
180182
* @typedef {finalizedSessionRepository} FinalizedSessionRepository
@@ -225,6 +227,7 @@ const dependencies = {
225227
certificationAssessmentRepository,
226228
certificationBadgesService,
227229
certificationCandidateRepository,
230+
certificationEvaluationCandidateRepository,
228231
certificationCenterForAdminRepository,
229232
certificationCenterInvitationRepository,
230233
certificationCenterInvitationService,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export class Candidate {
2+
/**
3+
* @param {Object} params
4+
* @param {boolean} [params.accessibilityAdjustmentNeeded]
5+
*/
6+
constructor({ accessibilityAdjustmentNeeded } = {}) {
7+
this.accessibilityAdjustmentNeeded = !!accessibilityAdjustmentNeeded;
8+
}
9+
}

api/src/certification/evaluation/infrastructure/repositories/certification-candidate-repository.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { knex } from '../../../../../db/knex-database-connection.js';
22
import { CertificationCandidateNotFoundError } from '../../../../shared/domain/errors.js';
3-
import { Candidate } from '../../../enrolment/domain/models/Candidate.js';
3+
import { Candidate } from '../../domain/models/Candidate.js';
44

55
const findByAssessmentId = async function ({ assessmentId }) {
66
const result = await knex('certification-candidates')

api/src/shared/domain/models/Examiner.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@ class Examiner {
1010
this.validator = validator;
1111
}
1212

13-
evaluate({ answer, challengeFormat, isFocusedChallenge, isCertificationEvaluation, hasLastQuestionBeenFocusedOut }) {
13+
evaluate({
14+
answer,
15+
challengeFormat,
16+
isFocusedChallenge,
17+
isCertificationEvaluation,
18+
hasLastQuestionBeenFocusedOut,
19+
accessibilityAdjustmentNeeded,
20+
}) {
1421
const correctedAnswer = new Answer(answer);
1522

1623
if (answer.value === Answer.FAKE_VALUE_FOR_SKIPPED_QUESTIONS) {
@@ -68,7 +75,7 @@ class Examiner {
6875
}
6976

7077
if (isCorrectAnswer && isFocusedChallenge && answer.isFocusedOut && isCertificationEvaluation) {
71-
correctedAnswer.result = AnswerStatus.FOCUSEDOUT;
78+
correctedAnswer.result = accessibilityAdjustmentNeeded ? AnswerStatus.OK : AnswerStatus.FOCUSEDOUT;
7279
correctedAnswer.isFocusedOut = true;
7380
}
7481

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import {
2+
createServer,
3+
databaseBuilder,
4+
expect,
5+
generateValidRequestAuthorizationHeader,
6+
knex,
7+
mockLearningContent,
8+
} from '../../../test-helper.js';
9+
10+
describe('Certification | Evaluation | Acceptance | answer-route', function () {
11+
let server;
12+
13+
beforeEach(async function () {
14+
server = await createServer();
15+
});
16+
17+
describe('POST /api/answers', function () {
18+
context('when challenge is focused out and answer is correct', function () {
19+
context('when the candidate needs an accessibility adjustment', function () {
20+
it('should save the answer as correct', async function () {
21+
// given
22+
const { competenceId, challengeId } = _buildLearningContent();
23+
const { assessmentId, userId } = await _setupTestData(databaseBuilder, {
24+
competenceId,
25+
doesCandidateNeedAccessibilityAdjustment: true,
26+
});
27+
const options = _setupRequestOptions({ userId, challengeId, assessmentId });
28+
29+
// when
30+
await server.inject(options);
31+
32+
// then
33+
const [answer] = await knex('answers');
34+
expect(answer.result).to.equal('ok');
35+
expect(answer.isFocusedOut).to.equal(true);
36+
});
37+
});
38+
39+
context('when the candidate does not need an accessibility adjustment', function () {
40+
it('should save the answer as focused out', async function () {
41+
// given
42+
const { competenceId, challengeId } = _buildLearningContent();
43+
const { assessmentId, userId } = await _setupTestData(databaseBuilder, {
44+
competenceId,
45+
doesCandidateNeedAccessibilityAdjustment: false,
46+
});
47+
const options = _setupRequestOptions({ userId, challengeId, assessmentId });
48+
49+
// when
50+
await server.inject(options);
51+
52+
// then
53+
const [answer] = await knex('answers');
54+
expect(answer.result).to.equal('focusedOut');
55+
expect(answer.isFocusedOut).to.equal(true);
56+
});
57+
});
58+
});
59+
});
60+
});
61+
62+
async function _setupTestData(databaseBuilder, { competenceId, doesCandidateNeedAccessibilityAdjustment }) {
63+
const userId = databaseBuilder.factory.buildUser().id;
64+
65+
const session = databaseBuilder.factory.buildSession({});
66+
67+
databaseBuilder.factory.buildCertificationCandidate({
68+
sessionId: session.id,
69+
userId,
70+
accessibilityAdjustmentNeeded: doesCandidateNeedAccessibilityAdjustment,
71+
});
72+
73+
const certificationCourse = databaseBuilder.factory.buildCertificationCourse({
74+
userId,
75+
sessionId: session.id,
76+
});
77+
78+
const assessment = databaseBuilder.factory.buildAssessment({
79+
type: 'CERTIFICATION',
80+
userId,
81+
competenceId,
82+
certificationCourseId: certificationCourse.id,
83+
});
84+
85+
await databaseBuilder.commit();
86+
87+
return { assessmentId: assessment.id, userId };
88+
}
89+
90+
function _setupRequestOptions({ userId, challengeId, assessmentId }) {
91+
return {
92+
method: 'POST',
93+
url: '/api/answers',
94+
headers: { authorization: generateValidRequestAuthorizationHeader(userId) },
95+
payload: {
96+
data: {
97+
type: 'answers',
98+
attributes: {
99+
value: 'correct',
100+
'focused-out': true,
101+
},
102+
relationships: {
103+
assessment: {
104+
data: {
105+
type: 'assessments',
106+
id: assessmentId,
107+
},
108+
},
109+
challenge: {
110+
data: {
111+
type: 'challenges',
112+
id: challengeId,
113+
},
114+
},
115+
},
116+
},
117+
},
118+
};
119+
}
120+
121+
function _buildLearningContent() {
122+
const challengeId = 'a_challenge_id';
123+
const competenceId = 'recCompetence';
124+
125+
const learningContent = {
126+
areas: [{ id: 'recArea1', competenceIds: ['recCompetence'] }],
127+
competences: [
128+
{
129+
id: 'recCompetence',
130+
areaId: 'recArea1',
131+
skillIds: ['recSkill1'],
132+
origin: 'Pix',
133+
name_i18n: {
134+
fr: 'Nom de la competence FR',
135+
en: 'Nom de la competence EN',
136+
},
137+
statue: 'active',
138+
},
139+
],
140+
skills: [
141+
{
142+
id: 'recSkill1',
143+
name: '@recArea1_Competence1_Tube1_Skill1',
144+
status: 'actif',
145+
competenceId: competenceId,
146+
pixValue: '5',
147+
},
148+
],
149+
challenges: [
150+
{
151+
id: challengeId,
152+
competenceId: competenceId,
153+
skillId: 'recSkill1',
154+
status: 'validé',
155+
solution: 'correct',
156+
proposals: '${a}',
157+
locales: ['fr-fr'],
158+
type: 'QROC',
159+
focusable: true,
160+
},
161+
],
162+
};
163+
mockLearningContent(learningContent);
164+
165+
return {
166+
competenceId,
167+
challengeId,
168+
};
169+
}

api/tests/certification/evaluation/integration/infrastructure/repositories/certification-candidate-repository_test.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,8 @@ describe('Integration | Repository | certification candidate', function () {
3636

3737
// then
3838
expect(result).to.deep.equal(
39-
domainBuilder.certification.enrolment.buildCandidate({
39+
domainBuilder.certification.evaluation.buildCandidate({
4040
...candidate,
41-
subscriptions: [],
4241
}),
4342
);
4443
});
@@ -124,9 +123,8 @@ describe('Integration | Repository | certification candidate', function () {
124123

125124
// then
126125
expect(result).to.deep.equal(
127-
domainBuilder.certification.enrolment.buildCandidate({
126+
domainBuilder.certification.evaluation.buildCandidate({
128127
...candidate,
129-
subscriptions: [],
130128
}),
131129
);
132130
});

0 commit comments

Comments
 (0)