[AZ-808] [AZ-811] Strict validation on region POST + lat/lon GET

AZ-808: FluentValidation for POST /api/satellite/request
- RegionRequestValidator: id non-empty, lat/lon/sizeMeters/zoomLevel ranges
- RequestRegionRequest: [JsonRequired] on every property, no implicit defaults
- Wired via .WithValidation<RequestRegionRequest>() in MapPost chain
- Unit + integration tests + curl probe script
- New contract: contracts/api/region-request.md v1.0.0

AZ-811: FluentValidation + envelope filter for GET /api/satellite/tiles/latlon
- GetTileByLatLonQuery: nullable record (double?/int?) so the minimal-API
  binder never short-circuits with BadHttpRequestException before filters
- GetTileByLatLonQueryValidator: Cascade(Stop) + NotNull + InclusiveBetween
  per param; missing surfaces as `\`<name>\` is required.`
- RejectUnknownQueryParamsEndpointFilter: reusable IEndpointFilter that
  rejects any query key outside the allowed set with errors[<key>] map;
  catches legacy `?Latitude=` typos and hostile probes (`?debug=1&admin=1`)
- Handler: [AsParameters] GetTileByLatLonQuery + .Value deref post-validator
- Unit (validator + filter) + integration tests + curl probe script
- New contract: contracts/api/tile-latlon.md v1.0.0

Shared hygiene
- Promote AssertErrorsContainsMention from per-test-file private helpers to
  ProblemDetailsAssertions (closes batch-1 Low-severity DRY warning)
- Sync Swagger param descriptions, README, blackbox/security/perf scripts,
  uuidv5 doc with the new lat/lon/zoom query-param names

Docs
- system-flows.md F1/F2 reference the new contracts + validation layers
- modules/api_program.md adds Api/Validators + Api/DTOs sections
- _autodev_state.md: batch 2 of 4 complete; next batch = AZ-809

