Skip to content

Commit

Permalink
[BUGFIX] Empêcher un candidat de répondre à un challenge si celui-ci …
Browse files Browse the repository at this point in the history
…dispose d'une alerte validée (PIX-15263).

 #10534
  • Loading branch information
pix-service-auto-merge authored Nov 13, 2024
2 parents b753272 + c8a9280 commit 157e39d
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,13 @@ const correctAnswerThenUpdateAssessment = async function ({
let certificationCandidate;

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,23 @@ const getOngoingByChallengeIdAndAssessmentId = async ({ challengeId, assessmentI
return _toDomain(certificationChallengeLiveAlertDto);
};

const getOngoingOrValidatedByChallengeIdAndAssessmentId = async ({ challengeId, assessmentId }) => {
const certificationChallengeLiveAlertDto = await knex('certification-challenge-live-alerts')
.where({
'certification-challenge-live-alerts.challengeId': challengeId,
'certification-challenge-live-alerts.assessmentId': assessmentId,
'certification-challenge-live-alerts.status': CertificationChallengeLiveAlertStatus.ONGOING,
})
.orWhere({
'certification-challenge-live-alerts.challengeId': challengeId,
'certification-challenge-live-alerts.assessmentId': assessmentId,
'certification-challenge-live-alerts.status': CertificationChallengeLiveAlertStatus.VALIDATED,
})
.first();

return _toDomain(certificationChallengeLiveAlertDto);
};

const _toDomain = (certificationChallengeLiveAlertDto) => {
if (!certificationChallengeLiveAlertDto) {
return null;
Expand All @@ -73,5 +90,6 @@ export {
getLiveAlertValidatedChallengeIdsByAssessmentId,
getOngoingByChallengeIdAndAssessmentId,
getOngoingBySessionIdAndUserId,
getOngoingOrValidatedByChallengeIdAndAssessmentId,
save,
};
Original file line number Diff line number Diff line change
Expand Up @@ -312,4 +312,91 @@ describe('Integration | Repository | Certification Challenge Live Alert', functi
});
});
});

