Files
missions/_docs/tasks/done/AZ-579_test_waypoints_health_positive.md
T
Oleksandr Bezdieniezhnykh 6b2c2d998e [AZ-577] [AZ-578] [AZ-579] [AZ-580] Implement E2E test batch 2
Adds 26 blackbox tests (FT-P-01..18, FT-N-01..08) covering full AC
matrices for Vehicles/Missions/Waypoints/Health/Errors. Three
spec-vs-code carry-forwards documented in batch_02_report.md and
pinned with [Trait("carry_forward", ...)].

Shared scaffolding: ApiDtos.cs, AssertProblemEnvelopeAsync helper,
Seeds.cs, StubSchema.cs, CascadeF3/F4 fixtures, PostgresStopStart
fixture (gated by COMPOSE_RESTART_ENABLED). Removes the 4 placeholder
Sanity.cs files (now superseded). docker-compose.test.yml gains the
expected_results volume mount + FIXTURE_SQL_DIR for the consumer.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-15 08:28:37 +03:00

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 missions service and pass.
  • Each test produces a CSV row with Category=Blackbox, Traces=AC-4.x or AC-7.x, Result=pass, within the documented Max 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:0 and checking the new value is 0 (not the preserved 120).
  • The "PG stopped" health test asserts the process answers 200 even with postgres-test stopped — 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 OrderNum ASC — seed_5_waypoints_unordered inserts in [3,1,2,5,4] order.
  • FT-P-14 Waypoint create echoes GeoPoint fields (no auto lat/lon ↔ MGRS conversion today — preserves the documented divergence from spec).
  • FT-P-15 Waypoint update is full overwrite — Height:0 overwrites Height:120, OrderNum changes, GeoPoint:null clears.
  • FT-P-16 Health 200 anonymous — no Authorization header, 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 on wp1 chain go to 0; sibling wp2 chain 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.json drift 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-test to Up before exiting (try/finally with docker compose start postgres-test in the fixture teardown) — otherwise subsequent tests fail with ConnectionRefused.
  • 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 via https://jwks-mock:8443/sign with permissions=FL (for waypoint endpoints); FT-P-16 and FT-P-17 explicitly send no Authorization header.
  • FT-P-17 uses ComposeRestartFixture-style helper that runs docker compose -f docker-compose.test.yml stop postgres-test then docker compose -f docker-compose.test.yml start postgres-test in 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 / // Assert per 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:0 might be interpreted as "leave Height unchanged" rather than overwrite.
  • Mitigation: The test ALSO sets GeoPoint:null and 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 for postgres-test to reach healthy (poll pg_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.sql failed to insert wp2's chain, the post-delete assertion wp2 chain > 0 fails trivially — but with a misleading message.
  • Mitigation: The test asserts pre-delete counts FIRST (wp1 chain > 0 AND wp2 chain > 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* and http://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.md rows 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 admin JWT issuer (jwks-mock container) and the DB-only stub tables for media, 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, or Program.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.