mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-21 19:11:14 +00:00
f979e18811
Option B per user decision: production ships with empty Jwt.Issuer / Jwt.Audience in appsettings.json so the API process refuses to start unless JWT_ISSUER + JWT_AUDIENCE env vars are supplied. Development ships with grep-friendly DEV-ONLY- placeholders so local + docker flows keep working unchanged. AuthenticationServiceCollectionExtensions flips ValidateIssuer + ValidateAudience to true and wires ValidIssuer / ValidAudience via a new ResolveRequiredOrThrow helper that all three required values (secret, iss, aud) now share. JwtTokenFactory.Create + CreateExpired gain optional iss / aud parameters (default null) so existing call sites compile unchanged. JwtTestHelpers adds MintAuthenticated / MintExpired wrappers that resolve iss + aud from env, plus ResolveIssuerOrThrow / ResolveAudienceOrThrow. PerfBootstrap.MintToken + Program.cs JWT bootstrap migrated to the new surface so the perf harness and the integration runner both validate against the same contract. Adds 4 fail-fast unit tests (missing/empty issuer + audience), 2 negative integration scenarios (WrongIssuer_Returns401, WrongAudience_Returns401), and re-tags every existing integration mint site via MintAuthenticated. Compose, .env.example, run-tests.sh, run-performance-tests.sh all load + export JWT_ISSUER + JWT_AUDIENCE alongside JWT_SECRET. Resolves F-AUTH-2 (security_report.md + owasp_review.md). AC-7 (cross-repo suite/_docs/10_auth.md write) deferred — outside this workspace; tracked in deploy_cycle2.md R3 follow-up. Co-authored-by: Cursor <cursoragent@cursor.com>
90 lines
9.3 KiB
Markdown
90 lines
9.3 KiB
Markdown
# Module: Tests/SatelliteProvider.IntegrationTests
|
||
|
||
## Purpose
|
||
Console application that runs end-to-end integration tests against a live API instance. Designed to run in Docker alongside the API and PostgreSQL containers.
|
||
|
||
## Public Interface
|
||
|
||
### Test Classes
|
||
- `TileTests` — tile download via lat/lon endpoint
|
||
- `RegionTests` — region request → polling → completion flow
|
||
- `BasicRouteTests` — route creation with intermediate points
|
||
- `ComplexRouteTests` — routes with geofencing
|
||
- `ExtendedRouteTests` — routes with `requestMaps: true` and tile ZIP creation
|
||
- `MigrationTests` — direct PostgreSQL schema/index validation (no HTTP). AZ-484 cycle added: `NewUniqueConstraintIncludesSourceColumn_AZ484_AC1`, `BackfillUpdateAssignsGoogleMapsAndCapturedAt_AZ484_AC4`, `MultiSourceInsertCoexistsUnderNewIndex_AZ484_AC1`, `MostRecentAcrossSourcesSelection_AZ484_AC2`, `SameSourceUpsertReplacesPreviousRow_AZ484_AC3` (latter four use temp tables to keep production data untouched).
|
||
- `JwtIntegrationTests` (added by AZ-487 cycle 2; helpers consolidated by AZ-491 cycle 3; iss/aud scenarios added by AZ-494 cycle 3) — `AnonymousRequest_To_AnyEndpoint_Returns401`, `ExpiredToken_Returns401`, `InvalidSignature_Returns401`, `ValidToken_Returns200_OnHealthyEndpoint`, `WrongIssuer_Returns401` (AZ-494 AC-1), `WrongAudience_Returns401` (AZ-494 AC-2), `SwaggerDocument_AdvertisesBearerSecurityScheme`. HS256 token minting lives in the shared `SatelliteProvider.TestSupport.JwtTokenFactory` (consumed via `ProjectReference`); runner-specific concerns (`JwtTestHelpers.ResolveSecretOrThrow` / `ResolveIssuerOrThrow` / `ResolveAudienceOrThrow`, `MintAuthenticated` / `MintExpired` convenience wrappers that auto-fill iss+aud from env, `AttachDefaultAuthorization`, `DefaultSubject = "integration-tests"`) remain in this project. The test runner sets `JWT_SECRET` + `JWT_ISSUER` + `JWT_AUDIENCE` on the API container and attaches a Bearer token (with matching iss/aud) to every existing test's HTTP requests so the pre-cycle-2 suite continues to pass.
|
||
- `UavUploadTests` (added by AZ-488, cycle 2; coordinate-counter promoted to defense-in-depth by AZ-493 cycle 3) — `HappyPathSingleItem_PersistsRow`, `MixedBatch_ReturnsPerItemResults`, `MultiSourceCoexistence_AZ484_Cycle2`, `SameSourceUpsert_AZ484_Cycle2`, `NoToken_Returns401`, `ValidTokenWithoutGpsPermission_Returns403`, `OversizedBatch_Returns400`. The wall-clock-seeded `_coordinateCounter` is retained as a belt-and-suspenders safeguard alongside the AZ-493 startup DB-reset (below) — if a developer runs with `--keep-state`, or the DB-reset path is skipped for any reason, the wall-clock seed still spreads coordinates across runs so the per-source unique index does not collide.
|
||
- `StubAndErrorContractTests` (existing) — updated in cycle 2 to drop the legacy `StubUpload_Returns501` expectation since AZ-488 implemented the endpoint.
|
||
|
||
### Supporting Classes
|
||
- `Models.cs` — HTTP response DTOs for deserialization
|
||
- `RouteTestHelpers.cs` — shared utilities (wait-for-completion polling, geofence polygon builders, test data)
|
||
- `Program.cs` — test runner entry point (handles `--smoke` / `--full` mode selection, `--keep-state` opt-out flag, default-token issuance via `JwtTokenFactory`, the AZ-493 DB-reset hook, and the AZ-492 `--mint-only` / `--gen-uav-fixture` perf-bootstrap subcommands that short-circuit before any HTTP / DB setup)
|
||
- `JwtTestHelpers.cs` — runner-side JWT concerns:
|
||
- `ResolveSecretOrThrow` reads `JWT_SECRET` env var with size validation
|
||
- `ResolveIssuerOrThrow` / `ResolveAudienceOrThrow` (AZ-494) read `JWT_ISSUER` / `JWT_AUDIENCE` with fail-fast contract
|
||
- `MintAuthenticated(secret, …)` (AZ-494) auto-fills iss/aud from env and delegates to `JwtTokenFactory.Create`; accepts `overrideIssuer` / `overrideAudience` for negative-AC scenarios (`WrongIssuer_Returns401` / `WrongAudience_Returns401`)
|
||
- `MintExpired(secret, …)` (AZ-494) mirrors `MintAuthenticated` for the expired-token fixture
|
||
- `AttachDefaultAuthorization` puts a Bearer token on the shared `HttpClient`
|
||
- `DefaultSubject = "integration-tests"` is the canonical runner subject value
|
||
- Token *minting* lives in the shared `SatelliteProvider.TestSupport.JwtTokenFactory` (AZ-491) — runner-side concerns (env reads, HttpClient mutation, the iss/aud-aware mint wrapper) deliberately stay here.
|
||
- `IntegrationTestDatabaseReset.cs` (AZ-493) — instance class with a single `EnsureCleanStateAsync()` method that truncates the integration-test target tables in FK-safe order. Guarded via `SatelliteProvider.TestSupport.IntegrationTestResetGuard` (env + Host allowlist) so it cannot run against a non-test database.
|
||
- `PerfBootstrap.cs` (AZ-492) — static helpers for the perf harness bootstrap subcommands. `MintToken()` mints a 4-hour HS256 token with subject `perf-tests` and a `permissions: GPS` claim via the canonical `SatelliteProvider.TestSupport.JwtTokenFactory.Create`; `GenerateUavFixture(args)` writes a 256×256 random-noise JPEG via `SixLabors.ImageSharp` to the path passed on the CLI. Invoked from `scripts/run-performance-tests.sh` via `dotnet <SatelliteProvider.IntegrationTests.dll> --mint-only` and `--gen-uav-fixture <path>`.
|
||
|
||
## Internal Logic
|
||
- Makes HTTP calls to the API at `API_URL` environment variable (default: `http://api:8080`)
|
||
- Tests are methods called sequentially from `Program.cs` (not xUnit — plain console app)
|
||
- Poll-based waiting for async operations (region/route completion)
|
||
- Validates response structure, status transitions, file creation
|
||
|
||
## Dependencies
|
||
- `ProjectReference` to `SatelliteProvider.TestSupport` (added by AZ-491; provides `JwtTokenFactory`. Added by AZ-493; provides `IntegrationTestResetGuard`).
|
||
- Communicates with the API exclusively via HTTP for end-to-end tests; communicates with PostgreSQL directly only via the dedicated DB-reset hook + the existing `MigrationTests` schema assertions.
|
||
- NuGet: `Npgsql` 9.0.2 (Postgres client for DB-reset + MigrationTests), `SixLabors.ImageSharp` 3.1.11 (UAV fixture image generation).
|
||
|
||
## Consumers
|
||
- `docker-compose.tests.yml` — runs as a container that depends on the API service
|
||
|
||
## Configuration
|
||
- `API_URL` environment variable (set in docker-compose.tests.yml to `http://api:8080`)
|
||
- `INTEGRATION_TESTS_MODE` — `smoke` or `full` (default `full`). Drives `TestRunMode.Smoke`.
|
||
- `INTEGRATION_KEEP_STATE` — set to `1` or `true` (or pass `--keep-state` to `Program.cs` / `scripts/run-tests.sh`) to skip the AZ-493 DB-reset hook. Useful for debugging a failed run.
|
||
- `ASPNETCORE_ENVIRONMENT=Testing` — guard for the DB-reset hook. The reset refuses to run unless this is set (see Reliability § Test isolation below).
|
||
- `JWT_SECRET` — shared HMAC secret with the API container; must be ≥ 32 bytes (UTF-8).
|
||
- `JWT_ISSUER` — expected `iss` claim, must match the API container (AZ-494). Fail-fast at startup if unset.
|
||
- `JWT_AUDIENCE` — expected `aud` claim, must match the API container (AZ-494). Fail-fast at startup if unset.
|
||
- `DB_CONNECTION_STRING` — Npgsql connection string; the reset hook additionally requires the Host to be in the allowed-host list (`postgres`, `localhost`, `127.0.0.1`).
|
||
|
||
## Reliability
|
||
|
||
### Test isolation (AZ-493)
|
||
|
||
`Program.cs` runs `IntegrationTestDatabaseReset.EnsureCleanStateAsync()` at startup, before any test class executes. The hook truncates `route_regions`, `route_points`, `routes`, `regions`, `tiles` (in that FK-safe order, with `RESTART IDENTITY CASCADE`) so each run starts from a known empty state. The Postgres named volume in `docker-compose.yml` is intentionally persisted across `docker-compose down` cycles for fast iteration; the AZ-493 reset hook is what gives back per-run isolation in spite of that.
|
||
|
||
Two guards protect against accidental truncate against a non-test database:
|
||
|
||
1. `ASPNETCORE_ENVIRONMENT` MUST equal `Testing` (case-insensitive). Set by `docker-compose.tests.yml`; absent in production / dev.
|
||
2. `DB_CONNECTION_STRING` Host MUST be one of `postgres`, `localhost`, `127.0.0.1`. Set by `docker-compose.tests.yml` and developer machines; a remote-host connection string is rejected even with the env guard satisfied.
|
||
|
||
Both guards are pure-string checks in `SatelliteProvider.TestSupport.IntegrationTestResetGuard` — unit-tested in `SatelliteProvider.Tests/TestSupport/IntegrationTestResetGuardTests.cs`. Failure of either guard surfaces a clear `InvalidOperationException` and exits the runner with code 1.
|
||
|
||
To debug leftover state from a failed run, opt out of the reset:
|
||
|
||
- CLI: `./scripts/run-tests.sh --full --keep-state`
|
||
- Direct: `INTEGRATION_KEEP_STATE=1 docker compose ... up`
|
||
- In the runner Main: `dotnet run --project SatelliteProvider.IntegrationTests -- --keep-state`
|
||
|
||
### Adding new tables
|
||
|
||
If a new task adds a table that integration tests insert into AND that table participates in foreign-key relationships with `tiles` / `regions` / `routes`, update `IntegrationTestDatabaseReset.TruncateOrder` to include the new table in FK-safe order. The current order assumes the AZ-484 + AZ-488 schema; future migrations that introduce new FK chains need a corresponding order revision. The `CASCADE` clause is a safety net but is not a substitute for an explicit order — the order is the audit trail for "what does an integration-test runner see at startup".
|
||
|
||
## External Integrations
|
||
- HTTP to the SatelliteProvider API
|
||
- Reads output files from mounted `./ready/` and `./tiles/` volumes
|
||
|
||
## Security
|
||
None.
|
||
|
||
## Tests
|
||
This IS the integration test suite.
|