[AZ-487] [AZ-488] docs: cycle 2 doc sync (task mode)
ci/woodpecker/push/01-test Pipeline was successful
ci/woodpecker/push/02-build-push Pipeline was successful

Step 13 (Update Docs) for cycle 2. Most cross-cutting docs were
already updated during Step 10 (architecture.md, glossary.md,
components/03_tile_downloader, modules/api_program.md, data_model.md,
contracts/api/uav-tile-upload.md). This commit completes the remaining
module-level + module-layout updates and writes the cycle-2 ripple log.

* modules/common_configs.md: + UavQualityConfig section and
  appsettings-section row (UavQuality).
* modules/common_dtos.md: + UavTileMetadata, UavTileBatchMetadataPayload,
  UavTileBatchUploadResponse, UavTileUploadResultItem, UavTileUploadStatus,
  UavTileRejectReasons (closed enumeration v1.0.0).
* module-layout.md: refresh Common (+ UavQualityConfig + UAV DTOs),
  TileDownloader (+ UavTileQualityGate, UavTileUploadHandler, +
  SixLabors.ImageSharp 3.1.11 PackageReference), and WebApi (+
  Authentication/*, DTOs/UavTileBatchUploadRequest, + JwtBearer 8.0.21
  PackageReference). Updates the "Last Updated" stamp to cycle 2.
* modules/tests_unit.md: replace the obsolete "only a dummy test"
  description; add cycle-2 AZ-487 / AZ-488 test classes
  (AuthenticationServiceCollectionExtensionsTests, JwtTokenFactoryTests,
  UavTileQualityGateTests, UavTileUploadHandlerTests, UavTileFilePathTests,
  PermissionsRequirementTests) + new ProjectReference / package
  references.
* modules/tests_integration.md: + JwtIntegrationTests, UavUploadTests
  (incl. wall-clock-seeded coordinate counter rationale from the Step 11
  fix), and the StubAndErrorContractTests update for the removed 501
  stub.
* ripple_log_cycle2.md (new): cycle-2 reverse-dependency scan results
  showing every importer of the new symbols resolves inside the three
  already-updated components (WebApi, TileDownloader, Common). No
  unexpected ripple, no heuristic fallback needed.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-12 00:04:05 +03:00
parent 98cdcd17c1
commit e3cd388577
7 changed files with 149 additions and 18 deletions
@@ -26,6 +26,16 @@ Configuration POCOs that bind to `appsettings.json` sections via `IOptions<T>` p
### DatabaseConfig
- `ConnectionString` (string): DB connection string (unused — connection string is resolved directly from `IConfiguration` in `Program.cs`)
### UavQualityConfig
Knobs for the 5-rule UAV tile quality gate (`UavTileQualityGate`) and the multipart upload envelope. Bound from the `UavQuality` section in `appsettings.json`.
- `MinBytes` (int, default: `5 * 1024` = 5 KiB): lower bound for Rule 2 (`SIZE_OUT_OF_BAND`).
- `MaxBytes` (int, default: `5 * 1024 * 1024` = 5 MiB): upper bound for Rule 2; also drives `KestrelServerOptions.Limits.MaxRequestBodySize = MaxBatchSize * MaxBytes` and `FormOptions.MultipartBodyLengthLimit` in `Program.cs`.
- `MaxAgeDays` (int, default: 7): Rule 4 cutoff for `CAPTURED_AT_TOO_OLD`.
- `CapturedAtFutureSkewSeconds` (int, default: 30): Rule 4 forward clock-skew tolerance for `CAPTURED_AT_FUTURE`.
- `MinLuminanceVariance` (double, default: 10.0): Rule 5 threshold for `IMAGE_TOO_UNIFORM` measured on a downsampled `L8` image.
- `MaxBatchSize` (int, default: 100): hard cap on per-request batch length; violations return 400 envelope-level (see AC-8, RL-05).
- `LuminanceSampleSize` (int, default: 32): edge length of the square downsample fed into Rule 5; keeps the per-item gate cost bounded.
## Internal Logic
`StorageConfig.GetTileSubdirectoryPath` buckets tiles by dividing X/Y coordinates by 1000, preventing filesystem performance degradation from too many files in one directory.
@@ -52,6 +62,7 @@ These classes **define** the configuration shape consumed by all services.
| StorageConfig | `StorageConfig` |
| ProcessingConfig | `ProcessingConfig` |
| DatabaseConfig | (not wired — connection string read directly) |
| UavQualityConfig | `UavQuality` (added in AZ-488; consumed by `UavTileQualityGate`, `UavTileUploadHandler`, and `Program.cs` request-size limits) |
## External Integrations
None.
+37
View File
@@ -72,6 +72,43 @@ Axis-aligned bounding box defined by NW and SE corners.
Container for multiple geofence polygons.
- `Polygons` (List\<GeofencePolygon\>)
### UavTileMetadata (added AZ-488)
Per-tile metadata payload inside a UAV batch upload (`POST /api/satellite/upload`). Indexed-correlated with the multipart `IFormFileCollection`.
- `Latitude`, `Longitude` (double)
- `TileZoom` (int)
- `TileSizeMeters` (double)
- `CapturedAt` (DateTime, UTC; subject to AZ-488 Rule 4 future-skew / age checks)
### UavTileBatchMetadataPayload (added AZ-488)
JSON envelope deserialized from the `metadata` form field of a UAV batch upload.
- `Items` (IReadOnlyList\<UavTileMetadata\>)
### UavTileBatchUploadResponse (added AZ-488)
Wire response for `POST /api/satellite/upload`. Returned with HTTP 200 regardless of per-item outcomes; envelope-level failures (auth, oversize, deserialization) bypass this shape.
- `Items` (List\<UavTileUploadResultItem\>)
### UavTileUploadResultItem (added AZ-488)
Per-item result inside `UavTileBatchUploadResponse`.
- `Index` (int): zero-based index into the request batch.
- `Status` (string): one of `UavTileUploadStatus.Accepted` / `UavTileUploadStatus.Rejected`.
- `TileId` (Guid?): set on accept (matches the new/updated `tiles.id`); null on reject.
- `RejectReason` (string?): closed-enum reason code from `UavTileRejectReasons`; null on accept.
- `RejectDetails` (string?): short human-readable note. MUST NOT leak server-internal paths / exception types / hostnames (AZ-488 Security NFR; covered by SEC-11).
### UavTileUploadStatus (added AZ-488, static string constants)
- `Accepted = "accepted"`
- `Rejected = "rejected"`
### UavTileRejectReasons (added AZ-488, static string constants — closed enumeration v1.0.0)
Authoritative reject-reason codes for the UAV upload quality gate. Adding a new code requires a minor-version bump of `_docs/02_document/contracts/api/uav-tile-upload.md`.
- `InvalidFormat = "INVALID_FORMAT"` — Rule 1 (content-type or JPEG magic bytes).
- `SizeOutOfBand = "SIZE_OUT_OF_BAND"` — Rule 2 (bytes outside `[MinBytes, MaxBytes]`).
- `WrongDimensions = "WRONG_DIMENSIONS"` — Rule 3 (image width/height ≠ `MapConfig.TileSizePixels`).
- `CapturedAtFuture = "CAPTURED_AT_FUTURE"` — Rule 4 (timestamp ahead of now + `CapturedAtFutureSkewSeconds`).
- `CapturedAtTooOld = "CAPTURED_AT_TOO_OLD"` — Rule 4 (timestamp older than `MaxAgeDays`).
- `ImageTooUniform = "IMAGE_TOO_UNIFORM"` — Rule 5 (luminance variance below `MinLuminanceVariance`).
- `StorageFailure = "STORAGE_FAILURE"` — reserved for the orphan-row-recovery path when the on-disk write succeeds but the DB UPSERT fails; surfaced per-item without failing the envelope (AZ-488 Reliability NFR).
## Internal Logic
- `GeoPoint` uses a precision tolerance of `0.00005` degrees (~5.5 meters) for equality comparison.
- `SatTile` eagerly computes its bounding box corners on construction by calling `GeoUtils.TileToWorldPos`.
@@ -12,6 +12,9 @@ Console application that runs end-to-end integration tests against a live API in
- `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) — `AnonymousRequest_To_AnyEndpoint_Returns401`, `ExpiredToken_Returns401`, `InvalidSignature_Returns401`, `ValidToken_Returns200_OnHealthyEndpoint`, `SwaggerDocument_AdvertisesBearerSecurityScheme`. Helpers in `JwtTestHelpers` mint HS256 tokens; 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) — `HappyPathSingleItem_PersistsRow`, `MixedBatch_ReturnsPerItemResults`, `MultiSourceCoexistence_AZ484_Cycle2`, `SameSourceUpsert_AZ484_Cycle2`, `NoToken_Returns401`, `ValidTokenWithoutGpsPermission_Returns403`, `OversizedBatch_Returns400`. Uses a wall-clock-seeded coordinate counter (`_coordinateCounter` initialized from `DateTime.UtcNow`) so each docker-compose run picks a fresh coordinate band — the postgres named volume persists across runs and a naïve `int = 0` counter collided with prior runs on the per-source unique index (fixed mid-Step-11).
- `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
+22 -10
View File
@@ -1,23 +1,35 @@
# Module: Tests/SatelliteProvider.Tests
## Purpose
Unit test project. Currently contains only a single dummy test as a placeholder.
Unit test project for component-internal logic. Original AZ-2/AZ-3 era had only a placeholder dummy; the suite has since grown across the AZ-285..AZ-380 baseline + cycle 1 (AZ-484) + cycle 2 (AZ-487, AZ-488) tracks. The "dummy test only" note in older revisions of this file is obsolete — the project now hosts the full unit suite executed by `scripts/run-tests.sh --unit-only` and CI's `01-test.yml`.
## Public Interface
## Public Interface (test classes)
### DummyTests
- `Dummy_ShouldWork()`: asserts `1 == 1`
Existing baseline (pre-cycle-2) test classes cover `TileService`, `RegionService`, `RouteService`, geo math, repositories, validators, idempotency, and migration helpers — not enumerated exhaustively here. Cycle-2 additions:
### AZ-487 — JWT validation baseline
- `Authentication/AuthenticationServiceCollectionExtensionsTests``AddSatelliteJwt_RegistersJwtBearerScheme`, `AddSatelliteJwt_ThrowsOnMissingSecret`, `AddSatelliteJwt_ThrowsOnShortSecret`.
- `Authentication/JwtTokenFactoryTests``Create_ProducesTokenValidatedByMatchingParameters`, `CreateExpired_TokenFailsValidationWithLifetimeException`, `Create_WithExtraClaims_PropagatesClaimsThroughValidation`.
- `TestUtilities/JwtTokenFactory` — helper that mints HS256 tokens with the same `TokenValidationParameters` used in production. Adjusts `notBefore` for negative-lifetime requests so `JwtSecurityToken` accepts the value and downstream lifetime validation can fire (`IDX12401` workaround documented inline).
### AZ-488 — UAV tile upload
- `UavTileQualityGateTests` — one happy path + ≥ 1 reject path per rule (Rule 1 INVALID_FORMAT × 2, Rule 2 SIZE_OUT_OF_BAND × 2, Rule 3 WRONG_DIMENSIONS × 1, Rule 4 CAPTURED_AT_FUTURE / _TOO_OLD × 2, Rule 5 IMAGE_TOO_UNIFORM × 1) + rule-ordering determinism. Uses a `FixedTimeProvider` for Rule-4 isolation and `UavTileImageFactory` for deterministic JPEG fixtures.
- `UavTileUploadHandlerTests` — end-to-end with a mocked `ITileRepository`: 1-item happy path, 3-item mixed batch (file written + `InsertAsync` called only for accepted), per-source UPSERT pass-through.
- `UavTileFilePathTests` — verifies `BuildUavTileFilePath` produces `tiles/uav/{z}/{x}/{y}.jpg` for sample (z, x, y) tuples and that integer-typed coordinates make string-injection of path traversal impossible.
- `Authentication/PermissionsRequirementTests``PermissionsAuthorizationHandler` correctly accepts a `permissions` claim shaped as a single string OR as a JSON array, rejects when the requested permission is absent, and short-circuits when the principal has no `permissions` claim at all.
- `TestUtilities/UavTileImageFactory` — programmatic JPEG factories used by the gate + handler tests: `CreateValidJpeg(width, height, seed)`, `CreateUniformJpeg`, `CreatePng` (for Rule 1 negative path).
## Internal Logic
No meaningful test logic.
- Tests follow Arrange / Act / Assert. Time-dependent paths inject a `FixedTimeProvider` (cycle-2 addition) so Rule 4 has deterministic age windows.
- `JwtSecurityTokenHandler.MapInboundClaims = false` is set explicitly in JWT tests so claims read by their original names (`sub`, `permissions`, …) rather than the framework-remapped names.
## Dependencies
- Project references: `SatelliteProvider.Services.TileDownloader`, `SatelliteProvider.Services.RegionProcessing`, `SatelliteProvider.Services.RouteManagement`, `SatelliteProvider.Common`, `SatelliteProvider.DataAccess`
- NuGet: xUnit (2.5.3), Moq (4.20.72), FluentAssertions (8.8.0), coverlet.collector (6.0.0), Microsoft.NET.Test.Sdk (17.8.0), Microsoft.Extensions.* (Caching.Memory, Configuration, DI, Logging, Options, Http)
- Has `appsettings.json` copied to output (empty config for potential future test setups)
- Project references: `SatelliteProvider.Services.TileDownloader`, `SatelliteProvider.Services.RegionProcessing`, `SatelliteProvider.Services.RouteManagement`, `SatelliteProvider.Common`, `SatelliteProvider.DataAccess`, `SatelliteProvider.Api` (for the Authentication tests — added in AZ-487).
- NuGet: xUnit (2.5.3), Moq (4.20.72), FluentAssertions (8.8.0), coverlet.collector (6.0.0), Microsoft.NET.Test.Sdk (17.8.0), Microsoft.Extensions.* (Caching.Memory, Configuration, DI, Logging, Options, Http), `Microsoft.AspNetCore.Authentication.JwtBearer` 8.0.21 (added by AZ-487 for the DI + handler tests), `SixLabors.ImageSharp` 3.1.11 (added by AZ-488 for the gate tests).
- `appsettings.json` copied to output (used by Authentication tests for the `Jwt` section binding scenario).
## Consumers
- CI pipeline (`01-test.yml`) runs `dotnet test` against this project
- CI pipeline (`01-test.yml`) and `scripts/run-tests.sh --unit-only` run `dotnet test` against this project.
## Tests
This IS the test module. Coverage: effectively zero (only a dummy test).
This IS the test module. Cycle-2 added ~25 unit tests on top of the existing baseline; the full project executes in seconds (no external services required).