import { test } from '@japa/runner' import testUtils from '@adonisjs/core/services/test_utils' import PlanStep from '#models/plan_step' import { createTestUser, createTwoOrgs } from '../helpers/auth.js' import { body, type ApiOk } from '../helpers/response.js' type PlanShape = { id: string slug: string | null name: string description: string isDefault: boolean steps: Array<{ id: string; order: number; tone: string }> usageCount?: number } test.group('Plans — GET /plans', (group) => { group.each.setup(() => testUtils.db().withGlobalTransaction()) test('retourne les 4 plans pré-fournis avec steps préchargés', async ({ client, assert, }) => { const { bearer } = await createTestUser() const r = await client.get('/api/v1/plans').headers(bearer) r.assertStatus(200) const plans = body>(r).data assert.lengthOf(plans, 4) for (const p of plans) { assert.isTrue(p.isDefault) assert.isAbove(p.steps.length, 0) assert.equal(p.usageCount, 0) } }) test('isolation cross-org : chaque org voit ses propres copies', async ({ client, assert, }) => { const { a, b } = await createTwoOrgs() const fromA = await client.get('/api/v1/plans').headers(a.bearer) const fromB = await client.get('/api/v1/plans').headers(b.bearer) const idsA = body>(fromA).data.map((p) => p.id).sort() const idsB = body>(fromB).data.map((p) => p.id).sort() for (const id of idsA) assert.notInclude(idsB, id) }) }) test.group('Plans — GET /plans/:slug', (group) => { group.each.setup(() => testUtils.db().withGlobalTransaction()) test('lookup par slug avec steps ordonnés', async ({ client, assert }) => { const { bearer } = await createTestUser() const r = await client.get('/api/v1/plans/standard-30j').headers(bearer) r.assertStatus(200) const plan = body>(r).data assert.equal(plan.slug, 'standard-30j') const orders = plan.steps.map((s) => s.order) assert.deepEqual(orders, [...orders].sort((a, b) => a - b)) }) test('404 si slug inconnu', async ({ client }) => { const { bearer } = await createTestUser() const r = await client.get('/api/v1/plans/inexistant').headers(bearer) r.assertStatus(404) }) }) test.group('Plans — PATCH /plans/:slug', (group) => { group.each.setup(() => testUtils.db().withGlobalTransaction()) test('édite name + remplace les steps en bloc', async ({ client, assert }) => { const { bearer } = await createTestUser() const before = await client.get('/api/v1/plans/standard-30j').headers(bearer) const planId = body>(before).data.id const r = await client .patch('/api/v1/plans/standard-30j') .headers(bearer) .json({ name: 'Standard édité', steps: [ { order: 0, offsetDays: 5, tone: 'amical', subject: 'Hop', body: 'Hop hop', requiresManualValidation: false, }, ], }) r.assertStatus(200) const data = body>(r).data assert.equal(data.name, 'Standard édité') assert.lengthOf(data.steps, 1) const steps = await PlanStep.query().where('plan_id', planId) assert.lengthOf(steps, 1) }) test('rejette tone invalide (422)', async ({ client }) => { const { bearer } = await createTestUser() const r = await client .patch('/api/v1/plans/standard-30j') .headers(bearer) .json({ steps: [ { order: 0, offsetDays: 1, tone: 'pas-un-ton', subject: 'X', body: 'X', requiresManualValidation: false, }, ], }) r.assertStatus(422) }) test("cross-org : chaque org édite SA copie, pas celle de l'autre", async ({ client, assert, }) => { const { a, b } = await createTwoOrgs() const r = await client .patch('/api/v1/plans/standard-30j') .headers(b.bearer) .json({ name: 'Renommé par B' }) r.assertStatus(200) const fromA = await client.get('/api/v1/plans/standard-30j').headers(a.bearer) assert.equal(body>(fromA).data.name, 'Standard B2B') }) })