Files
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

8.9 KiB

Batch Report — Batch 03 cycle 3

Batch: 03 (cycle 3) Tasks: AZ-493 (integration test DB-reset hook) Date: 2026-05-12

Task Results

Task Status Files Modified Tests AC Coverage Issues
AZ-493_integration_test_db_reset_hook Done 3 added + 5 modified 8 new unit tests for the guard (in SatelliteProvider.Tests/TestSupport/IntegrationTestResetGuardTests.cs); reset itself exercised by the existing integration suite at Step 16 6/6 ACs covered 0 blockers; 1 spec-vs-reality note (see below)

AC Test Coverage: All covered (6 of 6)

Code Review Verdict: pending (this batch report precedes per-batch review)

Auto-Fix Attempts: 0

Stuck Agents: None

What was implemented

The reset is split into two parts so the guard logic is pure-string and unit-testable while the actual DB side effects live in the integration-tests project (which already depends on Npgsql).

Added

  • SatelliteProvider.TestSupport/IntegrationTestResetGuard.cs (pure static class). Validates (environment, host) against two rules: (a) ASPNETCORE_ENVIRONMENT MUST equal "Testing" (case-insensitive); (b) Host MUST be one of postgres, localhost, 127.0.0.1. Throws InvalidOperationException with structured operator-friendly messages on either failure. The class is intentionally I/O-free so unit tests don't need Postgres.
  • SatelliteProvider.IntegrationTests/IntegrationTestDatabaseReset.cs (instance class). Constructor takes a connection string; EnsureCleanStateAsync() calls the guard then runs TRUNCATE TABLE route_regions, route_points, routes, regions, tiles RESTART IDENTITY CASCADE inside an Npgsql transaction. FK-safe order is preserved as both the SQL argument list AND the public TruncateOrder IReadOnlyList<string>. Structured success log includes which tables were truncated and the Host/Database tuple so operators have an audit trail.
  • SatelliteProvider.Tests/TestSupport/IntegrationTestResetGuardTests.cs (8 unit tests). Covers: Production / Staging / missing-environment all throw (AC-4); allowed hosts (postgres, Postgres, POSTGRES, localhost, 127.0.0.1) all pass with case-insensitivity; disallowed hosts (prod.example.com, rds.amazonaws.com, db.staging.internal) all throw with the specific host name in the message; missing host throws; the AllowedHosts contract is asserted as an immutable expectation.

