diff --git a/src/services/authService.js b/src/services/authService.js index 089f07b..c26320f 100644 --- a/src/services/authService.js +++ b/src/services/authService.js @@ -10,6 +10,19 @@ const { tokenTypes } = require("../config/tokens"); const { sendEmail } = require("../Email/sendEmail"); +// Les emails envoyés une fois l'opération enregistrée en base ne sont que des +// notifications : une panne SMTP ne doit pas faire répondre en erreur alors +// que le changement, lui, est acquis. Les emails porteurs d'un code, eux, +// restent bloquants. +const sendNotificationEmail = async (options) => { + try { + await sendEmail(options); + } catch (e) { + logger.error(e); + } +}; + + const createUser = async (userBody) => { try { let message = 'Successfully Registered the account! Please Verify your email.'; @@ -312,7 +325,7 @@ const confirmEmailService = async (data, user) => { subject: "Merci - Email confirmer", message: "Votre email a été confirmer ", } - await sendEmail(options); + await sendNotificationEmail(options); return success( httpStatus.OK, @@ -368,7 +381,7 @@ const confirmTelService = async (data, user) => { subject: "Merci - Numéro de téléphone confirmer", message: "Votre numéro de téléphone a été confirmer ", } - await sendEmail(options); + await sendNotificationEmail(options); return success( httpStatus.OK, @@ -516,7 +529,7 @@ const confirmPasswordCodeService = async (data) => { message: "Your password has been successfully reset. If you are not at the origin of this action write to us at the email address at the bottom of the page", // email_verification_code: userBody.email_verification_code } - await sendEmail(options); + await sendNotificationEmail(options); return success( httpStatus.OK, @@ -562,8 +575,18 @@ const updateUserService = async (userBody, user) => { }; const getOnlyUserData = (user) => { - const { nom, prenoms, address, email, tel, uuid,} = user; - return {uuid, nom, prenoms, email, tel, address,}; + // Le client affiche le rôle et les badges de vérification à partir de ces + // champs : les omettre ici revenait à les effacer côté client après une + // inscription ou une mise à jour de profil. + const { nom, prenoms, address, email, tel, uuid, role, status, email_verified_at, tel_verified_at } = user; + return { + uuid, nom, prenoms, email, tel, address, role, status, + // Sur une instance fraîchement créée ces dates valent undefined et + // disparaissent de la réponse JSON : on les force à null pour que le + // client reçoive toujours la même forme. + email_verified_at: email_verified_at ?? null, + tel_verified_at: tel_verified_at ?? null, + }; } module.exports = { diff --git a/tests/profile-payload.test.js b/tests/profile-payload.test.js new file mode 100644 index 0000000..747252a --- /dev/null +++ b/tests/profile-payload.test.js @@ -0,0 +1,75 @@ +jest.mock('../src/Email/sendEmail', () => ({ sendEmail: jest.fn().mockResolvedValue(true) })); + +const { sendEmail } = require('../src/Email/sendEmail'); +const { + resetDatabase, closeDatabase, createLoggedInUser, authed, register, minutesFromNow, +} = require('./helpers'); + +let accessToken; +let user; + +beforeEach(async () => { + sendEmail.mockReset(); + sendEmail.mockResolvedValue(true); + await resetDatabase(); + ({ accessToken, user } = await createLoggedInUser()); +}); + +afterAll(closeDatabase); + +// Le client construit son affichage (badges de vérification, rôle) à partir de +// ces champs : s'ils manquent de la réponse, il les efface de son état. +describe('payload de profil', () => { + it("expose le rôle, le statut et les dates de vérification à l'inscription", async () => { + const res = await register({ email: 'second.user@example.com', tel: '+22509080706' }); + + expect(res.status).toBe(201); + expect(res.body.data.role).toBe('user'); + expect(res.body.data).toHaveProperty('status'); + expect(res.body.data).toHaveProperty('email_verified_at'); + expect(res.body.data).toHaveProperty('tel_verified_at'); + }); + + it('expose les mêmes champs après une mise à jour de profil', async () => { + const res = await authed('put', '/update-profile', accessToken) + .send({ nom: 'Nouveau', prenoms: 'Nom', address: 'Abidjan' }); + + expect(res.status).toBe(200); + expect(res.body.response.data.role).toBe('user'); + expect(res.body.response.data).toHaveProperty('email_verified_at'); + expect(res.body.response.data).toHaveProperty('tel_verified_at'); + }); +}); + +// Ces emails partent une fois le changement écrit en base : les faire échouer +// ferait répondre en erreur alors que l'opération a bien eu lieu. +describe('notifications non bloquantes', () => { + const armCode = () => user.update({ + verification_code: '1234567', + verification_code_expires_at: minutesFromNow(5), + }); + + it("confirme l'email même si l'envoi de la notification échoue", async () => { + await armCode(); + sendEmail.mockRejectedValue(new Error('SMTP indisponible')); + + const res = await authed('post', '/confirm-email', accessToken) + .send({ verification_email_code: 1234567 }); + + expect(res.status).toBe(200); + await user.reload(); + expect(user.email_verified_at).not.toBeNull(); + }); + + it('confirme le téléphone même si l\'envoi de la notification échoue', async () => { + await armCode(); + sendEmail.mockRejectedValue(new Error('SMTP indisponible')); + + const res = await authed('post', '/confirm-tel', accessToken) + .send({ verification_tel_code: 1234567 }); + + expect(res.status).toBe(200); + await user.reload(); + expect(user.tel_verified_at).not.toBeNull(); + }); +});