# Test Environment > **Status**: produced by autodev `/test-spec` Phase 2 (2026-05-14). > **Naming**: post-rename target — `missions` service, `Azaion.Missions.*` namespace, `/vehicles` + `/missions` + `/missions/{id}/waypoints` routes. Until B5–B8 land, the `missions` service image is built from the existing `Azaion.Flights.csproj` source — tests will be RED until the rename converges. This is the autodev-aligned path: Step 8 (Refactor) closes the gap. > **Hardware Assessment** section is filled by `phases/hardware-assessment.md` between Phase 3 and Phase 4. ## Overview **System under test**: the `missions` .NET 10 REST service exposed on `http://missions:8080` inside the test network. Public surface = the HTTP endpoints documented in `_docs/00_problem/input_data/data_parameters.md` § 7. **Consumer app purpose**: a standalone xUnit test project (`tests/Azaion.Missions.E2E.Tests.csproj`) that exercises the running service through HTTP only. No `Azaion.Missions.*` types are referenced; the consumer never opens a `DataConnection` to the system-under-test's runtime DB except via a side-channel `postgres-test` connection used to (a) seed fixtures and (b) assert DB side-effects (cascade row counts, default-vehicle invariants). The side-channel DB access is allowed because the AC catalogue (AC-1.2, AC-1.4, AC-3.1, AC-3.3, AC-10.2) explicitly defines DB state as the verifiable observable. It is NEVER used to mutate state under-test that the API would normally own — only to (1) seed fixtures and (2) assert. ## Docker Environment ### Services | Service | Image / Build | Purpose | Ports (host:container) | |---------|--------------|---------|-------| | `missions` | build context `./` (`Dockerfile`); image tag `azaion/missions:test` | System under test | `5002:8080` | | `postgres-test` | `postgres:16-alpine` | Owned PostgreSQL for test isolation. Started fresh per test class via Testcontainers OR via `docker compose down -v && docker compose up -d` between scenarios that mutate startup-sensitive state (AC-6.5 legacy drop, AC-6.6 idempotency) | `5433:5432` | | `e2e-consumer` | build context `tests/Azaion.Missions.E2E.Tests/`; runs `dotnet test` | xUnit test runner; produces `report.csv` | — | | `pg-side` (optional) | reused `postgres-test` connection on a side port | Side-channel DB connection for fixture seeding + post-call assertions | shares `postgres-test` | No external mock services are required: - `admin` (JWT issuer): the test runner mints HS256 tokens itself using a known `JWT_SECRET=test-secret-32-chars-min!!!!!!!!!`. - `annotations`, `detection`, `autopilot`: their tables (`media`, `annotations`, `detection`, `map_objects`) are seeded directly by the side-channel for cascade tests; the services themselves are not running. - `flight-gate`, Watchtower, suite reverse proxy: not required for service-level e2e. ### Networks | Network | Services | Purpose | |---------|----------|---------| | `e2e-net` | `missions`, `postgres-test`, `e2e-consumer` | Isolated bridge network; no host network access | ### Volumes | Volume | Mounted to | Purpose | |--------|-----------|---------| | `pg-test-data` | `postgres-test:/var/lib/postgresql/data` | Ephemeral; recreated per scenario class (`docker compose down -v` between class boundaries when the test asserts startup behavior) | | `e2e-results` | `e2e-consumer:/app/results` and host `./e2e-results/` | Output of `report.csv` | ### docker-compose structure ```yaml # Outline only — not runnable code (the actual scripts/run-tests.sh wires this up) services: postgres-test: image: postgres:16-alpine environment: POSTGRES_DB: azaion POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres-test healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres -d azaion"] interval: 1s timeout: 1s retries: 30 missions: build: context: . environment: DATABASE_URL: postgresql://postgres:postgres-test@postgres-test:5432/azaion JWT_SECRET: test-secret-32-chars-min!!!!!!!!! depends_on: postgres-test: condition: service_healthy e2e-consumer: build: context: tests/Azaion.Missions.E2E.Tests environment: MISSIONS_BASE_URL: http://missions:8080 DB_SIDE_CHANNEL: Host=postgres-test;Port=5432;Database=azaion;Username=postgres;Password=postgres-test JWT_SECRET: test-secret-32-chars-min!!!!!!!!! depends_on: missions: condition: service_started volumes: - ./e2e-results:/app/results ``` ## Consumer Application **Tech stack**: xUnit 2.x + `Microsoft.AspNetCore.Mvc.Testing` (HttpClient via `IClassFixture`) OR plain `HttpClient` against the dockerized service. Bogus 35.x for synthetic data. JWT minting via `System.IdentityModel.Tokens.Jwt`. PostgreSQL side-channel via Npgsql (NOT linq2db — keep the consumer free of system-under-test runtime libs). **Entry point**: `dotnet test tests/Azaion.Missions.E2E.Tests/Azaion.Missions.E2E.Tests.csproj --logger "trx;LogFileName=results.trx"` followed by a small post-processor that converts trx → `report.csv`. ### Communication with system under test | Interface | Protocol | Endpoint | Authentication | |-----------|----------|----------|----------------| | Vehicle API | HTTP/1.1 JSON | `http://missions:8080/vehicles[?name=&isDefault=]` and `/vehicles/{id}[/setDefault]` | `Authorization: Bearer ` | | Mission API | HTTP/1.1 JSON | `http://missions:8080/missions[?name=&fromDate=&toDate=&page=&pageSize=]` | same | | Waypoint API | HTTP/1.1 JSON | `http://missions:8080/missions/{id}/waypoints[/{wpId}]` | same | | Health | HTTP/1.1 JSON | `http://missions:8080/health` | anonymous | | DB side-channel (assertions only) | TCP/Postgres wire | `postgres-test:5432` | `postgres:postgres-test` | ### What the consumer does NOT have access to - No `using Azaion.Missions.*;` — the consumer is a separate csproj with no project reference to the system under test. - No `AppDataConnection` instantiation; the side-channel uses raw Npgsql `NpgsqlCommand` only. - No file-system overlap; `e2e-consumer` is a separate container. - No environment variable shared in process; the system-under-test's `JWT_SECRET` is supplied through compose env, the consumer mints with the same value via its own env. ## CI/CD Integration **When to run**: on every push to `dev` (Woodpecker pipeline `.woodpecker/test-arm.yml` and `.woodpecker/test-amd.yml` after the existing `build-arm.yml` job). Currently the repo has only `build-arm.yml` (per O4); the test runner pipeline is a follow-up artifact produced by Step 6 (Implement Tests) — see `scripts/run-tests.sh` (Phase 4). **Pipeline stage**: post-build, pre-push (the test runner pulls the just-built `azaion/missions:test` tag). **Gate behavior**: blocking on `dev` branch. Per O4, today's pipeline has no test stage; this gate is added by Step 6 implementation. **Timeout**: max 15 minutes total wall-clock. Cascade-delete fixtures and the bootstrap-failure scenarios (AC-6.6, AC-6.7) dominate. ## Reporting **Format**: CSV **Columns**: `TestId, TestName, Category, Traces, ExecutionTimeMs, Result, ErrorMessage` **Output path**: `./e2e-results/report.csv` Categories: `BLACKBOX`, `PERF`, `RES`, `SEC`, `RES_LIM`. `Traces` is a comma-separated list of AC and restriction IDs from `traceability-matrix.md`. ## Hardware Assessment To be filled by `phases/hardware-assessment.md` between Phase 3 and Phase 4. Today's expected outcome: no GPU, no specialised hardware, no model inference — this is a CRUD service. Test execution requires only a Postgres-capable container and the .NET 10 SDK image. AMD64 + ARM64 both supported (matches H2). Resource ceiling: 2 GB RAM total for `missions + postgres-test + e2e-consumer` is sufficient.