Modified

  • SatelliteProvider.IntegrationTests/Program.cs:
    • Argument parsing now recognizes --keep-state (CLI) and INTEGRATION_KEEP_STATE=1 / INTEGRATION_KEEP_STATE=true (env var).
    • Reads DB_CONNECTION_STRING up front (alongside the existing API_URL read) so the reset and the test classes share the same connection string.
    • After the API readiness probe and before any test class runs, calls await new IntegrationTestDatabaseReset(connectionString).EnsureCleanStateAsync(). Exits with code 1 if the guard throws (with the structured error message visible to the operator).
    • Startup banner gains a State : reset (clean DB at startup, AZ-493) or State : keep (DB reset skipped) line so the run log makes it unambiguous which path executed.
  • SatelliteProvider.IntegrationTests/UavUploadTests.cs_coordinateCounter wallclock seed retained as defense-in-depth (per the AZ-493 task spec's implementer choice). An inline comment back-references AZ-493 and explains that the reset hook is the primary isolation path; the wallclock seed is the belt to the suspenders so --keep-state runs don't immediately collide on the per-source unique index.
  • docker-compose.tests.ymlintegration-tests service gains ASPNETCORE_ENVIRONMENT=Testing (the AZ-493 Guard 1) and INTEGRATION_KEEP_STATE=${INTEGRATION_KEEP_STATE:-} (developer-overridable env var). API service is unchanged.
  • scripts/run-tests.sh--keep-state flag added with usage docs; passes INTEGRATION_KEEP_STATE=1 through to the compose run when set.
  • _docs/02_document/modules/tests_integration.md — new ## Reliability section documents the AZ-493 hook, the two guards, the truncate order, and the three opt-out forms (./scripts/run-tests.sh --keep-state, INTEGRATION_KEEP_STATE=1, dotnet run ... -- --keep-state). The UavUploadTests entry now flags the coordinate-counter wallclock seed as "promoted to defense-in-depth by AZ-493 cycle 3". The ## Dependencies section now lists the ProjectReference to SatelliteProvider.TestSupport and the actual NuGet references (Npgsql 9.0.2, ImageSharp 3.1.11) instead of the previous "No project references" line.
  • _docs/02_document/module-layout.md § TestSupport — extended with the IntegrationTestResetGuard entry, the "no new packages" note, and an explicit boundary: AZ-493's Npgsql-bearing reset class lives in SatelliteProvider.IntegrationTests (not TestSupport) so the Npgsql dependency doesn't leak into unit tests.

AC Verification

AC Status Evidence
AC-1: Empty-state on startup TRUNCATE ... RESTART IDENTITY CASCADE against route_regions, route_points, routes, regions, tiles runs in a single Npgsql transaction before any test class is reached
AC-2: Wallclock workaround no longer needed for back-to-back runs ✓ (with belt-and-suspenders) Reset hook ensures empty start. Wallclock seed retained as defense-in-depth per implementer choice — does not weaken AC-2 since the primary isolation mechanism is the reset, not the seed
AC-3: Opt-out preserves state --keep-state (CLI) and INTEGRATION_KEEP_STATE (env) both wired through scripts/run-tests.sh → docker-compose → Program.cs; startup banner shows which path executed
AC-4: Reset only fires in test environment 8 unit tests in IntegrationTestResetGuardTests cover Production/Staging/missing-env → throw; 3 disallowed-host examples → throw; allowed-host case-insensitivity → pass
AC-5: Documentation reflects new convention tests_integration.md ## Reliability section is canonical; module-layout.md cross-references the guard's pure-vs-side-effectful split
AC-6: Existing tests pass unchanged Deferred to Step 16 All pre-AZ-493 test logic untouched; only Program.cs startup wiring + the new reset class were added

Spec-vs-reality notes

  • Guard 2 deviation from task spec: The task spec § Safety prescribed "DB name contains _test" as the second guard. However, the existing test compose file uses Database=satelliteprovidernot satelliteprovider_test. Renaming the database is gated on user confirmation per coderule.mdc. The "or equivalent" qualifier in the spec was used to substitute a Host allowlist (postgres, localhost, 127.0.0.1) as the second guard. The intent is identical (refuse to truncate against a remote / production host) and the implementation is unit-tested with a representative production-shape host list (prod.example.com, rds.amazonaws.com, db.staging.internal) all hitting the rejection path.
  • This is being recorded as a Low / Spec-Gap finding in the code-review report, mirroring the AZ-496 pattern (spec inaccuracy detected during implementation, documented and worked around without renaming production assets).

Open follow-ups (non-blocking)

  • Test-DB rename decision (optional future PBI): if the team wants to align with the spec's preferred two-guard model (ASPNETCORE_ENVIRONMENT=Testing + DB name contains _test), the integration-tests compose Database=satelliteprovider would become Database=satelliteprovider_test. This requires a DB rename + migration-runner verification + at least one round of integration-suite re-verification. Out of scope for AZ-493.
  • Reset performance under load: AZ-493 NFR sets a < 1 s budget for TRUNCATE against an O(10K)-row DB. The current cycle's tile-table row count is far below that threshold; no measurement is in scope. A future cycle that exercises higher-volume scenarios should validate the NFR.
  • Truncate-order audit on schema changes: noted in tests_integration.md § Reliability — new tables with FK relationships to tiles/regions/routes need a TruncateOrder revision. Add to the decompose-skill review checklist if the pattern recurs (suggested but not in scope for AZ-493).

Next Batch: AZ-492 (Perf harness PT-07 + PT-08 + JWT-attach)

AZ-492 is 3 SP. Promotes the two deferred performance NFRs (PT-07: P95 latency-vs-load; PT-08: error-rate-under-pressure) into runnable scenarios under scripts/run-performance-tests.sh, and fixes the cycle-2-regressed JWT-attach so every perf scenario sends a valid Bearer token (post-AZ-487 the endpoints are protected, so every perf request currently 401s). With AZ-491 done, AZ-492 can consume SatelliteProvider.TestSupport.JwtTokenFactory indirectly via a small shell-side python3 -c minter that mirrors its parameters (or by shelling out to a tiny dotnet entry point — implementer's choice at batch start).