import { test } from '@japa/runner' import testUtils from '@adonisjs/core/services/test_utils' import Client from '#models/client' import { createTestUser, createTwoOrgs } from '../helpers/auth.js' import { body, type ApiError, type ApiOk, type ApiConflict } from '../helpers/response.js' type ClientShape = { id: string organizationId: string name: string email: string phone: string | null address: string | null siret: string | null notes: string | null } test.group('Clients — POST /clients', (group) => { group.each.setup(() => testUtils.db().withGlobalTransaction()) test('refuse sans email (422 + field=email)', async ({ client, assert }) => { const { bearer } = await createTestUser() const response = await client .post('/api/v1/clients') .headers(bearer) .json({ name: 'Boulangerie X' }) response.assertStatus(422) assert.equal(body(response).errors[0].field, 'email') }) test('refuse SIRET non-14-chiffres (422)', async ({ client }) => { const { bearer } = await createTestUser() const response = await client .post('/api/v1/clients') .headers(bearer) .json({ name: 'Test SIRET', email: 'compta@test.fr', siret: '123', // 3 chiffres au lieu de 14 }) response.assertStatus(422) }) test("crée un client (201) + UUID + association à l'org du user", async ({ client, assert, }) => { const { bearer, org } = await createTestUser() const response = await client .post('/api/v1/clients') .headers(bearer) .json({ name: 'Boulangerie X', email: 'compta@x.fr' }) response.assertStatus(201) const data = body>(response).data assert.match(data.id, /^[0-9a-f-]{36}$/u) assert.equal(data.organizationId, org.id) assert.equal(data.email, 'compta@x.fr') }) test('rejette doublon par nom case-insensitive (409 + payload existing)', async ({ client, assert, }) => { const { bearer } = await createTestUser() await client .post('/api/v1/clients') .headers(bearer) .json({ name: 'Boulangerie Martin', email: 'a@martin.fr' }) const dup = await client .post('/api/v1/clients') .headers(bearer) .json({ name: 'BOULANGERIE MARTIN', email: 'b@martin.fr' }) dup.assertStatus(409) const payload = body>(dup) assert.equal(payload.errors[0].code, 'duplicate_client') assert.exists(payload.existing.id) }) }) test.group('Clients — GET /clients', (group) => { group.each.setup(() => testUtils.db().withGlobalTransaction()) test("liste seulement les clients de l'org du user (cross-org)", async ({ client, assert, }) => { const { a, b } = await createTwoOrgs() await client .post('/api/v1/clients') .headers(a.bearer) .json({ name: 'Client A1', email: 'a1@a.fr' }) await client .post('/api/v1/clients') .headers(b.bearer) .json({ name: 'Client B1', email: 'b1@b.fr' }) const fromA = await client.get('/api/v1/clients').headers(a.bearer) fromA.assertStatus(200) const namesFromA = body>(fromA).data.map((c) => c.name) assert.deepEqual(namesFromA, ['Client A1']) const fromB = await client.get('/api/v1/clients').headers(b.bearer) fromB.assertStatus(200) const namesFromB = body>(fromB).data.map((c) => c.name) assert.deepEqual(namesFromB, ['Client B1']) }) test('?withStats=1 enrichit avec compteurs (zéros sans factures)', async ({ client, assert, }) => { const { bearer } = await createTestUser() await client .post('/api/v1/clients') .headers(bearer) .json({ name: 'Boulangerie', email: 'a@b.fr' }) const response = await client.get('/api/v1/clients?withStats=1').headers(bearer) response.assertStatus(200) const c = body>>(response).data[0] assert.equal(c.invoiceCount, 0) assert.equal(c.lateInvoiceCount, 0) assert.equal(c.paidLifetimeCents, 0) assert.isNull(c.lastActivityAt) }) test('?q=foo filtre par nom et email (ILIKE)', async ({ client, assert }) => { const { bearer } = await createTestUser() await client .post('/api/v1/clients') .headers(bearer) .json({ name: 'Boulangerie Martin', email: 'martin@bp.fr' }) await client .post('/api/v1/clients') .headers(bearer) .json({ name: 'Atelier Durand', email: 'durand@a.fr' }) const r = await client.get('/api/v1/clients?q=BOULANG').headers(bearer) r.assertStatus(200) const data = body>(r).data assert.lengthOf(data, 1) assert.equal(data[0].name, 'Boulangerie Martin') }) }) test.group('Clients — perms cross-org', (group) => { group.each.setup(() => testUtils.db().withGlobalTransaction()) test("user B ne peut pas GET /clients/:id d'un client de A (404)", async ({ client, }) => { const { a, b } = await createTwoOrgs() const created = await client .post('/api/v1/clients') .headers(a.bearer) .json({ name: 'Client A', email: 'a@a.fr' }) const id = body>(created).data.id const fromB = await client.get(`/api/v1/clients/${id}`).headers(b.bearer) fromB.assertStatus(404) }) test("user B ne peut pas PATCH /clients/:id d'un client de A (404)", async ({ client, assert, }) => { const { a, b } = await createTwoOrgs() const created = await client .post('/api/v1/clients') .headers(a.bearer) .json({ name: 'Client A', email: 'a@a.fr' }) const id = body>(created).data.id const r = await client .patch(`/api/v1/clients/${id}`) .headers(b.bearer) .json({ phone: '06 11 22 33 44' }) r.assertStatus(404) // Le client A n'a pas été touché const fresh = await Client.findOrFail(id) assert.isNull(fresh.phone) }) })