mirror of
https://github.com/azaion/ui.git
synced 2026-06-21 15:41:10 +00:00
[AZ-457] [AZ-459] [AZ-465] [AZ-481] Batch 2 - auth/enum/i18n/CI tests
Implements 22 blackbox test scenarios across the four batch-2 tasks:
AZ-457 - Auth & token handling (11 scenarios, fast + e2e):
- src/api/client.test.ts: FT-P-02, NFT-SEC-04, NFT-PERF-02, NFT-RES-01,
NFT-RES-08 (apiClient surface)
- src/auth/AuthContext.test.tsx: FT-P-01 (it.fails - Step 4 drift),
FT-P-03, NFT-SEC-01, NFT-SEC-02
- src/auth/ProtectedRoute.test.tsx: FT-N-04, NFT-RES-08 (router half)
- e2e/tests/auth.e2e.ts: FT-P-02 e2e, NFT-SEC-01/02/03 (cookie attrs
via Playwright context.cookies(), gated by suite stack)
AZ-459 - Wire-contract enums (4 scenarios):
- tests/wire_contract.test.ts: FT-P-04 (AnnotationStatus, it.fails),
FT-P-05 (MediaStatus + Affiliation it.fails; CombatReadiness skip
per verification_pending), FT-P-06 (AnnotationSource control +
spec value-set membership), FT-N-15 (typed-enum shape + skip for
value-set verification)
- e2e/tests/wire_contract.e2e.ts: FT-P-06 against real annotations/
service, drift-gated via AZAION_RUN_DRIFT_E2E
- scripts/run-tests.sh STC-FN15: ripgrep static for MediaType
magic-literal hygiene
AZ-465 - i18n (4 scenarios, all static + quarantined fast):
- scripts/check-i18n-coverage.mjs: FT-P-22 (en vs ua key parity) +
FT-P-23 (no raw user strings outside t() in src/**/*.tsx); refined
JSX text-node regex with negative lookbehind to drop TS generics
+ arrow-function false positives
- tests/i18n-allowlist.json: snapshot of current pre-existing raw
strings (CI gates growth per AZ-465 Constraints)
- tests/i18n.test.tsx: FT-P-24 + FT-P-25 it.skip (QUARANTINE - i18n
detector + persistence not wired today; control tests assert the
gap so the skip flips to a real test once Step 4 lands)
AZ-481 - CI image labels (3 scenarios, static against
.woodpecker/build-arm.yml):
- scripts/check-ci-image-labels.mjs: NFT-RES-LIM-11 (tag scheme
${CI_COMMIT_BRANCH}-arm), NFT-RES-LIM-12 (revision/created/source
PASS, image.title reported as DRIFT - foundation/CI-CD owns the
fix), NFT-RES-LIM-13 (revision = $CI_COMMIT_SHA)
Cross-cutting:
- scripts/run-tests.sh: src_grep now excludes *.test.{ts,tsx} +
*.spec.{ts,tsx} so production-source static checks (STC-SEC4,
STC-FN15, etc.) don't false-positive on test prose
- tsconfig.json: exclude src/**/*.{test,spec}.{ts,tsx} so production
tsc -b doesn't see jest-dom matchers
- _docs/03_implementation/batch_02_report.md: full per-task AC
coverage matrix + drift inventory + verification run
- _docs/_autodev_state.md: 22 tasks remain after batch 2
Verification (host):
fast : 7 files, 38 passed | 4 skipped (quarantined)
static : 19/19 checks PASS (was 13 in batch 1; +6 from batch 2)
e2e : not run on host (Risk 4 - requires suite docker stack)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,79 +0,0 @@
|
||||
# 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 (`<AuthContext>` + `src/api/client.ts` + `<ProtectedRoute>`) 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`, `<AuthContext>`, or `<ProtectedRoute>` 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 `<ProtectedRoute>` 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 `<AuthContext>` 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` + `<AuthContext>` + `<ProtectedRoute>` + 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`, `<AuthContext>`, `<ProtectedRoute>`, or any other internal SPA module. If `<AuthContext>` 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.
|
||||
@@ -1,76 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,66 +0,0 @@
|
||||
# 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: <reason>` honored by the walker.
|
||||
@@ -1,54 +0,0 @@
|
||||
# Test — CI Image Tag Scheme & OCI Labels
|
||||
|
||||
**Task**: AZ-481_test_ci_image_labels
|
||||
**Name**: CI image tag `${branch}-arm` + OCI labels + revision label
|
||||
**Description**: Implement the 3 blackbox tests pinning the CI-produced image surface: tag scheme is `${branch}-arm` (NFT-RES-LIM-11), required OCI labels are present (NFT-RES-LIM-12), and the `org.opencontainers.image.revision` label equals `$CI_COMMIT_SHA` (NFT-RES-LIM-13).
|
||||
**Complexity**: 2 points
|
||||
**Dependencies**: AZ-456_test_infrastructure
|
||||
**Component**: 00_foundation (CI/CD) (Blackbox Tests)
|
||||
**Tracker**: AZ-481
|
||||
**Epic**: AZ-455
|
||||
|
||||
## Problem
|
||||
|
||||
Without a tag canary, two builds on the same branch can overwrite each other silently (no `-arm` suffix); without OCI labels, the image is not traceable to a source revision. Both regressions are operationally catastrophic and surface only after a deploy.
|
||||
|
||||
## Outcome
|
||||
|
||||
- 3 scenarios pass.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
|
||||
| Scenario | Profile | Source file |
|
||||
|----------|---------|-------------|
|
||||
| NFT-RES-LIM-11 — CI image tag scheme is `${branch}-arm` | static (CI config) + e2e (against pushed image) | resource-limit-tests.md |
|
||||
| NFT-RES-LIM-12 — OCI labels present on the pushed image | static + e2e | resource-limit-tests.md |
|
||||
| NFT-RES-LIM-13 — Revision label equals `$CI_COMMIT_SHA` | e2e | resource-limit-tests.md |
|
||||
|
||||
### Excluded
|
||||
|
||||
- nginx route / image-base assertions (covered in 25_test_prod_image_nginx_ram).
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Tag scheme**
|
||||
NFT-RES-LIM-11 — CI config (`.gitlab-ci.yml` / equivalent) builds with tag pattern `${CI_COMMIT_REF_SLUG}-arm`; the static check asserts the pattern is intact. E2e additionally asserts `docker manifest inspect $REGISTRY/$IMAGE:${BRANCH}-arm` succeeds.
|
||||
|
||||
**AC-2: OCI labels present**
|
||||
NFT-RES-LIM-12 — `docker inspect $IMAGE` reports the required OCI labels: `org.opencontainers.image.source`, `org.opencontainers.image.revision`, `org.opencontainers.image.title`, `org.opencontainers.image.created` — all non-empty.
|
||||
|
||||
**AC-3: Revision binding**
|
||||
NFT-RES-LIM-13 — `docker inspect --format='{{index .Config.Labels "org.opencontainers.image.revision"}}'` returns exactly `$CI_COMMIT_SHA`.
|
||||
|
||||
## System Under Test Boundary
|
||||
|
||||
- System under test: CI config (`.gitlab-ci.yml` / equivalent) + the pushed image's metadata.
|
||||
- Allowed stubs: a tag fixture for static-mode validation; in e2e the test inspects an actual locally-built image.
|
||||
- Disallowed: bypassing CI to inject labels for the test.
|
||||
|
||||
## Constraints
|
||||
|
||||
- E2e tests run only on CI; locally `bun run test:e2e -- --skip-tags @requires-ci` skips them.
|
||||
- CI environment provides `CI_COMMIT_SHA` and `CI_COMMIT_REF_SLUG`.
|
||||
Reference in New Issue
Block a user