From 9e01fe7e41bb34b23473c493a0b0354201cffecd Mon Sep 17 00:00:00 2001 From: Alexandre COIN Date: Thu, 21 Nov 2024 15:42:21 +0100 Subject: [PATCH 1/4] refactor(api): improve scenario simulator acceptance test suite --- .../scenario-simulator-controller_test.js | 55 +++++++------------ 1 file changed, 19 insertions(+), 36 deletions(-) diff --git a/api/tests/acceptance/application/scenario-simulator/scenario-simulator-controller_test.js b/api/tests/acceptance/application/scenario-simulator/scenario-simulator-controller_test.js index 5731e2fab0f..9b1bb68bb9f 100644 --- a/api/tests/acceptance/application/scenario-simulator/scenario-simulator-controller_test.js +++ b/api/tests/acceptance/application/scenario-simulator/scenario-simulator-controller_test.js @@ -146,42 +146,25 @@ describe('Acceptance | Controller | scenario-simulator-controller', function () }; }); - describe('when a number of challenges to pass is specified', function () { - it('should return a payload with the same number of simulation scenario results', async function () { - // given - options.headers.authorization = adminAuthorization; - options.payload = validPayload; - - // when - const response = await server.inject(options); - - // then - expect(response).to.have.property('statusCode', 200); - const parsedResponse = parseJsonStream(response); - expect(parsedResponse[0].simulationReport).to.have.lengthOf(2); - expect(parsedResponse[0].simulationReport[0].challengeId).to.exist; - expect(parsedResponse[0].simulationReport[0].capacity).to.exist; - expect(parsedResponse[0].simulationReport[0].difficulty).to.exist; - expect(parsedResponse[0].simulationReport[0].discriminant).to.exist; - expect(parsedResponse[0].simulationReport[0].reward).to.exist; - expect(parsedResponse[0].simulationReport[0].errorRate).to.exist; - expect(parsedResponse[0].simulationReport[0].answerStatus).to.exist; - }); - }); - - describe('when the scenario is capacity', function () { - it('should return a payload with simulation the capacity scenario results', async function () { - // given - options.headers.authorization = adminAuthorization; - options.payload = validPayload; - - // when - const response = await server.inject(options); - - // then - const parsedResponse = parseJsonStream(response); - expect(parsedResponse[0].simulationReport).to.have.lengthOf(2); - }); + it('should return a report with the same number of simulation scenario reports as the number of challenges in the configuration', async function () { + // given + options.headers.authorization = adminAuthorization; + options.payload = validPayload; + + // when + const response = await server.inject(options); + + // then + expect(response).to.have.property('statusCode', 200); + const parsedResponse = parseJsonStream(response); + expect(parsedResponse[0].simulationReport).to.have.lengthOf(2); + expect(parsedResponse[0].simulationReport[0].challengeId).to.exist; + expect(parsedResponse[0].simulationReport[0].capacity).to.exist; + expect(parsedResponse[0].simulationReport[0].difficulty).to.exist; + expect(parsedResponse[0].simulationReport[0].discriminant).to.exist; + expect(parsedResponse[0].simulationReport[0].reward).to.exist; + expect(parsedResponse[0].simulationReport[0].errorRate).to.exist; + expect(parsedResponse[0].simulationReport[0].answerStatus).to.exist; }); describe('when there is no connected user', function () { From 9600327ab14ac2edbd4f566aefbbb4ffc912f27e Mon Sep 17 00:00:00 2001 From: P-Jeremy Date: Mon, 25 Nov 2024 11:43:59 +0100 Subject: [PATCH 2/4] :recycle: api: refactor joi validation on simulator route --- .../application/scenario-simulator-route.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/api/src/certification/flash-certification/application/scenario-simulator-route.js b/api/src/certification/flash-certification/application/scenario-simulator-route.js index d4483c55698..a5ebec53887 100644 --- a/api/src/certification/flash-certification/application/scenario-simulator-route.js +++ b/api/src/certification/flash-certification/application/scenario-simulator-route.js @@ -3,13 +3,6 @@ import Joi from 'joi'; import { securityPreHandlers } from '../../../shared/application/security-pre-handlers.js'; import { scenarioSimulatorController } from './scenario-simulator-controller.js'; -const _baseScenarioParametersValidator = Joi.object().keys({ - initialCapacity: Joi.number().integer().min(-8).max(8), - numberOfIterations: Joi.number().integer().min(0), - challengePickProbability: Joi.number().min(0).max(100), - variationPercent: Joi.number().min(0).max(1), -}); - const register = async (server) => { server.route([ { @@ -26,8 +19,12 @@ const register = async (server) => { options: { allowUnknown: true, }, - payload: _baseScenarioParametersValidator + payload: Joi.object() .keys({ + initialCapacity: Joi.number().integer().min(-8).max(8), + numberOfIterations: Joi.number().integer().min(0), + challengePickProbability: Joi.number().min(0).max(100), + variationPercent: Joi.number().min(0).max(1), capacity: Joi.number().min(-8).max(8).required(), }) .required(), From 2ffb8604bfbc2093ff0461daaf52310f38d9aa7a Mon Sep 17 00:00:00 2001 From: P-Jeremy Date: Mon, 25 Nov 2024 15:08:55 +0100 Subject: [PATCH 3/4] :sparkles: api: add accessibilityAdjustmentNeeded param to #findActiveFlashCompatible repo method Co-authored-by: Yannick Francois --- .../repositories/challenge-repository.js | 14 ++++- .../repositories/challenge-repository_test.js | 53 ++++++++++++++++--- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/api/src/shared/infrastructure/repositories/challenge-repository.js b/api/src/shared/infrastructure/repositories/challenge-repository.js index 55465aa6958..dded7afcebd 100644 --- a/api/src/shared/infrastructure/repositories/challenge-repository.js +++ b/api/src/shared/infrastructure/repositories/challenge-repository.js @@ -3,6 +3,7 @@ import _ from 'lodash'; import { httpAgent } from '../../../../lib/infrastructure/http/http-agent.js'; import { config } from '../../config.js'; import { NotFoundError } from '../../domain/errors.js'; +import { Accessibility } from '../../domain/models/Challenge.js'; import { Challenge } from '../../domain/models/index.js'; import * as solutionAdapter from '../../infrastructure/adapters/solution-adapter.js'; import * as skillAdapter from '../adapters/skill-adapter.js'; @@ -88,9 +89,20 @@ const findOperativeBySkills = async function (skills, locale) { const findActiveFlashCompatible = async function ({ locale, successProbabilityThreshold = config.features.successProbabilityThreshold, + accessibilityAdjustmentNeeded = false, } = {}) { _assertLocaleIsDefined(locale); - const challengeDataObjects = await challengeDatasource.findActiveFlashCompatible(locale); + let challengeDataObjects = await challengeDatasource.findActiveFlashCompatible(locale); + if (accessibilityAdjustmentNeeded) { + challengeDataObjects = challengeDataObjects.filter((challengeDataObject) => { + return ( + (challengeDataObject.accessibility1 === Accessibility.OK || + challengeDataObject.accessibility1 === Accessibility.RAS) && + (challengeDataObject.accessibility2 === Accessibility.OK || + challengeDataObject.accessibility2 === Accessibility.RAS) + ); + }); + } const activeSkills = await skillDatasource.findActive(); return _toDomainCollection({ challengeDataObjects, skills: activeSkills, successProbabilityThreshold }); }; diff --git a/api/tests/shared/integration/infrastructure/repositories/challenge-repository_test.js b/api/tests/shared/integration/infrastructure/repositories/challenge-repository_test.js index 9c9d08b3d38..52b68fbe223 100644 --- a/api/tests/shared/integration/infrastructure/repositories/challenge-repository_test.js +++ b/api/tests/shared/integration/infrastructure/repositories/challenge-repository_test.js @@ -512,6 +512,7 @@ describe('Integration | Repository | challenge-repository', function () { status: 'périmé', locales: ['nl'], }); + const learningContent = { skills: [{ ...skill, status: 'actif', level: skill.difficulty }], challenges: [ @@ -555,7 +556,6 @@ describe('Integration | Repository | challenge-repository', function () { expect(actualChallenges[1]).to.deep.contain({ status: 'archivé', }); - expect(actualChallenges[2]).to.deep.contain({ status: 'périmé', }); @@ -586,12 +586,33 @@ describe('Integration | Repository | challenge-repository', function () { status: 'périmé', locales, }); + const nonAccessibleChallenge = domainBuilder.buildChallenge({ + id: 'nonAccessibleChallenge', + skill, + status: 'validé', + locales, + }); const learningContent = { skills: [{ ...skill, status: 'actif', level: skill.difficulty }], challenges: [ - { ...activeChallenge, skillId: 'recSkill1', alpha: 3.57, delta: -8.99 }, - { ...archivedChallenge, skillId: 'recSkill1' }, - { ...outdatedChallenge, skillId: 'recSkill1' }, + { + ...activeChallenge, + accessibility1: 'OK', + accessibility2: 'OK', + skillId: 'recSkill1', + alpha: 3.57, + delta: -8.99, + }, + { ...archivedChallenge, accessibility1: 'OK', accessibility2: 'OK', skillId: 'recSkill1' }, + { ...outdatedChallenge, accessibility1: 'OK', accessibility2: 'OK', skillId: 'recSkill1' }, + { + ...nonAccessibleChallenge, + accessibility1: 'KO', + accessibility2: 'OK', + skillId: 'recSkill1', + alpha: 3.57, + delta: -8.99, + }, ], }; await mockLearningContent(learningContent); @@ -609,7 +630,7 @@ describe('Integration | Repository | challenge-repository', function () { }); // then - expect(actualChallenges).to.have.lengthOf(1); + expect(actualChallenges).to.have.lengthOf(2); expect(actualChallenges[0]).to.be.instanceOf(Challenge); expect(actualChallenges[0]).to.deep.contain({ id: 'activeChallenge', @@ -635,10 +656,30 @@ describe('Integration | Repository | challenge-repository', function () { }); // then - expect(actualChallenges).to.have.lengthOf(1); + expect(actualChallenges).to.have.lengthOf(2); expect(actualChallenges[0]).to.be.instanceOf(Challenge); expect(actualChallenges[0].minimumCapability).to.equal(-8.682265465359073); }); + + context('when requesting only accessible challenges', function () { + it('should return all accessible flash compatible challenges with skills', async function () { + // given + const successProbabilityThreshold = 0.95; + + // when + const actualChallenges = await challengeRepository.findActiveFlashCompatible({ + locale: 'fr-fr', + successProbabilityThreshold, + accessibilityAdjustmentNeeded: true, + }); + + // then + expect(actualChallenges).to.have.lengthOf(1); + expect(actualChallenges[0]).to.deep.contain({ + status: 'validé', + }); + }); + }); }); describe('#findValidatedBySkillId', function () { From eea3d1da01c1898ed2b91e0b2dba144d51ddb1a9 Mon Sep 17 00:00:00 2001 From: P-Jeremy Date: Mon, 25 Nov 2024 16:09:14 +0100 Subject: [PATCH 4/4] :sparkles: api: add accessibilityAdjustmentNeeded param to simulator route Co-authored-by: Yannick Francois --- .../application/scenario-simulator-controller.js | 2 ++ .../application/scenario-simulator-route.js | 1 + .../domain/usecases/simulate-flash-assessment-scenario.js | 3 ++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/api/src/certification/flash-certification/application/scenario-simulator-controller.js b/api/src/certification/flash-certification/application/scenario-simulator-controller.js index 9d60a44d4b8..4ffdfc4879f 100644 --- a/api/src/certification/flash-certification/application/scenario-simulator-controller.js +++ b/api/src/certification/flash-certification/application/scenario-simulator-controller.js @@ -26,6 +26,7 @@ async function simulateFlashAssessmentScenario( challengePickProbability, variationPercent, capacity, + accessibilityAdjustmentNeeded, } = request.payload; const pickAnswerStatus = dependencies.pickAnswerStatusService.pickAnswerStatusForCapacity(capacity); @@ -45,6 +46,7 @@ async function simulateFlashAssessmentScenario( locale, initialCapacity, variationPercent, + accessibilityAdjustmentNeeded, }, _.isUndefined, ); diff --git a/api/src/certification/flash-certification/application/scenario-simulator-route.js b/api/src/certification/flash-certification/application/scenario-simulator-route.js index a5ebec53887..11a7b9caabb 100644 --- a/api/src/certification/flash-certification/application/scenario-simulator-route.js +++ b/api/src/certification/flash-certification/application/scenario-simulator-route.js @@ -26,6 +26,7 @@ const register = async (server) => { challengePickProbability: Joi.number().min(0).max(100), variationPercent: Joi.number().min(0).max(1), capacity: Joi.number().min(-8).max(8).required(), + accessibilityAdjustmentNeeded: Joi.boolean(), }) .required(), }, diff --git a/api/src/certification/flash-certification/domain/usecases/simulate-flash-assessment-scenario.js b/api/src/certification/flash-certification/domain/usecases/simulate-flash-assessment-scenario.js index 73cb40ba744..b35b54413b3 100644 --- a/api/src/certification/flash-certification/domain/usecases/simulate-flash-assessment-scenario.js +++ b/api/src/certification/flash-certification/domain/usecases/simulate-flash-assessment-scenario.js @@ -12,8 +12,9 @@ export async function simulateFlashAssessmentScenario({ challengeRepository, flashAlgorithmService, sharedFlashAlgorithmConfigurationRepository, + accessibilityAdjustmentNeeded, }) { - const challenges = await challengeRepository.findActiveFlashCompatible({ locale }); + const challenges = await challengeRepository.findActiveFlashCompatible({ locale, accessibilityAdjustmentNeeded }); const configurationUsedInProduction = await sharedFlashAlgorithmConfigurationRepository.getMostRecent();