Skip to content

Commit

Permalink
Merge pull request #2391 from sophiemoustard/COM-3718
Browse files Browse the repository at this point in the history
Com 3718
  • Loading branch information
ulysseferreira committed Jun 7, 2024
2 parents fcf9d18 + 53d54f5 commit a135c44
Show file tree
Hide file tree
Showing 13 changed files with 235 additions and 9 deletions.
13 changes: 12 additions & 1 deletion src/controllers/questionnaireHistoryController.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,15 @@ const addQuestionnaireHistory = async (req) => {
}
};

module.exports = { addQuestionnaireHistory };
const update = async (req) => {
try {
await QuestionnaireHistoryHelper.updateQuestionnaireHistory(req.params._id);

return { message: translate[language].questionnaireHistoryUpdated };
} catch (e) {
req.log('error', e);
return Boom.isBoom(e) ? e : Boom.badImplementation(e);
}
};

module.exports = { addQuestionnaireHistory, update };
1 change: 1 addition & 0 deletions src/data/rights.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/helpers/questionnaireHistories.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,6 @@ exports.addQuestionnaireHistory = async (payload) => {
{ ...payload, company: traineesCompanyAtCourseRegistrationList[0].company, ...(timeline && { timeline }) }
);
};

exports.updateQuestionnaireHistory = async questionnaireHistoryId => QuestionnaireHistory
.updateOne({ _id: questionnaireHistoryId }, { $set: { isValidated: true } });
3 changes: 2 additions & 1 deletion src/helpers/questionnaires.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ const formatQuestionnaireAnswersWithCourse = async (courseId, questionnaireAnswe
};

