Skip to content

Commit

Permalink
[FEATURE] Enregistrer comme correcte une réponse focused out si le ca…
Browse files Browse the repository at this point in the history
…ndidat a un besoin d'aménagement (PIX-14242). (#10211)
  • Loading branch information
alexandrecoin authored Sep 30, 2024
1 parent 65acc35 commit f2fdb86
Show file tree
Hide file tree
Showing 11 changed files with 449 additions and 104 deletions.
38 changes: 30 additions & 8 deletions api/lib/domain/usecases/correct-answer-then-update-assessment.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ import { Examiner } from '../../../src/shared/domain/models/Examiner.js';
import { KnowledgeElement } from '../../../src/shared/domain/models/index.js';
import { logger } from '../../../src/shared/infrastructure/utils/logger.js';

const evaluateAnswer = function ({ challenge, answer, assessment, examiner: injectedExaminer }) {
const evaluateAnswer = function ({
challenge,
answer,
assessment,
examiner: injectedExaminer,
accessibilityAdjustmentNeeded,
}) {
const examiner = injectedExaminer ?? new Examiner({ validator: challenge.validator });
try {
return examiner.evaluate({
Expand All @@ -18,6 +24,7 @@ const evaluateAnswer = function ({ challenge, answer, assessment, examiner: inje
isFocusedChallenge: challenge.focused,
hasLastQuestionBeenFocusedOut: assessment.hasLastQuestionBeenFocusedOut,
isCertificationEvaluation: assessment.isCertification(),
accessibilityAdjustmentNeeded,
});
} catch (error) {
throw new AnswerEvaluationError(challenge);
Expand Down Expand Up @@ -126,6 +133,7 @@ const correctAnswerThenUpdateAssessment = async function ({
skillRepository,
campaignRepository,
knowledgeElementRepository,
certificationEvaluationCandidateRepository,
flashAssessmentResultRepository,
certificationChallengeLiveAlertRepository,
flashAlgorithmService,
Expand All @@ -152,17 +160,31 @@ const correctAnswerThenUpdateAssessment = async function ({

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

const onGoingCertificationChallengeLiveAlert =
await certificationChallengeLiveAlertRepository.getOngoingByChallengeIdAndAssessmentId({
challengeId: challenge.id,
let certificationCandidate;

if (assessment.isCertification()) {
const onGoingCertificationChallengeLiveAlert =
await certificationChallengeLiveAlertRepository.getOngoingByChallengeIdAndAssessmentId({
challengeId: challenge.id,
assessmentId: assessment.id,
});

if (onGoingCertificationChallengeLiveAlert) {
throw new ForbiddenAccess('An alert has been set.');
}

certificationCandidate = await certificationEvaluationCandidateRepository.findByAssessmentId({
assessmentId: assessment.id,
});

if (onGoingCertificationChallengeLiveAlert) {
throw new ForbiddenAccess('An alert has been set.');
}

const correctedAnswer = evaluateAnswer({ challenge, answer, assessment, examiner });
const correctedAnswer = evaluateAnswer({
challenge,
answer,
assessment,
examiner,
accessibilityAdjustmentNeeded: certificationCandidate?.accessibilityAdjustmentNeeded,
});
const now = dateUtils.getNowDate();
const lastQuestionDate = assessment.lastQuestionDate || now;
correctedAnswer.setTimeSpentFrom({ now, lastQuestionDate });
Expand Down
3 changes: 3 additions & 0 deletions api/lib/domain/usecases/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import * as centerRepository from '../../../src/certification/enrolment/infrastr
import * as certificationCandidateRepository from '../../../src/certification/enrolment/infrastructure/repositories/certification-candidate-repository.js';
import * as certificationCpfCityRepository from '../../../src/certification/enrolment/infrastructure/repositories/certification-cpf-city-repository.js';
import * as sessionEnrolmentRepository from '../../../src/certification/enrolment/infrastructure/repositories/session-repository.js';
import * as certificationEvaluationCandidateRepository from '../../../src/certification/evaluation/infrastructure/repositories/certification-candidate-repository.js';
import * as flashAlgorithmService from '../../../src/certification/flash-certification/domain/services/algorithm-methods/flash.js';
import * as certificationOfficerRepository from '../../../src/certification/session-management/infrastructure/repositories/certification-officer-repository.js';
import * as finalizedSessionRepository from '../../../src/certification/session-management/infrastructure/repositories/finalized-session-repository.js';
Expand Down Expand Up @@ -175,6 +176,7 @@ function requirePoleEmploiNotifier() {
* @typedef {certificationBadgesService} CertificationBadgesService
* @typedef {certificationCenterRepository} CertificationCenterRepository
* @typedef {certificationRepository} CertificationRepository
* @typedef {certificationEvaluationCandidateRepository} CertificationEvaluationCandidateRepository
* @typedef {complementaryCertificationRepository} ComplementaryCertificationRepository
* @typedef {complementaryCertificationCourseRepository} ComplementaryCertificationCourseRepository
* @typedef {finalizedSessionRepository} FinalizedSessionRepository
Expand Down Expand Up @@ -225,6 +227,7 @@ const dependencies = {
certificationAssessmentRepository,
certificationBadgesService,
certificationCandidateRepository,
certificationEvaluationCandidateRepository,
certificationCenterForAdminRepository,
certificationCenterInvitationRepository,
certificationCenterInvitationService,
Expand Down
9 changes: 9 additions & 0 deletions api/src/certification/evaluation/domain/models/Candidate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export class Candidate {
/**
* @param {Object} params
* @param {boolean} [params.accessibilityAdjustmentNeeded]
*/
constructor({ accessibilityAdjustmentNeeded } = {}) {
this.accessibilityAdjustmentNeeded = !!accessibilityAdjustmentNeeded;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { knex } from '../../../../../db/knex-database-connection.js';
import { CertificationCandidateNotFoundError } from '../../../../shared/domain/errors.js';
import { Candidate } from '../../../enrolment/domain/models/Candidate.js';
import { Candidate } from '../../domain/models/Candidate.js';

const findByAssessmentId = async function ({ assessmentId }) {
const result = await knex('certification-candidates')
Expand Down
11 changes: 9 additions & 2 deletions api/src/shared/domain/models/Examiner.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ class Examiner {
this.validator = validator;
}

evaluate({ answer, challengeFormat, isFocusedChallenge, isCertificationEvaluation, hasLastQuestionBeenFocusedOut }) {
evaluate({
answer,
challengeFormat,
isFocusedChallenge,
isCertificationEvaluation,
hasLastQuestionBeenFocusedOut,
accessibilityAdjustmentNeeded,
}) {
const correctedAnswer = new Answer(answer);

if (answer.value === Answer.FAKE_VALUE_FOR_SKIPPED_QUESTIONS) {
Expand Down Expand Up @@ -68,7 +75,7 @@ class Examiner {
}

if (isCorrectAnswer && isFocusedChallenge && answer.isFocusedOut && isCertificationEvaluation) {
correctedAnswer.result = AnswerStatus.FOCUSEDOUT;
correctedAnswer.result = accessibilityAdjustmentNeeded ? AnswerStatus.OK : AnswerStatus.FOCUSEDOUT;
correctedAnswer.isFocusedOut = true;
}

Expand Down
169 changes: 169 additions & 0 deletions api/tests/certification/evaluation/acceptance/answer-route_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import {
createServer,
databaseBuilder,
expect,
generateValidRequestAuthorizationHeader,
knex,
mockLearningContent,
} from '../../../test-helper.js';

describe('Certification | Evaluation | Acceptance | answer-route', function () {
let server;

beforeEach(async function () {
server = await createServer();
});

describe('POST /api/answers', function () {
context('when challenge is focused out and answer is correct', function () {
context('when the candidate needs an accessibility adjustment', function () {
it('should save the answer as correct', async function () {
// given
const { competenceId, challengeId } = _buildLearningContent();
const { assessmentId, userId } = await _setupTestData(databaseBuilder, {
competenceId,
doesCandidateNeedAccessibilityAdjustment: true,
});
const options = _setupRequestOptions({ userId, challengeId, assessmentId });

// when
await server.inject(options);

// then
const [answer] = await knex('answers');
expect(answer.result).to.equal('ok');
expect(answer.isFocusedOut).to.equal(true);
});
});

context('when the candidate does not need an accessibility adjustment', function () {
it('should save the answer as focused out', async function () {
// given
const { competenceId, challengeId } = _buildLearningContent();
const { assessmentId, userId } = await _setupTestData(databaseBuilder, {
competenceId,
doesCandidateNeedAccessibilityAdjustment: false,
});
const options = _setupRequestOptions({ userId, challengeId, assessmentId });

// when
await server.inject(options);

// then
const [answer] = await knex('answers');
expect(answer.result).to.equal('focusedOut');
expect(answer.isFocusedOut).to.equal(true);
});
});
});
});
});

async function _setupTestData(databaseBuilder, { competenceId, doesCandidateNeedAccessibilityAdjustment }) {
const userId = databaseBuilder.factory.buildUser().id;

const session = databaseBuilder.factory.buildSession({});

databaseBuilder.factory.buildCertificationCandidate({
sessionId: session.id,
userId,
accessibilityAdjustmentNeeded: doesCandidateNeedAccessibilityAdjustment,
});

const certificationCourse = databaseBuilder.factory.buildCertificationCourse({
userId,
sessionId: session.id,
});

const assessment = databaseBuilder.factory.buildAssessment({
type: 'CERTIFICATION',
userId,
competenceId,
certificationCourseId: certificationCourse.id,
});

await databaseBuilder.commit();

return { assessmentId: assessment.id, userId };
}

function _setupRequestOptions({ userId, challengeId, assessmentId }) {
return {
method: 'POST',
url: '/api/answers',
headers: { authorization: generateValidRequestAuthorizationHeader(userId) },
payload: {
data: {
type: 'answers',
attributes: {
value: 'correct',
'focused-out': true,
},
relationships: {
assessment: {
data: {
type: 'assessments',
id: assessmentId,
},
},
challenge: {
data: {
type: 'challenges',
id: challengeId,
},
},
},
},
},
};
}

function _buildLearningContent() {
const challengeId = 'a_challenge_id';
const competenceId = 'recCompetence';

const learningContent = {
areas: [{ id: 'recArea1', competenceIds: ['recCompetence'] }],
competences: [
{
id: 'recCompetence',
areaId: 'recArea1',
skillIds: ['recSkill1'],
origin: 'Pix',
name_i18n: {
fr: 'Nom de la competence FR',
en: 'Nom de la competence EN',
},
statue: 'active',
},
],
skills: [
{
id: 'recSkill1',
name: '@recArea1_Competence1_Tube1_Skill1',
status: 'actif',
competenceId: competenceId,
pixValue: '5',
},
],
challenges: [
{
id: challengeId,
competenceId: competenceId,
skillId: 'recSkill1',
status: 'validé',
solution: 'correct',
proposals: '${a}',
locales: ['fr-fr'],
type: 'QROC',
focusable: true,
},
],
};
mockLearningContent(learningContent);

return {
competenceId,
challengeId,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,8 @@ describe('Integration | Repository | certification candidate', function () {

// then
expect(result).to.deep.equal(
domainBuilder.certification.enrolment.buildCandidate({
domainBuilder.certification.evaluation.buildCandidate({
...candidate,
subscriptions: [],
}),
);
});
Expand Down Expand Up @@ -124,9 +123,8 @@ describe('Integration | Repository | certification candidate', function () {

// then
expect(result).to.deep.equal(
domainBuilder.certification.enrolment.buildCandidate({
domainBuilder.certification.evaluation.buildCandidate({
...candidate,
subscriptions: [],
}),
);
});
Expand Down
Loading

0 comments on commit f2fdb86

Please sign in to comment.