Files
satellite-provider/_docs/02_document/modules/tests_integration.md
T
Oleksandr Bezdieniezhnykh f979e18811 [AZ-494] Enable JWT iss/aud validation with fail-fast startup
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>
2026-05-12 02:28:48 +03:00

90 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.