[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:
Oleksandr Bezdieniezhnykh
2026-05-15 06:37:00 +03:00
parent 2840ccb9b6
commit b0c7132889
13 changed files with 1524 additions and 4 deletions
@@ -0,0 +1,114 @@
# Vehicles Positive Flow Tests
**Task**: AZ-577_test_vehicles_positive
**Name**: Vehicles positive tests (FT-P-01..06)
**Description**: Implement xUnit blackbox tests for the 6 happy-path Vehicle CRUD scenarios — create non-default, create default (demotes prior), setDefault, list (no-pagination + Name ASC), filter (case-INSENSITIVE name + exact isDefault), delete with no references.
**Complexity**: 5 points
**Dependencies**: AZ-576_test_infrastructure
**Component**: Blackbox Tests
**Tracker**: AZ-577
**Epic**: AZ-575
## Problem
The `/vehicles` surface implements two non-obvious invariants that documentation alone cannot guarantee: (1) creating a default vehicle clears any prior default in the same logical step, and (2) the list filter is case-INSENSITIVE on `name` (the docs said case-sensitive until 2026-05-14 — drift now corrected, but only an executable test can pin the actual code path). Without these tests, a future refactor of `VehicleService` could silently re-introduce two default rows or a case-sensitive filter and break consumers (`autopilot` reads the default vehicle on boot).
## Outcome
- All six FT-P-01..06 scenarios run against the dockerised `missions` service via HTTP + Npgsql side-channel and pass.
- Each test produces a CSV row with `Category=Blackbox`, `Traces=AC-1.x`, `Result=pass`, and an `ExecutionTimeMs` under the documented `Max execution time` (5s for create paths, 2s for read/delete).
- The list test asserts both shape (`array` not `PaginatedResponse`) and ordering (`Name ASC`).
- The filter test asserts case-INSENSITIVE matching for two casings (`BR` and `br`).
- The default-clear invariant is verified via DB count (`is_default=true` count == 1 after every default-creating action).
## Scope
### Included
- FT-P-01 Create non-default — `POST /vehicles` body shape + PascalCase response + DB row count.
- FT-P-02 Create default demotes prior default — `seed_one_default_vehicle` precondition; assert exactly one default after.
- FT-P-03 setDefault promotes existing vehicle — `POST /vehicles/{id}/setDefault`; assert clear-then-set via side-channel.
- FT-P-04 List unpaginated + Name ASC — assert body is JSON array (not `{Items,Page,…}`), assert length and ordering.
- FT-P-05 Filter `name=BR&isDefault=true` then `name=br&…` — assert case-INSENSITIVE substring match against `seed_3_vehicles_2_default`.
- FT-P-06 Delete with no references — `204` + DB count 0.
### Excluded
- FT-N-03 "delete vehicle in use returns 409" lives in Task 13 (negative tests).
- Validation-of-input scenarios (empty `Name`, negative `BatteryCapacity`, unknown `Type` int) are carry-forwards documented in `test-data.md` § Data Validation Rules; they are NOT tested here because the spec marks them as "accepted today" — they belong to the Refactor Backlog, not this task.
- TOCTOU race on default-vehicle exclusivity (NFT-RES-08) lives in Task 17.
## Acceptance Criteria
**AC-1: FT-P-01 returns 201 with PascalCase body**
Given `seed_empty` and a JWT with `permissions=FL`
When `POST /vehicles` is issued with the documented body
Then response is `201 Created`, body parses as `Vehicle` with PascalCase keys, `Id` parses as UUID, side-channel `SELECT COUNT(*) FROM vehicles WHERE id=<returned>` returns 1
**AC-2: FT-P-02 demotes prior default**
Given `seed_one_default_vehicle` (prior row `P1.is_default=true`)
When `POST /vehicles { …, IsDefault:true }` is issued
Then response is `201`, side-channel shows new row `is_default=true`, row `P1.is_default=false`, and `SELECT COUNT(*) WHERE is_default=true` == 1
**AC-3: FT-P-03 setDefault clears prior**
Given `seed_one_default_vehicle` plus a non-default row `P2`
When `POST /vehicles/{P2}/setDefault { IsDefault:true }` is issued
Then response is `200` with `Id==P2, IsDefault==true`, and side-channel shows `P2.is_default=true`, `P1.is_default=false`, count==1
**AC-4: FT-P-04 list is unpaginated and ordered**
Given `seed_3_vehicles_2_default` containing `BR-01, BR-02, MQ-9` in any insert order
When `GET /vehicles` is issued
Then response is `200`, body parses as a JSON array (NOT an object with `Items`), `body.length == 3`, and `[v.Name for v in body] == ["BR-01","BR-02","MQ-9"]`
**AC-5: FT-P-05 filter is case-INSENSITIVE**
Given `seed_3_vehicles_2_default`
When `GET /vehicles?name=BR&isDefault=true` AND `GET /vehicles?name=br&isDefault=true` are issued
Then both responses are `200` with `body.length == 1` and `body[0].Name == "BR-01"`
**AC-6: FT-P-06 delete is 204 + row gone**
Given one vehicle row with no missions referencing it
When `DELETE /vehicles/{id}` is issued
Then response is `204 No Content` with empty body, and side-channel shows `count == 0` for that id
## Non-Functional Requirements
**Performance**
- Each test must complete inside the documented `Max execution time` from `blackbox-tests.md` (5s for FT-P-01..03, 5s for FT-P-07-style writes, 2s for FT-P-04..06). The xUnit `[Trait("max_ms", "5000")]` or per-test `Timeout` must reflect this.
**Reliability**
- Tests share a `[Collection("Vehicles")]` xUnit collection and use `IClassFixture<DbResetFixture>` to TRUNCATE between scenarios. No state must leak between FT-P-01 and FT-P-04.
## Blackbox Tests
| AC Ref | Initial Data/Conditions | What to Test | Expected Behavior | NFR References |
|--------|------------------------|-------------|-------------------|----------------|
| AC-1 | `seed_empty`, JWT permissions=FL | `POST /vehicles` non-default body | `201` + PascalCase `Vehicle` + DB count 1 | — |
| AC-2 | `seed_one_default_vehicle` (P1) | `POST /vehicles { IsDefault:true }` | `201` + DB shows count==1 default after | AC-1.2 invariant |
| AC-3 | `seed_one_default_vehicle` + extra P2 | `POST /vehicles/{P2}/setDefault` | `200` + DB count==1 default; P1 cleared | AC-1.2 / AC-1.4 |
| AC-4 | `seed_3_vehicles_2_default` (`BR-01,BR-02,MQ-9`) | `GET /vehicles` shape + order | `200` + array + Name ASC | AC-1.5 |
| AC-5 | `seed_3_vehicles_2_default` | `GET /vehicles?name=BR…` + `?name=br…` | `200` + len 1 + `BR-01` for both casings | AC-1.6 |
| AC-6 | One row, zero missions | `DELETE /vehicles/{id}` | `204` + DB count 0 | AC-1.10 |
## Constraints
- HTTP only against `http://missions:8080` (no project reference to `Azaion.Missions.csproj`).
- Bearer token minted via `https://jwks-mock:8443/sign` with `permissions=FL`.
- DB assertions through the Npgsql side-channel only; marked `[Trait("db_access","seed-or-assert-only")]`.
- AAA pattern with `// Arrange` / `// Act` / `// Assert` comments per `coderule.mdc`.
- PascalCase JSON contract (`PropertyNamingPolicy = null`) is part of the SUT contract; the test must NOT silently accept camelCase.
## Risks & Mitigation
**Risk 1: Tests depend on side-channel SQL that drifts from the SUT migrator**
- *Risk*: If the migrator changes the `vehicles` column set, hand-rolled `INSERT` in the seed fixture breaks.
- *Mitigation*: Seed fixtures use the schema produced by the SUT's own startup migrator — `docker compose up` runs first, then the fixture inserts into the already-migrated tables.
**Risk 2: Ordering test (AC-4) is flaky if insert order accidentally matches alphabetic order**
- *Risk*: A non-deterministic seed insert could mask a missing `OrderBy`.
- *Mitigation*: Seed fixture inserts rows in `[MQ-9, BR-02, BR-01]` order (reverse alphabetic) so the test fails if the SUT omits the `OrderBy(a => a.Name)`.
## System Under Test Boundary
- Tests drive the product through the public HTTP surface (`http://missions:8080/vehicles*`) 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-1 1.1, 1.2, 1.4, 1.5, 1.6, 1.10.
- Stubs are allowed ONLY for the external `admin` JWT issuer (the `jwks-mock` container per `tests/Azaion.Missions.JwksMock/`).
- Stubs, fakes, monkeypatches, deterministic fallbacks, or direct imports are NOT allowed for any internal product module — including `VehicleService`, `VehiclesController`, `AppDataConnection`, `DatabaseMigrator`, `JwtExtensions`, or `ErrorHandlingMiddleware`. If any of these is not implemented (e.g., the SUT image hasn't been built), the test MUST fail/block as missing product implementation — it must not pass by replacing the module with a test stub.