Files
missions/_docs/tasks/done/AZ-578_test_missions_positive.md
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.7 KiB

Missions Positive Flow Tests

Task: AZ-578_test_missions_positive Name: Missions positive tests (FT-P-07..12) Description: Implement xUnit blackbox tests for the 6 happy-path Mission scenarios — create with default CreatedDate, paginated list (PageSize=20, CreatedDate DESC, case-INSENSITIVE name filter), page 2, date-range filter, partial update preserving null fields, and full cascade delete across map_objects/detection/annotations/media/waypoints/missions. Complexity: 5 points Dependencies: AZ-576_test_infrastructure Component: Blackbox Tests Tracker: AZ-578 Epic: AZ-575

Problem

The /missions surface is the project's most consequential read+write path. Three behaviours are easy to silently break: (1) the default CreatedDate = UtcNow when the body omits it (AC-2.1), (2) PaginatedResponse<Mission> envelope with Page,PageSize,TotalCount,Items PascalCase keys + CreatedDate DESC ordering (AC-2.3), and (3) the cascade delete walking every dependency table including DB-only stub tables map_objects, detection, annotations, media (AC-3.1). The cascade is not transaction-wrapped (NFT-RES-01 in Task 16 pins that invariant); the positive scenario here verifies the happy-path walk completes.

Outcome

  • All six FT-P-07..12 scenarios run against the dockerised missions service and pass.
  • Each test produces a CSV row with Category=Blackbox, Traces=AC-2.x or AC-3.1, Result=pass, within the documented Max execution time (5s for create, 2s for list/update, 10s for cascade delete).
  • The pagination test asserts both the envelope shape (Items, TotalCount, Page, PageSize PascalCase) AND CreatedDate DESC ordering across all 20 items.
  • The cascade test compares per-table delete counts against _docs/00_problem/input_data/expected_results/cascade_F3_walk.json via json_diff.

Scope

Included

  • FT-P-07 Mission create with default CreatedDate — assert |body.CreatedDate - t0| ≤ 5s.
  • FT-P-08 Mission list default page — envelope shape, Page==1, PageSize==20, TotalCount==25, Items.length==20, CreatedDate DESC ordering, plus case-INSENSITIVE ?name=re filter.
  • FT-P-09 Mission list page 2 — Page==2, Items.length==5, UUID-set disjoint from page 1.
  • FT-P-10 Mission list date range — ?fromDate=&toDate= inclusivity (January 2026 returns 5 of 25).
  • FT-P-11 Mission partial update — PUT /missions/{id} with VehicleId:null preserves prior VehicleId.
  • FT-P-12 Mission cascade delete (F3) — DELETE /missions/{id} walks every dependency table; per-table counts compared against cascade_F3_walk.json.

Excluded

  • FT-N-04 "create mission with non-existent VehicleId returns 400" lives in Task 13.
  • FT-N-05 "GET mission 404" lives in Task 13.
  • FT-N-06 "cascade delete short-circuits on missing mission (no DELETE issued against dependency tables)" lives in Task 13.
  • Cascade NOT-transaction-wrapped invariant (NFT-RES-01) lives in Task 16.

Acceptance Criteria

AC-1: FT-P-07 mission create defaults CreatedDate to UtcNow Given seed_one_default_vehicle and a JWT with permissions=FL When the consumer captures t0 = UtcNow then issues POST /missions { Name:"Recon-01", VehicleId:<id>, CreatedDate:null } Then response is 201, body.CreatedDate parses as UTC, and abs(body.CreatedDate - t0) ≤ 5s

AC-2: FT-P-08 list returns PaginatedResponse with DESC ordering and case-INSENSITIVE name filter Given seed_25_missions (5 January, 20 February 2026, mix of Recon-* names) When GET /missions is issued Then response is 200 with Page==1, PageSize==20, TotalCount==25, Items.length==20, all PascalCase keys, AND for every i ∈ [0..18] Items[i].CreatedDate >= Items[i+1].CreatedDate (strictly DESC ordering) And when GET /missions?name=re (lowercase) is issued, body.TotalCount > 0 (case-INSENSITIVE substring match against Recon-*)

AC-3: FT-P-09 page 2 returns the remaining 5 items, disjoint from page 1 Given seed_25_missions When GET /missions?page=2&pageSize=20 is issued Then response is 200, Page==2, Items.length==5, AND the set of Items[*].Id is disjoint from the page-1 response

AC-4: FT-P-10 date range filter is inclusive of bounds Given seed_25_missions (5 in January 2026, 20 in February 2026) When GET /missions?fromDate=2026-01-01T00:00:00Z&toDate=2026-01-31T23:59:59Z is issued Then response is 200, TotalCount==5, and every Items[i].CreatedDate is within January 2026 UTC

AC-5: FT-P-11 partial update preserves null fields Given one mission row with known Name="Original" and VehicleId=V1 When PUT /missions/{id} { Name:"Renamed", VehicleId:null } is issued Then response is 200, body.Name == "Renamed", AND body.VehicleId == V1 (preserved)

