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>
13 KiB
Batch Report
Batch: 02 Tasks: AZ-457 (auth & token handling), AZ-459 (wire-contract enums), AZ-465 (i18n), AZ-481 (CI image labels) Date: 2026-05-11 Cycle: Phase A baseline, Step 6 — Implement Tests Total complexity: 12 pts (5 + 2 + 3 + 2)
Task Results
| Task | Status | Files Modified | Tests | AC Coverage | Issues |
|---|---|---|---|---|---|
| AZ-457_test_auth_token_handling | Done | 4 created (3 fast + 1 e2e) | 16 fast PASS, 4 e2e gated by suite stack | 4 / 4 ACs covered | 1 documented drift (FT-P-01 it.fails() until Step 4 fix) |
| AZ-459_test_wire_contract_enums | Done | 2 created (1 fast + 1 e2e) | 11 fast (2 skipped per verification_pending), 1 e2e (drift-gated) |
4 / 4 ACs covered | 3 documented drifts (AnnotationStatus, MediaStatus, Affiliation — Step 4 .NET inspection pending) |
| AZ-465_test_i18n | Done | 4 created (1 fast + 2 static helpers + 1 allowlist) | 4 fast (2 quarantined), 2 static checks PASS | 4 / 4 ACs covered | 2 quarantined (FT-P-24/25 — detector + persistence not yet implemented in production) |
| AZ-481_test_ci_image_labels | Done | 1 created (static check), wired into runner | 6 static findings (5 PASS, 1 DRIFT for image.title) |
3 / 3 ACs covered | 1 documented drift (org.opencontainers.image.title missing — foundation/CI-CD follow-up) |
AC Test Coverage: All covered
AZ-457 — Auth & token handling (11 scenarios, 4 ACs)
| Scenario | Where | Profile | Status (this run) |
|---|---|---|---|
FT-P-01 (row 02) bootstrap refresh credentials:'include' |
src/auth/AuthContext.test.tsx |
fast | PASS via it.fails() (drift documented; flips when Step 4 fix lands) |
| FT-P-02 (rows 03, 12) 401→refresh→retry | src/api/client.test.ts (fast) + e2e/tests/auth.e2e.ts (e2e) |
fast + e2e | fast PASS; e2e gated by suite stack |
| FT-P-03 (row 11) refresh transparency | src/auth/AuthContext.test.tsx |
fast | PASS |
FT-N-04 (row 09) unauthenticated /admin → /login |
src/auth/ProtectedRoute.test.tsx |
fast | PASS |
| NFT-SEC-01 bearer never in browser storage | src/auth/AuthContext.test.tsx (fast) + e2e companion |
fast + e2e | fast PASS; e2e gated |
NFT-SEC-02 refresh cookie not in document.cookie |
src/auth/AuthContext.test.tsx (fast) + e2e companion |
fast + e2e | fast PASS; e2e gated |
NFT-SEC-03 refresh cookie Secure; HttpOnly; SameSite=Strict |
e2e/tests/auth.e2e.ts |
e2e only | gated |
NFT-SEC-04 credentials:'include' on every authed fetch |
src/api/client.test.ts |
fast | one assertion PASS, broader claim it.fails() (Step 4 quarantine) |
| NFT-PERF-02 exactly one refresh per cycle | src/api/client.test.ts |
fast | PASS |
| NFT-RES-01 transparent recovery | src/api/client.test.ts |
fast | PASS |
NFT-RES-08 expired refresh → /login |
src/api/client.test.ts + src/auth/ProtectedRoute.test.tsx |
fast | PASS |
AZ-459 — Wire-contract enums (4 scenarios, 4 ACs)
| Scenario | Where | Profile | Status |
|---|---|---|---|
| FT-P-04 (row 14) AnnotationStatus | tests/wire_contract.test.ts |
fast | PASS via it.fails() (drift documented) |
| FT-P-05 (rows 15-17) MediaStatus / Affiliation / CombatReadiness | tests/wire_contract.test.ts |
fast | MediaStatus + Affiliation it.fails() (drift); CombatReadiness it.skip (verification_pending) |
| FT-P-06 (rows 18, 19) detection wire payload | tests/wire_contract.test.ts (fast control) + e2e/tests/wire_contract.e2e.ts (@drift) |
fast + e2e | fast PASS; e2e gated by AZAION_RUN_DRIFT_E2E=1 |
| FT-N-15 MediaType magic-literal hygiene | scripts/run-tests.sh (STC-FN15, static) + tests/wire_contract.test.ts (fast) |
static + fast | static PASS; fast PASS for typed-shape, it.skip for value-set (verification_pending) |
AZ-465 — i18n (4 scenarios, 4 ACs)
| Scenario | Where | Profile | Status |
|---|---|---|---|
| FT-P-22 (row 45) en↔ua key parity | scripts/check-i18n-coverage.mjs via STC-FP22 |
static | PASS |
FT-P-23 (row 46) no raw user strings outside t() |
scripts/check-i18n-coverage.mjs --coverage-only via STC-FP23 + tests/i18n-allowlist.json |
static | PASS (allow-list seeded with current pre-existing raw strings; CI gates growth) |
| FT-P-24 (row 47) detector path on first boot | tests/i18n.test.tsx |
fast + e2e | it.skip (QUARANTINE: detector not wired in src/i18n/i18n.ts today) + control test asserts the gap |
| FT-P-25 (row 48) persistence across reload | tests/i18n.test.tsx |
fast + e2e | it.skip (QUARANTINE: no persistence adapter today) + control test asserts the gap |
AZ-481 — CI image labels (3 scenarios, 3 ACs)
| Scenario | Where | Profile | Status |
|---|---|---|---|
NFT-RES-LIM-11 tag scheme ${branch}-arm |
scripts/check-ci-image-labels.mjs via STC-CI11 (parses .woodpecker/build-arm.yml) |
static | PASS |
| NFT-RES-LIM-12 OCI labels present | same | static | revision/created/source PASS; org.opencontainers.image.title reported DRIFT (lifting the drift is a follow-up CI hygiene task — not in scope here) |
NFT-RES-LIM-13 revision label = $CI_COMMIT_SHA |
same | static | PASS |
| (e2e portion against pushed image) | not run on host | requires-ci | gated; requires-ci per _dependencies_table.md |
Code Review Verdict: PASS_WITH_WARNINGS
Self-review (4-task batch, sequential per .cursor/rules/no-subagents.mdc). Phases 1–7 of code-review/SKILL.md walked inline:
- Phase 1 (Context): each task spec re-read;
_docs/02_document/module-layout.mdBlackbox Tests envelope respected;_docs/00_problem/input_data/enum_spec_snapshot.jsonis the contract pin for AZ-459;.woodpecker/build-arm.ymlis the SUT for AZ-481. - Phase 2 (Spec compliance): every AC across the four task specs has at least one test (running,
it.fails(), orit.skipwith quarantine reason). Drift handling (AZ-459 §AC-2 "Drift surfaces, not silently passes" + verification_pending markers) implemented uniformly viait.fails()for documented UI drift andit.skipforverification_pendingenums; AZ-481's static analogue uses aDRIFTfinding category to surface the missingimage.titlelabel without gating CI. - Phase 3 (Code quality): helper functions
compareEnum,describeDrift,probeLabel,instrumentStorageeach carry one responsibility; no barecatch; meaningful messages; arrange/act/assert respected; tests do not import<AuthContext>internals or<ProtectedRoute>internals (onlysetToken/getToken/setNavigateToLoginaccessors per AZ-457 AC-2 and the autodev Step 4 testability accessors). - Phase 4 (Security): no new secrets in test fixtures (re-uses AZ-456's placeholder argon2 hashes via the seed helpers); no use of
eval/shell=True; static checks tightened to exclude*.test.{ts,tsx,spec.ts,spec.tsx}from production grep so test prose can mention forbidden tokens (e.g.document.cookie) without false positives. - Phase 5 (Performance): fast suite ~3s wall-clock for 38 + 4-skipped tests (well under 5-min budget); static profile ~13s including
vite buildandtsc --noEmit (test). - Phase 6 (Cross-task consistency): the four tasks touch disjoint subsystems (auth vs enums vs i18n vs CI config); no contract collisions, no duplicate symbols. The shared
tests/helpers/{auth,navigate,render}.ts(landed in batch 1) is the only cross-task surface and is consumed read-only. - Phase 7 (Architecture compliance):
- Test files only import the public accessors of
01_api-transport(api,setToken,getToken) and02_auth(ProtectedRoutedefault export — the public boundary), plus00_foundation/i18nfor AZ-465's reflective control test. No imports of<AuthContext>internals, no imports of_internal/or*.internal.*files. Per the batch-1 finding (Low / Architecture / Interpretation), this is the same "test setup helper imports public accessors" pattern, now extended to the test bodies via thetests/helpers/indirection. - No new cyclic module dependencies introduced (test files are leaves in the import graph).
- Test files only import the public accessors of
Findings
-
Low / Maintainability —
tests/i18n-allowlist.jsonwas seeded with the current set of raw strings insrc/so thatFT-P-23(no raw user strings outsidet()) passes today. This is per AZ-465 §Constraints ("Allow-list file lives attests/i18n-allowlist.json; CI enforces it must not grow without a code-review reason") — the allow-list is a snapshot, not a permanent exemption. The static check enforces "must not grow without code review" because the JSON file is committed and any growth would be visible in PR diffs. Recommendation: a follow-up i18n-cleanup task (out of scope here) should drain the allow-list as keys are migrated tot(...). -
Low / Style / Drift —
scripts/check-ci-image-labels.mjsintroduces aDRIFTfinding category (parallels AZ-459'sit.fails()) for the missingorg.opencontainers.image.titleOCI label in.woodpecker/build-arm.yml. The script reportsDRIFTto stdout but does not exit non-zero. Rationale: lifting the drift requires editing CI config (foundation/CI-CD ownership envelope), which is out of scope for a test-only batch. Recommendation: file a follow-up CI hygiene task to add the--label org.opencontainers.image.title=azaion-uiclause; once landed, theDRIFTflips toPASSwith no test change required. -
Low / Architecture / Interpretation (carried over from batch 1) — same issue as batch 1: tests rely on
tests/helpers/{render,auth,navigate}.tswhich import production accessors. Reaffirmed here that "Black-box discipline applies to test bodies, not to test setup helpers / composition-root wrappers". The batch-1 recommendation (clarify the layout rule) still stands.
Auto-Fix Attempts: 0
Stuck Agents: None
Files Changed (12)
Created — src/ (3)
src/api/client.test.ts # AZ-457 fast — 9 tests
src/auth/AuthContext.test.tsx # AZ-457 fast — 4 tests
src/auth/ProtectedRoute.test.tsx # AZ-457 fast — 3 tests
Created — tests/ (3)
tests/wire_contract.test.ts # AZ-459 fast — 11 tests (2 skipped)
tests/i18n.test.tsx # AZ-465 fast — 4 tests (2 skipped)
tests/i18n-allowlist.json # AZ-465 raw-string allow-list (seed)
Created — e2e/tests/ (2)
e2e/tests/auth.e2e.ts # AZ-457 e2e — 4 scenarios (gated by suite stack)
e2e/tests/wire_contract.e2e.ts # AZ-459 e2e — drift-gated annotation save body
Created — scripts/ (2)
scripts/check-i18n-coverage.mjs # AZ-465 STC-FP22 + STC-FP23
scripts/check-ci-image-labels.mjs # AZ-481 STC-CI11
Modified (3)
scripts/run-tests.sh # +6 static checks (STC-FN15, STC-SEC4 refinement, STC-FP22, STC-FP23, STC-CI11) + src_grep test-file exclusion
tsconfig.json # +exclude src/**/*.{test,spec}.{ts,tsx} from production tsc -b
_docs/_autodev_state.md # batch 2 sub_step pointer
Verification Run (host)
$ bun run test:fast
✓ mission-planner/src/test/jsonImport.test.ts (6 tests) 7ms
✓ tests/wire_contract.test.ts (11 tests | 2 skipped) 9ms
✓ tests/infrastructure.test.ts (5 tests) 35ms
✓ tests/i18n.test.tsx (4 tests | 2 skipped) 3ms
✓ src/api/client.test.ts (9 tests) 58ms
✓ src/auth/ProtectedRoute.test.tsx (3 tests) 76ms
✓ src/auth/AuthContext.test.tsx (4 tests) 241ms
Test Files 7 passed (7)
Tests 38 passed | 4 skipped (42)
$ ./scripts/run-tests.sh --static-only
[run-tests] static profile PASSED — 19/19 checks (was 13 in batch 1; +6 from batch 2)
$ ./scripts/run-tests.sh
[run-tests] static profile : ran (PASS)
[run-tests] fast profile : ran (PASS)
[run-tests] e2e profile : skipped (host)
[run-tests] exit code : 0
E2E profile not exercised in this batch — same Risk 4 as batch 1 (requires docker compose -f e2e/docker-compose.suite-e2e.yml up -d plus parent-suite :test images). Per AZ-457 e2e companion, NFT-SEC-03 specifically requires Playwright's context.cookies() against the real admin/ service.
Next Batch
Remaining: 22 test-implementation tasks in _docs/02_tasks/todo/ (AZ-458, AZ-460..AZ-464, AZ-466..AZ-480, AZ-482). All carry Component: Blackbox Tests and Dependencies: AZ-456 (✓ done) — soft cross-deps:
- AZ-473 depends on AZ-472 (DetectionClasses fixtures)
- AZ-458 (SSE bearer rotation) consumes the auth helpers landed by AZ-457 (✓ now done)
- AZ-467 (ProtectedRoute spinner + RBAC) consumes the same helpers (✓)
- AZ-468 (Header dropdown — uses authed page) consumes the same helpers (✓)
Suggested next batch (4 tasks, ~10 pts): AZ-458 (SSE lifecycle, 5pts), AZ-467 (ProtectedRoute spinner + RBAC, 4pts), AZ-468 (Header flight dropdown, 2pts), and one small parallel — for example AZ-482 (secrets/banned-libs, 3pts) so the four tasks remain dependency-disjoint at the file level.
Recommendation: continue in a new conversation. Batch 2 added 12 new files and 6 new static checks; the next batch will load distinct task specs and SSE / RBAC subsystems.