From 316ea870e76f600d41be0a50280db90d7af84f46 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 20:57:17 +0000 Subject: [PATCH] Expose role and verification dates, make notifications non-blocking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deux points restants pour que le client puisse afficher correctement le profil. getOnlyUserData omettait role, status et les dates de vérification. Ces champs alimentent le badge de rôle et les badges « Verified » du client, qui remplace son état par la réponse reçue après une inscription ou une mise à jour de profil : les omettre les effaçait. Les dates sont ramenées à null quand elles valent undefined, sinon elles disparaissent de la réponse JSON sur une instance fraîchement créée et la forme du payload change d'un appel à l'autre. Les notifications envoyées après une confirmation d'email, de téléphone ou une réinitialisation de mot de passe faisaient répondre en erreur si le SMTP tombait, alors que le changement était déjà écrit en base. Le client ne rafraîchissait alors pas le profil et son badge restait périmé, et pour la réinitialisation l'utilisateur pouvait croire à un échec puis réessayer avec un code déjà consommé. L'inscription avait déjà été traitée de cette manière, les trois autres suivent. Les emails porteurs d'un code restent bloquants. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VP7toBNjKohZASo9wF67i4 --- src/services/authService.js | 33 ++++++++++++--- tests/profile-payload.test.js | 75 +++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 tests/profile-payload.test.js 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(); + }); +});