AC-6: FT-P-12 cascade delete walks every dependency table Given fixture_cascade_F3 applied (one mission with 2 waypoints → 2 media → 2 annotations → 2 detection rows + 3 map_objects) When DELETE /missions/{mid} is issued Then response is 204, AND side-channel SELECT COUNT(*) returns 0 for map_objects, detection, annotations, media, waypoints, missions rows in the seeded chain And the per-table counts after deletion match _docs/00_problem/input_data/expected_results/cascade_F3_walk.json via deep JSON diff

Non-Functional Requirements

Performance

  • FT-P-07: ≤ 5s. FT-P-08..11: ≤ 2s each. FT-P-12: ≤ 10s (cascade through 5 tables).

Reliability

  • FT-P-12 must use IClassFixture<DbResetFixture> that recreates fixture_cascade_F3 fresh per scenario (the fixture is destructive). FT-P-08..10 share seed_25_missions across the same class.

Blackbox Tests

AC Ref Initial Data/Conditions What to Test Expected Behavior NFR References
AC-1 seed_one_default_vehicle POST /missions { CreatedDate:null } 201 + |body.CreatedDate - t0| ≤ 5s AC-2.1
AC-2 seed_25_missions GET /missions then GET /missions?name=re 200 + envelope + DESC + case-INSENSITIVE match AC-2.3, AC-8.7
AC-3 seed_25_missions GET /missions?page=2&pageSize=20 200 + Page=2 + len 5 + disjoint UUIDs AC-2.3
AC-4 seed_25_missions GET /missions?fromDate=…&toDate=… (January window) 200 + TotalCount=5 + all in window AC-2.3
AC-5 One row with Name=Original, VehicleId=V1 PUT /missions/{id} { Name:"Renamed", VehicleId:null } 200 + Name updated + VehicleId preserved AC-2.5
AC-6 fixture_cascade_F3 DELETE /missions/{mid} 204 + DB counts 0 across 6 tables + cascade_F3_walk.json match AC-3.1

Constraints

  • HTTP only against http://missions:8080/missions* (no project reference to Azaion.Missions.csproj).
  • Bearer token minted via https://jwks-mock:8443/sign with permissions=FL.
  • FT-P-12 fixture uses the SQL file at _docs/00_problem/input_data/expected_results/fixture_cascade_F3.sql (NOT a hand-rolled INSERT — the SQL file is the contract).
  • Per-table count comparison in FT-P-12 uses json_diff against cascade_F3_walk.json; if the file is missing, the test must fail (not silently pass).
  • AAA pattern with // Arrange / // Act / // Assert per test.
  • seed_25_missions MUST use deterministic UUIDs and deterministic CreatedDate values so the disjoint-set assertion in AC-3 and the date-range assertion in AC-4 are reproducible.

Risks & Mitigation

Risk 1: cascade_F3_walk.json drifts from fixture_cascade_F3.sql

  • Risk: Updating the seed SQL without updating the walk JSON makes AC-6 silently pass with wrong counts.
  • Mitigation: Both files live under the same expected_results/ directory; the test loads the walk JSON at runtime and verifies BOTH that pre-delete counts match the walk's before values AND post-delete counts match the walk's after values. A drift fails the "before" assertion first.

Risk 2: AC-2 ordering assertion is flaky if seed CreatedDate values collide

  • Risk: Two missions with identical CreatedDate produce a tie-breaker-dependent order; the DESC assertion would be deterministic only if the comparator is stable.
  • Mitigation: seed_25_missions SQL assigns distinct CreatedDate values spaced ≥ 1 second apart; any future seed change must preserve this invariant.

Risk 3: cascade test pollutes neighbour scenarios

  • Risk: F3 fixture deletes rows across 6 tables; if FT-P-12 runs in the same xUnit class as a read-path test, that test sees an empty DB.
  • Mitigation: FT-P-12 lives in its own xUnit [Collection("CascadeF3")] and uses IClassFixture<DbResetFixture> to reset between every scenario in the class.

System Under Test Boundary

  • Tests drive the product through the public HTTP surface (http://missions:8080/missions*) 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-2 2.1, 2.3, 2.4, 2.5, 2.7 and AC-3 row 3.1, and against the machine-readable file _docs/00_problem/input_data/expected_results/cascade_F3_walk.json for the cascade walk.
  • Stubs are allowed ONLY for: the external admin JWT issuer (jwks-mock container) and the DB-only stub tables for media, annotations, detection, map_objects (seeded via side-channel SQL because the owning services are out of scope per environment.md).
  • Stubs, fakes, deterministic fallbacks, monkeypatches, or direct imports are NOT allowed for any internal product module — including MissionService, MissionsController, WaypointService, AppDataConnection, DatabaseMigrator, JwtExtensions, or ErrorHandlingMiddleware. 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.