diff --git a/src/helpers/questionnaireHistories.js b/src/helpers/questionnaireHistories.js index 988623ad8..545067854 100644 --- a/src/helpers/questionnaireHistories.js +++ b/src/helpers/questionnaireHistories.js @@ -1,4 +1,5 @@ const Boom = require('@hapi/boom'); +const keyBy = require('lodash/keyBy'); const QuestionnaireHistory = require('../models/QuestionnaireHistory'); const Questionnaire = require('../models/Questionnaire'); const { @@ -50,11 +51,29 @@ exports.addQuestionnaireHistory = async (payload) => { }; exports.updateQuestionnaireHistory = async (questionnaireHistoryId, payload) => { - const { trainerComment } = payload; + const { trainerComment, trainerAnswers } = payload; - return QuestionnaireHistory - .updateOne( - { _id: questionnaireHistoryId }, - { $set: { isValidated: true, ...(trainerComment && { trainerComment }) } } - ); + let setFields = { isValidated: true, ...(trainerComment && { trainerComment }) }; + if (trainerAnswers.some(a => a.answer)) { + const trainerAnswersByCard = keyBy(trainerAnswers, 'card'); + const questionnaireHistory = await QuestionnaireHistory + .findOne({ _id: questionnaireHistoryId }, { questionnaireAnswersList: 1 }) + .lean(); + + const questionnaireAnswersList = []; + for (const bddAnswer of questionnaireHistory.questionnaireAnswersList) { + const trainerAnswer = trainerAnswersByCard[bddAnswer.card]; + + if (!trainerAnswer.answer) { + questionnaireAnswersList.push(bddAnswer); + continue; + } + + questionnaireAnswersList.push({ ...bddAnswer, trainerAnswerList: [trainerAnswer.answer] }); + } + + setFields = { ...setFields, questionnaireAnswersList }; + } + + return QuestionnaireHistory.updateOne({ _id: questionnaireHistoryId }, { $set: setFields }); }; diff --git a/src/models/QuestionnaireHistory.js b/src/models/QuestionnaireHistory.js index d0a8e5652..bed2e5609 100644 --- a/src/models/QuestionnaireHistory.js +++ b/src/models/QuestionnaireHistory.js @@ -9,6 +9,7 @@ const QuestionnaireHistorySchema = mongoose.Schema({ questionnaireAnswersList: [{ card: { type: mongoose.Schema.Types.ObjectId, ref: 'Card' }, answerList: { type: [String] }, + trainerAnswerList: { type: [String], default: undefined }, }], company: { type: mongoose.Schema.Types.ObjectId, ref: 'Company', required: true }, origin: { type: String, enum: ORIGIN_OPTIONS, required: true, immutable: true, default: MOBILE }, diff --git a/src/routes/preHandlers/questionnaireHistories.js b/src/routes/preHandlers/questionnaireHistories.js index 579e4e8df..225d47ef3 100644 --- a/src/routes/preHandlers/questionnaireHistories.js +++ b/src/routes/preHandlers/questionnaireHistories.js @@ -42,12 +42,14 @@ exports.authorizeQuestionnaireHistoryUpdate = async (req) => { const cardIds = trainerAnswers.map(answer => answer.card); const questionnaire = await Questionnaire - .findOne({ _id: questionnaireHistory.questionnaire, cards: { $in: cardIds } }) - .lean(); + .countDocuments({ _id: questionnaireHistory.questionnaire, cards: { $in: cardIds } }); if (!questionnaire) throw Boom.notFound(); const answersHasGoodLength = trainerAnswers.length === questionnaireHistory.questionnaireAnswersList.length; if (!answersHasGoodLength) throw Boom.badRequest(); + const everyAnswerIsAuthorized = trainerAnswers.every(a => !a.answer || ['1', '2', '3', '4', '5'].includes(a.answer)); + if (!everyAnswerIsAuthorized) throw Boom.badRequest(); + return null; }; diff --git a/src/routes/questionnaireHistories.js b/src/routes/questionnaireHistories.js index c910ffa07..a8b623dd8 100644 --- a/src/routes/questionnaireHistories.js +++ b/src/routes/questionnaireHistories.js @@ -40,7 +40,7 @@ exports.plugin = { validate: { params: Joi.object({ _id: Joi.objectId().required() }), payload: Joi.object({ - trainerAnswers: Joi.array().items(Joi.object({ card: Joi.objectId() })), + trainerAnswers: Joi.array().items(Joi.object({ card: Joi.objectId(), answer: Joi.string() })), trainerComment: Joi.string(), }), }, diff --git a/tests/integration/questionnaireHistories.test.js b/tests/integration/questionnaireHistories.test.js index 929c6a8fd..b62fad5bb 100644 --- a/tests/integration/questionnaireHistories.test.js +++ b/tests/integration/questionnaireHistories.test.js @@ -453,7 +453,10 @@ describe('QUESTIONNAIRE HISTORIES ROUTES - PUT /questionnairehistories/{_id}', ( }); it('should update questionnaireHistory', async () => { - const payload = { trainerAnswers: [{ card: cardsList[1]._id }], trainerComment: 'Appréciation du formateur' }; + const payload = { + trainerAnswers: [{ card: cardsList[1]._id, answer: '1' }], + trainerComment: 'Appréciation du formateur', + }; const response = await app.inject({ method: 'PUT', url: `/questionnairehistories/${endSelfPositionningQuestionnaireHistoryId}`, @@ -521,6 +524,21 @@ describe('QUESTIONNAIRE HISTORIES ROUTES - PUT /questionnairehistories/{_id}', ( expect(response.statusCode).toBe(400); }); + + it('should return 400 if a trainerAnswer is not allowed', async () => { + const payload = { + trainerAnswers: [{ card: cardsList[1]._id, answer: 'mauvaiseReponse' }], + trainerComment: 'Appréciation du formateur', + }; + const response = await app.inject({ + method: 'PUT', + url: `/questionnairehistories/${endSelfPositionningQuestionnaireHistoryId}`, + payload, + headers: { Cookie: `alenvi_token=${authToken}` }, + }); + + expect(response.statusCode).toBe(400); + }); }); describe('OTHER ROLES', () => { diff --git a/tests/integration/seed/questionnairesSeed.js b/tests/integration/seed/questionnairesSeed.js index 4af750f01..492702a11 100644 --- a/tests/integration/seed/questionnairesSeed.js +++ b/tests/integration/seed/questionnairesSeed.js @@ -282,8 +282,8 @@ const questionnaireHistories = [ questionnaire: questionnairesList[3]._id, user: traineeList[0]._id, questionnaireAnswersList: [ - { card: cardsList[1]._id, answerList: ['test'] }, - { card: cardsList[3]._id, answerList: ['blabla2'] }, + { card: cardsList[1]._id, answerList: ['3'] }, + { card: cardsList[3]._id, answerList: ['4'] }, ], timeline: START_COURSE, }, diff --git a/tests/integration/seed/seedsVerification.test.js b/tests/integration/seed/seedsVerification.test.js index 5bf78404c..80bf26b31 100644 --- a/tests/integration/seed/seedsVerification.test.js +++ b/tests/integration/seed/seedsVerification.test.js @@ -2083,6 +2083,31 @@ describe('SEEDS VERIFICATION', () => { expect(everySelfPositionningHistoryHasTimeline).toBeTruthy(); }); + + it('should pass if every trainee\'s answer is authorized (SELF POSITIONNING QUESTIONNAIRE)', () => { + const everySelfPositionningHistoryHasAuthorizedTraineeAnswer = questionnaireHistoryList + .filter(qh => qh.questionnaire.type === SELF_POSITIONNING) + .every(qh => qh.questionnaireAnswersList.every(a => ['1', '2', '3', '4', '5'].includes(a.answerList[0]))); + + expect(everySelfPositionningHistoryHasAuthorizedTraineeAnswer).toBeTruthy(); + }); + + it('should pass if every trainer\'s answer is authorized (SELF POSITIONNING QUESTIONNAIRE)', () => { + const everySelfPositionningHistoryHasAuthorizedTrainerAnswer = questionnaireHistoryList + .filter(qh => qh.questionnaire.type === SELF_POSITIONNING) + .every(qh => qh.questionnaireAnswersList + .every(a => !a.trainerAnswerList || ['1', '2', '3', '4', '5'].includes(a.trainerAnswerList[0]))); + + expect(everySelfPositionningHistoryHasAuthorizedTrainerAnswer).toBeTruthy(); + }); + + it('should pass if validated histories are linked to end self-positioning questionnaire', () => { + const everySelfPositionningHistoryHasAuthorizedTrainerAnswer = questionnaireHistoryList + .filter(qh => qh.isValidated) + .every(qh => qh.questionnaire.type === SELF_POSITIONNING && qh.timeline === END_COURSE); + + expect(everySelfPositionningHistoryHasAuthorizedTrainerAnswer).toBeTruthy(); + }); }); describe('Collection SectorHistory', () => { diff --git a/tests/unit/helpers/questionnaireHistories.test.js b/tests/unit/helpers/questionnaireHistories.test.js index c534c75e3..6cebc77a6 100644 --- a/tests/unit/helpers/questionnaireHistories.test.js +++ b/tests/unit/helpers/questionnaireHistories.test.js @@ -306,30 +306,84 @@ describe('addQuestionnaireHistory', () => { describe('updateQuestionnaireHistory', () => { const questionnaireHistoryId = new ObjectId(); let updateOne; + let findOne; beforeEach(() => { updateOne = sinon.stub(QuestionnaireHistory, 'updateOne'); + findOne = sinon.stub(QuestionnaireHistory, 'findOne'); }); afterEach(() => { updateOne.restore(); + findOne.restore(); }); it('should update questionnaireHistory', async () => { - const payload = { trainerAnswers: [{ _id: new ObjectId() }], trainerComment: '' }; + const payload = { trainerAnswers: [{ card: new ObjectId() }], trainerComment: '' }; + await QuestionnaireHistoriesHelper.updateQuestionnaireHistory(questionnaireHistoryId, payload); + sinon.assert.notCalled(findOne); sinon.assert.calledWithExactly(updateOne, { _id: questionnaireHistoryId }, { $set: { isValidated: true } }); }); it('should update questionnaireHistory with trainer comment', async () => { - const payload = { trainerAnswers: [{ _id: new ObjectId() }], trainerComment: 'Test avec un commentaire' }; + const payload = { trainerAnswers: [{ card: new ObjectId() }], trainerComment: 'Test avec un commentaire' }; + await QuestionnaireHistoriesHelper.updateQuestionnaireHistory(questionnaireHistoryId, payload); + sinon.assert.notCalled(findOne); sinon.assert.calledWithExactly( updateOne, { _id: questionnaireHistoryId }, { $set: { isValidated: true, trainerComment: payload.trainerComment } } ); }); + + it('should update questionnaireHistory with trainer answers', async () => { + const cardIds = [new ObjectId(), new ObjectId(), new ObjectId()]; + const payload = { + trainerAnswers: [ + { card: cardIds[0], answer: '4' }, + { card: cardIds[1] }, + { card: cardIds[2], answer: '5' }, + ], + }; + const questionnaireHistory = { + _id: questionnaireHistoryId, + questionnaire: new ObjectId(), + user: new ObjectId(), + timeline: END_COURSE, + questionnaireAnswersList: [ + { card: cardIds[0], answerList: ['3'] }, + { card: cardIds[1], answerList: ['4'] }, + { card: cardIds[2], answerList: ['4'] }, + ], + }; + findOne.returns(SinonMongoose.stubChainedQueries(questionnaireHistory, ['lean'])); + + await QuestionnaireHistoriesHelper.updateQuestionnaireHistory(questionnaireHistoryId, payload); + + SinonMongoose.calledOnceWithExactly( + findOne, + [ + { query: 'findOne', args: [{ _id: questionnaireHistoryId }, { questionnaireAnswersList: 1 }] }, + { query: 'lean' }, + ] + ); + sinon.assert.calledWithExactly( + updateOne, + { _id: questionnaireHistoryId }, + { + $set: { + isValidated: true, + questionnaireAnswersList: [ + { card: cardIds[0], answerList: ['3'], trainerAnswerList: ['4'] }, + { card: cardIds[1], answerList: ['4'] }, + { card: cardIds[2], answerList: ['4'], trainerAnswerList: ['5'] }, + ], + }, + } + ); + }); });