mirror of
https://github.com/azaion/missions.git
synced 2026-06-22 04:11:07 +00:00
chore: update configuration and Docker setup for JWT and test results
ci/woodpecker/push/build-arm Pipeline was successful
ci/woodpecker/push/build-arm Pipeline was successful
Enhanced the .gitignore to exclude test results and updated the Dockerfile to include a new entrypoint script for improved container initialization. Refactored JWT configuration to support additional parameters for automatic refresh intervals, ensuring better control over token management. Updated the ConfigurationResolver to enforce required environment variables without hardcoded fallbacks, enhancing security and flexibility.
This commit is contained in:
@@ -20,19 +20,19 @@ The side-channel DB access is allowed because the AC catalogue (AC-1.2, AC-1.4,
|
||||
|---------|--------------|---------|-------|
|
||||
| `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` | — |
|
||||
| `jwks-mock` | build context `tests/Azaion.Missions.JwksMock/`; image tag `azaion/jwks-mock:test` | **In-process stand-in for the `admin` service's JWKS endpoint.** Holds a fixed ECDSA P-256 keypair (in-memory), serves the public key as JWKS at `https://jwks-mock:8443/.well-known/jwks.json` (HTTPS-only, self-signed CA mounted into `missions` and `e2e-consumer`), and signs tokens for the consumer via `POST /sign`. Supports `POST /rotate-key` for NFT-RES-07 (JWKS rotation). The mock's `Cache-Control: max-age` is set to 60s in tests (vs admin's 3600s) so rotation completes within the 15-min CI gate. | — (internal only) |
|
||||
| `e2e-consumer` | build context `tests/Azaion.Missions.E2E.Tests/`; runs `dotnet test` | xUnit test runner; produces `report.csv`. Fetches signed test tokens from `jwks-mock` instead of minting locally — the private key never leaves `jwks-mock`, eliminating the class of bugs where the consumer signs with a key that doesn't match the published JWKS. | — |
|
||||
| `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.
|
||||
The only external services not running are the sibling backend services (`annotations`, `detection`, `autopilot`, `flight-gate`, Watchtower, suite reverse proxy) — their tables (`media`, `annotations`, `detection`, `map_objects`) are seeded directly by the side-channel for cascade tests; the services themselves are out of scope for service-level e2e.
|
||||
|
||||
The `jwks-mock` service replaces the pre-2026-05-14 "consumer mints HS256 tokens with a shared secret" pattern. The current code path (per `Auth/JwtExtensions.cs`) is ECDSA-SHA256 + JWKS — there is no shared secret to mint with anymore. See `test-data.md` § External Dependency Mocks for the mock's contract.
|
||||
|
||||
### Networks
|
||||
|
||||
| Network | Services | Purpose |
|
||||
|---------|----------|---------|
|
||||
| `e2e-net` | `missions`, `postgres-test`, `e2e-consumer` | Isolated bridge network; no host network access |
|
||||
| `e2e-net` | `missions`, `postgres-test`, `jwks-mock`, `e2e-consumer` | Isolated bridge network; no host network access |
|
||||
|
||||
### Volumes
|
||||
|
||||
@@ -43,8 +43,9 @@ No external mock services are required:
|
||||
|
||||
### docker-compose structure
|
||||
|
||||
The canonical compose file is `docker-compose.test.yml` at the repo root. Below is the abbreviated structural outline — see the actual file for the full healthchecks, depends_on, and volume mounts.
|
||||
|
||||
```yaml
|
||||
# Outline only — not runnable code (the actual scripts/run-tests.sh wires this up)
|
||||
services:
|
||||
postgres-test:
|
||||
image: postgres:16-alpine
|
||||
@@ -52,39 +53,51 @@ services:
|
||||
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
|
||||
# healthcheck: pg_isready
|
||||
|
||||
jwks-mock:
|
||||
build: { context: tests/Azaion.Missions.JwksMock }
|
||||
environment:
|
||||
JWT_ISSUER: https://admin-test.azaion.local
|
||||
JWT_AUDIENCE: azaion-edge
|
||||
OLD_KEY_GRACE_SECONDS: 5
|
||||
# healthcheck: GET /.well-known/jwks.json over HTTPS (--no-check-certificate)
|
||||
|
||||
missions:
|
||||
build:
|
||||
context: .
|
||||
build: { context: . }
|
||||
environment:
|
||||
DATABASE_URL: postgresql://postgres:postgres-test@postgres-test:5432/azaion
|
||||
JWT_SECRET: test-secret-32-chars-min!!!!!!!!!
|
||||
JWT_ISSUER: https://admin-test.azaion.local
|
||||
JWT_AUDIENCE: azaion-edge
|
||||
JWT_JWKS_URL: https://jwks-mock:8443/.well-known/jwks.json
|
||||
ASPNETCORE_ENVIRONMENT: Test # NOT Production -- CORS falls back to permissive with a warning log line
|
||||
volumes:
|
||||
- ./tests/jwks-mock-ca.crt:/usr/local/share/ca-certificates/jwks-mock-ca.crt:ro
|
||||
depends_on:
|
||||
postgres-test:
|
||||
condition: service_healthy
|
||||
postgres-test: { condition: service_healthy }
|
||||
jwks-mock: { condition: service_healthy }
|
||||
|
||||
e2e-consumer:
|
||||
build:
|
||||
context: tests/Azaion.Missions.E2E.Tests
|
||||
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
|
||||
JWKS_MOCK_SIGN_URL: https://jwks-mock:8443/sign
|
||||
JWT_ISSUER: https://admin-test.azaion.local
|
||||
JWT_AUDIENCE: azaion-edge
|
||||
volumes:
|
||||
- ./e2e-results:/app/results
|
||||
- ./test-results:/app/results
|
||||
- ./tests/jwks-mock-ca.crt:/usr/local/share/ca-certificates/jwks-mock-ca.crt:ro
|
||||
depends_on:
|
||||
missions: { condition: service_healthy }
|
||||
jwks-mock: { condition: service_healthy }
|
||||
```
|
||||
|
||||
**Production-gate (E9) variant**: for the CORS production-gate lock test (E9), the test runner spawns `missions` with `ASPNETCORE_ENVIRONMENT=Production` and an empty `CorsConfig:AllowedOrigins` and asserts startup THROWS `InvalidOperationException`. This variant runs OUTSIDE the main compose stack via `docker run` to avoid disturbing the rest of the suite.
|
||||
|
||||
## 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).
|
||||
**Tech stack**: xUnit 2.x + plain `HttpClient` against the dockerized `missions` service. Bogus 35.x for synthetic data. JWT acquisition via HTTPS `POST jwks-mock:8443/sign` (no in-process JWT library on the consumer side — the consumer treats tokens as opaque bearer strings). 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`.
|
||||
|
||||
@@ -92,18 +105,21 @@ services:
|
||||
|
||||
| Interface | Protocol | Endpoint | Authentication |
|
||||
|-----------|----------|----------|----------------|
|
||||
| Vehicle API | HTTP/1.1 JSON | `http://missions:8080/vehicles[?name=&isDefault=]` and `/vehicles/{id}[/setDefault]` | `Authorization: Bearer <HS256, permissions=FL>` |
|
||||
| Vehicle API | HTTP/1.1 JSON | `http://missions:8080/vehicles[?name=&isDefault=]` and `/vehicles/{id}[/setDefault]` | `Authorization: Bearer <ECDSA-SHA256, iss=$JWT_ISSUER, aud=$JWT_AUDIENCE, permissions=FL>` |
|
||||
| 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` |
|
||||
| JWKS mock sign endpoint | HTTPS/1.1 JSON | `https://jwks-mock:8443/sign` body `{ "iss":..., "aud":..., "exp":..., "permissions":..., ... }` returns signed JWT | none (test-network internal only) |
|
||||
| JWKS mock JWKS endpoint | HTTPS/1.1 JSON | `https://jwks-mock:8443/.well-known/jwks.json` | none (consumed by `missions` itself, not by the test consumer) |
|
||||
| JWKS mock rotate-key endpoint | HTTPS/1.1 JSON | `POST https://jwks-mock:8443/rotate-key` body `{}` returns `{ "newKid": "..." }` and starts the `OldKeyGraceSeconds` window | none |
|
||||
|
||||
### 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.
|
||||
- No JWT signing key in the consumer process — the consumer requests signed tokens from `jwks-mock` via HTTPS `POST /sign`. The ECDSA private key never leaves the mock container. This guarantees the test setup cannot drift away from "consumer-signed token matches missions-cached JWKS public key".
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
@@ -125,4 +141,50 @@ Categories: `BLACKBOX`, `PERF`, `RES`, `SEC`, `RES_LIM`. `Traces` is a comma-sep
|
||||
|
||||
## 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.
|
||||
> Filled by autodev `/test-spec` Hardware Assessment phase (2026-05-14).
|
||||
|
||||
### Decision: Docker execution
|
||||
|
||||
The project is **NOT hardware-dependent**. Test execution is fully containerised; no local-mode runner is needed.
|
||||
|
||||
### Hardware dependencies found: NONE
|
||||
|
||||
Documentation scan (`restrictions.md`, `solution.md`, `architecture.md`, `components/*/description.md`):
|
||||
- H1–H6 cover edge-device deployment shape (Jetson Orin / OrangePI / operator-PC, multi-arch ARM64+AMD64, vertical scale only) but none of those concerns require hardware code paths inside the test runner — the suite-level CI matrix builds for both arches separately (per O4 + H2).
|
||||
- No GPU / model inference / sensor / camera / GPIO / V4L2 mention in any docs.
|
||||
- `solution.md` § 2.1 explicitly classifies every component as standard ASP.NET Core + linq2db over Postgres — no hardware adapter.
|
||||
|
||||
Code scan (`*.csproj`, `Dockerfile`, all `.cs` source):
|
||||
- `Azaion.Flights.csproj` (post-B5: `Azaion.Missions.csproj`) packages: `linq2db 6.2.0`, `Npgsql 10.0.2`, `Microsoft.AspNetCore.Authentication.JwtBearer 10.0.5`, `Swashbuckle.AspNetCore 10.1.5`. None is hardware-specific.
|
||||
- `Dockerfile`: stock `mcr.microsoft.com/dotnet/sdk:10.0` build stage + `mcr.microsoft.com/dotnet/aspnet:10.0` runtime stage. Multi-arch via `--platform=$BUILDPLATFORM` + `dotnet publish --os linux --arch $arch`. No `runtime: nvidia`, no GPU device mounts.
|
||||
- No `RuntimeInformation.IsOSPlatform`, no `coreml` / `cuda` / `gpio` / `v4l2` / `opencl` / `vulkan` / `tpu` / `fpga` references in any production source file (verified via grep — matches were only in skill templates and docs, not in `Controllers/`, `Services/`, `Database/`, `Auth/`, `Middleware/`, `Program.cs`).
|
||||
|
||||
Multi-arch matters for the **production deploy** (per H2), but it does NOT affect the test runner: tests exercise the API black-box, identical on both architectures. The suite CI matrix (`.woodpecker/build-arm.yml` + the future `.woodpecker/build-amd.yml`) tests each arch in its own container.
|
||||
|
||||
### Execution instructions — Docker mode (the only mode)
|
||||
|
||||
**Prerequisites** on the test host:
|
||||
- Docker Engine ≥ 24.0 with Docker Compose v2 plugin.
|
||||
- 2.5 GB free RAM (1 GB for `missions`, 256 MB for `postgres-test`, 256 MB for `jwks-mock`, 512 MB for `e2e-consumer`, 512 MB headroom).
|
||||
- 2 CPU cores recommended for the test wall-clock to fit under the 15-minute CI gate.
|
||||
- Free TCP ports `5002` (host → `missions:8080`) and `5433` (host → `postgres-test:5432`) — used only when running outside compose. The `jwks-mock` HTTPS port (`8443` inside the container) is NOT mapped to the host; only `missions` and `e2e-consumer` reach it via the internal `e2e-net` network.
|
||||
|
||||
**Run command** (from repo root):
|
||||
|
||||
```bash
|
||||
./scripts/run-tests.sh
|
||||
```
|
||||
|
||||
The runner script (Phase 4) wires up `docker compose up`, waits for `missions` health, executes `dotnet test` inside `e2e-consumer`, collects `report.csv`, and tears down the compose stack. See `scripts/run-tests.sh` for the canonical command sequence.
|
||||
|
||||
**Performance tests**:
|
||||
|
||||
```bash
|
||||
./scripts/run-performance-tests.sh
|
||||
```
|
||||
|
||||
Same compose stack, but with the perf seed (1000 missions for NFT-PERF-04, 100 minimal missions for NFT-PERF-01) and the `[Trait("Category","Perf")]` filter. See `scripts/run-performance-tests.sh` (Phase 4).
|
||||
|
||||
### Resource ceiling
|
||||
|
||||
Total RAM: ≤ 2.5 GB for `missions + postgres-test + jwks-mock + e2e-consumer` together. Test wall-clock budget: ≤ 15 minutes (CI gate). Storage: ephemeral (the `pg-test-data` tmpfs is recreated per scenario class via `docker compose down -v` for the bootstrap-sensitive scenarios — NFT-RES-03, NFT-RES-04, NFT-RES-05, NFT-RES-06, NFT-RES-07).
|
||||
|
||||
Reference in New Issue
Block a user