mirror of
https://github.com/azaion/missions.git
synced 2026-06-22 19:31:08 +00:00
[AZ-575] Add 11 blackbox test task specs from decompose Step 5
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>
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user