Decompose Step 5 (tests-only mode) produced the test-task ladder for the Blackbox Tests epic. Test infrastructure (AZ-576) blocks the rest; all 10 blackbox child tasks fan out from it. Tasks (epic AZ-575): - AZ-576 test_infrastructure (5 SP) - AZ-577 test_vehicles_positive (5 SP) - AZ-578 test_missions_positive (5 SP) - AZ-579 test_waypoints_health_positive (5 SP) - AZ-580 test_validation_authz_negative (3 SP) - AZ-581 test_security_auth_claims (5 SP) - AZ-582 test_security_alg_rotation_cors (5 SP) - AZ-583 test_resilience_cascade_migrator (3 SP) - AZ-584 test_resilience_config_db_rotation_race (5 SP) - AZ-585 test_resource_limits (3 SP) - AZ-586 test_performance (3 SP) Total: 45 SP across 11 tasks. Coverage verified against blackbox/security/resilience/resource-limit/performance test specs (56 scenarios). _docs/_autodev_state.md advanced to Step 6 (Implement Tests). Co-authored-by: Cursor <cursoragent@cursor.com>
9.8 KiB
Waypoints + Health Positive Flow Tests
Task: AZ-579_test_waypoints_health_positive Name: Waypoints + Health positive tests (FT-P-13..18) Description: Implement xUnit blackbox tests for the 6 happy-path Waypoint + Health scenarios — waypoint list ordered by OrderNum ASC, waypoint create echoes geo fields (no auto-conversion), waypoint update is full overwrite, health 200 anonymous, health 200 with Postgres stopped (no DB ping), and waypoint cascade delete scoped to one waypoint (sibling chain intact). Complexity: 5 points Dependencies: AZ-576_test_infrastructure Component: Blackbox Tests Tracker: AZ-579 Epic: AZ-575
Problem
Waypoints carry two non-obvious behaviors: (1) the list endpoint orders by OrderNum ASC regardless of insert order (AC-4.3), and (2) PUT /missions/{id}/waypoints/{wpId} is a FULL overwrite even though the DTO looks "partial" (non-nullable enums + numerics) — passing Height:0 overwrites the previous Height:120 (AC-4.4). The waypoint cascade delete (AC-4.5) is the tighter sibling of the mission cascade — it must remove the target waypoint's chain (media → annotations → detection) without touching a sibling waypoint's chain. The health endpoint (AC-7.1, AC-7.2) is the suite's probe contract: it MUST return 200 anonymously AND MUST NOT ping the database, because the suite reverse proxy uses /health to decide whether to route traffic — a DB outage must not depool a healthy process.
Outcome
- All six FT-P-13..18 scenarios run against the dockerised
missionsservice and pass. - Each test produces a CSV row with
Category=Blackbox,Traces=AC-4.xorAC-7.x,Result=pass, within the documentedMax execution time(2s for FT-P-13..16, 5s for FT-P-17 to allow PG stop, 10s for FT-P-18 cascade). - The list test asserts both shape (JSON array) and ordering (
[1,2,3,4,5]ASC from a[3,1,2,5,4]insert order). - The update test asserts the FULL overwrite by passing
Height:0and checking the new value is 0 (not the preserved 120). - The "PG stopped" health test asserts the process answers
200even withpostgres-teststopped — proving the probe does not ping the DB. - The cascade test (F4) asserts target-waypoint chain deleted AND sibling-waypoint chain preserved, with per-table counts compared against
cascade_F4_walk.json.
Scope
Included
- FT-P-13 Waypoint list ordered by
OrderNumASC —seed_5_waypoints_unorderedinserts in[3,1,2,5,4]order. - FT-P-14 Waypoint create echoes
GeoPointfields (no auto lat/lon ↔ MGRS conversion today — preserves the documented divergence from spec). - FT-P-15 Waypoint update is full overwrite —
Height:0overwritesHeight:120,OrderNumchanges,GeoPoint:nullclears. - FT-P-16 Health 200 anonymous — no
Authorizationheader, exact JSON{ "status": "healthy" }. - FT-P-17 Health 200 with PG stopped — proves process-liveness only, no DB ping.
- FT-P-18 Waypoint cascade delete (F4) —
DELETE /missions/{mid}/waypoints/{wp1}; per-table counts onwp1chain go to 0; siblingwp2chain intact.
Excluded
- FT-N-07 "waypoint operation against missing mission returns 404" lives in Task 13.
- Waypoint nested existence check (single composite-FK predicate per
state.jsondrift entry) is implementation detail; the blackbox test only asserts the observable 404 in FT-N-07.
Acceptance Criteria
AC-1: FT-P-13 waypoint list is ordered by OrderNum ASC
Given seed_5_waypoints_unordered under one mission, with order_num values [3,1,2,5,4] inserted in that order
When GET /missions/{id}/waypoints is issued with a valid JWT
Then response is 200, body parses as JSON array, body.length == 5, AND [w.OrderNum for w in body] == [1,2,3,4,5]
AC-2: FT-P-14 waypoint create echoes geo fields, no MGRS conversion
Given one mission row
When POST /missions/{id}/waypoints { GeoPoint:{Lat:50.45, Lon:30.52, Mgrs:null}, WaypointSource:0, WaypointObjective:0, OrderNum:1, Height:120 } is issued
Then response is 201, body.GeoPoint.Lat == 50.45, body.GeoPoint.Lon == 30.52, AND body.GeoPoint.Mgrs == null (NO auto-conversion)
AC-3: FT-P-15 waypoint update is full overwrite
Given one waypoint with Height=120, OrderNum=1, GeoPoint=(Lat:50.45, …)
When PUT /missions/{id}/waypoints/{wpId} { GeoPoint:null, WaypointSource:1, WaypointObjective:1, OrderNum:2, Height:0 } is issued
Then response is 200, body.Height == 0 (overwritten from 120), body.OrderNum == 2, AND body.GeoPoint == null
AC-4: FT-P-16 health is 200 anonymous
Given a running missions container
When GET /health is issued with NO Authorization header
Then response is 200, body is exactly { "status": "healthy" } with case-sensitive key
AC-5: FT-P-17 health is 200 with PG stopped
Given missions is running AND docker compose stop postgres-test has succeeded
When GET /health is issued
Then response is 200, body is exactly { "status": "healthy" } — proving the probe does NOT ping the DB
AC-6: FT-P-18 waypoint cascade scope is one waypoint
Given fixture_cascade_F4 (target waypoint wp1 with chain media → annotations → detection; sibling waypoint wp2 with its own chain)
When DELETE /missions/{mid}/waypoints/{wp1} is issued
Then response is 204, AND side-channel SELECT COUNT(*) returns 0 for the wp1 chain rows in detection, annotations, media, AND for wp1 itself in waypoints
And side-channel returns 1 for wp2 in waypoints AND > 0 for the wp2 chain rows in media, annotations, detection
And the per-table counts after deletion match _docs/00_problem/input_data/expected_results/cascade_F4_walk.json via deep JSON diff
Non-Functional Requirements
Performance
- FT-P-13..16: ≤ 2s each. FT-P-17: ≤ 5s (allow PG stop time). FT-P-18: ≤ 10s (cascade through 4 tables).
Reliability
- FT-P-17 must restore
postgres-testtoUpbefore exiting (try/finally withdocker compose start postgres-testin the fixture teardown) — otherwise subsequent tests fail withConnectionRefused. - FT-P-18 uses
IClassFixture<DbResetFixture>with the F4 fixture recreated per scenario.
Blackbox Tests
| AC Ref | Initial Data/Conditions | What to Test | Expected Behavior | NFR References |
|---|---|---|---|---|
| AC-1 | seed_5_waypoints_unordered ([3,1,2,5,4]) |
GET /missions/{id}/waypoints |
200 + array + OrderNum ASC |
AC-4.3 |
| AC-2 | One mission row | POST /missions/{id}/waypoints { GeoPoint:{Lat,Lon,Mgrs:null} } |
201 + GeoPoint echoed + Mgrs null (no conversion) |
AC-4 (data_parameters § 2.3) |
| AC-3 | One waypoint Height=120 | PUT … { Height:0, GeoPoint:null } |
200 + Height=0 + GeoPoint=null (full overwrite) |
AC-4.4 |
| AC-4 | Running container | GET /health no auth |
200 + exact {"status":"healthy"} |
AC-7.1 |
| AC-5 | PG stopped | GET /health |
200 + exact {"status":"healthy"} |
AC-7.2, AC-7.3 |
| AC-6 | fixture_cascade_F4 |
DELETE /missions/{mid}/waypoints/{wp1} |
204 + wp1 chain 0 + wp2 chain intact + cascade_F4_walk.json match |
AC-4.5 |
Constraints
- HTTP only against
http://missions:8080; bearer token viahttps://jwks-mock:8443/signwithpermissions=FL(for waypoint endpoints); FT-P-16 and FT-P-17 explicitly send noAuthorizationheader. - FT-P-17 uses
ComposeRestartFixture-style helper that runsdocker compose -f docker-compose.test.yml stop postgres-testthendocker compose -f docker-compose.test.yml start postgres-testin teardown. - FT-P-18 fixture uses
_docs/00_problem/input_data/expected_results/fixture_cascade_F4.sql(NOT a hand-rolled INSERT). - AAA pattern with
// Arrange/// Act/// Assertper test.
Risks & Mitigation
Risk 1: FT-P-15 silently passes if SUT exposes a "partial" update path
- Risk: If a future refactor adds a JSON-merge update mode, sending
Height:0might be interpreted as "leave Height unchanged" rather than overwrite. - Mitigation: The test ALSO sets
GeoPoint:nulland asserts the value is null after — proving the path is full-overwrite, not patch.
Risk 2: FT-P-17 PG-stop leaks to other tests
- Risk: If the test fails before teardown, subsequent tests run against a dead DB.
- Mitigation: The fixture uses
try/finally; the teardown waits forpostgres-testto reachhealthy(pollpg_isready) before yielding control back to xUnit.
Risk 3: FT-P-18 sibling-intact assertion gives false-pass if F4 fixture is empty
- Risk: If
fixture_cascade_F4.sqlfailed to insertwp2's chain, the post-delete assertionwp2 chain > 0fails trivially — but with a misleading message. - Mitigation: The test asserts pre-delete counts FIRST (
wp1chain > 0 ANDwp2chain > 0); fixture failure is caught in the Arrange phase, not the Assert phase.
System Under Test Boundary
- Tests drive the product through the public HTTP surface (
http://missions:8080/missions/{id}/waypoints*andhttp://missions:8080/health) plus the documented DB side-channel for fixture seeding and post-call assertions. Expected outputs are compared against_docs/00_problem/input_data/expected_results/results_report.mdrows AC-4 4.2, 4.3, 4.4, 4.5 and AC-7 rows 7.1, 7.2, and against the machine-readable file_docs/00_problem/input_data/expected_results/cascade_F4_walk.json. - Stubs are allowed ONLY for: the external
adminJWT issuer (jwks-mockcontainer) and the DB-only stub tables formedia,annotations,detection(seeded via side-channel SQL). - Stubs, fakes, deterministic fallbacks, monkeypatches, or direct imports are NOT allowed for any internal product module — including
WaypointService,MissionsController(health route),AppDataConnection, orProgram.cs's health middleware. If any of these is not implemented, the test MUST fail/block as missing product implementation — it must not pass by replacing the module with a test stub.