Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TECH] Affichage d'une erreur spécifique à l'enregistrement d'un candidat lorsque la session est finalisée (PIX-13862) #10248

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
});
}
}
Expand All @@ -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 },
});
}
}
Expand Down
2 changes: 1 addition & 1 deletion api/src/shared/application/error-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions api/src/shared/domain/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
}

Expand Down
2 changes: 1 addition & 1 deletion api/tests/shared/unit/application/error-manager_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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`);
}
Expand All @@ -248,6 +250,15 @@ export default class EnrolledCandidates extends Component {
}
}

_handleApiError(errorResponse) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

un ptit test ? :)

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') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 })];
Expand Down Expand Up @@ -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],
});
Expand Down Expand Up @@ -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 })];

Expand All @@ -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 })];

Expand Down Expand Up @@ -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 })];

Expand All @@ -221,7 +203,7 @@ module('Integration | Component | Sessions | SessionDetails | EnrolledCandidates
</template>,
);

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);
Expand All @@ -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 })];

Expand All @@ -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);
});
});
Expand Down Expand Up @@ -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 })];

Expand All @@ -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,
Expand All @@ -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,
});

Expand Down Expand Up @@ -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,
});

Expand Down Expand Up @@ -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,
});

Expand Down Expand Up @@ -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 })];

Expand All @@ -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();
});
});
});
Expand All @@ -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 = [
Expand Down Expand Up @@ -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 = '[email protected]',
resultRecipientEmail = '[email protected]',
firstName = 'Eddy',
lastName = 'Taurial',
birthdate = '1990-03-22',
birthCity = 'Sainte-Anne',
birthProvinceCode = '01',
birthCountry = 'France',
email = '[email protected]',
resultRecipientEmail = '[email protected]',
externalId = 'an external id',
extraTimePercentage = 0.3,
isLinked = false,
billingMode = null,
billingMode = 'FREE',
prepaymentCode = null,
accessibilityAdjustmentNeeded = false,
subscriptions = [],
Expand Down
3 changes: 2 additions & 1 deletion certif/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.<br />Please download and import the candidate list template again",
"SESSION_ALREADY_FINALIZED": "Cannot finalize session more than once.",
Expand Down Expand Up @@ -1029,4 +1030,4 @@
"page-title": "Terms and conditions"
}
}
}
}
3 changes: 2 additions & 1 deletion certif/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.<br />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.",
Expand Down Expand Up @@ -1029,4 +1030,4 @@
"page-title": "Conditions générales"
}
}
}
}
Loading