Files
satellite-provider/_docs/02_document/modules/tests_integration.md
T
Oleksandr Bezdieniezhnykh bc04ba7f99 [AZ-794] [AZ-795] [AZ-796] Cycle 7 Steps 12-15 sync (test-spec / docs / security / perf)
Step 12 (Test-Spec Sync): adds BT-27 for the AZ-796 9-rule
validation surface and 12 cycle-7 AC rows + Coverage Summary
update to traceability-matrix.md.

Step 13 (Update Docs): module-layout + module docs for the new
SatelliteProvider.Api/Validators namespace + GlobalExceptionHandler
+ updated TileInventory DTO; tests_unit + tests_integration
document the new InventoryRequestValidatorTests (16 unit tests
covering all 9 rules) + TileInventoryValidationTests (16
integration tests) + ProblemDetailsAssertions support;
glossary entries for Validation Problem Details / FluentValidation
/ Unmapped Member Handling; system-flows F8 (Tile Inventory Bulk
Lookup) expanded with deserializer + validator gates and a 13-row
Validation Surface table; data_parameters § Tile Inventory
documents the v2 input schema + constraints; ripple_log_cycle7
captures the doc-side ripple decisions.

Step 14 (Security Audit): 5-phase audit ran; verdict
PASS_WITH_WARNINGS (3 Low findings — D-AZ795-1 FluentValidation
12.0.0 -> 12.1.1 recommended bump, F-AZ795-1 JsonException.Message
leak in 400 detail, F-AZ795-2 BadHttpRequestException.Message leak).
No Critical / High; auth runs before validation (confirmed in
Program.cs); two NuGet additions (FluentValidation 12.0.0 +
.DependencyInjectionExtensions 12.0.0) both CVE-clean. Per-phase
reports plus consolidated security_report_cycle7.md.

Step 15 (Performance Test): docker compose stack used for perf
run, scripts/run-performance-tests.sh exited 0 with 8/8 scenarios
PASS (second consecutive clean exit-0); added PT-09 cycle-7 smoke
probe (v2 z/x/y schema, 2500-tile all-miss batch) measuring
min=27ms median=44ms p95=73ms max=86ms (13.7x under AZ-505 AC-4
1000ms budget). PT-07/08 improvements traced to the cycle-6 TLS
handshake-overhead identification, not application-side change.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 11:24:27 +03:00

14 KiB
Raw Blame History

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: 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 (asserts idx_tiles_unique_identity columns + COALESCE(flight_id, ...) predicate), Az503LocationHashBackfillIsDeterministic (computes pg_temp.uuidv5("18/12345/23456") and compares byte-for-byte against the C# Uuidv5.Create output on 3 sampled live rows); the AZ-484 supersession test was renamed to Az503MigrationSupersedesAz484UniqueIndex and asserts idx_tiles_unique_location_source no 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 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; 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, one location_hash, two file_paths under ./tiles/uav/{flight_id}/...) and FloatRoundingDoesNotBreakIdempotence_AZ503_AC4 (two uploads with float-distinct latitude recomputed from TileToWorldPos collapse to a single row because the conflict key is integer-only). The AZ-503 migration made location_hash NOT NULL, so the cycle-2 MultiSourceCoexistence_AZ484_Cycle2 seeder was updated to compute location_hash via Uuidv5.Create (canonical name "{zoom}/0/0") before the raw SQL INSERT — this required adding a ProjectReference from SatelliteProvider.IntegrationTests to SatelliteProvider.Common. 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 unique index does not collide.
  • StubAndErrorContractTests (existing) — updated in cycle 2 to drop the legacy StubUpload_Returns501 expectation since AZ-488 implemented the endpoint.
  • TileInventoryTests (added cycle 6 — AZ-505) — OrderingAndPresentAbsentShaping_AC1, LeafletReadReturnsMostRecentViaLocationHash_AC2, ValidationRejectsBothPopulated_AC6, ValidationRejectsNeitherPopulated_AC6, ValidationRejectsOversizedBatch_AC6, UnauthenticatedRequestReturns401_AC6, PerformanceBudget_AC4 (full-suite only). Tests are cycle-7-stable — they use the post-AZ-794 {z, x, y} wire shape and a minor x/y reduction was applied in cycle 7 to keep the synthetic coords within the z=18 slippy bounds enforced by TileCoordValidator.
  • TileInventoryValidationTests (added cycle 7 — AZ-796) — 16 tests: HappyPath_Returns200, EmptyBody_Returns400, NeitherPopulated_Returns400, BothPopulated_Returns400, EmptyTilesArray_Returns400, TilesOverCap_Returns400, MissingZ_Returns400WithFieldPath, MissingXAndY_Returns400, ZoomOutOfRange_Returns400WithFieldPath, XBeyondZoomBounds_Returns400, YBeyondZoomBounds_Returns400, NegativeAxis_Returns400, UnknownRootField_Returns400, UnknownNestedField_Returns400, OldV1FieldName_Returns400 (AZ-794 + AZ-796 intersection — exact AZ-777 Phase 1 reproducer body, asserts legacy tileZoom/tileX/tileY now yields 400), TypeMismatch_Returns400. Each test exercises one of the 9 validation rules end-to-end through ValidationEndpointFilter<TileInventoryRequest> + GlobalExceptionHandler, asserts HTTP 400 + RFC 7807 ValidationProblemDetails shape via the shared ProblemDetailsAssertions helper.
  • IdempotentPostTests — pre-existing; cycle 7 adjusted the route-point payload from PascalCase (Latitude/Longitude) to camelCase (lat/lon) because the post-AZ-795 UnmappedMemberHandling.Disallow would otherwise reject the previously-silently-ignored fields. The RoutePoint DTO has carried JsonPropertyName("lat"/"lon") since AZ-309; cycle 7's strict JSON parsing exposed the test was sending the wrong shape and getting away with it via the pre-cycle-7 permissive deserializer.

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>.
  • ProblemDetailsAssertions.cs (added cycle 7 — AZ-795) — shared static helpers for asserting RFC 7807 ProblemDetails bodies on integration-test responses. ReadProblemDetailsAsync(HttpResponseMessage, label) deserialises the response body into a JsonElement with helpful failure messages when the content-type / shape doesn't match. AssertProblemDetails(problem, expectedStatus, label) asserts the base ProblemDetails shape (type, title, status). AssertValidationProblem(problem, expectedStatus, label, expectedErrorPath?, expectedErrorContains?) extends the base assertion to require the errors map per error-shape.md Inv-2 and optionally checks a specific field path / message substring. Consumed by TileInventoryValidationTests; designed to be reused by every future per-endpoint child task under AZ-795.

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).
  • ProjectReferences: SatelliteProvider.Api (running service for the integration runner), SatelliteProvider.TestSupport (canonical JwtTokenFactory + IntegrationTestResetGuard), SatelliteProvider.Common (added by AZ-503 so the MultiSourceCoexistence_AZ484_Cycle2 seeder can compute location_hash via Uuidv5.Create instead 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_URL environment variable (set in docker-compose.tests.yml to http://api:8080)
  • INTEGRATION_TESTS_MODEsmoke 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.