const getFollowUpForReview = async (questionnaire, courseId) => {
const followUp = questionnaire.histories.map(h => pick(h, ['user', 'questionnaireAnswersList', 'timeline']));
const fieldsToPick = ['user', 'questionnaireAnswersList', 'timeline', '_id', 'isValidated'];
const followUp = questionnaire.histories.map(h => pick(h, fieldsToPick));

const course = await Course.findOne({ _id: courseId })
.select('subProgram companies misc type holding trainees')
Expand Down
2 changes: 2 additions & 0 deletions src/helpers/translate.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ module.exports = {
publishedQuestionnaireWithSameTypeExists: 'A questionnaire with the same type is already published.',
questionnaireUpdated: 'questionnaire updated.',
questionnaireHistoryCreated: 'Questionnaire history created.',
questionnaireHistoryUpdated: 'Questionnaire history updated.',
questionnaireQRCodeGenerated: 'Questionnaire QR Code generated.',
/* QuestionnaireHistories */
questionnaireHistoryConflict: 'A questionnaire history already exists.',
Expand Down Expand Up @@ -610,6 +611,7 @@ module.exports = {
publishedQuestionnaireWithSameTypeExists: 'Un questionnaire du même type est déjà publié.',
questionnaireUpdated: 'Questionnaire mis à jour.',
questionnaireHistoryCreated: 'Historique de questionnaire créé.',
questionnaireHistoryUpdated: 'Historique de questionnaire mis à jour.',
questionnaireQRCodeGenerated: 'QR Code généré.',
/* QuestionnaireHistories */
questionnaireHistoryConflict: 'Vous avez déjà répondu à ce questionnaire.',
Expand Down
1 change: 1 addition & 0 deletions src/models/QuestionnaireHistory.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const QuestionnaireHistorySchema = mongoose.Schema({
company: { type: mongoose.Schema.Types.ObjectId, ref: 'Company', required: true },
origin: { type: String, enum: ORIGIN_OPTIONS, required: true, immutable: true, default: MOBILE },
timeline: { type: String, enum: TIMELINE_OPTIONS, immutable: true },
isValidated: { type: Boolean },
}, { timestamps: true });

QuestionnaireHistorySchema.pre('find', validateQuery);
Expand Down
34 changes: 34 additions & 0 deletions src/routes/preHandlers/questionnaireHistories.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
const Boom = require('@hapi/boom');
const get = require('lodash/get');
const Questionnaire = require('../../models/Questionnaire');
const User = require('../../models/User');
const Course = require('../../models/Course');
const QuestionnaireHistory = require('../../models/QuestionnaireHistory');
const { END_COURSE } = require('../../helpers/constants');
const UtilsHelper = require('../../helpers/utils');
const { checkQuestionnaireAnswersList } = require('./utils');

exports.authorizeAddQuestionnaireHistory = async (req) => {
Expand All @@ -17,3 +21,33 @@ exports.authorizeAddQuestionnaireHistory = async (req) => {

return null;
};

exports.authorizeQuestionnaireHistoryUpdate = async (req) => {
const { _id: questionnaireHistoryId } = req.params;
const { trainerAnswers } = req.payload;
const credentials = get(req, 'auth.credentials');

const questionnaireHistory = await QuestionnaireHistory
.findOne(
{ _id: questionnaireHistoryId, timeline: END_COURSE },
{ questionnaire: 1, questionnaireAnswersList: 1, course: 1 }
)
.populate({ path: 'course', select: 'trainer' })
.lean();
if (!questionnaireHistory) throw Boom.notFound();

const courseTrainer = questionnaireHistory.course.trainer;
const loggedUserIsCourseTrainer = UtilsHelper.areObjectIdsEquals(courseTrainer, credentials._id);
if (!loggedUserIsCourseTrainer) throw Boom.forbidden();

const cardIds = trainerAnswers.map(answer => answer.card);
const questionnaire = await Questionnaire
.findOne({ _id: questionnaireHistory.questionnaire, cards: { $in: cardIds } })
.lean();
if (!questionnaire) throw Boom.notFound();

const answersHasGoodLength = trainerAnswers.length === questionnaireHistory.questionnaireAnswersList.length;
if (!answersHasGoodLength) throw Boom.badRequest();

return null;
};
22 changes: 20 additions & 2 deletions src/routes/questionnaireHistories.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

const Joi = require('joi');
Joi.objectId = require('joi-objectid')(Joi);
const { addQuestionnaireHistory } = require('../controllers/questionnaireHistoryController');
const { addQuestionnaireHistory, update } = require('../controllers/questionnaireHistoryController');
const { WEBAPP } = require('../helpers/constants');
const { authorizeAddQuestionnaireHistory } = require('./preHandlers/questionnaireHistories');
const {
authorizeAddQuestionnaireHistory,
authorizeQuestionnaireHistoryUpdate,
} = require('./preHandlers/questionnaireHistories');

exports.plugin = {
name: 'routes-questionnaire-histories',
Expand All @@ -30,5 +33,20 @@ exports.plugin = {
},
handler: addQuestionnaireHistory,
});
server.route({
method: 'PUT',
path: '/{_id}',
options: {
validate: {
params: Joi.object({ _id: Joi.objectId().required() }),
payload: Joi.object({
trainerAnswers: Joi.array().items(Joi.object({ card: Joi.objectId() })),
}),
},
auth: { scope: ['questionnairehistories:edit'] },
pre: [{ method: authorizeQuestionnaireHistoryUpdate }],
},
handler: update,
});
},
};
117 changes: 116 additions & 1 deletion tests/integration/questionnaireHistories.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ const {
questionnaireHistoriesUsersList,
cardsList,
coursesList,
questionnaireHistoriesList,
} = require('./seed/questionnaireHistoriesSeed');
const { getTokenByCredentials } = require('./helpers/authentication');
const { getTokenByCredentials, getToken } = require('./helpers/authentication');
const { companyWithoutSubscription } = require('../seed/authCompaniesSeed');
const { noRoleNoCompany } = require('../seed/authUsersSeed');
const QuestionnaireHistory = require('../../src/models/QuestionnaireHistory');
Expand Down Expand Up @@ -440,3 +441,117 @@ describe('QUESTIONNAIRE HISTORIES ROUTES - POST /questionnairehistories', () =>
});
});
});

