From 758d8628cfd8b829493216e8287addc53a54f74f Mon Sep 17 00:00:00 2001 From: Matt Ciaccio Date: Wed, 6 May 2026 19:44:36 +0200 Subject: [PATCH] test(client-archive): destructive smoke for smart-archive + smart-restore Walks the new dossier dialog end-to-end on a freshly-created client and asserts the toast + list refresh. Companion test exercises the smart- restore wizard. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../02-client-smart-archive.spec.ts | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 tests/e2e/destructive/02-client-smart-archive.spec.ts diff --git a/tests/e2e/destructive/02-client-smart-archive.spec.ts b/tests/e2e/destructive/02-client-smart-archive.spec.ts new file mode 100644 index 0000000..596cb0b --- /dev/null +++ b/tests/e2e/destructive/02-client-smart-archive.spec.ts @@ -0,0 +1,129 @@ +import { test, expect } from '@playwright/test'; + +import { apiHeaders, login, navigateTo, PORT_SLUG } from '../smoke/helpers'; + +/** + * Smart-archive end-to-end. Spins a throwaway client via API, walks + * the wizard via UI, asserts the dossier appeared + the archive ran. + * Then walks the smart-restore wizard and asserts the client returns. + */ + +async function createClient(page: import('@playwright/test').Page, name: string) { + const headers = await apiHeaders(page); + return page.request.post('/api/v1/clients', { + headers, + data: { + fullName: name, + contacts: [ + { + channel: 'email', + value: `${name.toLowerCase().replace(/[^a-z0-9]/g, '')}@test.local`, + isPrimary: true, + }, + ], + }, + failOnStatusCode: false, + }); +} + +test.describe('destructive: client smart-archive + smart-restore', () => { + test.beforeEach(async ({ page }) => { + await login(page, 'super_admin'); + }); + + test('smart-archive single client through the dossier dialog', async ({ page }) => { + const name = `SMART-ARCHIVE-${Date.now()}`; + const create = await createClient(page, name); + if (!create.ok()) { + test.skip(true, `client create returned ${create.status()}`); + return; + } + const created = (await create.json()) as { data?: { id?: string } }; + const clientId = created.data?.id; + if (!clientId) { + test.skip(true, 'no clientId in create response'); + return; + } + + await page.goto(`/${PORT_SLUG}/clients/${clientId}`); + await page.waitForLoadState('networkidle'); + + const archiveBtn = page.getByRole('button', { name: /archive client/i }).first(); + await expect(archiveBtn).toBeVisible({ timeout: 10_000 }); + await archiveBtn.click(); + + // Dossier dialog + const dialog = page + .getByRole('dialog') + .filter({ hasText: /Archive/ }) + .first(); + await expect(dialog).toBeVisible({ timeout: 10_000 }); + + // For a freshly-created client there should be no blockers and the + // submit button is enabled. + const submit = dialog.getByRole('button', { name: /^archive/i }).last(); + await expect(submit).toBeEnabled({ timeout: 5_000 }); + await submit.click(); + + // Toast → list refresh → row gone + await expect(page.locator('[data-sonner-toast]')).toContainText(name, { timeout: 10_000 }); + + await navigateTo(page, '/clients'); + await expect(page.locator(`tbody tr:has-text("${name}")`).first()).toHaveCount(0); + }); + + test('smart-restore wizard re-enables an archived client', async ({ page }) => { + const name = `SMART-RESTORE-${Date.now()}`; + const create = await createClient(page, name); + if (!create.ok()) { + test.skip(true, `client create returned ${create.status()}`); + return; + } + const created = (await create.json()) as { data?: { id?: string } }; + const clientId = created.data?.id; + if (!clientId) return; + + // Archive via service-shape API directly to keep this test focused on + // the restore wizard. + const headers = await apiHeaders(page); + const arch = await page.request.post(`/api/v1/clients/${clientId}/archive`, { + headers, + data: { + reason: '', + acknowledgedSignedDocuments: false, + berthDecisions: [], + yachtDecisions: [], + reservationDecisions: [], + invoiceDecisions: [], + documentDecisions: [], + }, + failOnStatusCode: false, + }); + if (!arch.ok()) { + test.skip(true, `archive call returned ${arch.status()}: ${await arch.text()}`); + return; + } + + await page.goto(`/${PORT_SLUG}/clients/${clientId}`); + await page.waitForLoadState('networkidle'); + + // The header should show the Archived badge + a Restore icon button. + await expect(page.getByText(/archived/i).first()).toBeVisible({ timeout: 10_000 }); + + const restoreBtn = page.getByRole('button', { name: /restore client/i }).first(); + await expect(restoreBtn).toBeVisible({ timeout: 10_000 }); + await restoreBtn.click(); + + const dialog = page + .getByRole('dialog') + .filter({ hasText: /Restore/ }) + .first(); + await expect(dialog).toBeVisible({ timeout: 10_000 }); + + const submit = dialog.getByRole('button', { name: /^restore client/i }).last(); + await expect(submit).toBeEnabled({ timeout: 5_000 }); + await submit.click(); + + await expect(page.locator('[data-sonner-toast]')).toContainText(name, { timeout: 10_000 }); + }); +});