import { test, expect } from '@playwright/test' import { resetDb } from './helpers/api' /** * Scénario quota Free 2 factures (cf. ADR-023). * * Nouveau user → org en Free pure (pas de grace_period_ends_at posé sur * les nouvelles orgs, cf. billing.ts §contexte). À la 3e facture active, * la création doit être bloquée par `canCreateInvoices` → 402 * plan_limit_reached côté API. * * On valide ici la chaîne : SPA → API → DB. Les tests unit billing.spec.ts * couvrent les variations détaillées (delta, statuts). */ async function signupAndOnboard(page: import('@playwright/test').Page) { const email = `quota+${Date.now()}@rubis.test` await page.goto('/signup') await page.getByLabel(/Prénom \/ Nom/i).fill('Quota Test') await page.getByLabel(/Email professionnel/i).fill(email) await page.getByLabel(/Mot de passe/i).fill('motdepasse-fort-123') await page.getByRole('button', { name: /créer mon compte/i }).click() await page.waitForURL(/\/onboarding\/compte/, { timeout: 10_000 }) await page.getByRole('button', { name: /continuer/i }).click() await page.waitForURL(/\/onboarding\/entreprise/) await page.getByLabel(/nom de l'entreprise/i).fill('Atelier Quota') await page.getByRole('button', { name: /continuer/i }).click() await page.waitForURL(/\/onboarding\/signature/) await page.getByRole('button', { name: /terminer/i }).click() await page.waitForURL('/', { timeout: 10_000 }) } async function createInvoiceForNewClient( page: import('@playwright/test').Page, opts: { clientName: string; clientEmail: string; numero: string; amount: string }, ) { await page.getByRole('button', { name: /^saisir$/i }).first().click() await expect(page.getByRole('dialog')).toBeVisible() const combobox = page.getByPlaceholder(/rechercher.*créer un client/i) await combobox.fill(opts.clientName) // Une fois 1er client créé, il apparaît dans le dropdown — on prend // soit l'option "Créer le client" pour un nouveau, soit l'option // matchée pour un existant. const createOption = page.getByRole('button', { name: new RegExp(`créer le client.*${opts.clientName}`, 'i'), }) const existingOption = page.getByRole('option', { name: new RegExp(opts.clientName, 'i'), }) // Race : on prend ce qui apparaît en premier await Promise.race([ createOption.waitFor({ state: 'visible', timeout: 3_000 }).then(() => createOption.click()), existingOption.waitFor({ state: 'visible', timeout: 3_000 }).then(() => existingOption.click()), ]) // Le champ email apparaît si c'est un nouveau client const emailField = page.getByLabel(/email du client/i) if (await emailField.isVisible().catch(() => false)) { await emailField.fill(opts.clientEmail) } await page.getByLabel(/N° de facture/i).fill(opts.numero) await page.getByLabel(/Montant TTC/i).fill(opts.amount) await page.getByLabel(/Plan de relance/i).click() await page.getByRole('option').first().click() await page.getByRole('button', { name: /^créer la facture$/i }).click() } test.describe('Billing — quota Free 2 factures', () => { test.beforeEach(async () => { await resetDb() }) test('Free post-grace : 2 factures OK, la 3e est bloquée avec message clair', async ({ page, }) => { await signupAndOnboard(page) // 1re facture OK await createInvoiceForNewClient(page, { clientName: 'Client Quota A', clientEmail: 'a@quota.test', numero: 'F-QUOTA-1', amount: '100', }) await expect(page.getByText(/facture créée/i)).toBeVisible({ timeout: 5_000 }) // 2e OK (on a maintenant 2 factures actives sur 2) await createInvoiceForNewClient(page, { clientName: 'Client Quota B', clientEmail: 'b@quota.test', numero: 'F-QUOTA-2', amount: '200', }) await expect(page.getByText(/facture créée/i).first()).toBeVisible({ timeout: 5_000, }) // 3e doit être bloquée (Free 2 max, post-grace) await createInvoiceForNewClient(page, { clientName: 'Client Quota C', clientEmail: 'c@quota.test', numero: 'F-QUOTA-3', amount: '300', }) // Toast d'erreur "Limite atteinte : 2 factures actives sur le plan Free" await expect( page.getByText(/limite.*atteinte|2 factures.*Free|passez pro/i).first(), ).toBeVisible({ timeout: 5_000 }) // La facture F-QUOTA-3 ne doit PAS apparaître dans /factures await page.goto('/factures') await expect(page.getByText('F-QUOTA-1').first()).toBeVisible() await expect(page.getByText('F-QUOTA-2').first()).toBeVisible() await expect(page.getByText('F-QUOTA-3')).not.toBeVisible() }) test('Banner "Limite Free atteinte" apparaît sur /factures à 2/2', async ({ page, }) => { await signupAndOnboard(page) // Créer 2 factures pour atteindre la limite await createInvoiceForNewClient(page, { clientName: 'Banner A', clientEmail: 'banner-a@quota.test', numero: 'F-BAN-1', amount: '100', }) await expect(page.getByText(/facture créée/i)).toBeVisible({ timeout: 5_000 }) await createInvoiceForNewClient(page, { clientName: 'Banner B', clientEmail: 'banner-b@quota.test', numero: 'F-BAN-2', amount: '200', }) await expect(page.getByText(/facture créée/i).first()).toBeVisible({ timeout: 5_000, }) // Sur /factures, le banner doit afficher "Limite Free atteinte" await page.goto('/factures') await expect( page.getByText(/limite Free atteinte|Passer Pro/i).first(), ).toBeVisible({ timeout: 5_000 }) }) })