From 5b8898ba9ccbb7a5f5874da85e82c436aa490b78 Mon Sep 17 00:00:00 2001 From: ordinarthur <@arthurbarre.js@gmail.com> Date: Mon, 18 May 2026 17:04:08 +0200 Subject: [PATCH] =?UTF-8?q?test(auth):=20enrichit=20l'assertion=20signup?= =?UTF-8?q?=20duplicate=20email=20(format=20de=20r=C3=A9ponse)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le test "refuse un email déjà pris" vérifiait seulement `assertStatus(422)`. On enrichit pour vérifier la shape complète : - body.errors est un array non-vide - au moins une entrée a `field === 'email'` - cette entrée a un code et un message strings Pourquoi : un mutation test du 2026-05-18 a révélé qu'un simple `assertStatus(422)` laissait passer le retrait du `.unique()` côté validator Vine — la contrainte DB UNIQUE(email) prenait le relais silencieusement via le handler PG 23505, qui renvoie le même 422. Apprentissage : .unique() Vine et DB unique sont équivalents côté contrat API (le handler `23505` met `field='email'` identique). Le .unique() Vine est donc une optimisation (skip round-trip DB) plutôt qu'une garantie de sécurité. On garde la double protection comme best practice (defense-in-depth). Le test enrichi ne catche pas le retrait du .unique() spécifiquement (les 2 paths sont indistinguables côté client) MAIS catche des régressions plus graves : - Si le handler 23505 casse → 500 au lieu de 422 - Si `field` n'est plus posé → SPA ne peut plus highlight l'input - Si la shape {errors:[...]} change → contrat API cassé Co-Authored-By: Claude Opus 4.7 --- apps/api/tests/functional/auth.spec.ts | 29 +++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/apps/api/tests/functional/auth.spec.ts b/apps/api/tests/functional/auth.spec.ts index ced8044..942c608 100644 --- a/apps/api/tests/functional/auth.spec.ts +++ b/apps/api/tests/functional/auth.spec.ts @@ -55,7 +55,10 @@ test.group('Auth — POST /auth/signup', (group) => { response.assertStatus(422) }) - test('refuse un email déjà pris (422)', async ({ client }) => { + test('refuse un email déjà pris (422 + erreur typée sur le champ email)', async ({ + client, + assert, + }) => { await client.post('/api/v1/auth/signup').json({ email: 'twice@spec.test', password: 'password123', @@ -68,6 +71,30 @@ test.group('Auth — POST /auth/signup', (group) => { fullName: 'Second', }) response.assertStatus(422) + + // Vérifie le format précis de la réponse — pas juste un 422 vague. + // Permet de détecter une régression où : + // 1. La validation unique disparait (validator Vine ou contrainte DB) + // 2. Le handler d'exception change la shape (`{ errors: [{ ... }] }`) + // 3. Le `field` n'est plus posé sur l'erreur (le SPA s'en sert pour + // mettre en rouge le bon input) + // + // Ce test a été enrichi suite à un mutation test du 2026-05-18 qui a + // révélé qu'un seul `assertStatus(422)` laissait passer le retrait + // du `.unique()` côté validator (la contrainte DB prenait le relais + // silencieusement, sans test). + const body = response.body() as { + errors?: Array<{ code?: string; field?: string; message?: string }> + } + assert.isArray(body.errors) + assert.isAtLeast(body.errors!.length, 1) + const emailError = body.errors!.find((e) => e.field === 'email') + assert.exists( + emailError, + "le payload d'erreur doit avoir au moins une entrée avec field='email'" + ) + assert.isString(emailError!.code) + assert.isString(emailError!.message) }) })