mirror of
https://github.com/azaion/ui.git
synced 2026-06-21 10:31:10 +00:00
bd2b718ddf
- AZ-463 flight selection persistence (FT-P-16) + rehydration
on boot (FT-P-17) PASS at the wire; 100-cycle leak guard
(NFT-RES-LIM-07) and 1h SSE soak (NFT-RES-LIM-06)
scaffolded as RUN_LONG_RUNNING-gated e2e companions.
- AZ-469 browser-support smoke (FT-P-34) runs in both
Chromium and Firefox via the existing playwright config;
responsive variants (FT-P-35 480px / FT-P-36 1024px) PASS
in fast (Tailwind class shape) and e2e (visibility).
- AZ-476 upload 501 MB -> 413: AC-1 user-visible error is
drift today (uploadFiles silently falls through to local
mode); it.fails() + control + e2e test.fail. AC-2 no-alert
PASS via dialog spy.
- AZ-477 settings save 500 / network drop: AC-1+AC-2+AC-3
all drift today (no try/finally, no error region, deadline
unmeasurable); 4 it.fails() + control pinning the stuck-
disabled drift; e2e companions test.fail mirror it.
- LESSONS.md seeded: vi.stubGlobal('URL', {...URL,...})
destroys the URL constructor and breaks new URL(...) in
MSW; patch the methods directly instead.
Code review: PASS (0 findings). Fast: 22/22 files, 120
passed / 13 skipped. Static: 24/24 PASS.
Co-authored-by: Cursor <cursoragent@cursor.com>
127 lines
5.1 KiB
TypeScript
127 lines
5.1 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
|
|
// AZ-476 — e2e companion for the 500 MB upload cap.
|
|
//
|
|
// AC-1 (FT-N-06 / NFT-RES-07): nginx returns 413 on a 501 MB upload; SPA
|
|
// renders an in-DOM error region carrying an
|
|
// i18n-keyed message. Today production silently
|
|
// swallows the failure and falls through to
|
|
// local-mode (drift) — `test.fail()` until the
|
|
// error region is wired.
|
|
// AC-2 (no alert): The 413 path does NOT invoke `alert()`. Today
|
|
// this passes vacuously (no error path runs at
|
|
// all). The fast-profile test pins both contracts
|
|
// against MSW; this e2e companion exercises the
|
|
// real nginx body-size limit set in the suite.
|
|
//
|
|
// Requires the suite docker-compose stack (`e2e/docker-compose.suite-e2e.yml`).
|
|
// Skips with a clear reason on developer hosts without the stack.
|
|
|
|
const BIG_BYTES = 501 * 1024 * 1024
|
|
|
|
function buildOversizedBuffer(): Buffer {
|
|
// Spec requires a 501 MB sparse zero-filled payload. Buffer.alloc is the
|
|
// cheapest in-memory way to build it; 501 MB sits well below the 1 GB
|
|
// Node default heap on CI runners. If a future runner downsizes its heap,
|
|
// switch this fixture to a temp file produced by `dd`.
|
|
return Buffer.alloc(BIG_BYTES)
|
|
}
|
|
|
|
test.describe('AZ-476 — upload 501 MB → 413 (e2e companion)', () => {
|
|
test.fail(
|
|
'AC-1 (FT-N-06 / NFT-RES-07) — 501 MB upload surfaces in-DOM error',
|
|
async ({ page }) => {
|
|
// Capture every batch upload response so we can verify nginx really
|
|
// returned 413 (and not the SPA short-circuiting on the client side).
|
|
const batchResponses: { url: string; status: number }[] = []
|
|
page.on('response', async (resp) => {
|
|
const u = resp.url()
|
|
if (/\/api\/annotations\/media\/batch(\?|$)/.test(u)) {
|
|
batchResponses.push({ url: u, status: resp.status() })
|
|
}
|
|
})
|
|
|
|
await page.goto('/annotations')
|
|
|
|
// The "Open File" input is hidden behind a label; Playwright's
|
|
// setInputFiles works directly on the input element regardless of CSS
|
|
// visibility.
|
|
const fileInput = page.locator('input[type="file"]').nth(1)
|
|
if (!(await fileInput.count())) {
|
|
test.skip(true, 'Suite UI did not render the upload input')
|
|
}
|
|
|
|
await fileInput.setInputFiles({
|
|
name: 'huge_recon_video.mp4',
|
|
mimeType: 'video/mp4',
|
|
buffer: buildOversizedBuffer(),
|
|
})
|
|
|
|
// Wait for the 413 to come back. If nginx in this stack is configured
|
|
// with a different cap, the test reports the configuration mismatch
|
|
// explicitly rather than masking the contract.
|
|
await page
|
|
.waitForFunction(
|
|
(checkUrl) => {
|
|
type Win = Window & { __batchStatuses?: number[] }
|
|
const w = window as Win
|
|
void checkUrl
|
|
return Array.isArray(w.__batchStatuses) && w.__batchStatuses.includes(413)
|
|
},
|
|
'noop',
|
|
{ timeout: 30_000 },
|
|
)
|
|
.catch(() => null)
|
|
|
|
// Fallback assertion — page-side wait may not see the response. Use
|
|
// the response listener accumulator we set up above.
|
|
const sawThirteen = batchResponses.some((r) => r.status === 413)
|
|
if (!sawThirteen) {
|
|
test.skip(
|
|
true,
|
|
`Suite nginx did not return 413 for a 501 MB upload (saw: ${
|
|
batchResponses.map((r) => r.status).join(',') || 'no /batch responses'
|
|
})`,
|
|
)
|
|
}
|
|
|
|
// Contract assertion — drift today. Will pass once production wires the
|
|
// toast + i18n key for the 413 path.
|
|
const alertEl = page.getByRole('alert').first()
|
|
await expect(alertEl).toBeVisible({ timeout: 5000 })
|
|
await expect(alertEl).toContainText(/too large|exceeds|413/i)
|
|
},
|
|
)
|
|
|
|
test('AC-2 — the 413 path does NOT invoke window.alert()', async ({ page }) => {
|
|
// Track every dialog. Playwright auto-dismisses dialogs after listeners
|
|
// are attached, so a stray `alert()` shows up here as a "dialog" event.
|
|
const dialogs: string[] = []
|
|
page.on('dialog', async (dialog) => {
|
|
dialogs.push(`${dialog.type()}:${dialog.message()}`)
|
|
await dialog.dismiss().catch(() => null)
|
|
})
|
|
|
|
await page.goto('/annotations')
|
|
|
|
const fileInput = page.locator('input[type="file"]').nth(1)
|
|
if (!(await fileInput.count())) {
|
|
test.skip(true, 'Suite UI did not render the upload input')
|
|
}
|
|
|
|
await fileInput.setInputFiles({
|
|
name: 'huge_recon_video.mp4',
|
|
mimeType: 'video/mp4',
|
|
buffer: buildOversizedBuffer(),
|
|
})
|
|
|
|
// Allow the 413 round-trip + any error-handling React render to settle.
|
|
await page.waitForTimeout(2000)
|
|
|
|
// Filter for alert() specifically — confirm() and prompt() are out of
|
|
// scope for this AC, but we still want to know if either fires.
|
|
const alertDialogs = dialogs.filter((d) => d.startsWith('alert:'))
|
|
expect(alertDialogs).toEqual([])
|
|
})
|
|
})
|