Wrap up cycle 5 verification + documentation: - Steps 10/11 wrap-up reports (implementation_completeness + implementation_report) for the AZ-503-foundation + AZ-504 batch. - Step 12 test-spec sync: AZ-503-foundation/AZ-504 ACs appended; AZ-505 deferred ACs recorded. - Step 13 update-docs: architecture, data-model, glossary, module- layout, uav-tile-upload contract (v1.1.0), DataAccess + Services + Tests module docs synced; new common_uuidv5.md module doc. - Step 14 security audit: PASS_WITH_WARNINGS; 0 new Critical/High; 2 new Low informational (F1 flightId provenance, F2 pgcrypto deploy gap). - Step 15 performance test: PASS_WITH_INFRA_WARNINGS; PT-08 passed twice (AZ-504 fix verified); PT-01/02 failed due to recurring local Docker/colima DNS cold-start (not an app regression). Cycle-3 perf-harness leftover stays OPEN with replay #5 documented. - Autodev state moved to Step 16 (Deploy). Co-authored-by: Cursor <cursoragent@cursor.com>
11 KiB
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 endpointRegionTests— region request → polling → completion flowBasicRouteTests— route creation with intermediate pointsComplexRouteTests— routes with geofencingExtendedRouteTests— routes withrequestMaps: trueand tile ZIP creationMigrationTests— direct PostgreSQL schema/index validation (no HTTP). AZ-484 cycle added:BackfillUpdateAssignsGoogleMapsAndCapturedAt_AZ484_AC4,MultiSourceInsertCoexistsUnderNewIndex_AZ484_AC1,MostRecentAcrossSourcesSelection_AZ484_AC2,SameSourceUpsertReplacesPreviousRow_AZ484_AC3(latter four use temp tables to keep production data untouched). AZ-503 (cycle 5) added:Az503ColumnsExistAndLocationHashIsNotNull(asserts the 4 new columns +location_hash NOT NULL),Az503NewUniqueIndexCoversIntegerKeyAndFlightId(assertsidx_tiles_unique_identitycolumns +COALESCE(flight_id, ...)predicate),Az503LocationHashBackfillIsDeterministic(computespg_temp.uuidv5("18/12345/23456")and compares byte-for-byte against the C#Uuidv5.Createoutput on 3 sampled live rows); the AZ-484 supersession test was renamed toAz503MigrationSupersedesAz484UniqueIndexand assertsidx_tiles_unique_location_sourceno longer exists.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 sharedSatelliteProvider.TestSupport.JwtTokenFactory(consumed viaProjectReference); runner-specific concerns (JwtTestHelpers.ResolveSecretOrThrow/ResolveIssuerOrThrow/ResolveAudienceOrThrow,MintAuthenticated/MintExpiredconvenience wrappers that auto-fill iss+aud from env,AttachDefaultAuthorization,DefaultSubject = "integration-tests") remain in this project. The test runner setsJWT_SECRET+JWT_ISSUER+JWT_AUDIENCEon 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; AZ-503 cycle 5 added 2 more tests) —HappyPathSingleItem_PersistsRow,MixedBatch_ReturnsPerItemResults,MultiSourceCoexistence_AZ484_Cycle2,SameSourceUpsert_AZ484_Cycle2,NoToken_Returns401,ValidTokenWithoutGpsPermission_Returns403,OversizedBatch_Returns400, plus AZ-503:MultiFlightUavRowsCoexist_AZ503_AC3(two flights at the same cell → two rows, onelocation_hash, twofile_paths under./tiles/uav/{flight_id}/...) andFloatRoundingDoesNotBreakIdempotence_AZ503_AC4(two uploads with float-distinctlatituderecomputed fromTileToWorldPoscollapse to a single row because the conflict key is integer-only). The AZ-503 migration madelocation_hash NOT NULL, so the cycle-2MultiSourceCoexistence_AZ484_Cycle2seeder was updated to computelocation_hashviaUuidv5.Create(canonical name"{zoom}/0/0") before the raw SQLINSERT— this required adding aProjectReferencefromSatelliteProvider.IntegrationTeststoSatelliteProvider.Common. The wall-clock-seeded_coordinateCounteris 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 unique index does not collide.StubAndErrorContractTests(existing) — updated in cycle 2 to drop the legacyStubUpload_Returns501expectation since AZ-488 implemented the endpoint.
Supporting Classes
Models.cs— HTTP response DTOs for deserializationRouteTestHelpers.cs— shared utilities (wait-for-completion polling, geofence polygon builders, test data)Program.cs— test runner entry point (handles--smoke/--fullmode selection,--keep-stateopt-out flag, default-token issuance viaJwtTokenFactory, the AZ-493 DB-reset hook, and the AZ-492--mint-only/--gen-uav-fixtureperf-bootstrap subcommands that short-circuit before any HTTP / DB setup)JwtTestHelpers.cs— runner-side JWT concerns:ResolveSecretOrThrowreadsJWT_SECRETenv var with size validationResolveIssuerOrThrow/ResolveAudienceOrThrow(AZ-494) readJWT_ISSUER/JWT_AUDIENCEwith fail-fast contractMintAuthenticated(secret, …)(AZ-494) auto-fills iss/aud from env and delegates toJwtTokenFactory.Create; acceptsoverrideIssuer/overrideAudiencefor negative-AC scenarios (WrongIssuer_Returns401/WrongAudience_Returns401)MintExpired(secret, …)(AZ-494) mirrorsMintAuthenticatedfor the expired-token fixtureAttachDefaultAuthorizationputs a Bearer token on the sharedHttpClientDefaultSubject = "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 singleEnsureCleanStateAsync()method that truncates the integration-test target tables in FK-safe order. Guarded viaSatelliteProvider.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 subjectperf-testsand apermissions: GPSclaim via the canonicalSatelliteProvider.TestSupport.JwtTokenFactory.Create;GenerateUavFixture(args)writes a 256×256 random-noise JPEG viaSixLabors.ImageSharpto the path passed on the CLI. Invoked fromscripts/run-performance-tests.shviadotnet <SatelliteProvider.IntegrationTests.dll> --mint-onlyand--gen-uav-fixture <path>.
Internal Logic
- Makes HTTP calls to the API at
API_URLenvironment 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
ProjectReferencetoSatelliteProvider.TestSupport(added by AZ-491; providesJwtTokenFactory. Added by AZ-493; providesIntegrationTestResetGuard).- 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
MigrationTestsschema assertions. - NuGet:
Npgsql9.0.2 (Postgres client for DB-reset + MigrationTests),SixLabors.ImageSharp3.1.11 (UAV fixture image generation). - ProjectReferences:
SatelliteProvider.Api(running service for the integration runner),SatelliteProvider.TestSupport(canonicalJwtTokenFactory+IntegrationTestResetGuard),SatelliteProvider.Common(added by AZ-503 so theMultiSourceCoexistence_AZ484_Cycle2seeder can computelocation_hashviaUuidv5.Createinstead of duplicating the UUIDv5 algorithm in T-SQL fixtures).
Consumers
docker-compose.tests.yml— runs as a container that depends on the API service
Configuration
API_URLenvironment variable (set in docker-compose.tests.yml tohttp://api:8080)INTEGRATION_TESTS_MODE—smokeorfull(defaultfull). DrivesTestRunMode.Smoke.INTEGRATION_KEEP_STATE— set to1ortrue(or pass--keep-statetoProgram.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— expectedissclaim, must match the API container (AZ-494). Fail-fast at startup if unset.JWT_AUDIENCE— expectedaudclaim, 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:
ASPNETCORE_ENVIRONMENTMUST equalTesting(case-insensitive). Set bydocker-compose.tests.yml; absent in production / dev.DB_CONNECTION_STRINGHost MUST be one ofpostgres,localhost,127.0.0.1. Set bydocker-compose.tests.ymland 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.