mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-22 10:11:15 +00:00
Compare commits
2 Commits
dc3dabe7bd
...
e3cd388577
| Author | SHA1 | Date | |
|---|---|---|---|
| e3cd388577 | |||
| 98cdcd17c1 |
@@ -5,7 +5,7 @@
|
||||
**Language**: csharp
|
||||
**Layout Convention**: custom (per-component .csproj per logical component)
|
||||
**Root**: ./
|
||||
**Last Updated**: 2026-05-11 (post AZ-350 03-code-quality-refactoring run; corrects DataAccess→Common dependency)
|
||||
**Last Updated**: 2026-05-11 (cycle 2 — AZ-487 JWT validation baseline + AZ-488 UAV tile upload added; supersedes prior post-AZ-350 update)
|
||||
|
||||
## Layout Rules
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
- `SatelliteProvider.Common/Configs/StorageConfig.cs`
|
||||
- `SatelliteProvider.Common/Configs/ProcessingConfig.cs`
|
||||
- `SatelliteProvider.Common/Configs/DatabaseConfig.cs`
|
||||
- `SatelliteProvider.Common/DTO/*.cs` (all DTOs)
|
||||
- `SatelliteProvider.Common/Configs/UavQualityConfig.cs` (added by AZ-488; UAV quality-gate + request-envelope knobs)
|
||||
- `SatelliteProvider.Common/DTO/*.cs` (all DTOs; AZ-488 added `UavTileMetadata`, `UavTileBatchMetadataPayload`, `UavTileBatchUploadResponse`, `UavTileUploadResultItem`, `UavTileUploadStatus`, `UavTileRejectReasons` — placed in Common to keep `TileDownloader` from depending on the API layer)
|
||||
- `SatelliteProvider.Common/Enums/RegionStatus.cs`
|
||||
- `SatelliteProvider.Common/Enums/RoutePointType.cs`
|
||||
- `SatelliteProvider.Common/Enums/TileSource.cs` (added by AZ-484; backed by the `tile-storage` v1.0.0 contract)
|
||||
@@ -67,10 +68,13 @@
|
||||
- **Public API**:
|
||||
- `SatelliteProvider.Services.TileDownloader/GoogleMapsDownloaderV2.cs` (implements `ISatelliteDownloader`)
|
||||
- `SatelliteProvider.Services.TileDownloader/TileService.cs` (implements `ITileService`)
|
||||
- `SatelliteProvider.Services.TileDownloader/TileDownloaderServiceCollectionExtensions.cs` (DI: `AddTileDownloader()`)
|
||||
- `SatelliteProvider.Services.TileDownloader/UavTileQualityGate.cs` + `IUavTileQualityGate` (added by AZ-488; 5-rule synchronous validator over `ReadOnlyMemory<byte>` JPEGs, uses `SixLabors.ImageSharp` 3.1.11 + `TimeProvider`)
|
||||
- `SatelliteProvider.Services.TileDownloader/UavTileUploadHandler.cs` + `IUavTileUploadHandler` (added by AZ-488; orchestrates batch validation → file-first persistence → `ITileRepository.InsertAsync` UPSERT; owns the UAV `./tiles/uav/{z}/{x}/{y}.jpg` path layout)
|
||||
- `SatelliteProvider.Services.TileDownloader/TileDownloaderServiceCollectionExtensions.cs` (DI: `AddTileDownloader()` — also registers the AZ-488 quality gate and upload handler as singletons)
|
||||
- **Internal**: (none)
|
||||
- **Owns**: `SatelliteProvider.Services.TileDownloader/**`
|
||||
- **ProjectReferences**: `SatelliteProvider.Common`, `SatelliteProvider.DataAccess`
|
||||
- **PackageReferences (added by AZ-488)**: `SixLabors.ImageSharp` 3.1.11 (image identify / `L8` decode / downsample for the variance heuristic).
|
||||
- **Imports from**: Common, DataAccess
|
||||
- **Consumed by**: RegionProcessing (via `ITileService` from Common; no direct ProjectReference), WebApi
|
||||
|
||||
@@ -107,10 +111,14 @@
|
||||
|
||||
- **Directory**: `SatelliteProvider.Api/`
|
||||
- **Public API**:
|
||||
- `SatelliteProvider.Api/Program.cs` (minimal API endpoints, DI setup)
|
||||
- `SatelliteProvider.Api/Program.cs` (minimal API endpoints, DI setup, middleware chain — `UseAuthentication` + `UseAuthorization` added in AZ-487; `/api/satellite/upload` rewired in AZ-488)
|
||||
- `SatelliteProvider.Api/Authentication/AuthenticationServiceCollectionExtensions.cs` (added by AZ-487; `AddSatelliteJwt(IConfiguration)` registers `JwtBearer` with the suite-wide HS256 contract from `suite/_docs/10_auth.md`; validates `JWT_SECRET` ≥ 32 bytes at startup)
|
||||
- `SatelliteProvider.Api/Authentication/PermissionsRequirement.cs` + `PermissionsAuthorizationHandler` + `SatellitePermissions` (added by AZ-488; custom requirement that accepts a `permissions` claim shaped as either a single string or a JSON array; powers the `UavUploadPolicy` requiring the `GPS` permission)
|
||||
- `SatelliteProvider.Api/DTOs/UavTileBatchUploadRequest.cs` (added by AZ-488; multipart form binding envelope — kept in WebApi because it depends on `IFormFileCollection` + `[FromForm]`, both API-layer types)
|
||||
- **Internal**: (none)
|
||||
- **Owns**: `SatelliteProvider.Api/**`
|
||||
- **Imports from**: Common, DataAccess, TileDownloader, RegionProcessing, RouteManagement
|
||||
- **PackageReferences (added by AZ-487)**: `Microsoft.AspNetCore.Authentication.JwtBearer` 8.0.21 (pinned to the same minor as the existing ASP.NET Core 8 packages).
|
||||
- **Imports from**: Common (incl. AZ-488 UAV DTOs + `UavQualityConfig`), DataAccess, TileDownloader (incl. AZ-488 `IUavTileUploadHandler`), RegionProcessing, RouteManagement
|
||||
- **Consumed by**: (none — top-level entry point)
|
||||
|
||||
## Shared / Cross-Cutting
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# Cycle 2 — Documentation Ripple Log
|
||||
|
||||
**Cycle**: 2 (AZ-487 JWT validation baseline + AZ-488 UAV tile upload)
|
||||
**Generated by**: `/document` skill (task mode) during autodev Step 13
|
||||
**Resolution method**: `Grep --type cs` against every new symbol introduced by the two tasks. C# `using`-based import analysis (`SatelliteProvider.Api.Authentication`, `SatelliteProvider.Common.Configs.UavQualityConfig`, the AZ-488 `UavTile*` DTOs, and the new `IUavTileQualityGate` / `IUavTileUploadHandler` interfaces). No static-analyzer (NDepend, etc.) was used — the search surface is small enough that a literal usage scan is exhaustive.
|
||||
|
||||
## Directly-changed source files (cycle 2)
|
||||
|
||||
- `SatelliteProvider.Api/Program.cs` (AZ-487 middleware + Authorize, AZ-488 endpoint rewire + Kestrel/FormOptions limits)
|
||||
- `SatelliteProvider.Api/Authentication/AuthenticationServiceCollectionExtensions.cs` (AZ-487, new)
|
||||
- `SatelliteProvider.Api/Authentication/PermissionsRequirement.cs` (AZ-488, new — also defines `PermissionsAuthorizationHandler` + `SatellitePermissions`)
|
||||
- `SatelliteProvider.Api/DTOs/UavTileBatchUploadRequest.cs` (AZ-488, new)
|
||||
- `SatelliteProvider.Api/DTOs/UploadImageRequest.cs` (AZ-488, deleted — legacy 501-stub DTO)
|
||||
- `SatelliteProvider.Api/appsettings.json` (AZ-487 `Jwt` section, AZ-488 `UavQuality` section)
|
||||
- `SatelliteProvider.Common/Configs/UavQualityConfig.cs` (AZ-488, new)
|
||||
- `SatelliteProvider.Common/DTO/UavTileMetadata.cs` (AZ-488, new — incl. `UavTileBatchMetadataPayload`)
|
||||
- `SatelliteProvider.Common/DTO/UavTileBatchUploadResponse.cs` (AZ-488, new — incl. `UavTileUploadResultItem`, `UavTileUploadStatus`, `UavTileRejectReasons`)
|
||||
- `SatelliteProvider.Services.TileDownloader/UavTileQualityGate.cs` (AZ-488, new — incl. `IUavTileQualityGate`)
|
||||
- `SatelliteProvider.Services.TileDownloader/UavTileUploadHandler.cs` (AZ-488, new — incl. `IUavTileUploadHandler`)
|
||||
- `SatelliteProvider.Services.TileDownloader/TileDownloaderServiceCollectionExtensions.cs` (AZ-488, updated to register the new services)
|
||||
- `SatelliteProvider.Services.TileDownloader/SatelliteProvider.Services.TileDownloader.csproj` (AZ-488, added `SixLabors.ImageSharp` 3.1.11)
|
||||
- `SatelliteProvider.Tests/SatelliteProvider.Tests.csproj` (AZ-487 `JwtBearer`, AZ-488 `ImageSharp`; new `ProjectReference` to `SatelliteProvider.Api` for the Authentication tests)
|
||||
- `SatelliteProvider.IntegrationTests/*` (AZ-487 `JwtTestHelpers`, AZ-488 `UavUploadTests` + multipart helpers; runner `Program.cs` invokes both suites)
|
||||
|
||||
## Importer scan results
|
||||
|
||||
| Symbol | Importer count | Importer files | Component touched |
|
||||
|--------|----------------|----------------|-------------------|
|
||||
| `AuthenticationServiceCollectionExtensions.AddSatelliteJwt` | 2 | `SatelliteProvider.Api/Program.cs`, `SatelliteProvider.Tests/Authentication/AuthenticationServiceCollectionExtensionsTests.cs` | WebApi, Tests (unit) |
|
||||
| `PermissionsRequirement` / `PermissionsAuthorizationHandler` / `SatellitePermissions` | 2 | `SatelliteProvider.Api/Program.cs`, `SatelliteProvider.Tests/Authentication/PermissionsRequirementTests.cs` | WebApi, Tests (unit) |
|
||||
| `UavTileBatchUploadRequest` | 1 | `SatelliteProvider.Api/Program.cs` | WebApi |
|
||||
| `UavQualityConfig` | 4 | `Program.cs`, `UavTileQualityGate.cs`, `UavTileUploadHandler.cs`, `UavTileQualityGateTests.cs` | WebApi, TileDownloader, Tests (unit) |
|
||||
| `UavTileMetadata` / `UavTileBatchMetadataPayload` | 3 | `Program.cs`, `UavTileUploadHandler.cs`, integration `UavUploadTests` (via JSON only) | WebApi, TileDownloader, Tests (integration) |
|
||||
| `UavTileBatchUploadResponse` / `UavTileUploadResultItem` / `UavTileUploadStatus` / `UavTileRejectReasons` | 4 | `Program.cs`, `UavTileQualityGate.cs`, `UavTileUploadHandler.cs`, `UavUploadTests` (integration deserialization) | WebApi, TileDownloader, Tests (integration) |
|
||||
| `IUavTileQualityGate` / `UavTileQualityGate` | 3 | `UavTileUploadHandler.cs` (consumer), `TileDownloaderServiceCollectionExtensions.cs` (DI registration), `UavTileQualityGateTests.cs` | TileDownloader, Tests (unit) |
|
||||
| `IUavTileUploadHandler` / `UavTileUploadHandler` | 2 | `Program.cs` (consumer via DI), `TileDownloaderServiceCollectionExtensions.cs` | WebApi, TileDownloader |
|
||||
|
||||
## Doc refresh decisions
|
||||
|
||||
All importers land inside three components that already received targeted updates during Step 10 (Implement) and this Step 13:
|
||||
|
||||
- **WebApi** — `_docs/02_document/modules/api_program.md` updated during AZ-487/AZ-488 implementation (endpoint table, DTOs, DI, handler, config, security). Re-verified during Step 13; no further edits required this pass.
|
||||
- **TileDownloader** — `_docs/02_document/components/03_tile_downloader/description.md` updated during implementation (UavTileQualityGate + UavTileUploadHandler service sections). Re-verified Step 13; no further edits required.
|
||||
- **Common** — `_docs/02_document/modules/common_configs.md` (added `UavQualityConfig`) and `_docs/02_document/modules/common_dtos.md` (added UAV DTOs + reject-reason enum) updated in this Step-13 pass.
|
||||
|
||||
System-level docs also updated during implementation: `architecture.md` (Architecture Vision + Security Architecture + ADR-004 per-source layout), `glossary.md` (UAV + reject-reason terms), `data_model.md` (per-source `file_path` semantics), `contracts/api/uav-tile-upload.md` v1.0.0 (new frozen contract).
|
||||
|
||||
Step-13 additions to module-layout.md: Public API lines for Common (`UavQualityConfig` + UAV DTOs), TileDownloader (`UavTileQualityGate`, `UavTileUploadHandler` + ImageSharp PackageReference), and WebApi (`Authentication/*`, `DTOs/UavTileBatchUploadRequest.cs`, JwtBearer PackageReference). Tests module docs (`tests_unit.md`, `tests_integration.md`) refreshed with the new cycle-2 test classes; the obsolete "only a dummy test" claim in `tests_unit.md` was corrected.
|
||||
|
||||
## No-ripple components
|
||||
|
||||
These components were NOT touched by cycle-2 changes and require no doc update:
|
||||
|
||||
- DataAccess — no new symbols imported; `ITileRepository.InsertAsync` is consumed unchanged.
|
||||
- RegionProcessing — no imports against cycle-2 symbols.
|
||||
- RouteManagement — no imports against cycle-2 symbols.
|
||||
|
||||
## Parse-failure / heuristic notes
|
||||
|
||||
None — every symbol resolved via direct `Grep`. No fallback heuristic was needed.
|
||||
@@ -113,3 +113,59 @@
|
||||
**Trigger**: POST /api/satellite/route with geofence NW.lat < SE.lat
|
||||
**Expected**: Validation error
|
||||
**Pass criterion**: Error message about northWest latitude > southEast latitude
|
||||
|
||||
---
|
||||
|
||||
## Cycle 2 — AZ-488 UAV Tile Upload (POST /api/satellite/upload)
|
||||
|
||||
All Cycle-2 UAV scenarios run with a JWT containing `permissions: ["GPS"]` (per AZ-487 + AZ-488). Files use the contract at `_docs/02_document/contracts/api/uav-tile-upload.md` v1.0.0; per-item correlation is by ordinal index between metadata array and `IFormFileCollection`.
|
||||
|
||||
## BT-13: UAV Upload — Happy-Path 1-Item Batch Persists `source='uav'`
|
||||
|
||||
**Trigger**: POST `/api/satellite/upload` with a 1-item batch — a 256×256 JPEG (~50 KiB), `capturedAt = now`, valid coordinates inside the test region.
|
||||
**Precondition**: Empty `tiles` table for the chosen cell; valid `GPS` JWT.
|
||||
**Expected**: HTTP 200; response body has `items[0].status == "accepted"` and a non-empty `tileId`; a new row exists in `tiles` with `source='uav'`, `captured_at` matching the request (UTC, ≤ 1 s drift), `file_path == 'tiles/uav/{z}/{x}/{y}.jpg'`; the file exists on disk at that path with the uploaded bytes.
|
||||
**Pass criterion**: All of the above true.
|
||||
**AC trace**: AZ-488 AC-1.
|
||||
|
||||
## BT-14: UAV Upload — 3-Item Mixed Batch Returns Per-Item Results
|
||||
|
||||
**Trigger**: POST `/api/satellite/upload` with a 3-item batch where item-1 is a valid 256×256 JPEG, item-2 is a 512×512 JPEG (wrong dimensions), item-3 has PNG magic bytes (wrong format).
|
||||
**Precondition**: Empty `tiles` table; valid `GPS` JWT.
|
||||
**Expected**: HTTP 200; `items[0].status == "accepted"` with a `tileId`; `items[1].status == "rejected"` with `rejectReason == "WRONG_DIMENSIONS"`; `items[2].status == "rejected"` with `rejectReason == "INVALID_FORMAT"`. Exactly one new row appears in `tiles` (for item-1 only). No file written for items 2 or 3.
|
||||
**Pass criterion**: status array matches `[accepted, rejected, rejected]` AND reasons match exactly AND `COUNT(*) WHERE source='uav'` == 1 for the test region.
|
||||
**AC trace**: AZ-488 AC-2, AC-7a, AC-7c.
|
||||
|
||||
## BT-15: UAV Upload — Multi-Source Coexistence with `google_maps`
|
||||
|
||||
**Trigger**: Pre-seed `tiles` (raw INSERT) with a `source='google_maps'` row at `(L, Ln, z=18, size_m=200)` and `captured_at = T1 = now − 2h`. Then POST `/api/satellite/upload` with a UAV tile for the same cell and `capturedAt = T2 = now`.
|
||||
**Precondition**: AZ-484 migration 013 applied (5-column unique index in place); valid `GPS` JWT.
|
||||
**Expected**: HTTP 200; both rows exist in `tiles` after upload (no overwrite of the google_maps row); a follow-up `GetByTileCoordinatesAsync(L, Ln, 18, 200)` returns the `source='uav'` row (per AZ-484 selection rule: max `captured_at` across sources).
|
||||
**Pass criterion**: `SELECT source FROM tiles WHERE ...` returns both `'google_maps'` AND `'uav'`; the repository read returns the UAV row.
|
||||
**AC trace**: AZ-488 AC-3; cross-references AZ-484 AC-1 (storage Inv-3) and AZ-484 AC-2 (selection rule).
|
||||
|
||||
## BT-16: UAV Upload — Same-Source UPSERT Collapses to One Row
|
||||
|
||||
**Trigger**: POST `/api/satellite/upload` with a UAV tile for cell `(L, Ln, 18, 200)` at `capturedAt = T1 = now − 30m`, then a second POST for the same cell at `capturedAt = T2 = now` with different image bytes (different `seed`).
|
||||
**Precondition**: Cell is empty for `source='uav'` before T1; valid `GPS` JWT.
|
||||
**Expected**: HTTP 200 for both calls. After the second call, exactly one `source='uav'` row remains for the cell with `captured_at == T2`; the JPEG at `./tiles/uav/{z}/{x}/{y}.jpg` is overwritten with the T2 bytes. Any pre-existing `source='google_maps'` row is untouched.
|
||||
**Pass criterion**: `SELECT COUNT(*) FROM tiles WHERE source='uav' AND (L, Ln, 18, 200)` == 1 AND `MAX(captured_at) ≈ T2` AND on-disk JPEG checksum matches the T2 upload.
|
||||
**AC trace**: AZ-488 AC-4; cross-references AZ-484 AC-3.
|
||||
|
||||
## BT-17: UAV Upload — Quality-Gate Rule-Ordering Determinism
|
||||
|
||||
**Trigger**: POST `/api/satellite/upload` with a single item that violates BOTH Rule 1 (PNG magic instead of JPEG) AND Rule 3 (512×512 dimensions). Authenticated with `GPS` permission.
|
||||
**Expected**: HTTP 200; `items[0].status == "rejected"` with `rejectReason == "INVALID_FORMAT"` (Rule 1 fires first; Rule 3 never evaluated).
|
||||
**Pass criterion**: rejectReason equals exactly `INVALID_FORMAT`; never `WRONG_DIMENSIONS`.
|
||||
**AC trace**: AZ-488 AC-7; rule-ordering invariant from `_docs/02_document/contracts/api/uav-tile-upload.md` v1.0.0.
|
||||
|
||||
## Cycle 2 — AZ-487 Endpoint Parity (existing endpoints with Bearer token)
|
||||
|
||||
## BT-18: Existing Tile Endpoint Returns Identical Body with Valid Bearer
|
||||
|
||||
**Trigger**: GET `/api/satellite/tiles/latlon?Latitude=47.461747&Longitude=37.647063&ZoomLevel=18` with a valid Bearer token.
|
||||
**Precondition**: Tile may or may not be cached.
|
||||
**Expected**: Response body is structurally identical to BT-01 (`tileId`, `zoomLevel == 18`, `tileSizePixels == 256`, `imageType == "jpg"`, `filePath` matches `tiles/18/*/*`).
|
||||
**Pass criterion**: status == 200 AND BT-01's pass criterion AND no behavioral change vs pre-AZ-487 baseline.
|
||||
**AC trace**: AZ-487 AC-4 (handler unchanged); validates AZ-487 AC-8 (existing suite parity).
|
||||
|
||||
|
||||
@@ -23,3 +23,29 @@
|
||||
**Trigger**: Queue 25 region requests
|
||||
**Observable**: Processing parallelism
|
||||
**Pass criterion**: At most 20 regions processing simultaneously (configurable via ProcessingConfig.MaxConcurrentRegions); remaining wait in queue
|
||||
|
||||
---
|
||||
|
||||
## Cycle 2 — AZ-488 UAV Upload Limits
|
||||
|
||||
## RL-05: UAV Batch Size Cap (`MaxBatchSize = 100`)
|
||||
|
||||
**Trigger**: POST `/api/satellite/upload` with a multipart envelope containing 101 metadata entries (`MaxBatchSize + 1`) and 101 placeholder file parts. Authenticated with `GPS` permission.
|
||||
**Observable**: HTTP status code, response body, side-effects on `tiles` table and `./tiles/uav/`.
|
||||
**Pass criterion**: status == 400 with an envelope-level error (NOT a per-item reject array); zero new rows in `tiles`; zero new files under `./tiles/uav/`. Reject happens before any item is processed — confirmed by observing zero entries in the per-item result array.
|
||||
**Source**: AZ-488 AC-8; configurable via `UavQualityConfig.MaxBatchSize`.
|
||||
|
||||
## RL-06: UAV Per-Item Size Cap (`MaxBytes = 5 MiB`)
|
||||
|
||||
**Trigger**: POST `/api/satellite/upload` with a 1-item batch whose JPEG body is exactly `MaxBytes + 1 = 5 * 1024 * 1024 + 1` bytes (within Kestrel `MaxRequestBodySize` but above the per-item cap).
|
||||
**Observable**: HTTP status code, per-item `rejectReason`.
|
||||
**Pass criterion**: status == 200; `items[0].status == "rejected"`; `items[0].rejectReason == "SIZE_OUT_OF_BAND"`; no row, no file. Lower-bound twin: a 1-item batch at `MinBytes − 1` bytes also rejects with `SIZE_OUT_OF_BAND`.
|
||||
**Source**: AZ-488 AC-7b; configurable via `UavQualityConfig.MinBytes` / `MaxBytes`.
|
||||
|
||||
## RL-07: Kestrel Request-Body Cap For UAV Endpoint (`MaxBatchSize × MaxBytes` envelope)
|
||||
|
||||
**Trigger**: POST `/api/satellite/upload` with a multipart envelope whose total byte length exceeds `MaxBatchSize × MaxBytes` (the cap Program.cs configures via `KestrelServerOptions.Limits.MaxRequestBodySize` and `FormOptions.MultipartBodyLengthLimit`).
|
||||
**Observable**: HTTP status code, response body.
|
||||
**Pass criterion**: status == 413 (Payload Too Large) OR 400 with a body-size error; no row, no file; per-item result array NOT present (Kestrel/Form parser rejects before MVC binding). Behavior must NOT degrade into OOM, infinite buffering, or disk-fill on the server side.
|
||||
**Source**: AZ-488 § Risk 3 (Disk-fill mitigation); enforces the multiplicative envelope cap from `UavQualityConfig`.
|
||||
|
||||
|
||||
@@ -23,3 +23,66 @@
|
||||
**Trigger**: POST /api/satellite/route with invalid JSON body
|
||||
**Expected**: Parse error returned
|
||||
**Pass criterion**: HTTP 400; error message indicates parsing failure; no crash
|
||||
|
||||
---
|
||||
|
||||
## Cycle 2 — AZ-487 JWT validation baseline
|
||||
|
||||
The pre-AZ-487 assumption "no authentication" is superseded by these scenarios. The SEC-01..SEC-04 scenarios above still hold (they probe input handling, not the auth layer), but every authenticated test variant must now attach a Bearer token.
|
||||
|
||||
## SEC-05: Anonymous Request to Any Authenticated Endpoint Returns 401
|
||||
|
||||
**Trigger**: GET `/api/satellite/tiles/latlon?Latitude=...&Longitude=...&ZoomLevel=18` (or any `/api/satellite/*` endpoint) with NO `Authorization` header.
|
||||
**Precondition**: API running with `JWT_SECRET` configured.
|
||||
**Expected**: HTTP 401 Unauthorized; `WWW-Authenticate: Bearer` header present; response body does not leak validation internals.
|
||||
**Pass criterion**: status == 401 AND `WWW-Authenticate` header starts with `Bearer`.
|
||||
**AC trace**: AZ-487 AC-1.
|
||||
|
||||
## SEC-06: Expired Token Returns 401
|
||||
|
||||
**Trigger**: Same request as SEC-05 carrying a JWT signed with the configured secret but with `exp` in the past (clock-skew margin already exceeded).
|
||||
**Expected**: HTTP 401; the failure reason surfaces via `WWW-Authenticate` (e.g. `error="invalid_token"`, `error_description="The token is expired"`), never in the response body.
|
||||
**Pass criterion**: status == 401 AND response body does not contain `Expires:` / `NotBefore:` / stack traces / internal exception types.
|
||||
**AC trace**: AZ-487 AC-2.
|
||||
|
||||
## SEC-07: Tampered Signature Returns 401
|
||||
|
||||
**Trigger**: Same request as SEC-05 carrying a JWT whose payload was modified after signing so the HMAC no longer verifies.
|
||||
**Expected**: HTTP 401; body free of cryptographic detail.
|
||||
**Pass criterion**: status == 401 AND request never reaches downstream handlers (no DB write, no Google Maps fetch).
|
||||
**AC trace**: AZ-487 AC-3.
|
||||
|
||||
## SEC-08: Startup Fails on Missing / Short Secret
|
||||
|
||||
**Trigger**: Boot the API container with `JWT_SECRET` unset, empty, or shorter than 32 bytes.
|
||||
**Observable**: Container exit code, stdout error message.
|
||||
**Pass criterion**: container exits non-zero within 10s of start; stderr contains a message identifying the missing or short `JWT_SECRET` and the 32-byte minimum; Kestrel never binds to its port.
|
||||
**AC trace**: AZ-487 AC-5. Behavioral test — no input data.
|
||||
|
||||
## SEC-09: Valid Token Reaches Handler Unchanged
|
||||
|
||||
**Trigger**: GET `/api/satellite/tiles/latlon?...` with a JWT signed by the configured secret and `exp` in the future.
|
||||
**Expected**: Response is byte-identical (status, body, headers other than `Authorization`/`WWW-Authenticate`) to the pre-AZ-487 baseline for the same parameters.
|
||||
**Pass criterion**: status == 200 AND response body matches BT-01 expected schema.
|
||||
**AC trace**: AZ-487 AC-4. Also exercised by AZ-487 AC-8 / integration smoke parity.
|
||||
|
||||
---
|
||||
|
||||
## Cycle 2 — AZ-488 UAV upload authorization
|
||||
|
||||
## SEC-10: Valid Token Without GPS Permission Returns 403 on UAV Upload
|
||||
|
||||
**Trigger**: POST `/api/satellite/upload` carrying a JWT with `permissions: ["FL"]` (no `GPS`); body is an otherwise-valid 1-item batch.
|
||||
**Precondition**: AZ-487 in place; AZ-488 endpoint registered with the `GPS` permission policy.
|
||||
**Expected**: HTTP 403 Forbidden; no row in `tiles`; no file under `./tiles/uav/`.
|
||||
**Pass criterion**: status == 403 AND `SELECT COUNT(*) FROM tiles WHERE source='uav' AND ...` == pre-test count AND uploaded file does NOT exist on disk.
|
||||
**AC trace**: AZ-488 AC-6.
|
||||
|
||||
## SEC-11: Reject-Reason Details Do Not Leak Server Internals
|
||||
|
||||
**Trigger**: POST `/api/satellite/upload` with a batch where item-1 deliberately fails Rule 5 (`IMAGE_TOO_UNIFORM`) and item-2 deliberately fails Rule 1 (`INVALID_FORMAT`).
|
||||
**Precondition**: Authenticated request with `GPS` permission.
|
||||
**Expected**: HTTP 200 with per-item results; each `rejectDetails` is short, human-readable, and contains none of: server-side file paths, exception type names, stack traces, internal class names, secrets, or hostnames.
|
||||
**Pass criterion**: For every rejected item, `rejectDetails` matches `^[A-Za-z0-9 .,()<>=:%/-]{0,200}$` AND contains no path separator (`/` or `\`) followed by a directory name from the server image (`tiles`, `src`, `obj`, `bin`).
|
||||
**AC trace**: AZ-488 § Security NFR.
|
||||
|
||||
|
||||
@@ -33,6 +33,29 @@
|
||||
| AZ-484 AC-5 | Google Maps download path stamps Source='google_maps' (wire) + CapturedAt UTC | `BuildTileEntity_SetsGoogleMapsSourceAndUtcCapturedAt_AZ484_AC5` (unit) | ✓ |
|
||||
| AZ-484 AC-6 | Existing region/route flows unchanged post-T1 (200 unit + smoke baseline preserved) | Full unit suite (213 tests) + integration smoke scenarios BT-01..BT-12 | ✓ |
|
||||
| AZ-484 AC-7 | Vision + contract docs amended (architecture.md, glossary.md, module-layout.md, tile-storage.md frozen v1.0.0) | doc-state AC; verified by `monorepo-document` reviews | ✓ |
|
||||
| AZ-487 AC-1 | Anonymous request returns 401 from every authenticated endpoint | SEC-05 (blackbox); `JwtIntegrationTests.AnonymousRequest_*_Returns401` (integration) | ✓ |
|
||||
| AZ-487 AC-2 | Expired token returns 401; no internal leak in body | SEC-06 (blackbox); `JwtIntegrationTests.ExpiredToken_Returns401` (integration) | ✓ |
|
||||
| AZ-487 AC-3 | Tampered signature returns 401 | SEC-07 (blackbox); `JwtIntegrationTests.InvalidSignature_Returns401` (integration) | ✓ |
|
||||
| AZ-487 AC-4 | Valid token reaches handler with identical response | SEC-09, BT-18 (blackbox); `JwtIntegrationTests.ValidToken_Returns200_OnHealthyEndpoint` (integration) | ✓ |
|
||||
| AZ-487 AC-5 | Startup fails on missing / short `JWT_SECRET` | SEC-08 (behavioral); `AuthenticationServiceCollectionExtensionsTests.AddSatelliteJwt_Throws*` (unit) | ✓ |
|
||||
| AZ-487 AC-6 | `HttpContext.User` exposes claims (`sub`, `permissions`, …) | `JwtTokenFactoryTests.Create_WithExtraClaims_PropagatesClaimsThroughValidation` (unit) + indirect via AZ-488 AC-6 (live permission check) | ✓ |
|
||||
| AZ-487 AC-7 | Swagger UI Authorize button works | `JwtIntegrationTests.SwaggerDocument_AdvertisesBearerSecurityScheme` (integration; programmatic equivalent of UI flow) | ◐ doc-verified |
|
||||
| AZ-487 AC-8 | All existing tests pass with attached test token | Full `scripts/run-tests.sh --full` run (cycle 2 Step 11 — passed) | ✓ |
|
||||
| AZ-488 AC-1 | Happy-path 1-item batch persists with `source='uav'` | BT-13 (blackbox); `UavUploadTests.HappyPathSingleItem_PersistsRow` (integration) | ✓ |
|
||||
| AZ-488 AC-2 | 3-item mixed batch returns per-item results | BT-14 (blackbox); `UavUploadTests.MixedBatch_ReturnsPerItemResults` (integration) | ✓ |
|
||||
| AZ-488 AC-3 | UAV upload coexists with pre-seeded `google_maps` row | BT-15 (blackbox); `UavUploadTests.MultiSourceCoexistence_AZ484_Cycle2` (integration); reuses AZ-484 AC-1 + AC-2 invariants | ✓ |
|
||||
| AZ-488 AC-4 | Same-source UPSERT keeps one `source='uav'` row | BT-16 (blackbox); `UavUploadTests.SameSourceUpsert_AZ484_Cycle2` (integration); reuses AZ-484 AC-3 invariant | ✓ |
|
||||
| AZ-488 AC-5 | Unauthenticated upload returns 401 (covered by AZ-487) | `UavUploadTests.NoToken_Returns401` (integration); AZ-487 AC-1 row covers contract | ✓ |
|
||||
| AZ-488 AC-6 | Authenticated request without `GPS` permission returns 403 | SEC-10 (blackbox); `UavUploadTests.ValidTokenWithoutGpsPermission_Returns403` (integration); `PermissionsRequirementTests` (unit) | ✓ |
|
||||
| AZ-488 AC-7a | `INVALID_FORMAT` reject reason on wrong content-type or magic bytes | `UavTileQualityGateTests.Validate_NonJpegContentType_*` and `Validate_WrongMagicBytes_*` (unit) | ✓ |
|
||||
| AZ-488 AC-7b | `SIZE_OUT_OF_BAND` reject reason on bytes outside `[MinBytes, MaxBytes]` | RL-06 (resource-limit); `UavTileQualityGateTests.Validate_BytesBelowMin_*` and `Validate_BytesAboveMax_*` (unit) | ✓ |
|
||||
| AZ-488 AC-7c | `WRONG_DIMENSIONS` reject reason on non-256×256 images | BT-14, BT-17 (blackbox); `UavTileQualityGateTests.Validate_WrongDimensions_*` (unit) | ✓ |
|
||||
| AZ-488 AC-7d | `CAPTURED_AT_FUTURE` / `CAPTURED_AT_TOO_OLD` reject reasons | `UavTileQualityGateTests.Validate_CapturedAtFuture_*` and `Validate_CapturedAtTooOld_*` (unit) | ✓ |
|
||||
| AZ-488 AC-7e | `IMAGE_TOO_UNIFORM` reject reason on uniform / low-variance JPEGs | `UavTileQualityGateTests.Validate_UniformGreyImage_RejectsImageTooUniform` (unit) | ✓ |
|
||||
| AZ-488 AC-7 (ordering) | First-applicable rule wins (e.g. format-fail beats dimensions-fail) | BT-17 (blackbox); `UavTileQualityGateTests.Validate_MultipleViolations_*` (unit) | ✓ |
|
||||
| AZ-488 AC-8 | Oversized batch (> `MaxBatchSize`) returns 400 envelope error | RL-05 (resource-limit); `UavUploadTests.OversizedBatch_Returns400` (integration) | ✓ |
|
||||
| AZ-488 AC-9 | Contract `uav-tile-upload.md` v1.0.0 frozen and matches implementation | doc-state AC; verified by Step 13 (Update Docs) review | ✓ |
|
||||
| AZ-488 AC-10 | All existing tests + new AZ-487/AZ-488 tests pass; no AZ-484 regression | Full `scripts/run-tests.sh --full` run (cycle 2 Step 11 — passed) | ✓ |
|
||||
|
||||
## Restrictions → Test Mapping
|
||||
|
||||
@@ -45,7 +68,7 @@
|
||||
| Max 20 concurrent regions | RL-04 | ✓ |
|
||||
| Queue capacity 1000 | RS-04, RL-02 | ✓ |
|
||||
| Max ZIP 50 MB | RL-01 | ✓ |
|
||||
| No authentication | SEC-01 through SEC-04 (all requests accepted without auth) | ✓ |
|
||||
| ~~No authentication~~ → JWT (HS256) on every endpoint, `GPS` permission on UAV upload | SEC-01..SEC-04 (input handling); SEC-05..SEC-09 (AZ-487 JWT layer); SEC-10..SEC-11 (AZ-488 permission + leak hygiene) | ✓ (superseded by AZ-487 + AZ-488, cycle 2) |
|
||||
|
||||
## NFRs → Test Mapping
|
||||
|
||||
@@ -53,6 +76,13 @@
|
||||
|-----|--------|-------|----------|
|
||||
| AZ-484 Perf — `GetTilesByRegionAsync` p95 ≤ 1.10 × pre-AZ-484 baseline | AZ-484 task spec § Non-Functional Requirements | PT-07 (recorded; active perf comparison deferred to Step 15) | ◐ recorded |
|
||||
| AZ-484 Compatibility — no public HTTP response field added/removed; vestigial `maps_version`/`version` columns preserved (nullable) | AZ-484 task spec § Non-Functional Requirements | Existing integration suite (no API contract change observable); BT-01 / region status responses verify response shape | ✓ |
|
||||
| AZ-487 Performance — JWT validation < 1 ms overhead per request | AZ-487 task spec § Non-Functional Requirements | Not separately measured (HMAC-SHA256 + claims parse is sub-millisecond on any modern x86; no caching needed). Re-measure if PT-07 harness shows aggregate regression. | ◐ recorded |
|
||||
| AZ-487 Security — `RequireSignedTokens`, `RequireExpirationTime`, `ClockSkew = 30 s`, secret ≥ 32 bytes | AZ-487 task spec § Non-Functional Requirements + Constraints | `AuthenticationServiceCollectionExtensionsTests.AddSatelliteJwt_ThrowsOnShortSecret` (unit) + SEC-06/SEC-07 (blackbox) | ✓ |
|
||||
| AZ-487 Reliability — Fail-fast on missing / short `JWT_SECRET` at startup | AZ-487 task spec § Non-Functional Requirements | SEC-08 (behavioral) + unit `AddSatelliteJwt_ThrowsOnMissingSecret` | ✓ |
|
||||
| AZ-488 Performance — Per-item gate cost < 50 ms; p95 batch-of-10 < 2 s | AZ-488 task spec § Non-Functional Requirements | PT-08 (Deferred — harness reuses PT-07 work; tracked in `_docs/_process_leftovers/2026-05-11_perf-pt07-harness.md`). Active enforcement starts at cycle 2 Step 15. | ◐ recorded (Deferred) |
|
||||
| AZ-488 Reliability — File-first then DB row; per-item failures never fail the batch envelope (except 400/401/403) | AZ-488 task spec § Non-Functional Requirements | BT-14 (mixed-batch shows per-item isolation); `UavTileUploadHandlerTests.*PersistAsync*` (unit); reject reason `STORAGE_FAILURE` defined in contract for the orphan-row recovery path | ✓ |
|
||||
| AZ-488 Compatibility — Replaces 501 stub; coexists with AZ-484 `tile-storage` v1.0.0 contract on the write side | AZ-488 task spec § Non-Functional Requirements + Contract | `StubAndErrorContractTests` updated to drop the stub-501 expectation; BT-15 + BT-16 validate the AZ-484 invariants under live UAV writes | ✓ |
|
||||
| AZ-488 Security — Reject details never leak server internals; integer-only file-path construction | AZ-488 task spec § Non-Functional Requirements + Risk 2 | SEC-11 (blackbox); `UavTileFilePathTests` (unit) | ✓ |
|
||||
|
||||
## Coverage Summary
|
||||
|
||||
@@ -60,9 +90,15 @@
|
||||
|----------|-------------|-------------|---------------------|
|
||||
| Blackbox (positive) | 12 | 19/22 | — |
|
||||
| Blackbox (negative) | 5 | — | — |
|
||||
| Performance | 6 | 2 | 1 |
|
||||
| Performance | 8 | 4 | 1 |
|
||||
| Resilience | 6 | 4 | 3 |
|
||||
| Security | 4 | — | 1 |
|
||||
| Resource Limits | 4 | 3 | 4 |
|
||||
| Security | 11 | 9 (AZ-487 AC-1..AC-7, AZ-488 AC-6, leak-hygiene NFR) | 1 (AZ-487 supersedes "No authentication") |
|
||||
| Resource Limits | 7 | 5 | 4 |
|
||||
| Cycle 1 — AZ-484 (integration + unit) | 6 | 7/7 | — |
|
||||
| **Total** | **43** | **29/29 (100%)** | **8/8 (100%)** |
|
||||
| Cycle 2 — AZ-487 (integration + unit + behavioral) | 4 integration + 3 unit + 1 behavioral | 8/8 | — |
|
||||
| Cycle 2 — AZ-488 (integration + unit + blackbox) | 7 integration + 14 unit + 6 blackbox | 10/10 | — |
|
||||
| **Total** | **78** | **47/47 (100%)** | **8/8 (100%)** |
|
||||
|
||||
**Coverage shape notes (Cycle 2):**
|
||||
- AZ-487 AC-7 (Swagger UI Authorize) is verified programmatically (`SwaggerDocument_AdvertisesBearerSecurityScheme`) rather than via a real UI flow; marked `◐ doc-verified`. The end-to-end browser-UI Authorize-button check remains a manual smoke before deploy.
|
||||
- AZ-487 perf NFR (< 1 ms JWT overhead) and AZ-488 perf NFR (PT-08) are `◐ recorded`; active enforcement deferred to cycle 2 Step 15 (Performance Test). Both depend on the shared PT-07 harness expansion (`_docs/_process_leftovers/2026-05-11_perf-pt07-harness.md`).
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
## Current Step
|
||||
flow: existing-code
|
||||
step: 10
|
||||
name: Implement
|
||||
status: in_progress
|
||||
step: 14
|
||||
name: Security Audit
|
||||
status: not_started
|
||||
sub_step:
|
||||
phase: 6
|
||||
name: batch-loop
|
||||
detail: "batch 2 of 2 done (AZ-488); awaiting Step 11"
|
||||
phase: 0
|
||||
name: awaiting-invocation
|
||||
detail: ""
|
||||
retry_count: 0
|
||||
cycle: 2
|
||||
tracker: jira
|
||||
|
||||
Reference in New Issue
Block a user