diff --git a/api/src/certification/enrolment/domain/usecases/add-candidate-to-session.js b/api/src/certification/enrolment/domain/usecases/add-candidate-to-session.js index 5bdbec3a680..ecabb5e092f 100644 --- a/api/src/certification/enrolment/domain/usecases/add-candidate-to-session.js +++ b/api/src/certification/enrolment/domain/usecases/add-candidate-to-session.js @@ -101,7 +101,7 @@ export async function addCandidateToSession({ } catch { throw new CertificationCandidatesError({ code: CERTIFICATION_CANDIDATES_ERRORS.CANDIDATE_RESULT_RECIPIENT_EMAIL_NOT_VALID.code, - meta: { email: candidate.resultRecipientEmail }, + meta: { value: candidate.resultRecipientEmail }, }); } } @@ -111,7 +111,7 @@ export async function addCandidateToSession({ } catch { throw new CertificationCandidatesError({ code: CERTIFICATION_CANDIDATES_ERRORS.CANDIDATE_EMAIL_NOT_VALID.code, - meta: { email: candidate.email }, + meta: { value: candidate.email }, }); } } diff --git a/api/src/shared/application/error-manager.js b/api/src/shared/application/error-manager.js index 2b6539a7312..64ce2838253 100644 --- a/api/src/shared/application/error-manager.js +++ b/api/src/shared/application/error-manager.js @@ -469,7 +469,7 @@ function _mapToHttpError(error) { } if (error instanceof DomainErrors.CertificationCandidateOnFinalizedSessionError) { - return new HttpErrors.ForbiddenError(error.message); + return new HttpErrors.ForbiddenError(error.message, error.code); } if (error instanceof DomainErrors.OrganizationLearnerCannotBeDissociatedError) { diff --git a/api/src/shared/domain/errors.js b/api/src/shared/domain/errors.js index 40e29c6384b..0aa8f1c34f0 100644 --- a/api/src/shared/domain/errors.js +++ b/api/src/shared/domain/errors.js @@ -96,6 +96,7 @@ class CertificationEndedByFinalizationError extends DomainError { class CertificationCandidateOnFinalizedSessionError extends DomainError { constructor(message = "Cette session a déjà été finalisée, l'ajout de candidat n'est pas autorisé") { super(message); + this.code = 'CANDIDATE_NOT_ALLOWED_FOR_FINALIZED_SESSION'; } } diff --git a/api/tests/shared/unit/application/error-manager_test.js b/api/tests/shared/unit/application/error-manager_test.js index 78b242e1c1c..0e927843cc2 100644 --- a/api/tests/shared/unit/application/error-manager_test.js +++ b/api/tests/shared/unit/application/error-manager_test.js @@ -661,7 +661,7 @@ describe('Shared | Unit | Application | ErrorManager', function () { await handle(params.request, params.h, params.error); // then - expect(HttpErrors.ForbiddenError).to.have.been.calledWithExactly(error.message); + expect(HttpErrors.ForbiddenError).to.have.been.calledWithExactly(error.message, error.code); }); it('should instantiate ConflictError when CertificationEndedByFinalizationError', async function () { diff --git a/certif/app/components/sessions/session-details/enrolled-candidates/index.gjs b/certif/app/components/sessions/session-details/enrolled-candidates/index.gjs index 043deaa2f5b..e2defdd1478 100644 --- a/certif/app/components/sessions/session-details/enrolled-candidates/index.gjs +++ b/certif/app/components/sessions/session-details/enrolled-candidates/index.gjs @@ -234,6 +234,8 @@ export default class EnrolledCandidates extends Component { return this._handleEntityValidationError(errorResponse); case '400': return this._handleMissingQueryParamError(errorResponse); + case '403': + return this._handleApiError(errorResponse); default: return this.intl.t(`${TRANSLATE_PREFIX}.add-modal.notifications.error-add-unknown`); } @@ -248,6 +250,15 @@ export default class EnrolledCandidates extends Component { } } + _handleApiError(errorResponse) { + const error = errorResponse?.errors?.[0]; + if (error?.code) { + return this.intl.t(`common.api-error-messages.${error.code}`, { + ...error?.meta, + }); + } + } + _handleMissingQueryParamError(errorResponse) { const error = errorResponse?.errors?.[0]; if (error?.detail === 'CANDIDATE_BIRTHDATE_FORMAT_NOT_VALID') { diff --git a/certif/tests/integration/components/sessions/session-details/enrolled-candidates/index-test.gjs b/certif/tests/integration/components/sessions/session-details/enrolled-candidates/index-test.gjs index 631f8906033..cefe0405a67 100644 --- a/certif/tests/integration/components/sessions/session-details/enrolled-candidates/index-test.gjs +++ b/certif/tests/integration/components/sessions/session-details/enrolled-candidates/index-test.gjs @@ -48,10 +48,7 @@ module('Integration | Component | Sessions | SessionDetails | EnrolledCandidates test('it should have an accessible table description', async function (assert) { //given - const candidate = _buildCertificationCandidate({ - birthdate: new Date('2019-04-28'), - subscriptions: [], - }); + const candidate = _buildCertificationCandidate({ subscriptions: [] }); const certificationCandidates = [store.createRecord('certification-candidate', candidate)]; const countries = [store.createRecord('country', { name: 'CANADA', code: 99401 })]; @@ -89,7 +86,6 @@ module('Integration | Component | Sessions | SessionDetails | EnrolledCandidates complementaryCertificationId, }); const candidate = _buildCertificationCandidate({ - birthdate: new Date('2019-04-28'), accessibilityAdjustmentNeeded: true, subscriptions: [coreSubscription, complementarySubscription], }); @@ -131,9 +127,7 @@ module('Integration | Component | Sessions | SessionDetails | EnrolledCandidates test('it should display details button', async function (assert) { // given - const candidate = _buildCertificationCandidate({ - subscriptions: [], - }); + const candidate = _buildCertificationCandidate({ subscriptions: [] }); const certificationCandidates = [store.createRecord('certification-candidate', candidate)]; const countries = [store.createRecord('country', { name: 'CANADA', code: 99401 })]; @@ -158,9 +152,7 @@ module('Integration | Component | Sessions | SessionDetails | EnrolledCandidates test('it should display details modal', async function (assert) { // given - const candidate = _buildCertificationCandidate({ - subscriptions: [], - }); + const candidate = _buildCertificationCandidate({ subscriptions: [] }); const certificationCandidates = [store.createRecord('certification-candidate', candidate)]; const countries = [store.createRecord('country', { name: 'CANADA', code: 99401 })]; @@ -190,19 +182,9 @@ module('Integration | Component | Sessions | SessionDetails | EnrolledCandidates test('it should be possible to delete the candidate', async function (assert) { // given const certificationCandidates = [ - _buildCertificationCandidate({ - id: 1, - firstName: 'Riri', - lastName: 'Duck', - subscriptions: [], - }), - _buildCertificationCandidate({ id: 2, firstName: 'Fifi', lastName: 'Duck', subscriptions: [] }), - _buildCertificationCandidate({ - id: 3, - firstName: 'Loulou', - lastName: 'Duck', - subscriptions: [], - }), + _buildCertificationCandidate({ id: '1' }), + _buildCertificationCandidate({ id: '2', firstName: 'Lara', lastName: 'Pafromage' }), + _buildCertificationCandidate({ id: '3', firstName: 'Jean', lastName: 'Registre' }), ].map((candidateData) => store.createRecord('certification-candidate', candidateData)); const countries = [store.createRecord('country', { name: 'CANADA', code: 99401 })]; @@ -221,7 +203,7 @@ module('Integration | Component | Sessions | SessionDetails | EnrolledCandidates , ); - await click(screen.getByRole('button', { name: 'Supprimer le candidat Riri Duck' })); + await click(screen.getByRole('button', { name: 'Supprimer le candidat Eddy Taurial' })); // then sinon.assert.calledOnce(certificationCandidates[0].destroyRecord); @@ -235,21 +217,9 @@ module('Integration | Component | Sessions | SessionDetails | EnrolledCandidates test('it display candidates with delete button disabled', async function (assert) { // given const certificationCandidates = [ - _buildCertificationCandidate({ - id: 1, - firstName: 'Riri', - lastName: 'Duck', - isLinked: false, - subscriptions: [], - }), - _buildCertificationCandidate({ id: 2, firstName: 'Fifi', lastName: 'Duck', isLinked: true, subscriptions: [] }), - _buildCertificationCandidate({ - id: 3, - firstName: 'Loulou', - lastName: 'Duck', - isLinked: false, - subscriptions: [], - }), + _buildCertificationCandidate({ id: '1' }), + _buildCertificationCandidate({ id: '2', firstName: 'Lara', lastName: 'Pafromage', isLinked: true }), + _buildCertificationCandidate({ id: '3', firstName: 'Jean', lastName: 'Registre' }), ].map((candidateData) => store.createRecord('certification-candidate', candidateData)); const countries = [store.createRecord('country', { name: 'CANADA', code: 99401 })]; @@ -266,13 +236,13 @@ module('Integration | Component | Sessions | SessionDetails | EnrolledCandidates // then assert - .dom(screen.getByRole('button', { name: 'Supprimer le candidat Riri Duck' })) + .dom(screen.getByRole('button', { name: 'Supprimer le candidat Eddy Taurial' })) .hasClass(DELETE_BUTTON_SELECTOR); assert - .dom(screen.getByRole('button', { name: 'Supprimer le candidat Fifi Duck' })) + .dom(screen.getByRole('button', { name: 'Supprimer le candidat Lara Pafromage' })) .hasClass(DELETE_BUTTON_DISABLED_SELECTOR); assert - .dom(screen.getByRole('button', { name: 'Supprimer le candidat Loulou Duck' })) + .dom(screen.getByRole('button', { name: 'Supprimer le candidat Jean Registre' })) .hasClass(DELETE_BUTTON_SELECTOR); }); }); @@ -308,27 +278,9 @@ module('Integration | Component | Sessions | SessionDetails | EnrolledCandidates test('it display candidates with an edit button', async function (assert) { // given const certificationCandidates = [ - _buildCertificationCandidate({ - id: 1, - firstName: 'Riri', - lastName: 'Duck', - isLinked: false, - subscriptions: [], - }), - _buildCertificationCandidate({ - id: 2, - firstName: 'Fifi', - lastName: 'Duck', - isLinked: true, - subscriptions: [], - }), - _buildCertificationCandidate({ - id: 3, - firstName: 'Loulou', - lastName: 'Duck', - isLinked: false, - subscriptions: [], - }), + _buildCertificationCandidate({ id: '1' }), + _buildCertificationCandidate({ id: '2', firstName: 'Lara', lastName: 'Pafromage', isLinked: true }), + _buildCertificationCandidate({ id: '3', firstName: 'Jean', lastName: 'Registre' }), ].map((candidateData) => store.createRecord('certification-candidate', candidateData)); const countries = [store.createRecord('country', { name: 'CANADA', code: 99401 })]; @@ -346,12 +298,14 @@ module('Integration | Component | Sessions | SessionDetails | EnrolledCandidates // then // then - assert.dom(screen.getByRole('button', { name: 'Editer le candidat Riri Duck' })).hasClass(EDIT_BUTTON_SELECTOR); assert - .dom(screen.getByRole('button', { name: 'Editer le candidat Fifi Duck' })) + .dom(screen.getByRole('button', { name: 'Editer le candidat Eddy Taurial' })) + .hasClass(EDIT_BUTTON_SELECTOR); + assert + .dom(screen.getByRole('button', { name: 'Editer le candidat Lara Pafromage' })) .hasClass(EDIT_BUTTON_DISABLED_SELECTOR); assert - .dom(screen.getByRole('button', { name: 'Editer le candidat Loulou Duck' })) + .dom(screen.getByRole('button', { name: 'Editer le candidat Jean Registre' })) .hasClass(EDIT_BUTTON_SELECTOR); assert.strictEqual( screen.getAllByText("Ce candidat a déjà rejoint la session. Vous ne pouvez pas l'éditer.").length, @@ -363,7 +317,6 @@ module('Integration | Component | Sessions | SessionDetails | EnrolledCandidates test('should display candidate needs accessibility adjusted certification', async function (assert) { // given const candidate = _buildCertificationCandidate({ - birthdate: new Date('2019-04-28'), accessibilityAdjustmentNeeded: true, }); @@ -391,7 +344,6 @@ module('Integration | Component | Sessions | SessionDetails | EnrolledCandidates test('should display candidate doesnt need accessibility adjusted certification', async function (assert) { // given const candidate = _buildCertificationCandidate({ - birthdate: new Date('2019-04-28'), accessibilityAdjustmentNeeded: false, }); @@ -420,7 +372,6 @@ module('Integration | Component | Sessions | SessionDetails | EnrolledCandidates test('should not display accessibility adjusted certification needed information', async function (assert) { // given const candidate = _buildCertificationCandidate({ - birthdate: new Date('2019-04-28'), accessibilityAdjustmentNeeded: true, }); @@ -450,20 +401,8 @@ module('Integration | Component | Sessions | SessionDetails | EnrolledCandidates test('it does not display candidates with an edit button', async function (assert) { // given const certificationCandidates = [ - _buildCertificationCandidate({ - id: 1, - firstName: 'Riri', - lastName: 'Duck', - isLinked: false, - subscriptions: [], - }), - _buildCertificationCandidate({ - id: 2, - firstName: 'Fifi', - lastName: 'Duck', - isLinked: true, - subscriptions: [], - }), + _buildCertificationCandidate({ id: '1' }), + _buildCertificationCandidate({ id: '2', firstName: 'Lara', lastName: 'Pafromage', isLinked: true }), ].map((candidateData) => store.createRecord('certification-candidate', candidateData)); const countries = [store.createRecord('country', { name: 'CANADA', code: 99401 })]; @@ -479,8 +418,8 @@ module('Integration | Component | Sessions | SessionDetails | EnrolledCandidates ); // then - assert.dom(screen.queryByRole('button', { name: 'Editer le candidat Riri Duck' })).doesNotExist(); - assert.dom(screen.queryByRole('button', { name: 'Editer le candidat Fifi Duck' })).doesNotExist(); + assert.dom(screen.queryByRole('button', { name: 'Editer le candidat Eddy Taurial' })).doesNotExist(); + assert.dom(screen.queryByRole('button', { name: 'Editer le candidat Lara Pafromage' })).doesNotExist(); }); }); }); @@ -498,7 +437,6 @@ module('Integration | Component | Sessions | SessionDetails | EnrolledCandidates complementaryCertificationId: cleaCertificationId, }); const candidate = _buildCertificationCandidate({ - birthdate: new Date('2019-04-28'), subscriptions: [coreSubscription, complementarySubscription], }); const complementaryCertifications = [ @@ -728,18 +666,18 @@ module('Integration | Component | Sessions | SessionDetails | EnrolledCandidates function _buildCertificationCandidate({ id = '12345', - firstName = 'Bob', - lastName = 'Leponge', - birthdate = new Date(), - birthCity = 'Marseille', - birthProvinceCode = '', - birthCountry = '', - email = 'bob.leponge@la.mer', - resultRecipientEmail = 'recipient@college.fr', + firstName = 'Eddy', + lastName = 'Taurial', + birthdate = '1990-03-22', + birthCity = 'Sainte-Anne', + birthProvinceCode = '01', + birthCountry = 'France', + email = 'eddy.taurial@example.com', + resultRecipientEmail = 'pat.atrak@example.com', externalId = 'an external id', extraTimePercentage = 0.3, isLinked = false, - billingMode = null, + billingMode = 'FREE', prepaymentCode = null, accessibilityAdjustmentNeeded = false, subscriptions = [], diff --git a/certif/translations/en.json b/certif/translations/en.json index 39e5cbdfe64..9d479fd043c 100644 --- a/certif/translations/en.json +++ b/certif/translations/en.json @@ -21,6 +21,7 @@ "validate": "Validate" }, "api-error-messages": { + "CANDIDATE_NOT_ALLOWED_FOR_FINALIZED_SESSION": "This session has already been finalised and no new candidates may be added.", "CANDIDATE_NOT_FOUND": "An error has occurred, the candidate {firstName} {lastName} hasn't been found.", "INVALID_DOCUMENT": "This version of the document is unknown.
Please download and import the candidate list template again", "SESSION_ALREADY_FINALIZED": "Cannot finalize session more than once.", @@ -1029,4 +1030,4 @@ "page-title": "Terms and conditions" } } -} \ No newline at end of file +} diff --git a/certif/translations/fr.json b/certif/translations/fr.json index ebece47902c..3db223d2430 100644 --- a/certif/translations/fr.json +++ b/certif/translations/fr.json @@ -21,6 +21,7 @@ "validate": "Valider" }, "api-error-messages": { + "CANDIDATE_NOT_ALLOWED_FOR_FINALIZED_SESSION": "Cette session a déjà été finalisée, l'ajout de candidat n'est pas autorisé.", "CANDIDATE_NOT_FOUND": "Une erreur est survenue, le candidat {firstName} {lastName} n'a pas été trouvé.", "INVALID_DOCUMENT": "La version du document est inconnue.
Veuillez télécharger à nouveau le modèle de liste des candidats et l'importer à nouveau.", "SESSION_ALREADY_FINALIZED": "La session a déjà été finalisée.", @@ -1029,4 +1030,4 @@ "page-title": "Conditions générales" } } -} \ No newline at end of file +}