mirror of
https://github.com/azaion/missions.git
synced 2026-06-21 08:01:07 +00:00
7025f4d075
Updated JWT authentication to use configuration values instead of hardcoded secrets, improving security and flexibility. Enhanced CORS policy to conditionally allow origins based on configuration settings, with logging for permissive defaults. Updated README to reflect project renaming and clarify service context.
94 lines
11 KiB
Markdown
94 lines
11 KiB
Markdown
# Test Data Management
|
||
|
||
> **Status**: produced by autodev `/test-spec` Phase 2 (2026-05-14).
|
||
> **Naming**: post-rename target. Today's API surface is `/aircrafts`/`/flights`; tests will be RED until B5–B8 land. Fixture column names in `expected_results/fixture_cascade_*.sql` use post-rename names; pre-rename code path runs the same DDL via the `Azaion.Flights.Database.DatabaseMigrator` against the test DB before the fixture INSERT — the test orchestrator invokes the service container's startup, then runs fixture SQL via the side channel.
|
||
|
||
## Seed Data Sets
|
||
|
||
| Data Set | Description | Used by Tests | How Loaded | Cleanup |
|
||
|----------|-------------|---------------|-----------|---------|
|
||
| `seed_empty` | Schema migrated, no rows in any table | bootstrap, unauth, 404 scenarios | `docker compose down -v && docker compose up -d` then wait for `missions` startup (which runs the migrator) | `down -v` between scenarios |
|
||
| `seed_one_default_vehicle` | Schema + 1 row in `vehicles` with `is_default=true` | AC-1.2 (default-clear), AC-1.3 (TOCTOU race), AC-1.4 setDefault, AC-2.1 mission create | side-channel SQL `INSERT INTO vehicles ...` after `seed_empty` | Within scenario class via per-class `IClassFixture<DbResetFixture>` |
|
||
| `seed_3_vehicles_2_default` | 3 rows in `vehicles`, 2 with `is_default=true` (illegal under AC-1.2) | AC-1.5 list, AC-1.6 filter (with deterministic ordering) | side-channel SQL | per-class fixture |
|
||
| `seed_25_missions` | 25 `missions` rows (5 in January 2026, 20 in February 2026) referencing one vehicle | AC-2.3, AC-2.4, AC-2.5 pagination + date filter | side-channel SQL with deterministic UUIDs | per-class fixture |
|
||
| `fixture_cascade_F3` | One mission with full dependency chain: 1 mission → 2 waypoints → 2 media → 2 annotations → 2 detection rows + 3 map_objects | AC-3.1, AC-3.3, AC-3.4, AC-10.2 | `expected_results/fixture_cascade_F3.sql` (referenced from `results_report.md`) — applied via side-channel after `seed_empty` | `down -v` after each scenario in this class |
|
||
| `fixture_cascade_F4` | One mission with one waypoint that has 1 media → 1 annotation → 1 detection chain; sibling waypoint with NO chain (must remain after delete) | AC-4.5, AC-4.6 | `expected_results/fixture_cascade_F4.sql` | `down -v` after each scenario in this class |
|
||
| `seed_5_waypoints_unordered` | 5 waypoints with `order_num` `[3, 1, 2, 5, 4]` under one mission | AC-4.3 unpaginated ordering | side-channel SQL | per-class fixture |
|
||
| `seed_legacy_gps_tables` | Pre-B7 schema: `vehicles`, `missions`, `waypoints` PLUS `orthophotos` and `gps_corrections` populated with 1 row each | AC-3.5 (post-B7 absence), AC-6.5 (one-shot drop), AC-10.5 (legacy device migration) | side-channel SQL `CREATE TABLE` + `INSERT` in a fresh `seed_empty` | `down -v` between scenarios |
|
||
|
||
## Data Isolation Strategy
|
||
|
||
Three isolation tiers, by scenario type:
|
||
|
||
- **Class-scoped DB reset** (`IClassFixture<DbResetFixture>`): for scenarios that share the same seed within a test class but must not leak to other classes. Used for AC-1, AC-2, AC-4 read paths.
|
||
- **Scenario-scoped container restart** (`docker compose down -v && up -d`): for scenarios that assert startup-time behavior or migrator side-effects. Used for AC-6.3, AC-6.4 (idempotency), AC-6.5 (legacy drop), AC-6.6 (idempotent re-run), AC-6.7 (DB unreachable), AC-5.7 (JWT_SECRET rotation).
|
||
- **Per-test transaction roll-back is NOT used** — the system under test is a separate process and its `DataConnection` is not in the test transaction.
|
||
|
||
## Input Data Mapping
|
||
|
||
| Input Data File | Source Location | Description | Covers Scenarios |
|
||
|-----------------|----------------|-------------|-----------------|
|
||
| `data_parameters.md` § 7 (HTTP table) | `_docs/00_problem/input_data/data_parameters.md` | Documentation of every endpoint + DTO shape; the consumer constructs requests from these shapes | every FT-* and NFT-* scenario |
|
||
| `fixture_cascade_F3.sql` | `_docs/00_problem/input_data/expected_results/fixture_cascade_F3.sql` | Cascade chain seed for AC-3 | FT-P-12, FT-N-04, NFT-RES-01, NFT-PERF-01 |
|
||
| `fixture_cascade_F4.sql` | `_docs/00_problem/input_data/expected_results/fixture_cascade_F4.sql` | Cascade chain seed for AC-4 | FT-P-18, NFT-RES-02 |
|
||
| `cascade_F3_walk.json` | `_docs/00_problem/input_data/expected_results/cascade_F3_walk.json` | Per-table delete-count expectations | FT-P-12 |
|
||
| `cascade_F4_walk.json` | `_docs/00_problem/input_data/expected_results/cascade_F4_walk.json` | Per-table delete-count expectations | FT-P-18 |
|
||
|
||
## Expected Results Mapping
|
||
|
||
| Test Scenario ID | Input Data | Expected Result | Comparison Method | Tolerance | Expected Result Source |
|
||
|-----------------|------------|-----------------|-------------------|-----------|----------------------|
|
||
| FT-P-01 | `POST /vehicles` body per `data_parameters.md` § 2.1 | `201 Created`, `Vehicle` body, DB row exists | exact + db_query | N/A | `results_report.md` AC-1 row 1.1 |
|
||
| FT-P-02 | `POST /vehicles` body with `IsDefault:true` against `seed_one_default_vehicle` | `201`; new row default; prior row not default; default count == 1 | exact + db_query | N/A | AC-1 row 1.2 |
|
||
| FT-P-03 | `POST /vehicles/{id}/setDefault {IsDefault:true}` | `200`; default count == 1; only target row default | exact + db_query | N/A | AC-1 row 1.4 |
|
||
| FT-P-04 | `GET /vehicles` against `seed_3_vehicles_2_default` | `200`; body length == 3; PascalCase keys | exact + schema | N/A | AC-1 row 1.5 |
|
||
| FT-P-05 | `GET /vehicles?name=BR&isDefault=true` | `200`; body length == 1; `body[0].Name == "BR-01"` | exact | N/A | AC-1 row 1.6 |
|
||
| FT-N-01 | `GET /vehicles?name=br` (case mismatch) | `200`; body length == 0 | exact | N/A | AC-1 row 1.7 |
|
||
| FT-N-02 | `GET /vehicles/{random uuid}` | `404`; envelope `{ statusCode:404, message }` | exact + schema | N/A | AC-1 row 1.8 |
|
||
| FT-N-03 | `DELETE /vehicles/{id}` against vehicle referenced by ≥1 mission | `409`; row not deleted | exact + db_query | N/A | AC-1 row 1.9 |
|
||
| FT-P-06 | `DELETE /vehicles/{id}` against vehicle with 0 missions | `204`; row deleted | exact + db_query | N/A | AC-1 row 1.10 |
|
||
| FT-P-07 | `POST /missions` body per `data_parameters.md` § 2.2 | `201`; `CreatedDate ± 5s` of `now` | exact + numeric_tolerance | ±5s | AC-2 row 2.1 |
|
||
| FT-N-04 | `POST /missions {VehicleId:<random>}` | `400` (today, divergent from spec's 404) | exact | N/A | AC-2 row 2.2 |
|
||
| FT-P-08 | `GET /missions` against `seed_25_missions` | `200`; `PaginatedResponse<Mission>`; Page=1, PageSize=20, TotalCount=25, Items.length=20 | exact + schema | N/A | AC-2 row 2.3 |
|
||
| FT-P-09 | `GET /missions?page=2&pageSize=20` | `Page=2, Items.length=5` | exact | N/A | AC-2 row 2.4 |
|
||
| FT-P-10 | `GET /missions?fromDate=...&toDate=...` | `TotalCount=3` against the 5-row seed | exact | N/A | AC-2 row 2.5 |
|
||
| FT-P-11 | `PUT /missions/{id} {Name, VehicleId:null}` | `200`; Name updated, VehicleId preserved | exact | N/A | AC-2 row 2.7 |
|
||
| FT-N-05 | `GET /missions/{random}` | `404` | exact | N/A | AC-2 row 2.6 |
|
||
| FT-P-12 | `DELETE /missions/{id}` against `fixture_cascade_F3` | `204`; per-table counts == 0 per `cascade_F3_walk.json` | exact + db_query + file_reference | N/A | AC-3 row 3.1 |
|
||
| FT-N-06 | `DELETE /missions/{random}` | `404`; no DELETE issued against dependency tables (instrument SQL log) | exact + log_assertion | N/A | AC-3 row 3.2 |
|
||
| FT-P-13 | `GET /missions/{id}/waypoints` against `seed_5_waypoints_unordered` | `200`; ordered `OrderNum [1..5]` ASC | exact | N/A | AC-4 row 4.2 |
|
||
| FT-N-07 | `GET /missions/{random}/waypoints` | `404` | exact | N/A | AC-4 row 4.1 |
|
||
| FT-P-14 | `POST /missions/{id}/waypoints` body per `data_parameters.md` § 2.3 with non-null GeoPoint | `201`; `Lat,Lon` echoed; `Mgrs == null` (today, divergent — see §2.3 note) | exact | N/A | AC-4 row 4.3 |
|
||
| FT-P-15 | `PUT /missions/{id}/waypoints/{wpId}` body resetting Height to 0 | `200`; `Height==0` (full overwrite) | exact | N/A | AC-4 row 4.4 |
|
||
| FT-P-18 | `DELETE /missions/{id}/waypoints/{wpId}` against `fixture_cascade_F4` | `204`; only target waypoint's chain deleted; sibling chain intact | exact + db_query + file_reference | N/A | AC-4 row 4.5 |
|
||
| FT-P-16 | `GET /health` no auth | `200 { "status": "healthy" }` | exact | N/A | AC-7 row 7.1 |
|
||
| FT-P-17 | `GET /health` with PG stopped | `200 { "status": "healthy" }` (no DB ping) | exact | N/A | AC-7 row 7.2 |
|
||
|
||
NFT-* mappings (perf, resilience, security, resource-limit) are inline in the respective test files.
|
||
|
||
## External Dependency Mocks
|
||
|
||
| External Service | Mock/Stub | How Provided | Behavior |
|
||
|-----------------|-----------|-------------|----------|
|
||
| `admin` (JWT issuer) | In-process token mint | `System.IdentityModel.Tokens.Jwt` in the consumer using `JWT_SECRET=test-secret-32-chars-min!!!!!!!!!`, HS256 | Mints valid / expired / wrong-secret / claim-missing / claim-typo tokens on demand for AC-5 + AC-9 scenarios |
|
||
| `annotations` table owner | DB-only stub | Side-channel `CREATE TABLE annotations (id text PRIMARY KEY, media_id text)` then `INSERT` | Provides rows the cascade walk reads + deletes; no service running |
|
||
| `detection` table owner | DB-only stub | Side-channel `CREATE TABLE detection (id uuid PRIMARY KEY, annotation_id text)` + INSERT | Same as above |
|
||
| `media` table owner | DB-only stub | Side-channel `CREATE TABLE media (id text PRIMARY KEY, waypoint_id uuid)` + INSERT | Same |
|
||
| `autopilot` writer of `map_objects` | DB-only stub + race injector | Side-channel; for AC-3.4 race, a parallel goroutine-equivalent inserts a `map_objects` row immediately after the service's first `SELECT` (instrumented via test-only proxy) | One scenario only |
|
||
| `flight-gate`, Watchtower, suite reverse proxy, suite UI | NOT mocked | n/a | Out of scope for service-level e2e |
|
||
|
||
## Data Validation Rules
|
||
|
||
| Data Type | Validation today (per AC-* notes) | Invalid Examples | Expected System Behavior |
|
||
|-----------|-----------------------------------|-----------------|------------------------|
|
||
| Vehicle.Name | NONE (per data_parameters.md § 2.1 note) | `""` (empty) | accepted; row created; tests assert this is the current state, not the desirable one (carry-forward) |
|
||
| Vehicle.BatteryCapacity | NONE | `-1` | accepted; carry-forward |
|
||
| Vehicle.Type | NONE (any int accepted) | `99` | accepted; carry-forward |
|
||
| Mission.Page / PageSize | NONE | `-1`, `999999` | accepted by binding; carry-forward |
|
||
| Waypoint.GeoPoint | NONE; all-null accepted | `{Lat:null, Lon:null, Mgrs:null}` | accepted (`OrderNum + Height` still required-by-shape) |
|
||
| JWT lifetime | `ValidateLifetime=true` with 1-min skew | `exp = now-2min` | `401` |
|
||
| JWT signature | HS256 + shared secret | wrong secret / tampered payload | `401` |
|
||
| JWT claim `permissions` | exact string match `"FL"` | `"fl"`, `"ADMIN"`, missing | `403` |
|
||
| `Authorization` header | required on all `/vehicles/*`, `/missions/*` | absent | `401` |
|
||
| `DATABASE_URL` shape | `postgresql://...` URL OR raw Npgsql connection string | unparseable | process exits with error before HTTP server binds |
|