Files
missions/_docs/tasks/todo/AZ-577_test_vehicles_positive.md
T
Oleksandr Bezdieniezhnykh b0c7132889 [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>
2026-05-15 06:37:00 +03:00

8.2 KiB

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.