describe('QUESTIONNAIRE HISTORIES ROUTES - PUT /questionnairehistories/{_id}', () => {
let authToken;
beforeEach(populateDB);
const endSelfPositionningQuestionnaireHistoryId = questionnaireHistoriesList[1]._id;

describe('TRAINER', () => {
beforeEach(async () => {
authToken = await getToken('trainer');
});

it('should update questionnaireHistory', async () => {
const payload = { trainerAnswers: [{ card: cardsList[1]._id }] };
const response = await app.inject({
method: 'PUT',
url: `/questionnairehistories/${endSelfPositionningQuestionnaireHistoryId}`,
payload,
headers: { Cookie: `alenvi_token=${authToken}` },
});

expect(response.statusCode).toBe(200);
const questionnaireHistory = await QuestionnaireHistory
.countDocuments({ _id: endSelfPositionningQuestionnaireHistoryId, timeline: END_COURSE, isValidated: true });
expect(questionnaireHistory).toBe(1);
});

it('should return 404 if questionnaireHistory doesn\'t exist', async () => {
const payload = { trainerAnswers: [{ card: cardsList[1]._id }] };
const response = await app.inject({
method: 'PUT',
url: `/questionnairehistories/${new ObjectId()}`,
payload,
headers: { Cookie: `alenvi_token=${authToken}` },
});

expect(response.statusCode).toBe(404);
});

it('should return 404 if questionnaireHistory has START_COURSE timeline', async () => {
const startSelfPositionningQuestionnaireHistoryId = questionnaireHistoriesList[2]._id;

const payload = { trainerAnswers: [{ card: cardsList[1]._id }] };
const response = await app.inject({
method: 'PUT',
url: `/questionnairehistories/${startSelfPositionningQuestionnaireHistoryId}`,
payload,
headers: { Cookie: `alenvi_token=${authToken}` },
});

expect(response.statusCode).toBe(404);
});

it('should return 404 if card is not in questionnaire', async () => {
const payload = { trainerAnswers: [{ card: cardsList[2]._id }] };
const response = await app.inject({
method: 'PUT',
url: `/questionnairehistories/${endSelfPositionningQuestionnaireHistoryId}`,
payload,
headers: { Cookie: `alenvi_token=${authToken}` },
});

expect(response.statusCode).toBe(404);
});

it('should return 400 if trainerAnswers has not good number of elements', async () => {
const payload = { trainerAnswers: [{ card: cardsList[1]._id }, { card: cardsList[3]._id }] };
const response = await app.inject({
method: 'PUT',
url: `/questionnairehistories/${endSelfPositionningQuestionnaireHistoryId}`,
payload,
headers: { Cookie: `alenvi_token=${authToken}` },
});

expect(response.statusCode).toBe(400);
});
});

describe('OTHER ROLES', () => {
const roles = [
{ name: 'helper', expectedCode: 403 },
{ name: 'planning_referent', expectedCode: 403 },
{ name: 'coach', expectedCode: 403 },
];
roles.forEach((role) => {
it(`should return ${role.expectedCode} as user is ${role.name}`, async () => {
authToken = await getToken(role.name);
const payload = { trainerAnswers: [{ card: cardsList[1]._id }] };

const response = await app.inject({
method: 'PUT',
url: `/questionnairehistories/${endSelfPositionningQuestionnaireHistoryId}`,
payload,
headers: { Cookie: `alenvi_token=${authToken}` },
});

expect(response.statusCode).toBe(role.expectedCode);
});
});

it('should return 403 if user is not course trainer', async () => {
authToken = await getToken('training_organisation_manager');

const payload = { trainerAnswers: [{ card: cardsList[1]._id }] };
const response = await app.inject({
method: 'PUT',
url: `/questionnairehistories/${endSelfPositionningQuestionnaireHistoryId}`,
payload,
headers: { Cookie: `alenvi_token=${authToken}` },
});

expect(response.statusCode).toBe(403);
});
});
});
18 changes: 16 additions & 2 deletions tests/integration/seed/questionnaireHistoriesSeed.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const Step = require('../../../src/models/Step');
const SubProgram = require('../../../src/models/SubProgram');
const CourseSlot = require('../../../src/models/CourseSlot');
const Program = require('../../../src/models/Program');
const { userList, trainerOrganisationManager, vendorAdmin } = require('../../seed/authUsersSeed');
const { userList, trainerOrganisationManager, vendorAdmin, trainer } = require('../../seed/authUsersSeed');
const { deleteNonAuthenticationSeeds } = require('../helpers/db');
const {
INTER_B2B,
Expand All @@ -23,6 +23,7 @@ const {
SELF_POSITIONNING,
ON_SITE,
END_COURSE,
START_COURSE,
} = require('../../../src/helpers/constants');
const { authCompany, companyWithoutSubscription } = require('../../seed/authCompaniesSeed');

Expand Down Expand Up @@ -102,6 +103,7 @@ const coursesList = [
_id: new ObjectId(),
format: 'blended',
subProgram: subProgramsList[1]._id,
trainer: trainer._id,
type: INTER_B2B,
operationsRepresentative: vendorAdmin._id,
trainees: [questionnaireHistoriesUsersList[1]],
Expand All @@ -111,19 +113,30 @@ const coursesList = [

const questionnaireHistoriesList = [
{
_id: new ObjectId(),
course: coursesList[0]._id,
user: questionnaireHistoriesUsersList[2],
questionnaire: questionnairesList[0]._id,
company: authCompany._id,
questionnaireAnswersList: [{ card: cardsList[3]._id, answerList: ['blabla'] }],
},
{
_id: new ObjectId(),
course: coursesList[1]._id,
user: questionnaireHistoriesUsersList[1],
questionnaire: questionnairesList[2]._id,
company: authCompany._id,
timeline: END_COURSE,
questionnaireAnswersList: [{ card: cardsList[3]._id, answerList: ['blabla'] }],
questionnaireAnswersList: [{ card: cardsList[1]._id, answerList: ['2'] }],
},
{
_id: new ObjectId(),
course: coursesList[1]._id,
user: questionnaireHistoriesUsersList[1],
questionnaire: questionnairesList[2]._id,
company: authCompany._id,
timeline: START_COURSE,
questionnaireAnswersList: [{ card: cardsList[1]._id, answerList: ['1'] }],
},
];

Expand Down Expand Up @@ -211,4 +224,5 @@ module.exports = {
coursesList,
questionnaireHistoriesUsersList,
cardsList,
questionnaireHistoriesList,
};
2 changes: 2 additions & 0 deletions tests/unit/helpers/authorization.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ describe('validate', () => {
'programs:read',
'questionnaires:edit',
'questionnaires:read',
'questionnairehistories:edit',
'trainermissions:edit',
'trainermissions:read',
`company-${companyId}`,
Expand Down Expand Up @@ -384,6 +385,7 @@ describe('validate', () => {
'attendances:edit',
'holdings:read',
'questionnaires:read',
'questionnairehistories:edit',
],
role: { client: { name: 'coach' }, vendor: { name: 'trainer' } },
holding: null,
Expand Down
19 changes: 19 additions & 0 deletions tests/unit/helpers/questionnaireHistories.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -302,3 +302,22 @@ describe('addQuestionnaireHistory', () => {
);
});
});

describe('updateQuestionnaireHistory', () => {
const questionnaireHistoryId = new ObjectId();
let updateOne;

beforeEach(() => {
updateOne = sinon.stub(QuestionnaireHistory, 'updateOne');
});

afterEach(() => {
updateOne.restore();
});

it('should update questionnaireHistory', async () => {
await QuestionnaireHistoriesHelper.updateQuestionnaireHistory(questionnaireHistoryId);

sinon.assert.calledWithExactly(updateOne, { _id: questionnaireHistoryId }, { $set: { isValidated: true } });
});
});

0 comments on commit a135c44

Please sign in to comment.