All smoke tests green (mode=smoke, exit 0). AZ-808 + AZ-811 transitioned
to In Testing on Jira.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-22 16:29:41 +03:00
parent fcd494f67e
commit 34ee1e0b83
35 changed files with 1993 additions and 122 deletions
@@ -0,0 +1,106 @@
# Batch Report
**Batch**: 02 (cycle 8)
**Tasks**: AZ-808 (Region POST strict validation) + AZ-811 (lat/lon GET strict validation)
**Date**: 2026-05-22
## Task Results
| Task | Status | Files Modified | Tests | AC Coverage | Issues |
|------|--------|---------------|-------|-------------|--------|
| AZ-808_region_endpoint_validation | Done | 10 files (4 new) | smoke pass (mode=smoke, exit 0); 10 integration tests added | 8/8 ACs covered | none |
| AZ-811_latlon_get_endpoint_validation | Done | 19 files (8 new) | smoke pass; 8 integration tests + 4 filter unit tests + 9 validator unit tests added | 9/9 ACs covered | 1 Info (nullable DTO rationale, documented) |
## AC Test Coverage
### AZ-808 (8/8 ACs)
| AC | Coverage |
|----|----------|
| AC-1 | `RegionRequestValidator` exists at `SatelliteProvider.Api/Validators/RegionRequestValidator.cs` with rules for `id` (non-empty), `lat` (`[-90, 90]`), `lon` (`[-180, 180]`), `sizeMeters` (`[100, 10000]`), `zoomLevel` (`[0, 22]`). |
| AC-2 | Happy path: `RegionRequestValidationTests.HappyPath_Returns200` returns HTTP 200. Smoke green. |
| AC-3 | Wired via `.WithValidation<RequestRegionRequest>()` in `Program.cs` MapPost chain. |
| AC-4 | `RequestRegionRequest` has `[JsonRequired]` on every property (id, lat, lon, sizeMeters, zoomLevel, stitchTiles); missing-required produces `errors[]` via `GlobalExceptionHandler`'s `JsonException` path. Tested by `MissingId_Returns400` and `MissingStitchTiles_Returns400`. |
| AC-5 | Unit tests `SatelliteProvider.Tests/Validators/RegionRequestValidatorTests.cs` — 11 methods covering each rule with positive + negative cases. |
| AC-6 | Integration tests `SatelliteProvider.IntegrationTests/RegionRequestValidationTests.cs` — 10 methods covering happy + 9 failure modes; all green in smoke. |
| AC-7 | New contract `_docs/02_document/contracts/api/region-request.md` v1.0.0 published. References `error-shape.md` v1.0.0 for 400 body shape. |
| AC-8 | Probe script `scripts/probe_region_validation.sh` covers happy + each failure mode via curl. |
### AZ-811 (9/9 ACs)
| AC | Coverage |
|----|----------|
| AC-1 | 5 validations enforced: lat/lon/zoom range (validator), unknown-key (envelope filter), type-mismatch (model binder via `GlobalExceptionHandler`). All produce HTTP 400 + ValidationProblemDetails per `error-shape.md` v1.0.0. |
| AC-2 | Happy path: `GetTileByLatLonValidationTests.HappyPath_Returns200` returns HTTP 200 + `DownloadTileResponse`. Smoke green. |
| AC-3 | `GetTileByLatLonQueryValidator` lives at `SatelliteProvider.Api/Validators/`; unit tests cover 9 methods (3 per RuleFor + 3 null cases). |
| AC-4 | Integration tests cover 8 methods: happy + 3 range + 1 missing + 2 unknown (legacy + hostile) + 1 type-mismatch. |
| AC-5 | New contract `_docs/02_document/contracts/api/tile-latlon.md` v1.0.0 published. References `error-shape.md` v1.0.0 + `tile-inventory.md` v2.0.0. |
| AC-6 | `_docs/02_document/modules/api_program.md::GetTileByLatLon Handler` updated; references the validator + new contract + the envelope filter ordering. |
| AC-7 | OpenAPI: `.Accepts<>` not needed for GET; `.Produces<DownloadTileResponse>(200)` + `.ProducesProblem(400)` declared on the endpoint chain. Swagger `ParameterDescriptionFilter` updated to describe lat/lon/zoom (post-rename). |
| AC-8 | Probe script `scripts/probe_latlon_validation.sh` covers happy + missing-lat/lon/zoom + 3 out-of-range + 3 unknown-key + 1 type-mismatch = 11 probes. |
| AC-9 | `RejectUnknownQueryParamsEndpointFilter` documented in `_docs/02_document/modules/api_program.md::Api/Validators` as a reusable component for the next query-param endpoint. |
## Code Review Verdict: PASS_WITH_NOTES
See `_docs/03_implementation/reviews/batch_02_cycle8_review.md` for the single Info finding (nullable DTO rationale, documented in code + doc).
## Auto-Fix Attempts: 1 (mid-batch)
- AZ-811 initially used non-nullable types on `GetTileByLatLonQuery`. The first smoke run uncovered the failing case `UnknownQueryParam_LegacyLatitude_Returns400`: minimal-API binding threw `BadHttpRequestException` for missing `lat` BEFORE the envelope filter could run, producing a plain `ProblemDetails` (no `errors{}` envelope) — a spec-AC violation.
- Root-cause investigation via diagnostic instrumentation (`Console.Error.WriteLine` in the filter + `Console.WriteLine` of the raw body in the failing test) confirmed the binder short-circuit before the filter.
- Fix: nullable types on the DTO + `NotNull` + `CascadeMode.Stop` in the validator + `.Value` dereference in the handler. Rationale documented in `GetTileByLatLonQuery.cs` and `api_program.md::Api/DTOs`.
- Smoke re-run after fix: all green (no skipped tests, no flakes).
## Stuck Agents: None
## Files Modified
### AZ-808
| Path | Kind |
|------|------|
| `SatelliteProvider.Common/DTO/RequestRegionRequest.cs` | `[JsonRequired]` on every property + removed implicit defaults |
| `SatelliteProvider.Api/Validators/RegionRequestValidator.cs` | **NEW** |
| `SatelliteProvider.Api/Program.cs` | `.WithValidation<RequestRegionRequest>()` + removed inline size check |
| `SatelliteProvider.Tests/Validators/RegionRequestValidatorTests.cs` | **NEW** |
| `SatelliteProvider.IntegrationTests/RegionRequestValidationTests.cs` | **NEW** |
| `SatelliteProvider.IntegrationTests/Program.cs` | Wired into smoke + full suites |
| `scripts/probe_region_validation.sh` | **NEW** |
| `_docs/02_document/contracts/api/region-request.md` | **NEW** v1.0.0 |
| `_docs/02_document/modules/api_program.md` | RequestRegion handler description |
| `_docs/02_document/system-flows.md` | F2 description |
### AZ-811
| Path | Kind |
|------|------|
| `SatelliteProvider.Api/DTOs/GetTileByLatLonQuery.cs` | **NEW** (nullable record) |
| `SatelliteProvider.Api/Validators/GetTileByLatLonQueryValidator.cs` | **NEW** |
| `SatelliteProvider.Api/Validators/RejectUnknownQueryParamsEndpointFilter.cs` | **NEW** (reusable) |
| `SatelliteProvider.Api/Program.cs` | Endpoint filter + .WithValidation + handler signature + .Value deref |
| `SatelliteProvider.Api/Swagger/ParameterDescriptionFilter.cs` | lat/lon/zoom descriptions |
| `SatelliteProvider.Tests/Validators/GetTileByLatLonQueryValidatorTests.cs` | **NEW** (9 methods) |
| `SatelliteProvider.Tests/Validators/RejectUnknownQueryParamsEndpointFilterTests.cs` | **NEW** (4 methods) |
| `SatelliteProvider.IntegrationTests/GetTileByLatLonValidationTests.cs` | **NEW** (8 methods) |
| `SatelliteProvider.IntegrationTests/TileTests.cs` | URL `?lat=&lon=&zoom=` |
| `SatelliteProvider.IntegrationTests/JwtIntegrationTests.cs` | `ProtectedTilesPath` const |
| `SatelliteProvider.IntegrationTests/SecurityTests.cs` | SQLi probe URL |
| `SatelliteProvider.IntegrationTests/Program.cs` | Wired into smoke + full suites |
| `scripts/probe_latlon_validation.sh` | **NEW** |
| `scripts/run-performance-tests.sh` | PT-01 URL update |
| `README.md` | Endpoint example |
| `_docs/02_document/contracts/api/tile-latlon.md` | **NEW** v1.0.0 |
| `_docs/02_document/modules/api_program.md` | Handler + Api/Validators + Api/DTOs |
| `_docs/02_document/modules/common_uuidv5.md` | Example URL |
| `_docs/02_document/system-flows.md` | F1 description |
| `_docs/02_document/tests/blackbox-tests.md` | BT-01/N01/N02/18 triggers |
| `_docs/02_document/tests/security-tests.md` | SEC-01/05 triggers |
### Shared
| Path | Kind |
|------|------|
| `SatelliteProvider.IntegrationTests/ProblemDetailsAssertions.cs` | Promoted `AssertErrorsContainsMention` to shared helper (closes batch-1 DRY warning) |
| `SatelliteProvider.IntegrationTests/TileInventoryValidationTests.cs` | Use shared helper |
| `SatelliteProvider.IntegrationTests/RegionFieldRenameTests.cs` | Use shared helper |
## Tracker
- AZ-808: To Do → In Progress (batch 2 start) → **In Testing** (post-smoke).
- AZ-811: To Do → In Progress (batch 2 start) → **In Testing** (post-smoke).
## Next Batch
Batch 3: AZ-809 — route-creation validator (3 DTOs, cross-field constraint: regionSizeMeters covers geofence overlap). Spec calls for a slightly more complex pattern than batch-2 because the validator has to inspect three child DTOs (route metadata + intermediate-points policy + geofence array).