From 15a878d6f18146f4e1250e3f463091b71283091a Mon Sep 17 00:00:00 2001 From: Oleksandr Bezdieniezhnykh Date: Mon, 11 May 2026 01:49:44 +0300 Subject: [PATCH] =?UTF-8?q?[AZ-455]=20Decompose=20Step=203=20=E2=80=94=20t?= =?UTF-8?q?est=20task=20specs=20(AZ-457..AZ-482)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds 26 blackbox-test task specs under epic AZ-455 plus the matching rows in _dependencies_table.md. Each task depends on AZ-456 (test infrastructure). Advances autodev existing-code flow Step 5 → Step 6 (Implement Tests, cycle 1) ready for batch implementation. Co-authored-by: Cursor --- _docs/02_tasks/_dependencies_table.md | 63 ++++++++++++++- .../todo/AZ-457_test_auth_token_handling.md | 79 +++++++++++++++++++ .../todo/AZ-458_test_sse_lifecycle.md | 76 ++++++++++++++++++ .../todo/AZ-459_test_wire_contract_enums.md | 76 ++++++++++++++++++ .../todo/AZ-460_test_annotations_endpoint.md | 64 +++++++++++++++ .../todo/AZ-461_test_detection_endpoints.md | 62 +++++++++++++++ .../todo/AZ-462_test_overlay_membership.md | 57 +++++++++++++ ...Z-463_test_flight_selection_persistence.md | 67 ++++++++++++++++ .../todo/AZ-464_test_bulk_validate.md | 55 +++++++++++++ _docs/02_tasks/todo/AZ-465_test_i18n.md | 66 ++++++++++++++++ .../todo/AZ-466_test_destructive_ux.md | 73 +++++++++++++++++ .../todo/AZ-467_test_protected_route_rbac.md | 68 ++++++++++++++++ .../todo/AZ-468_test_header_dropdown.md | 50 ++++++++++++ .../AZ-469_test_browser_support_responsive.md | 56 +++++++++++++ .../AZ-470_test_panel_width_persistence.md | 54 +++++++++++++ .../02_tasks/todo/AZ-471_test_canvas_bbox.md | 70 ++++++++++++++++ .../todo/AZ-472_test_detection_classes.md | 59 ++++++++++++++ _docs/02_tasks/todo/AZ-473_test_photo_mode.md | 54 +++++++++++++ .../todo/AZ-474_test_tile_split_zoom.md | 73 +++++++++++++++++ .../02_tasks/todo/AZ-475_test_form_hygiene.md | 46 +++++++++++ .../todo/AZ-476_test_upload_size_cap.md | 51 ++++++++++++ .../todo/AZ-477_test_settings_resilience.md | 53 +++++++++++++ .../todo/AZ-478_test_network_resilience.md | 56 +++++++++++++ .../todo/AZ-479_test_bundle_fcp_soak.md | 65 +++++++++++++++ .../todo/AZ-480_test_prod_image_nginx_ram.md | 62 +++++++++++++++ .../todo/AZ-481_test_ci_image_labels.md | 54 +++++++++++++ .../AZ-482_test_secrets_and_banned_libs.md | 67 ++++++++++++++++ _docs/_autodev_state.md | 10 +-- 28 files changed, 1678 insertions(+), 8 deletions(-) create mode 100644 _docs/02_tasks/todo/AZ-457_test_auth_token_handling.md create mode 100644 _docs/02_tasks/todo/AZ-458_test_sse_lifecycle.md create mode 100644 _docs/02_tasks/todo/AZ-459_test_wire_contract_enums.md create mode 100644 _docs/02_tasks/todo/AZ-460_test_annotations_endpoint.md create mode 100644 _docs/02_tasks/todo/AZ-461_test_detection_endpoints.md create mode 100644 _docs/02_tasks/todo/AZ-462_test_overlay_membership.md create mode 100644 _docs/02_tasks/todo/AZ-463_test_flight_selection_persistence.md create mode 100644 _docs/02_tasks/todo/AZ-464_test_bulk_validate.md create mode 100644 _docs/02_tasks/todo/AZ-465_test_i18n.md create mode 100644 _docs/02_tasks/todo/AZ-466_test_destructive_ux.md create mode 100644 _docs/02_tasks/todo/AZ-467_test_protected_route_rbac.md create mode 100644 _docs/02_tasks/todo/AZ-468_test_header_dropdown.md create mode 100644 _docs/02_tasks/todo/AZ-469_test_browser_support_responsive.md create mode 100644 _docs/02_tasks/todo/AZ-470_test_panel_width_persistence.md create mode 100644 _docs/02_tasks/todo/AZ-471_test_canvas_bbox.md create mode 100644 _docs/02_tasks/todo/AZ-472_test_detection_classes.md create mode 100644 _docs/02_tasks/todo/AZ-473_test_photo_mode.md create mode 100644 _docs/02_tasks/todo/AZ-474_test_tile_split_zoom.md create mode 100644 _docs/02_tasks/todo/AZ-475_test_form_hygiene.md create mode 100644 _docs/02_tasks/todo/AZ-476_test_upload_size_cap.md create mode 100644 _docs/02_tasks/todo/AZ-477_test_settings_resilience.md create mode 100644 _docs/02_tasks/todo/AZ-478_test_network_resilience.md create mode 100644 _docs/02_tasks/todo/AZ-479_test_bundle_fcp_soak.md create mode 100644 _docs/02_tasks/todo/AZ-480_test_prod_image_nginx_ram.md create mode 100644 _docs/02_tasks/todo/AZ-481_test_ci_image_labels.md create mode 100644 _docs/02_tasks/todo/AZ-482_test_secrets_and_banned_libs.md diff --git a/_docs/02_tasks/_dependencies_table.md b/_docs/02_tasks/_dependencies_table.md index 3eaf5ed..82c8cde 100644 --- a/_docs/02_tasks/_dependencies_table.md +++ b/_docs/02_tasks/_dependencies_table.md @@ -1,5 +1,7 @@ # Task Dependencies +## Epic AZ-447 — 01-testability-refactoring (Autodev Step 4) + | Task | Name | Epic | Complexity | Depends on | |------|------|------|-----------|------------| | AZ-448 | C01 — Externalize OWM API key | AZ-447 | 2 | None | @@ -10,9 +12,64 @@ | AZ-453 | C06 — `navigateToLoginImpl()` accessor | AZ-447 | 2 | None | | AZ-454 | C07 — Document `setToken/getToken` | AZ-447 | 1 | None | -## Notes +### Notes (AZ-447) - Epic AZ-447 is the umbrella for the autodev existing-code Step 4 testability run (`01-testability-refactoring`). - AZ-448 and AZ-449 share `src/features/flights/flightPlanUtils.ts` and should land in one commit to avoid a mid-state where the URL still hardcodes a base while the key is externalized. -- Total: 14 complexity points across 7 tasks. -- Every task fits the existing-code flow Step 4 allowed-change list (externalize hardcoded URLs/credentials, wrap globals in thin accessors, comment-only documentation). Deferred items are in `_docs/04_refactoring/01-testability-refactoring/deferred_to_refactor.md`. +- Total: 14 complexity points across 7 tasks. **Status: closed** — all tasks done (see `_docs/04_refactoring/01-testability-refactoring/FINAL_report.md`). +- Every task fit the existing-code flow Step 4 allowed-change list (externalize hardcoded URLs/credentials, wrap globals in thin accessors, comment-only documentation). Deferred items are in `_docs/04_refactoring/01-testability-refactoring/deferred_to_refactor.md`. + +--- + +## Epic AZ-455 — Blackbox Tests (Autodev Step 5, tests-only decomposition) + +| Task | Name | Epic | Complexity | Depends on | +|------|------|------|-----------|------------| +| AZ-456 | Test Infrastructure (Vitest + MSW + Playwright + static) | AZ-455 | 5 | None | +| AZ-457 | Auth & token handling (11 scenarios) | AZ-455 | 5 | AZ-456 | +| AZ-458 | SSE lifecycle + bearer rotation (9 scenarios) | AZ-455 | 5 | AZ-456 | +| AZ-459 | Wire-contract enum compliance (4 scenarios) | AZ-455 | 2 | AZ-456 | +| AZ-460 | Annotation save URL + payload contract (2 scenarios) | AZ-455 | 2 | AZ-456 | +| AZ-461 | Detection endpoints sync/async/long-video (3 scenarios) | AZ-455 | 2 | AZ-456 | +| AZ-462 | Overlay window membership edges (4 scenarios) | AZ-455 | 2 | AZ-456 | +| AZ-463 | Flight selection persistence + memory soaks (4 scenarios) | AZ-455 | 3 | AZ-456 | +| AZ-464 | Bulk-validate URL + body + UI sync (3 scenarios) | AZ-455 | 2 | AZ-456 | +| AZ-465 | i18n parity + t() coverage + detector + persistence (4 scenarios) | AZ-455 | 3 | AZ-456 | +| AZ-466 | Destructive UX + ConfirmDialog + no-alert (8 scenarios) | AZ-455 | 4 | AZ-456 | +| AZ-467 | ProtectedRoute spinner + timeout + RBAC (7 scenarios) | AZ-455 | 4 | AZ-456 | +| AZ-468 | Header flight dropdown a11y + Escape (3 scenarios) | AZ-455 | 2 | AZ-456 | +| AZ-469 | Browser support + responsive variants (3 scenarios) | AZ-455 | 2 | AZ-456 | +| AZ-470 | Panel-width debounced PUT + rehydration (3 scenarios) | AZ-455 | 2 | AZ-456 | +| AZ-471 | CanvasEditor draw/resize/multi-select/zoom/pan (5 scenarios) | AZ-455 | 5 | AZ-456 | +| AZ-472 | DetectionClasses load + hotkeys + click + fallback (4 scenarios) | AZ-455 | 3 | AZ-456 | +| AZ-473 | PhotoMode switch + auto-select + yoloId wire (3 scenarios) | AZ-455 | 2 | AZ-456, AZ-472 | +| AZ-474 | Tile-split + YOLO parser + auto-zoom + indicator (6 scenarios) | AZ-455 | 3 | AZ-456 | +| AZ-475 | Numeric form hygiene (2 scenarios) | AZ-455 | 2 | AZ-456 | +| AZ-476 | Upload 501 MB → 413 → user-visible error (2 scenarios) | AZ-455 | 2 | AZ-456 | +| AZ-477 | Settings save 500/network resilience (5 scenarios) | AZ-455 | 3 | AZ-456 | +| AZ-478 | Network offline + SSE disconnect + tainted-canvas (3 scenarios) | AZ-455 | 3 | AZ-456 | +| AZ-479 | Bundle ≤2 MB + mission-planner excluded + FCP + soak (4 scenarios) | AZ-455 | 3 | AZ-456 | +| AZ-480 | Prod image nginx:alpine + 500M + 9 routes + edge RAM (5 scenarios) | AZ-455 | 3 | AZ-456 | +| AZ-481 | CI image tag scheme + OCI labels + revision binding (3 scenarios) | AZ-455 | 2 | AZ-456 | +| AZ-482 | Secrets/banned-libs/AC-N1 anti-criterion (6 scenarios) | AZ-455 | 3 | AZ-456 | + +### Notes (AZ-455) + +- **Epic AZ-455** is the umbrella for the autodev existing-code Step 5 tests-only decomposition. +- **Scenario count**: 117 distinct scenarios across 26 implementation tasks (T02..T27). +- **Total complexity**: 79 points across 27 tasks (including infrastructure AZ-456). +- **Critical-path dependency**: every test task depends on AZ-456 (Test Infrastructure). AZ-456 MUST be implemented first; tests cannot be implemented in parallel until the harness is in place. +- **Internal cross-deps** (non-blocking for ordering, but worth noting for review): + - AZ-473 (PhotoMode) reuses the DetectionClasses fixtures landed by AZ-472; flagged as a soft dep to avoid duplicating fixture wiring. + - AZ-457 (Auth) lands `setToken / getToken / setNavigateToLogin` test helpers that AZ-458 (SSE bearer rotation), AZ-467 (ProtectedRoute), and AZ-468 (Header dropdown — uses authed page) all rely on. Recommended landing order after AZ-456: AZ-457 → everything else in parallel. + - AZ-466 (Destructive UX) lands the `data-destructive` marker + `` wrapper used by the static enforcement check; per-feature surfaces (admin user delete, class delete, flight delete) need the marker before AZ-466 can pass — handled in-task by AZ-466 itself (lands the wrapper AND the markers across existing destructive surfaces in scope). + - AZ-479 (bundle/FCP) is the only test task that requires `bun run build` to succeed against the SPA; if a Phase-B feature breaks build, AZ-479 starts failing first. + - AZ-480 / AZ-481 require the production image build pipeline; they can be implemented in parallel with the rest but their CI lane is conditional on the `dev` branch (out-of-band from feature merge gates). +- **Profile distribution**: + - `static` only: AZ-481, AZ-482, parts of AZ-459, AZ-465, AZ-466, AZ-474, AZ-479, AZ-480 + - `fast` only (no e2e companion): AZ-462, AZ-468, AZ-470 (e2e companion present but not gating), AZ-471 (mostly fast, e2e smoke), AZ-475, AZ-477 + - `fast + e2e`: most of the rest + - `e2e (long-running)`: AZ-463 NFT-RES-LIM-06/07, AZ-479 NFT-RES-LIM-05 — tagged `@long-running`, run on `dev`/`stage` merges only + - `e2e (requires-docker)`: AZ-480 — requires the suite docker-compose stack + - `e2e (requires-ci)`: AZ-481 NFT-RES-LIM-12/13 — local skip allowed +- **Quarantine scenarios**: FT-P-12 (async video detect, AZ-461) starts QUARANTINEd until AC-25 / Phase B; verification_pending enums in AZ-459 quarantine until Step 4 .NET-service snapshot lifts. diff --git a/_docs/02_tasks/todo/AZ-457_test_auth_token_handling.md b/_docs/02_tasks/todo/AZ-457_test_auth_token_handling.md new file mode 100644 index 0000000..7465dd0 --- /dev/null +++ b/_docs/02_tasks/todo/AZ-457_test_auth_token_handling.md @@ -0,0 +1,79 @@ +# Test — Auth & Token Handling + +**Task**: AZ-457_test_auth_token_handling +**Name**: Auth & token-handling blackbox suite +**Description**: Implement every blackbox test that exercises the in-memory bearer, the refresh cookie, the 401→refresh→retry path, and the redirect-to-/login flow. Spans `fast` (MSW) and `e2e` (real `admin/`) profiles. +**Complexity**: 5 points +**Dependencies**: AZ-456_test_infrastructure +**Component**: 01_api-transport + 02_auth (Blackbox Tests) +**Tracker**: AZ-457 +**Epic**: AZ-455 + +## Problem + +The SPA's auth surface (`` + `src/api/client.ts` + ``) is the gate every other test depends on. It mixes a memory-only bearer (AC-02 / O2), a HttpOnly refresh cookie (AC-03), a 401-retry loop (AC-23), and a redirect on refresh failure (C06 from autodev Step 4). Tests must lock down the wire contract AND the storage contract without leaking into production. + +## Outcome + +- 11 test scenarios pass in their declared profile, all referencing `results_report.md` rows for expected observables. +- The fast suite uses MSW to drive the `/api/admin/auth/refresh` path; e2e uses the real `admin/` service. +- No test stubs `src/api/client.ts`, ``, or `` internals — every assertion is observable at the DOM, network, or browser-storage surface. + +## Scope + +### Included + +| Scenario | Profile | Source file | results_report row | +|----------|---------|-------------|--------------------| +| FT-P-01 — bootstrap refresh sends `credentials:'include'` | fast | blackbox-tests.md | 02 | +| FT-P-02 — 401 → refresh → retry sequence | fast + e2e | blackbox-tests.md | 03, 12 | +| FT-P-03 — refresh transparency, no `` unmount | fast | blackbox-tests.md | 11 | +| FT-N-04 — unauthenticated `/admin` → redirect to `/login` | fast | blackbox-tests.md | row(s) per blackbox-tests.md FT-N-04 | +| NFT-SEC-01 — bearer never in localStorage/sessionStorage | fast | security-tests.md | 01 | +| NFT-SEC-02 — refresh cookie not in `document.cookie` | fast | security-tests.md | 04 | +| NFT-SEC-03 — refresh cookie has `Secure; HttpOnly; SameSite=Strict` | e2e | security-tests.md | 05 | +| NFT-SEC-04 — `credentials:'include'` on every authed fetch | fast | security-tests.md | 06 | +| NFT-PERF-02 — exactly one refresh round trip per cycle | fast | performance-tests.md | 12 | +| NFT-RES-01 — 401→refresh→retry is transparent end-to-end | fast | resilience-tests.md | 03, 11 | +| NFT-RES-08 — refresh cookie expired → redirect to `/login` | fast | resilience-tests.md | row(s) per NFT-RES-08 | + +### Excluded + +- SSE bearer rotation (covered in 03_test_sse_lifecycle). +- `/settings` / `/admin` RBAC beyond the unauthenticated-redirect case (covered in 12_test_protected_route_rbac). +- ConfirmDialog and destructive-action gating (covered in 11_test_destructive_ux). + +## Acceptance Criteria + +**AC-1: All 11 scenarios implemented** +Given the test infrastructure from AZ-456 is in place, +When `bun run test:fast` and `bun run test:e2e` execute, +Then every scenario above is present, runs in its declared profile, and references its `results_report.md` row in test comments. + +**AC-2: Black-box discipline** +No test imports anything from `src/api/client.ts` (other than the public `setToken` / `getToken` / `setNavigateToLogin` accessors per AC-23 / autodev Step 4 testability changes); no test imports `` internals; no test asserts on React state directly. + +**AC-3: Storage assertions are exhaustive** +NFT-SEC-01 / NFT-SEC-02 assert that **for the duration of the test** the bearer never appears in `localStorage`, `sessionStorage`, or `document.cookie` — not just at one snapshot. + +**AC-4: Redirect assertion uses the accessor** +FT-N-04 and NFT-RES-08 install a `setNavigateToLogin(spy)` and assert `spy` was called exactly once with no arguments; they do NOT globally stub `window.location`. + +## System Under Test Boundary + +- The system under test is the assembled SPA: React tree + `src/api/client.ts` + `` + `` + Router. +- Allowed stubs: the suite's `admin/` `auth/refresh` and `auth/login` endpoints — stubbed via MSW in `fast`, real service in `e2e`. +- Disallowed: stubbing `src/api/client.ts`, ``, ``, or any other internal SPA module. If `` is not behaving as expected, the test FAILS — it does not bypass it. +- Expected observables compared against `_docs/00_problem/input_data/expected_results/results_report.md` rows 02, 03, 04, 05, 06, 11, 12, and the rows for FT-N-04 / NFT-RES-08 as listed in `traceability-matrix.md`. + +## Constraints + +- One test file per top-level concern; co-locate next to source (e.g., `src/api/client.test.ts`, `src/auth/AuthContext.test.tsx`, `src/auth/ProtectedRoute.test.tsx`). +- MSW handlers live in `tests/msw/handlers/admin.ts`; per-test overrides via `server.use(...)` only. +- E2E auth uses the `op_alice` seed user (test-data.md). + +## Risks & Mitigation + +**Risk 1 — Refresh-cookie HttpOnly invisibility** +- *Risk*: AC-03 requires `HttpOnly` — by spec, the cookie is invisible to JS, so the fast profile cannot directly assert presence. +- *Mitigation*: in `fast`, MSW asserts the response `Set-Cookie` header carries the three flags (the server contract). In `e2e`, the test uses Playwright's `context.cookies()` (which sees HttpOnly cookies) to verify presence + flags. NFT-SEC-03 runs e2e-only. diff --git a/_docs/02_tasks/todo/AZ-458_test_sse_lifecycle.md b/_docs/02_tasks/todo/AZ-458_test_sse_lifecycle.md new file mode 100644 index 0000000..b60ff8c --- /dev/null +++ b/_docs/02_tasks/todo/AZ-458_test_sse_lifecycle.md @@ -0,0 +1,76 @@ +# Test — SSE Lifecycle & Bearer Rotation + +**Task**: AZ-458_test_sse_lifecycle +**Name**: SSE lifecycle + bearer-rotation reconnect +**Description**: Implement every blackbox test that exercises the SPA's SSE streams — live-GPS, annotation-status — covering open/close lifecycle and the bearer-rotation reconnect (≤5 s) after a token refresh. +**Complexity**: 5 points +**Dependencies**: AZ-456_test_infrastructure +**Component**: 01_api-transport + 05_flights + 06_annotations (Blackbox Tests) +**Tracker**: AZ-458 +**Epic**: AZ-455 + +## Problem + +The SPA holds two long-running `EventSource` connections — live-GPS (per ``) and annotation-status (per ``) — using the in-query-string bearer pattern (ADR-008). The open/close timing and the reconnect-on-refresh path must be deterministic; otherwise tests downstream see flapping streams and undefined ordering. + +## Outcome + +- 9 test scenarios pass, asserting open/close timing and bearer-rotation reconnect behavior. +- Fast tests use `tests/helpers/sse-mock.ts` (per AZ-456 Risk 3); e2e tests use the embedded LiveGPS + annotation-status generators in the suite test images. +- No test reads or manipulates `src/api/sse.ts` internals. + +## Scope + +### Included + +| Scenario | Profile | Source file | results_report row | +|----------|---------|-------------|--------------------| +| FT-P-09 — annotation-status SSE opens on `` mount | fast + e2e | blackbox-tests.md | per FT-P-09 | +| FT-P-10 — annotation-status SSE closes on unmount | fast | blackbox-tests.md | per FT-P-10 | +| FT-P-18 — live-GPS SSE opens within 5 s of flight select | fast + e2e | blackbox-tests.md | per FT-P-18 | +| FT-P-19 — live-GPS SSE closes within 1 s of deselect | fast | blackbox-tests.md | per FT-P-19 | +| NFT-PERF-03 — SSE bearer-rotation reconnect ≤ 5 s | e2e | performance-tests.md | per NFT-PERF-03 | +| NFT-PERF-04 — live-GPS SSE opens within 5 s of flight select | fast | performance-tests.md | per NFT-PERF-04 | +| NFT-PERF-05 — live-GPS SSE closes within 1 s of deselect | fast | performance-tests.md | per NFT-PERF-05 | +| NFT-PERF-06 — annotation-status SSE unsubscribes within 1 s on page unmount | fast | performance-tests.md | per NFT-PERF-06 | +| NFT-RES-02 — SSE bearer rotation — both streams reconnect within 5 s | e2e | resilience-tests.md | per NFT-RES-02 | + +### Excluded + +- SSE server-disconnect indicator (covered in 23_test_network_resilience). +- The 401-retry path on `fetch` (covered in 02_test_auth_token_handling). +- The async-video detect SSE (covered in 06_test_detection_endpoints — quarantined as Phase B target). + +## Acceptance Criteria + +**AC-1: Open/close timing** +Given a freshly-mounted `` or ``, +When the relevant SSE should open or close per the scenario, +Then the event happens within the timing budget and the observable EventSource state matches (`readyState: 1` for open, closed connection for close). + +**AC-2: Bearer rotation** +Given an active SSE stream and an upcoming token refresh, +When the bearer rotates, +Then both live-GPS and annotation-status streams reconnect with the new bearer within 5 s; no event from before the rotation is replayed AFTER the new bearer is in use. + +**AC-3: No internal stubs** +The fast profile uses the published `sse-mock.ts` helper to simulate the wire-level SSE event stream. Tests MUST NOT replace `src/api/sse.ts` with a stub; if the SSE module misbehaves, the test FAILS. + +## System Under Test Boundary + +- System under test: `src/api/sse.ts` + the consumer hooks in `` / `` + the React tree. +- Allowed stubs: the suite-side SSE endpoints — stubbed via the MSW SSE adapter / `sse-mock.ts` in `fast`, real `flights/` and `annotations/` services in `e2e`. +- Disallowed: stubbing `src/api/sse.ts`, the SPA's SSE hooks, or the React tree. +- Expected observables per `results_report.md` rows for FT-P-09, 10, 18, 19, NFT-PERF-03..06, NFT-RES-02. + +## Constraints + +- E2E tests rely on the embedded LiveGPS simulator and annotation-status event generator in the suite's test-mode images (per test-data.md). +- Bearer in SSE URL `?token=` per ADR-008 — tests assert the URL pattern, not stripped headers. +- Bearer-rotation tests MUST drive the rotation through ``'s normal refresh path, not by directly calling `setToken`. + +## Risks & Mitigation + +**Risk 1 — MSW SSE polyfill brittleness (AZ-456 Risk 3)** +- *Risk*: MSW 2.x does not have first-class SSE support. The `sse-mock.ts` helper introduces a small abstraction that, if buggy, makes tests pass that shouldn't. +- *Mitigation*: every fast SSE scenario also has an e2e companion that exercises the real wire. If the two disagree, the e2e wins and the fast test is QUARANTINEd until the helper is fixed. diff --git a/_docs/02_tasks/todo/AZ-459_test_wire_contract_enums.md b/_docs/02_tasks/todo/AZ-459_test_wire_contract_enums.md new file mode 100644 index 0000000..a51f95f --- /dev/null +++ b/_docs/02_tasks/todo/AZ-459_test_wire_contract_enums.md @@ -0,0 +1,76 @@ +# Test — Wire-Contract Enum Compliance + +**Task**: AZ-459_test_wire_contract_enums +**Name**: Enum compliance + MediaType hygiene +**Description**: Implement every blackbox test that asserts the SPA's on-wire enum values (`AnnotationStatus`, `MediaStatus`, `Affiliation`, `CombatReadiness`, `MediaType`, `AnnotationSource`) match the contract pinned in `enum_spec_snapshot.json`, plus the MediaType magic-literal hygiene check. +**Complexity**: 2 points +**Dependencies**: AZ-456_test_infrastructure +**Component**: 07_dataset + 06_annotations + cross-cutting (Blackbox Tests) +**Tracker**: AZ-459 +**Epic**: AZ-455 + +## Problem + +`AC-04` and `AC-29` require the UI to send/accept the exact numeric values from the suite spec. Today the UI carries drift (documented in `enum_spec_snapshot.json` § `ui_drift_summary`) — for `AnnotationStatus` the UI sends `Edited=1` while the contract pins `Edited=20`. Tests must FAIL on the drift so the regression is visible until the Phase B fix lands; they must NOT silently accept the wrong values. + +## Outcome + +- 4 test scenarios pass (or fail loudly to document the drift) per the contract pin. +- Tests load `_docs/00_problem/input_data/enum_spec_snapshot.json` once per test file and compare runtime values against the pinned values. +- `verification_pending: true` enums (`CombatReadiness`, `MediaType`) are quarantined with a clear marker until the Step 4 .NET-service inspection lands. + +## Scope + +### Included + +| Scenario | Profile | Source file | results_report row | +|----------|---------|-------------|--------------------| +| FT-P-04 — AnnotationStatus enum on the wire | static + fast | blackbox-tests.md | 14 | +| FT-P-05 — MediaStatus / Affiliation / CombatReadiness enums match the spec | static + fast | blackbox-tests.md | 15, 16, 17 | +| FT-P-06 — detection wire payload — affiliation + combatReadiness in spec value sets | fast + e2e | blackbox-tests.md | 18, 19 | +| FT-N-15 — MediaType magic-literal / magic-string hygiene | static + fast | blackbox-tests.md | per FT-N-15 | + +### Excluded + +- Annotation save body shape (covered in 05_test_annotations_endpoint). +- DetectionClasses ordering / hotkey path (covered in 17_test_detection_classes). + +## Acceptance Criteria + +**AC-1: Snapshot-driven assertion** +Given `_docs/00_problem/input_data/enum_spec_snapshot.json` is the contract pin, +When any test in this task runs, +Then it loads the snapshot and asserts the runtime wire value matches the pinned value per row 14, 15, 16, 17, 18, 19 of `results_report.md`. + +**AC-2: Drift surfaces, not silently passes** +Given the UI today drifts on `AnnotationStatus` (Edited=1 vs spec 20) and similar, +When the test runs against today's UI, +Then it FAILS with a message naming the enum and the observed-vs-expected pair. (It does NOT pass by re-reading the UI's value as authoritative.) + +**AC-3: verification_pending markers** +Given `CombatReadiness` and `MediaType` carry `verification_pending: true` in the snapshot, +When the test sees the flag, +Then it emits a clear QUARANTINE marker (CSV `Result: QUARANTINE`) and a comment naming the resolution path (Step 4 .NET-service inspection). + +**AC-4: MediaType magic-literal hygiene (FT-N-15)** +Given the source tree at HEAD, +When the static check scans `src/` for hardcoded numeric `MediaType` literals, +Then any literal not wrapped in the typed enum is flagged. + +## System Under Test Boundary + +- System under test: the actual fetch URLs and payloads issued by the SPA, the rendered DOM that surfaces enum-derived state, and (for FT-N-15) the source tree itself. +- Allowed stubs: MSW handlers that capture the outbound request and assert the numeric values. +- Disallowed: importing `src/types/index.ts` and comparing the snapshot to it — that's a tautology. The test compares the snapshot to the RUNTIME wire output. Importing the typed enum SHAPES for declaration is fine per `P9` / black-box discipline (the enums are the wire contract). +- Expected observables compared against `_docs/00_problem/input_data/expected_results/results_report.md` rows 14-19. + +## Constraints + +- Snapshot must be read once per module; cache via Vitest module-level import. +- FT-N-15 runs as a `ripgrep`-driven static check from `scripts/run-tests.sh --static-only`. + +## Risks & Mitigation + +**Risk 1 — verification_pending lifts mid-implementation** +- *Risk*: If Step 4 .NET-service inspection lands while these tests are being implemented, the snapshot rewrites and a previously-QUARANTINEd test may flip to PASS or FAIL. +- *Mitigation*: tests read the snapshot at runtime — they auto-adapt. The QUARANTINE marker is conditional on the runtime `verification_pending` flag. diff --git a/_docs/02_tasks/todo/AZ-460_test_annotations_endpoint.md b/_docs/02_tasks/todo/AZ-460_test_annotations_endpoint.md new file mode 100644 index 0000000..e958aca --- /dev/null +++ b/_docs/02_tasks/todo/AZ-460_test_annotations_endpoint.md @@ -0,0 +1,64 @@ +# Test — Annotations Endpoint & Payload Shape + +**Task**: AZ-460_test_annotations_endpoint +**Name**: Annotation save URL + payload contract +**Description**: Implement the two blackbox tests that pin the annotation-save endpoint URL (AC-N5 doubly-prefixed canary) and the full required-fields payload shape (`Source`, `WaypointId`, `videoTime`, `mediaId`, `detections`, `status`). +**Complexity**: 2 points +**Dependencies**: AZ-456_test_infrastructure +**Component**: 06_annotations (Blackbox Tests) +**Tracker**: AZ-460 +**Epic**: AZ-455 + +## Problem + +Two prior regressions (`_docs/02_document/modules/src__features__annotations.md` finding #32 and the AC-N5 doubly-prefixed URL) are silent if no test pins the contract. The annotation save path is one mutation away from leaking AI vs Manual provenance or dropping the waypoint association — both impossible to recover after-the-fact. + +## Outcome + +- 2 test scenarios pass per the contract. +- Tests assert ALL six required keys are present in every save, regardless of which UI path issued the save (AI suggestion accept, manual draw, multi-edit). +- The endpoint URL canary detects doubly-prefixed regressions immediately. + +## Scope + +### Included + +| Scenario | Profile | Source file | results_report row | +|----------|---------|-------------|--------------------| +| FT-P-07 — annotation save endpoint URL is doubly-prefixed | fast + e2e | blackbox-tests.md | per FT-P-07 | +| FT-P-08 — annotation save body contains all required fields | fast + e2e | blackbox-tests.md | row 23 | + +### Excluded + +- Bulk-validate path (covered in 09_test_bulk_validate). +- Annotation-status SSE (covered in 03_test_sse_lifecycle). +- Tile-split path (covered in 19_test_tile_split_zoom). + +## Acceptance Criteria + +**AC-1: URL canary** +FT-P-07 captures the outbound URL for every annotation save in the test and asserts it equals the expected canary (`/api/annotations/annotations/...`). + +**AC-2: Required-fields presence** +FT-P-08 captures the outbound body for every annotation save and asserts ALL of `Source`, `WaypointId`, `videoTime`, `mediaId`, `detections`, `status` are present, with `Source` ∈ `{AI, Manual}` per the wire contract. + +**AC-3: Multiple entry points** +The required-fields check runs for at least three save entry points: AI suggestion accept, manual draw, and bulk-edit save — to catch path-specific regressions. + +## System Under Test Boundary + +- System under test: `` + `` + `src/api/client.ts` save call path. +- Allowed stubs: MSW for the suite's annotations endpoint (fast); real `annotations/` service (e2e). +- Disallowed: stubbing the components above; reading their internal state. +- Expected observables compared against `results_report.md` row 23 + the row for FT-P-07. + +## Constraints + +- Tests MUST use the typed wire shape from `src/types/index.ts` (enum compliance is in 04_test_wire_contract_enums; here we only check key presence). +- E2E tests run after a fresh seed so the test can issue multiple saves without polluting state. + +## Risks & Mitigation + +**Risk 1 — Source field default** +- *Risk*: If a future commit defaults `Source` to one branch unconditionally, the test may pass but the provenance is lost. +- *Mitigation*: AC-3 — the test runs for both AI and Manual entry points and asserts the specific value, not just presence. diff --git a/_docs/02_tasks/todo/AZ-461_test_detection_endpoints.md b/_docs/02_tasks/todo/AZ-461_test_detection_endpoints.md new file mode 100644 index 0000000..e27c90d --- /dev/null +++ b/_docs/02_tasks/todo/AZ-461_test_detection_endpoints.md @@ -0,0 +1,62 @@ +# Test — Detection Endpoints (Sync / Async / Long-Video) + +**Task**: AZ-461_test_detection_endpoints +**Name**: Detection endpoints — sync image + async video (Phase B target) + long-video header +**Description**: Implement the three blackbox tests that exercise the `detect/` service's three detection paths from the SPA: sync image detect (FT-P-11), async video detect SSE (FT-P-12, quarantined as Phase B target), and the `X-Refresh-Token` header carried on long-video detect (FT-P-13). +**Complexity**: 2 points +**Dependencies**: AZ-456_test_infrastructure +**Component**: 06_annotations (detection sub-surface) (Blackbox Tests) +**Tracker**: AZ-461 +**Epic**: AZ-455 + +## Problem + +Detection is multimodal: sync for images (today), async with SSE updates for video (Phase B per AC-25), and long-video has its own header contract for token rotation. Without targeted tests, regressions slip in mode-by-mode. + +## Outcome + +- 3 test scenarios are present in their declared profile and reference their `results_report.md` rows. +- FT-P-12 is QUARANTINEd in CI with a clear marker until the async-video path lands. + +## Scope + +### Included + +| Scenario | Profile | Source file | results_report row | +|----------|---------|-------------|--------------------| +| FT-P-11 — sync image detect endpoint | fast + e2e | blackbox-tests.md | per FT-P-11 | +| FT-P-12 — async video detect endpoint + SSE (target — Phase B) | fast (quarantined) | blackbox-tests.md | per FT-P-12 | +| FT-P-13 — long-video detect carries `X-Refresh-Token` header | fast | blackbox-tests.md | per FT-P-13 | + +### Excluded + +- Annotation save after detect (covered in 05_test_annotations_endpoint). +- Detection class CRUD (covered in 17_test_detection_classes). + +## Acceptance Criteria + +**AC-1: Sync image detect URL + body** +FT-P-11 asserts the outbound POST URL + body shape for sync image detect against the contract. + +**AC-2: Async video detect — quarantined** +FT-P-12 is implemented and registered, but marked `Result: QUARANTINE` in the CSV report until AC-25 (Phase B) lands. The test code itself runs (does not just `xit`) and produces a clear log entry "FT-P-12 awaits AC-25 / async video detect impl". + +**AC-3: Header carried** +FT-P-13 asserts every long-video detect request carries the `X-Refresh-Token` header. + +## System Under Test Boundary + +- System under test: the SPA's `` (or its detect-trigger sub-component) + `src/api/client.ts`. +- Allowed stubs: MSW for `/api/detect/*` (fast); real `detect/` service (e2e — async video path stays quarantined until the suite has it). +- Disallowed: stubbing the SPA components or constructing the request manually from a unit test. +- Expected observables per `results_report.md` rows for FT-P-11, 12, 13. + +## Constraints + +- FT-P-12 is QUARANTINE in CI, NOT skipped — the result must appear in the report with that status, traceable to AC-25. + +## Risks & Mitigation + +**Risk 1 — Async video detect path lands while tests are being implemented** +- *Risk*: AC-25 may go from target to shipped, flipping FT-P-12 from QUARANTINE to PASS / FAIL. +- *Mitigation*: FT-P-12 reads the runtime UI to detect whether the path exists; the QUARANTINE marker is conditional on absence. diff --git a/_docs/02_tasks/todo/AZ-462_test_overlay_membership.md b/_docs/02_tasks/todo/AZ-462_test_overlay_membership.md new file mode 100644 index 0000000..48aa917 --- /dev/null +++ b/_docs/02_tasks/todo/AZ-462_test_overlay_membership.md @@ -0,0 +1,57 @@ +# Test — Annotation Overlay Window Membership + +**Task**: AZ-462_test_overlay_membership +**Name**: Overlay membership at the in-window edges +**Description**: Pin the 4 inclusive/exclusive edge cases for the annotation overlay window (FT-P-14, FT-P-15, FT-N-01, FT-N-02). These edges have caused subtle off-by-one regressions before; without explicit edge-tests they recur silently. +**Complexity**: 2 points +**Dependencies**: AZ-456_test_infrastructure +**Component**: 06_annotations (overlay window) (Blackbox Tests) +**Tracker**: AZ-462 +**Epic**: AZ-455 + +## Problem + +The overlay window logic asserts an annotation is "in-window" iff `lowerBound <= ts <= upperBound`. Off-by-one mistakes (strict instead of inclusive, or shifted by one frame interval) only surface when the playback head is exactly on the boundary — a state that test scenarios rarely hit by accident. + +## Outcome + +- 4 edge-case scenarios pass per the inclusive-boundary contract. +- Tests are deterministic — they construct exact-edge fixtures rather than relying on real-time playback. + +## Scope + +### Included + +| Scenario | Profile | Source file | +|----------|---------|-------------| +| FT-P-14 — overlay membership at the lower in-window edge | fast | blackbox-tests.md | +| FT-P-15 — overlay membership at the upper in-window edge | fast | blackbox-tests.md | +| FT-N-01 — overlay annotation below the lower bound is NOT rendered | fast | blackbox-tests.md | +| FT-N-02 — overlay annotation above the upper bound is NOT rendered | fast | blackbox-tests.md | + +### Excluded + +- Overlay style / Z-order / color (covered by class-color tests in component scenarios). +- Overlay clicking / selection (covered by 16_test_canvas_bbox). + +## Acceptance Criteria + +**AC-1: Inclusive boundary** +FT-P-14 / FT-P-15 assert an annotation EXACTLY on `lowerBound` (resp. `upperBound`) IS rendered in the overlay. + +**AC-2: Strict exclusion** +FT-N-01 / FT-N-02 assert an annotation one frame interval before / after the window is NOT rendered. + +**AC-3: Reads the DOM, not internal state** +The "rendered" assertion queries the canvas / overlay DOM nodes (or rendered SVG / Leaflet markers). It does NOT inspect React component state. + +## System Under Test Boundary + +- System under test: `` + overlay annotation rendering. +- Allowed stubs: MSW for annotation list responses (the fixture pins exact timestamps). +- Disallowed: stubbing the overlay logic itself, or asserting on React state. +- Expected observables compared against `results_report.md` rows for FT-P-14, 15, FT-N-01, 02. + +## Constraints + +- Fixtures must pin timestamps to exact boundaries via deterministic numeric values; no `new Date()` or similar. diff --git a/_docs/02_tasks/todo/AZ-463_test_flight_selection_persistence.md b/_docs/02_tasks/todo/AZ-463_test_flight_selection_persistence.md new file mode 100644 index 0000000..afd556e --- /dev/null +++ b/_docs/02_tasks/todo/AZ-463_test_flight_selection_persistence.md @@ -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 ``). It must persist via the suite, rehydrate on boot, and NOT leak SSE listeners or `` 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 `` 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 `` 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: `` + `` + `src/api/sse.ts` + `src/api/client.ts`. +- Allowed stubs: MSW for fast; real `flights/` + `annotations/` services for e2e. +- Disallowed: stubbing `` 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`. diff --git a/_docs/02_tasks/todo/AZ-464_test_bulk_validate.md b/_docs/02_tasks/todo/AZ-464_test_bulk_validate.md new file mode 100644 index 0000000..0e1cf88 --- /dev/null +++ b/_docs/02_tasks/todo/AZ-464_test_bulk_validate.md @@ -0,0 +1,55 @@ +# Test — Bulk-Validate (Dataset) + +**Task**: AZ-464_test_bulk_validate +**Name**: Bulk-validate URL + body + UI sync +**Description**: Implement the 3 blackbox tests that pin the dataset bulk-validate path: outbound URL, request body shape, and the post-validate UI sync (≤2 s). +**Complexity**: 2 points +**Dependencies**: AZ-456_test_infrastructure +**Component**: 07_dataset (Blackbox Tests) +**Tracker**: AZ-464 +**Epic**: AZ-455 + +## Problem + +Bulk-validate is a single mutation that flips many `mediaStatus` values; getting the URL or the body wrong corrupts an arbitrary number of seeds. A targeted contract test pins both, plus a 2-s sync deadline catches stale-UI regressions. + +## Outcome + +- 3 scenarios pass per the contract. + +## Scope + +### Included + +| Scenario | Profile | Source file | +|----------|---------|-------------| +| FT-P-20 — bulk-validate request URL and body | fast + e2e | blackbox-tests.md | +| FT-P-21 — bulk-validate UI reflects new status within 2 s | fast + e2e | blackbox-tests.md | +| NFT-PERF-07 — bulk-validate UI reflects new status within 2 s | fast | performance-tests.md | + +### Excluded + +- Individual annotation status changes (covered in 05_test_annotations_endpoint). +- Dataset filtering / paging (out of scope for the baseline test suite — feature-cycle work). + +## Acceptance Criteria + +**AC-1: URL canary** +FT-P-20 captures the outbound bulk-validate URL and asserts it equals the contract value. + +**AC-2: Body shape** +FT-P-20 captures the outbound body and asserts it carries the expected media-ID set + the target status value. + +**AC-3: UI sync deadline** +FT-P-21 / NFT-PERF-07 measure the wall-clock from response receipt to DOM update of the dataset list rows; asserts ≤2 s. + +## System Under Test Boundary + +- System under test: `` + its bulk-validate action handler + `src/api/client.ts`. +- Allowed stubs: MSW for the dataset bulk-validate endpoint; real `annotations/` service for e2e. +- Disallowed: reading React state to assert UI sync — the test reads the rendered DOM (table rows / status badges). +- Expected observables compared against `results_report.md` rows 36-37. + +## Constraints + +- Use the `seed_media` fixture's 6-row baseline so the bulk operation is bounded. diff --git a/_docs/02_tasks/todo/AZ-465_test_i18n.md b/_docs/02_tasks/todo/AZ-465_test_i18n.md new file mode 100644 index 0000000..0ddddc1 --- /dev/null +++ b/_docs/02_tasks/todo/AZ-465_test_i18n.md @@ -0,0 +1,66 @@ +# Test — i18n Coverage & Persistence + +**Task**: AZ-465_test_i18n +**Name**: i18n key parity + t() coverage + detector + persistence +**Description**: Implement the 4 blackbox tests that pin the i18n contract: en↔ua key parity (static), `t()` coverage (no raw user-visible strings), boot-time language detector, and persistence across reload. +**Complexity**: 3 points +**Dependencies**: AZ-456_test_infrastructure +**Component**: 03_shared-ui + 10_app-shell (i18n) (Blackbox Tests) +**Tracker**: AZ-465 +**Epic**: AZ-455 + +## Problem + +A missing translation key or a hardcoded user-visible string only surfaces when a user switches language — by which point it's a customer-visible defect. Static checks + a behavioral test for detect/persist catch these at commit time. + +## Outcome + +- 4 scenarios pass per the contract. +- The "no raw strings" check is enforceable in CI and produces a clear allow-list mechanism for legitimate non-i18n text (e.g. brand names). + +## Scope + +### Included + +| Scenario | Profile | Source file | results_report row | +|----------|---------|-------------|--------------------| +| FT-P-22 — i18n key parity en ↔ ua | static | blackbox-tests.md | 45 | +| FT-P-23 — no raw user-visible strings outside `t(...)` | static | blackbox-tests.md | 46 | +| FT-P-24 — i18n detector path used at first boot | fast + e2e | blackbox-tests.md | 47 | +| FT-P-25 — i18n persistence across reload | fast + e2e | blackbox-tests.md | 48 | + +### Excluded + +- Adding a third language (project is en + ua per scope). +- RTL support (not required by any AC). + +## Acceptance Criteria + +**AC-1: Key parity** +Static check: `keys(en.json) == keys(ua.json)` (set equality). Test FAILS on any drift. + +**AC-2: t() coverage** +Static check via ripgrep + AST walker: every JSX text node and string-literal `aria-*` / `title` / `placeholder` either lives in an i18n key, is in the allow-list (brand names, version strings), or fails the check. + +**AC-3: Detector path** +First boot with no persisted language preference: SPA reads `navigator.language` and renders the matching bundle. + +**AC-4: Persistence** +After user switches to UA and reloads, the UA bundle is rendered without explicit user action. + +## System Under Test Boundary + +- System under test: `src/i18n/i18n.ts` + every React component rendering user-visible text. +- Allowed stubs: none beyond the standard test renderer. +- Disallowed: reading the i18n state directly — the test asserts the rendered DOM text. +- Expected observables per rows 45-48. + +## Constraints + +- Allow-list file lives at `tests/i18n-allowlist.json`; CI enforces it must not grow without a code-review reason. + +## Risks & Mitigation + +**Risk 1 — AST walker false positives** +- *Risk*: the t() coverage walker may misclassify dynamic strings (e.g. `t(\`key_${id}\`)`) or ternaries. +- *Mitigation*: explicit allow-list per file, plus a comment marker `// i18n-ok: ` honored by the walker. diff --git a/_docs/02_tasks/todo/AZ-466_test_destructive_ux.md b/_docs/02_tasks/todo/AZ-466_test_destructive_ux.md new file mode 100644 index 0000000..0e85eeb --- /dev/null +++ b/_docs/02_tasks/todo/AZ-466_test_destructive_ux.md @@ -0,0 +1,73 @@ +# Test — Destructive UX & ConfirmDialog + +**Task**: AZ-466_test_destructive_ux +**Name**: Destructive UX policy + ConfirmDialog a11y + no-alert + cancel paths +**Description**: Implement the 8 blackbox tests that pin the destructive-action policy: every destructive surface (delete class, delete user, etc.) shows a `` before issuing the request; the dialog has proper a11y; cancel suppresses the request; no `alert()` is ever used. +**Complexity**: 4 points +**Dependencies**: AZ-456_test_infrastructure +**Component**: 03_shared-ui (ConfirmDialog) + 08_admin (Blackbox Tests) +**Tracker**: AZ-466 +**Epic**: AZ-455 + +## Problem + +Without a uniform destructive-action gate, regressions add a delete button that fires directly — a one-click-data-loss bug. The policy is "no destructive action without ConfirmDialog, no `alert()` anywhere". + +## Outcome + +- 8 scenarios pass per the policy. +- A static check enumerates every destructive surface to keep the policy enforceable. + +## Scope + +### Included + +| Scenario | Profile | Source file | +|----------|---------|-------------| +| FT-P-26 — class-delete with confirmation — happy path | fast + e2e | blackbox-tests.md | +| FT-P-27 — destructive policy — dialog before request for every destructive surface | fast (static enumeration) | blackbox-tests.md | +| FT-P-28 — ConfirmDialog has dialog + modal a11y attributes | fast | blackbox-tests.md | +| FT-P-29 — ConfirmDialog focus trap (Tab cycles inside) | fast | blackbox-tests.md | +| FT-N-07 — class-delete Cancel path — NO DELETE request issued | fast | blackbox-tests.md | +| FT-N-08 — Escape on `` cancels — no destructive request | fast | blackbox-tests.md | +| NFT-SEC-07 — `alert()` is forbidden anywhere in the SPA | static | security-tests.md | +| NFT-SEC-08 — ConfirmDialog gates every destructive action | static + fast | security-tests.md | + +### Excluded + +- ConfirmDialog content / phrasing (covered by i18n parity in 10_test_i18n). +- Specific delete-target wire shapes (covered by per-feature tasks). + +## Acceptance Criteria + +**AC-1: Happy path** +FT-P-26 simulates user clicking Delete → confirming → asserts the DELETE request fires AFTER the confirm. + +**AC-2: Cancel paths** +FT-N-07 / FT-N-08 assert that pressing Cancel / Escape on the dialog suppresses the DELETE request entirely. + +**AC-3: a11y** +FT-P-28 / FT-P-29 assert `role="dialog"`, `aria-modal="true"`, `aria-labelledby` / `aria-describedby` linkage, and a focus trap that keeps Tab inside the dialog. + +**AC-4: Policy enforcement** +FT-P-27 / NFT-SEC-08 — a static check enumerates every surface with a `data-destructive` (or equivalent) attribute and asserts each one mounts a `` before its mutating handler runs. + +**AC-5: No alert()** +NFT-SEC-07 — ripgrep static check `grep -rn 'alert(' src/` returns no hits outside test files. + +## System Under Test Boundary + +- System under test: `` + every destructive surface (delete class, delete user, etc.). +- Allowed stubs: MSW for the suite's delete endpoints (fast); real services (e2e). +- Disallowed: stubbing ``; reading its React state. +- Expected observables per `results_report.md` rows 49-51 + the rows for NFT-SEC-07, 08. + +## Constraints + +- Static check (FT-P-27 / NFT-SEC-08) requires a discoverable marker on destructive surfaces; this task lands the test, and per-component tasks (already in scope above) wire the markers. + +## Risks & Mitigation + +**Risk 1 — A destructive surface is added without the marker** +- *Risk*: a new feature adds a delete-button that bypasses the static check. +- *Mitigation*: the marker is on the shared `` wrapper; using raw `