[AZ-463] [AZ-469] [AZ-476] [AZ-477] Batch 6 - flight/responsive/upload/settings tests

- 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>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-11 05:19:35 +03:00
parent 6d03643c2c
commit bd2b718ddf
16 changed files with 1627 additions and 6 deletions
@@ -0,0 +1,67 @@
# Test — Flight Selection, Persistence & Memory Soak
**Task**: AZ-463_test_flight_selection_persistence
**Name**: Flight selection persistence + 100-selection soak + 1-hour SSE soak
**Description**: Implement the blackbox tests that pin the `PUT /api/annotations/settings/user` persistence path for the selected flight, the rehydration-on-boot path, and the two memory-soak tests (100 selections + 1-hour live-GPS SSE) that catch listener leaks.
**Complexity**: 3 points
**Dependencies**: AZ-456_test_infrastructure
**Component**: 10_app-shell + 05_flights (Blackbox Tests)
**Tracker**: AZ-463
**Epic**: AZ-455
## Problem
The selected-flight state is the SPA's most-touched mutable singleton (per `<FlightContext>`). It must persist via the suite, rehydrate on boot, and NOT leak SSE listeners or `<FlightContext>` consumers across rapid selections. Past regressions in this surface manifested as memory creep over long sessions, hard to catch without a soak.
## Outcome
- 4 scenarios pass — selection persistence, rehydration, listener leak guard, memory soak.
- Soak tests run in CI but are gated to `e2e` and tagged `Long-running` so they don't gate every commit.
## Scope
### Included
| Scenario | Profile | Source file |
|----------|---------|-------------|
| FT-P-16 — flight selection persists via `PUT /api/annotations/settings/user` | fast + e2e | blackbox-tests.md |
| FT-P-17 — selected-flight rehydration on boot | fast + e2e | blackbox-tests.md |
| NFT-RES-LIM-06 — live-GPS SSE 1-hour soak — no listener leak, no memory creep | e2e (long-running) | resource-limit-tests.md |
| NFT-RES-LIM-07 — 100 sequential flight selections — no leaked SSEs, no leaked Contexts | e2e (long-running) | resource-limit-tests.md |
### Excluded
- Live-GPS SSE timing (covered in 03_test_sse_lifecycle).
- Flight CRUD / waypoint shape (covered in subsequent feature-cycle tasks; out of scope for the baseline test suite).
## Acceptance Criteria
**AC-1: Persistence wire pattern**
FT-P-16 captures the outbound `PUT /api/annotations/settings/user` body and asserts the new `selectedFlightId` is in the payload (per the wire contract).
**AC-2: Rehydration**
FT-P-17 boots a fresh `<App>` against a seed where `op_alice` has `selectedFlightId` set; asserts the SPA renders that flight as initially selected without explicit user action.
**AC-3: Listener leak guard**
NFT-RES-LIM-07 performs 100 sequential `select(flightA) → select(flightB)` cycles; asserts the active EventSource count never exceeds 1 at the end of each cycle and the total `<FlightContext>` consumer count remains bounded.
**AC-4: Memory soak**
NFT-RES-LIM-06 maintains a live-GPS SSE open for 1 hour with the simulator emitting at 1 Hz; asserts the heap snapshot at t=3600 s is within 10% of the heap snapshot at t=60 s (allows for fixture growth, rejects leaks).
## System Under Test Boundary
- System under test: `<FlightContext>` + `<FlightsPage>` + `src/api/sse.ts` + `src/api/client.ts`.
- Allowed stubs: MSW for fast; real `flights/` + `annotations/` services for e2e.
- Disallowed: stubbing `<FlightContext>` or its consumers; reading React state directly.
- Expected observables per `results_report.md` rows for FT-P-16, 17 + the resource-limit row binding for the soak tests (rows 64-65 region per traceability-matrix.md).
## Constraints
- Long-running tests (NFT-RES-LIM-06, 07) tagged `@long-running` in the Playwright config; CI only runs them on `dev`/`stage` merges, not on every commit.
- Memory measurements via `page.evaluate(() => performance.memory.usedJSHeapSize)` in Chromium; Firefox skipped for these two (Firefox does not expose `performance.memory`).
## Risks & Mitigation
**Risk 1 — Soak test flakiness on shared CI runners**
- *Risk*: A noisy neighbor on the runner inflates heap measurements, false-failing the soak.
- *Mitigation*: 10% tolerance + retry once on `dev`/`stage`; manual approval required on `main`.
@@ -0,0 +1,56 @@
# Test — Browser Support & Responsive Variants
**Task**: AZ-469_test_browser_support_responsive
**Name**: Browser-support smoke (Chromium + Firefox) + responsive variants (mobile + desktop)
**Description**: Implement the 3 blackbox tests pinning the cross-browser smoke (AC-18: Chromium + Firefox latest 2) and the responsive bottom-nav / top-bar variants at 480 px / 1024 px breakpoints.
**Complexity**: 2 points
**Dependencies**: AZ-456_test_infrastructure
**Component**: 10_app-shell (Header / nav) (Blackbox Tests)
**Tracker**: AZ-469
**Epic**: AZ-455
## Problem
Browser-support and responsive defects are class-of-bug regressions: they don't surface in dev unless the dev runs both browsers at both breakpoints. CI-pinned smoke catches them at commit time.
## Outcome
- 3 scenarios pass; Chromium + Firefox Playwright projects each execute every e2e scenario in this task.
## Scope
### Included
| Scenario | Profile | Source file |
|----------|---------|-------------|
| FT-P-34 — browser-support smoke (Chromium + Firefox) | e2e | blackbox-tests.md |
| FT-P-35 — mobile bottom-nav variant at 480 px | fast (RTL viewport) + e2e | blackbox-tests.md |
| FT-P-36 — desktop top-bar variant at 1024 px | fast + e2e | blackbox-tests.md |
### Excluded
- Per-page layout (covered by each page's tests).
- Tablet / non-target breakpoints (no AC).
## Acceptance Criteria
**AC-1: Cross-browser**
FT-P-34 navigates to `/flights`, `/annotations`, `/dataset` in both Chromium and Firefox; asserts core elements render in both.
**AC-2: Mobile variant**
At viewport 480×800 the bottom-nav is rendered; the desktop top-bar is hidden.
**AC-3: Desktop variant**
At viewport 1024×768 the top-bar is rendered; the bottom-nav is hidden.
## System Under Test Boundary
- System under test: app shell + responsive header / nav components.
- Allowed stubs: standard MSW handlers.
- Disallowed: stubbing media-query matchers or reading internal React state for layout.
- Expected observables per `results_report.md` rows 60-62.
## Constraints
- Playwright config defines two browser projects (Chromium, Firefox); this task does NOT introduce a third browser.
- Viewport assertion uses `page.setViewportSize()` in e2e and `matchMedia` polyfill + viewport mock in fast.
@@ -0,0 +1,51 @@
# Test — Upload 500 MB Cap
**Task**: AZ-476_test_upload_size_cap
**Name**: Upload >500 MB → 413 + user-visible error (no alert)
**Description**: Implement the 2 blackbox tests pinning the upload size cap behavior (per E9 / AC-10): nginx-side 413 on a 501 MB file is surfaced to the user via a friendly in-DOM error — never `alert()`.
**Complexity**: 2 points
**Dependencies**: AZ-456_test_infrastructure
**Component**: 06_annotations (upload) (Blackbox Tests)
**Tracker**: AZ-476
**Epic**: AZ-455
## Problem
A 413 hidden behind a `console.error` makes the SPA look unresponsive. The contract: when nginx rejects an oversized upload, the SPA shows an i18n-keyed error in the DOM, without `alert()`.
## Outcome
- 2 scenarios pass.
## Scope
### Included
| Scenario | Profile | Source file |
|----------|---------|-------------|
| FT-N-06 — upload of 501 MB file surfaces a user-visible 413 error | fast + e2e | blackbox-tests.md |
| NFT-RES-07 — nginx 413 on oversized upload surfaces user-visible error | fast + e2e | resilience-tests.md |
### Excluded
- Upload success path (out of scope for the baseline).
- Multi-part chunked uploads (not supported — out of scope).
## Acceptance Criteria
**AC-1: User-visible error**
FT-N-06 / NFT-RES-07 — attempt to upload a 501 MB file; nginx returns 413; the SPA renders a user-visible in-DOM error (toast or inline) carrying the i18n-keyed message.
**AC-2: No alert**
The error path does NOT invoke `alert()` (defence in depth — caught by NFT-SEC-07 as well, but asserted here too).
## System Under Test Boundary
- System under test: upload flow component(s) + `src/api/client.ts` upload helper.
- Allowed stubs: in `fast`, MSW responds 413; in `e2e`, the suite's real nginx enforces the cap.
- Disallowed: stubbing the upload component.
- Expected observables per `results_report.md` rows 38-39.
## Constraints
- E2E test sends a 501 MB sparse file (zero-filled) to avoid bloating CI bandwidth.
@@ -0,0 +1,53 @@
# Test — Settings Save Resilience & 2 s Error Budget
**Task**: AZ-477_test_settings_resilience
**Name**: Settings save 500 + network drop — `saving` flag reset + error surfaces ≤ 2 s
**Description**: Implement the 5 blackbox tests covering Settings save resilience: upstream 500, network drop, the 2-second error-surface deadline, and the try/finally state reset that prevents a stuck "saving…" indicator.
**Complexity**: 3 points
**Dependencies**: AZ-456_test_infrastructure
**Component**: 09_settings (Blackbox Tests)
**Tracker**: AZ-477
**Epic**: AZ-455
## Problem
Settings save has been observed to leave the `saving` flag set after an upstream failure (annotated in `src__features__settings__SettingsPage.md`). The user sees a forever-spinning button and the page becomes unusable. The contract: on ANY failure (HTTP error or network error), state resets within 2 s AND a user-visible error appears.
## Outcome
- 5 scenarios pass.
## Scope
### Included
| Scenario | Profile | Source file |
|----------|---------|-------------|
| FT-N-13 — Settings save with 500 response — `saving` flag reset; error surfaced | fast | blackbox-tests.md |
| FT-N-14 — Settings save with network failure — try/finally state reset | fast | blackbox-tests.md |
| NFT-PERF-09 — Settings save error surfaces within 2 s | fast | performance-tests.md |
| NFT-RES-05 — Settings save with upstream 500 — UI state recovers | fast | resilience-tests.md |
| NFT-RES-06 — Settings save with network drop — try/finally state reset | fast | resilience-tests.md |
### Excluded
- Settings happy path (covered by per-feature tests in Phase B).
- Settings RBAC redirect (covered in 12_test_protected_route_rbac).
## Acceptance Criteria
**AC-1: 500 recovery**
FT-N-13 / NFT-RES-05 — MSW returns 500 on settings PUT; assert: (a) `saving` flag is off (Save button no longer disabled / spinner not shown) within 2 s, (b) an error region is present in DOM.
**AC-2: Network drop**
FT-N-14 / NFT-RES-06 — MSW returns a network error; assert the same two conditions as AC-1.
**AC-3: Deadline**
NFT-PERF-09 — measure wall-clock from response receipt (or network failure) to DOM error visibility; assert ≤ 2 s.
## System Under Test Boundary
- System under test: `<SettingsPage>` + its save handler + `src/api/client.ts`.
- Allowed stubs: MSW for the settings PUT endpoint (returns 500 / network error per scenario).
- Disallowed: reading React state — test asserts DOM affordances (disabled button, error region).
- Expected observables per `results_report.md` rows 68-70 + the rows for NFT-RES-05/06.