Files
satellite-provider/_docs/02_document/modules/tests_integration.md
T
Oleksandr Bezdieniezhnykh 745f4840e6
ci/woodpecker/push/01-test Pipeline was successful
ci/woodpecker/push/02-build-push Pipeline was successful
[AZ-493] Cycle 3 batch 3: integration test DB-reset hook
AZ-493 (2 SP): replace the cycle-2 wallclock-seeded _coordinateCounter
workaround with a proper Postgres state-reset hook that runs at
integration test runner startup, eliminating the per-source-unique-index
collision risk that the persistent docker-compose Postgres volume
introduced post-AZ-484.

The reset is split into two surfaces:

* SatelliteProvider.TestSupport.IntegrationTestResetGuard - pure
  static class, I/O-free, unit-tested. Two independent guards: (a)
  ASPNETCORE_ENVIRONMENT must equal "Testing", (b) DB_CONNECTION_STRING
  Host must be in the allowed-host list (postgres, localhost, 127.0.0.1).
  Failure of either guard surfaces a structured operator-friendly
  InvalidOperationException.
* SatelliteProvider.IntegrationTests.IntegrationTestDatabaseReset -
  instance class owning the Npgsql side effects. Calls the guard then
  runs TRUNCATE TABLE route_regions, route_points, routes, regions,
  tiles RESTART IDENTITY CASCADE inside a single Npgsql transaction.

Spec-vs-reality: the task spec prescribed "DB name contains _test" as
Guard 2; the actual compose file uses Database=satelliteprovider and
DB rename is gated on user confirmation per coderule.mdc. Substituted
a Host allowlist as the equivalent guard (intent identical: reject
remote / production hosts). Recorded as Low/Spec-Gap in the review.

Program.cs adds --keep-state CLI flag and INTEGRATION_KEEP_STATE env
var (1/true) opt-outs so a developer can inspect leftover state when
debugging. Startup banner shows which path executed.
docker-compose.tests.yml gets ASPNETCORE_ENVIRONMENT=Testing +
passthrough for INTEGRATION_KEEP_STATE. scripts/run-tests.sh wires the
--keep-state flag through to compose.

UavUploadTests._coordinateCounter wallclock seed is retained as
defense-in-depth (per the task spec's implementer choice). The reset
is the primary isolation path; the seed is the belt-and-suspenders
fallback for --keep-state runs.

8 new unit tests in SatelliteProvider.Tests/TestSupport/
IntegrationTestResetGuardTests.cs cover Production/Staging/missing-env
throw, allowed-host case-insensitivity, disallowed-host rejection
with representative prod hostnames, and the AllowedHosts contract.

tests_integration.md gains a Reliability section that documents the
hook, the two guards, the truncate order, and the three opt-out forms.
module-layout.md TestSupport entry extended with the new pure guard
and the explicit "Npgsql stays in IntegrationTests" boundary.

Test-suite gate (AC-6) deferred to Step 16 Final Test Run per implement
skill convention. Per-batch review verdict: PASS_WITH_WARNINGS with 1
Low (spec-vs-reality on Guard 2, non-blocking).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 01:38:42 +03:00

7.6 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 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) — AnonymousRequest_To_AnyEndpoint_Returns401, ExpiredToken_Returns401, InvalidSignature_Returns401, ValidToken_Returns200_OnHealthyEndpoint, SwaggerDocument_AdvertisesBearerSecurityScheme. HS256 token minting lives in the shared SatelliteProvider.TestSupport.JwtTokenFactory (consumed via ProjectReference); runner-specific concerns (JwtTestHelpers.ResolveSecretOrThrow, AttachDefaultAuthorization, DefaultSubject = "integration-tests") remain in this project. The test runner sets JWT_SECRET on the API container and attaches a Bearer token 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, and the AZ-493 DB-reset hook)
  • JwtTestHelpers.cs — runner-side JWT concerns (ResolveSecretOrThrow reads the JWT_SECRET env var with size validation; 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 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.

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_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).
  • 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.