describe('getOngoingOrValidatedByChallengeIdAndAssessmentId', function () {
const challengeId = 'rec123';
const assessmentId = 456;

describe('when there are no matching live alerts', function () {
it('should return null', async function () {
// given / when
const liveAlert =
await certificationChallengeLiveAlertRepository.getOngoingOrValidatedByChallengeIdAndAssessmentId({
challengeId,
assessmentId,
});

// then
expect(liveAlert).to.be.null;
});
});

describe('when there is a matching validated live alert', function () {
it('should return the live alert', async function () {
// given
const certificationCourse = databaseBuilder.factory.buildCertificationCourse();

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

databaseBuilder.factory.buildCertificationChallengeLiveAlert({
assessmentId: assessment.id,
status: CertificationChallengeLiveAlertStatus.DISMISSED,
});

const certificationChallengeLiveAlert = databaseBuilder.factory.buildCertificationChallengeLiveAlert({
assessmentId: assessment.id,
status: CertificationChallengeLiveAlertStatus.VALIDATED,
});

await databaseBuilder.commit();

// when
const liveAlert =
await certificationChallengeLiveAlertRepository.getOngoingOrValidatedByChallengeIdAndAssessmentId({
challengeId: certificationChallengeLiveAlert.challengeId,
assessmentId: assessment.id,
});

// then
expect(liveAlert).to.deep.equal(certificationChallengeLiveAlert);
});
});

describe('when there is a matching ongoing validated alert', function () {
it('should return the live alert', async function () {
// given
const certificationCourse = databaseBuilder.factory.buildCertificationCourse();

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

databaseBuilder.factory.buildCertificationChallengeLiveAlert({
assessmentId: assessment.id,
status: CertificationChallengeLiveAlertStatus.DISMISSED,
});

const certificationChallengeLiveAlert = databaseBuilder.factory.buildCertificationChallengeLiveAlert({
assessmentId: assessment.id,
status: CertificationChallengeLiveAlertStatus.ONGOING,
});

await databaseBuilder.commit();

// when
const liveAlert =
await certificationChallengeLiveAlertRepository.getOngoingOrValidatedByChallengeIdAndAssessmentId({
challengeId: certificationChallengeLiveAlert.challengeId,
assessmentId: assessment.id,
});

// then
expect(liveAlert).to.deep.equal(certificationChallengeLiveAlert);
});
});
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { correctAnswerThenUpdateAssessment } from '../../../../lib/domain/usecases/correct-answer-then-update-assessment.js';
import { CertificationChallengeLiveAlertStatus } from '../../../../src/certification/shared/domain/models/CertificationChallengeLiveAlert.js';
import { EmptyAnswerError } from '../../../../src/evaluation/domain/errors.js';
import { AnswerJob } from '../../../../src/quest/domain/models/AnwserJob.js';
import {
Expand Down Expand Up @@ -58,7 +59,7 @@ describe('Unit | Domain | Use Cases | correct-answer-then-update-assessment', fu
flashAssessmentResultRepository = { save: sinon.stub() };
scorecardService = { computeScorecard: sinon.stub() };
knowledgeElementRepository = { findUniqByUserIdAndAssessmentId: sinon.stub() };
certificationChallengeLiveAlertRepository = { getOngoingByChallengeIdAndAssessmentId: sinon.stub() };
certificationChallengeLiveAlertRepository = { getOngoingOrValidatedByChallengeIdAndAssessmentId: sinon.stub() };
certificationEvaluationCandidateRepository = { findByAssessmentId: sinon.stub() };
flashAlgorithmService = { getCapacityAndErrorRate: sinon.stub() };
algorithmDataFetcherService = { fetchForFlashLevelEstimation: sinon.stub() };
Expand Down Expand Up @@ -1128,37 +1129,75 @@ describe('Unit | Domain | Use Cases | correct-answer-then-update-assessment', fu
});
});

context('when a live alert has been set in V3 certification', function () {
it('should throw an error', async function () {
// given
const challenge = domainBuilder.buildChallenge({ id: '123' });
const assessment = domainBuilder.buildAssessment({
userId,
lastQuestionDate: nowDate,
state: Assessment.states.STARTED,
});
const answer = domainBuilder.buildAnswer({ challengeId: challenge.id });
const certificationChallengeLiveAlert = domainBuilder.buildCertificationChallengeLiveAlert({
assessmentId: assessment.id,
challengeId: challenge.id,
});
assessmentRepository.get.resolves(assessment);
challengeRepository.get.withArgs(challenge.id).resolves(challenge);
context('when a live alert has been set for the current challenge in V3 certification', function () {
context('when the live alert is ongoing', function () {
it('should throw an error', async function () {
// given
const challenge = domainBuilder.buildChallenge({ id: '123' });
const assessment = domainBuilder.buildAssessment({
userId,
lastQuestionDate: nowDate,
state: Assessment.states.STARTED,
});
const answer = domainBuilder.buildAnswer({ challengeId: challenge.id });
const certificationChallengeLiveAlert = domainBuilder.buildCertificationChallengeLiveAlert({
assessmentId: assessment.id,
challengeId: challenge.id,
status: CertificationChallengeLiveAlertStatus.ONGOING,
});
assessmentRepository.get.resolves(assessment);
challengeRepository.get.withArgs(challenge.id).resolves(challenge);

certificationChallengeLiveAlertRepository.getOngoingByChallengeIdAndAssessmentId
.withArgs({ challengeId: challenge.id, assessmentId: assessment.id })
.resolves(certificationChallengeLiveAlert);
certificationChallengeLiveAlertRepository.getOngoingOrValidatedByChallengeIdAndAssessmentId
.withArgs({ challengeId: challenge.id, assessmentId: assessment.id })
.resolves(certificationChallengeLiveAlert);

// when
const error = await catchErr(correctAnswerThenUpdateAssessment)({
answer,
userId,
...dependencies,
// when
const error = await catchErr(correctAnswerThenUpdateAssessment)({
answer,
userId,
...dependencies,
});

// then
expect(error).to.be.an.instanceOf(ForbiddenAccess);
expect(error.message).to.equal('An alert has been set.');
});
});

// then
expect(error).to.be.an.instanceOf(ForbiddenAccess);
expect(error.message).to.equal('An alert has been set.');
context('when the live alert is validated', function () {
it('should throw an error', async function () {
// given
const challenge = domainBuilder.buildChallenge({ id: '123' });
const assessment = domainBuilder.buildAssessment({
userId,
lastQuestionDate: nowDate,
state: Assessment.states.STARTED,
});
const answer = domainBuilder.buildAnswer({ challengeId: challenge.id });
const certificationChallengeLiveAlert = domainBuilder.buildCertificationChallengeLiveAlert({
assessmentId: assessment.id,
challengeId: challenge.id,
status: CertificationChallengeLiveAlertStatus.VALIDATED,
});
assessmentRepository.get.resolves(assessment);
challengeRepository.get.withArgs(challenge.id).resolves(challenge);

certificationChallengeLiveAlertRepository.getOngoingOrValidatedByChallengeIdAndAssessmentId
.withArgs({ challengeId: challenge.id, assessmentId: assessment.id })
.resolves(certificationChallengeLiveAlert);

// when
const error = await catchErr(correctAnswerThenUpdateAssessment)({
answer,
userId,
...dependencies,
});

// then
expect(error).to.be.an.instanceOf(ForbiddenAccess);
expect(error.message).to.equal('An alert has been set.');
});
});
});

Expand Down
4 changes: 4 additions & 0 deletions mon-pix/app/components/challenge/item.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ export default class Item extends Component {

@action
async answerValidated(challenge, assessment, answerValue, answerTimeout, answerFocusedOut) {
if (assessment.hasOngoingChallengeLiveAlert) {
return;
}

const answer = await this._findOrCreateAnswer(challenge, assessment);
answer.setProperties({
value: answerValue.trim(),
Expand Down
21 changes: 21 additions & 0 deletions mon-pix/tests/unit/components/challenge/challenge-item-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,27 @@ module('Unit | Component | Challenge | Item', function (hooks) {
assert.ok(true);
});

module('when there is an ongoing live alert', function () {
test('it should not save the answer', async function (assert) {
// given
const assessment = EmberObject.create({ answers: [answerToChallengeOne], hasOngoingChallengeLiveAlert: true });
const component = createGlimmerComponent('challenge/item', { challenge: challengeOne });
component.router = { transitionTo: sinon.stub().returns() };
component.currentUser = { isAnonymous: false };
component.store = {
createRecord: createRecordStub,
};

// when
await component.answerValidated(challengeOne, assessment, answerValue, answerFocusedOut, answerTimeout);

// then
sinon.assert.notCalled(answerToChallengeOne.save);
sinon.assert.notCalled(component.router.transitionTo);
assert.ok(true);
});
});

module('when saving succeeds', function () {
test('should redirect to assessment-resume route', async function (assert) {
// given
Expand Down

0 comments on commit 157e39d

Please sign in to comment.