Skip to content

Commit

Permalink
[FEATURE] Permettre au surveillant de traiter un signalement d'extens…
Browse files Browse the repository at this point in the history
…ion sur Pix Certif (PIX-14820).

 #10337
  • Loading branch information
pix-service-auto-merge authored Oct 17, 2024
2 parents b848e05 + aedfe8f commit 29d5e60
Show file tree
Hide file tree
Showing 34 changed files with 1,171 additions and 322 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ const register = async function (server) {
server.route(routes);
};

const name = 'companion-alert-api';
const name = 'evaluation-companion-alert-api';
export { name, register };
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,13 @@ import {
CertificationCompanionLiveAlertStatus,
} from '../../../shared/domain/models/CertificationCompanionLiveAlert.js';

/**
* @typedef {import('../../../evaluation/domain/usecases/index.js').CertificationCompanionAlertRepository} CertificationCompanionAlertRepository
*/

export const createCompanionAlert = withTransaction(
/**
* @param {Object} params
* @param {number} params.assessmentId
* @param {CertificationCompanionAlertRepository} params.certificationCompanionAlertRepository
* @param {import('./index.js').CertificationCompanionAlertRepository} params.certificationCompanionAlertRepository
**/
async function ({ assessmentId, certificationCompanionAlertRepository }) {
async function createCompanionAlert({ assessmentId, certificationCompanionAlertRepository }) {
const companionAlert = new CertificationCompanionLiveAlert({
assessmentId,
status: CertificationCompanionLiveAlertStatus.ONGOING,
Expand Down
4 changes: 4 additions & 0 deletions api/src/certification/evaluation/domain/usecases/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import {
import * as certificationCandidateRepository from '../../infrastructure/repositories/certification-candidate-repository.js';
import * as certificationCompanionAlertRepository from '../../infrastructure/repositories/certification-companion-alert-repository.js';

/**
* @typedef {certificationCompanionAlertRepository} CertificationCompanionAlertRepository
*/

const dependencies = {
...sessionRepositories,
certificationCandidateRepository,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { usecases } from '../domain/usecases/index.js';

export const companionAlertController = {
async clear(request, h) {
const { sessionId, userId } = request.params;

await usecases.clearCompanionAlert({ sessionId, userId });

return h.response().code(204);
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import Joi from 'joi';

import { identifiersType } from '../../../shared/domain/types/identifiers-type.js';
import { responseObjectErrorDoc } from '../../../shared/infrastructure/open-api-doc/response-object-error-doc.js';
import { assessmentSupervisorAuthorization } from '../../shared/application/pre-handlers/session-supervisor-authorization.js';
import { companionAlertController } from './companion-alert-controller.js';

export function register(server) {
server.route([
{
method: 'PATCH',
path: '/api/sessions/{sessionId}/users/{userId}/clear-companion-alert',
config: {
plugins: {
'hapi-swagger': {
produces: ['application/json'],
consumes: ['application/json'],
},
},
response: {
failAction: 'log',
status: {
204: Joi.string.empty,
401: responseObjectErrorDoc,
403: responseObjectErrorDoc,
},
},
validate: {
params: Joi.object({
sessionId: identifiersType.sessionId,
userId: identifiersType.userId,
}),
},
pre: [
{
method: assessmentSupervisorAuthorization.verifyBySessionId,
assign: 'isSupervisorForSession',
},
],
handler: companionAlertController.clear,
tags: ['api', 'sessions', 'liveAlerts'],
notes: [
'Cette route est restreinte au surveillant',
'Elle permet de lever une alerte d’extension non détectée',
],
},
},
]);
}

export const name = 'session-management-companion-alert-api';
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { withTransaction } from '../../../../shared/domain/DomainTransaction.js';

export const clearCompanionAlert = withTransaction(
/**
* @param {Object} params
* @param {number} params.sessionId
* @param {number} params.userId
* @param {import('./index.js').CertificationCompanionAlertRepository} params.certificationCompanionAlertRepository
*/
async function clearCompanionAlert({ sessionId, userId, certificationCompanionAlertRepository }) {
const alert = await certificationCompanionAlertRepository.getOngoingAlert({ sessionId, userId });
if (!alert) return;

alert.clear();

await certificationCompanionAlertRepository.update(alert);
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import { cpfReceiptsStorage } from '../../infrastructure/storage/cpf-receipts-st
* @typedef {import('../../../../shared/domain/services/placement-profile-service.js')} PlacementProfileService
* @typedef {import('../../../shared/domain/services/certification-cpf-service.js')} CertificationCpfService
* @typedef {import('../../infrastructure/repositories/index.js').CertificationCandidateRepository} CertificationCandidateRepository
* @typedef {import('../../infrastructure/repositories/index.js').CertificationCompanionAlertRepository} CertificationCompanionAlertRepository
**/

/**
Expand Down Expand Up @@ -100,6 +101,7 @@ import { cpfReceiptsStorage } from '../../infrastructure/storage/cpf-receipts-st
* @typedef {flashAlgorithmConfigurationRepository} FlashAlgorithmConfigurationRepository
* @typedef {cpfExportRepository} CpfExportRepository
* @typedef {certificationCandidateRepository} CertificationCandidateRepository
* @typedef {certificationCompanionAlertRepository} CertificationCompanionAlertRepository
**/
const dependencies = {
...sessionRepositories,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { DomainTransaction } from '../../../../shared/domain/DomainTransaction.js';
import {
CertificationCompanionLiveAlert,
CertificationCompanionLiveAlertStatus,
} from '../../../shared/domain/models/CertificationCompanionLiveAlert.js';
const TABLE_NAME = 'certification-companion-live-alerts';

/**
*
* @param {object} params
* @param {number} params.sessionId
* @param {number} params.userId
* @param {object} options
* @param {import('knex').Knex} options.knex
*/
export async function getOngoingAlert({ sessionId, userId }, { knex = DomainTransaction.getConnection() } = {}) {
const alert = await knex
.select(`${TABLE_NAME}.*`)
.first()
.from('certification-courses')
.join('assessments', function () {
this.on('assessments.userId', '=', 'certification-courses.userId').andOn(
'assessments.certificationCourseId',
'=',
'certification-courses.id',
);
})
.join(TABLE_NAME, function () {
this.on(`${TABLE_NAME}.assessmentId`, '=', 'assessments.id').andOnVal(
`${TABLE_NAME}.status`,
'=',
CertificationCompanionLiveAlertStatus.ONGOING,
);
})
.where('certification-courses.sessionId', '=', sessionId)
.andWhere('certification-courses.userId', '=', userId)
.forUpdate(TABLE_NAME);

if (!alert) return null;

return new CertificationCompanionLiveAlert(alert);
}

/**
* @param {CertificationCompanionLiveAlert}
* @param {object} options
* @param {import('knex').Knex} options.knex
*/
export async function update({ id, status }, { knex = DomainTransaction.getConnection() } = {}) {
await knex(TABLE_NAME)
.update({
status,
updatedAt: knex.fn.now(),
})
.where({
id,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import * as complementaryCertificationCourseResultRepository from '../../../shar
import * as flashAlgorithmConfigurationRepository from '../../../shared/infrastructure/repositories/flash-algorithm-configuration-repository.js';
import * as certificationCandidateForSupervisingRepository from './certification-candidate-for-supervising-repository.js';
import * as certificationCandidateRepository from './certification-candidate-repository.js';
import * as certificationCompanionAlertRepository from './certification-companion-alert-repository.js';
import * as certificationOfficerRepository from './certification-officer-repository.js';
import * as competenceMarkRepository from './competence-mark-repository.js';
import * as courseAssessmentResultRepository from './course-assessment-result-repository.js';
Expand Down Expand Up @@ -69,6 +70,7 @@ import * as v3CertificationCourseDetailsForAdministrationRepository from './v3-c
* @typedef {cpfExportRepository} CpfExportRepository
* @typedef {juryCertificationSummaryRepository} JuryCertificationSummaryRepository
* @typedef {certificationCandidateRepository} CertificationCandidateRepository
* @typedef {typeof certificationCompanionAlertRepository} CertificationCompanionAlertRepository
*/
const repositoriesWithoutInjectedDependencies = {
assessmentRepository,
Expand Down Expand Up @@ -100,6 +102,7 @@ const repositoriesWithoutInjectedDependencies = {
certificationCpfCityRepository,
certificationCpfCountryRepository,
certificationCandidateRepository,
certificationCompanionAlertRepository,
};

/**
Expand Down
2 changes: 2 additions & 0 deletions api/src/certification/session-management/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as certificationDetails from './application/certification-details-route
import * as certificationIssueReport from './application/certification-issue-report-route.js';
import * as certificationOfficer from './application/certification-officer-route.js';
import * as certificationReport from './application/certification-report-route.js';
import * as companionAlert from './application/companion-alert-route.js';
import * as complementaryCertificationCourseResults from './application/complementary-certification-course-results-route.js';
import * as finalize from './application/finalize-route.js';
import * as finalizedSession from './application/finalized-session-route.js';
Expand All @@ -24,6 +25,7 @@ const certificationSessionRoutes = [
certificationOfficer,
certificationReport,
certificationIssueReport,
companionAlert,
complementaryCertificationCourseResults,
finalize,
finalizedSession,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
export class CertificationCompanionLiveAlert {
constructor({ assessmentId, status }) {
constructor({ id, assessmentId, status }) {
this.id = id;
this.assessmentId = assessmentId;
this.status = status;
}

clear() {
this.status = CertificationCompanionLiveAlertStatus.CLEARED;
}
}
/**
* Companion live alert statuses.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { CertificationCompanionLiveAlertStatus } from '../../../../../src/certification/shared/domain/models/CertificationCompanionLiveAlert.js';
import { Assessment } from '../../../../../src/shared/domain/models/Assessment.js';
import {
createServer,
databaseBuilder,
expect,
generateValidRequestAuthorizationHeader,
knex,
} from '../../../../test-helper.js';

describe('Certification | Session Management | Acceptance | Application | Routes | companion-alert', function () {
let server;

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

describe('PATCH /api/sessions/{sessionId}/users/{userId}/clear-companion-alert', function () {
it('should return 204 no content and set alert status to CLEARED', async function () {
// given
const { id: certificationCenterId } = databaseBuilder.factory.buildCertificationCenter();
const { id: sessionId } = databaseBuilder.factory.buildSession({ certificationCenterId });
const { id: certificationCandidateId, userId } = databaseBuilder.factory.buildCertificationCandidate({
sessionId,
});
databaseBuilder.factory.buildCoreSubscription({ certificationCandidateId });
const { id: certificationCourseId } = databaseBuilder.factory.buildCertificationCourse({
sessionId,
userId,
});
const { id: assessmentId } = databaseBuilder.factory.buildAssessment({
type: Assessment.types.CERTIFICATION,
state: Assessment.states.STARTED,
userId,
certificationCourseId,
});

databaseBuilder.factory.buildCertificationCompanionLiveAlert({
assessmentId,
status: CertificationCompanionLiveAlertStatus.ONGOING,
});

const { userId: supervisorId } = databaseBuilder.factory.buildSupervisorAccess({ sessionId });

await databaseBuilder.commit();

const headers = { authorization: generateValidRequestAuthorizationHeader(supervisorId, 'pix-certif') };

const options = {
headers,
method: 'PATCH',
url: `/api/sessions/${sessionId}/users/${userId}/clear-companion-alert`,
};

// when
const response = await server.inject(options);

// then
expect(response.statusCode).to.equal(204);
const alerts = await knex.select('assessmentId', 'status').from('certification-companion-live-alerts');
expect(alerts).to.deep.equal([{ assessmentId, status: CertificationCompanionLiveAlertStatus.CLEARED }]);
});

describe('when user does NOT have supervisor access on session', function () {
it('should return 401 unauthorized', async function () {
// given
const { id: certificationCenterId } = databaseBuilder.factory.buildCertificationCenter();
const { id: sessionId } = databaseBuilder.factory.buildSession({ certificationCenterId });
const { id: otherSessionId } = databaseBuilder.factory.buildSession({ certificationCenterId });

const { userId: supervisorId } = databaseBuilder.factory.buildSupervisorAccess({ sessionId: otherSessionId });

await databaseBuilder.commit();

const headers = { authorization: generateValidRequestAuthorizationHeader(supervisorId, 'pix-certif') };

const options = {
headers,
method: 'PATCH',
url: `/api/sessions/${sessionId}/users/666/clear-companion-alert`,
};

// when
const response = await server.inject(options);

// then
expect(response.statusCode).to.equal(401);
});
});
});
});
Loading

0 comments on commit 29d5e60

Please sign in to comment.