From fcd494f67eac70f29bee28bd2915d599d15e5739 Mon Sep 17 00:00:00 2001 From: Oleksandr Bezdieniezhnykh Date: Fri, 22 May 2026 15:54:53 +0300 Subject: [PATCH] =?UTF-8?q?[AZ-812]=20Region=20API:=20rename=20Latitude/Lo?= =?UTF-8?q?ngitude=20=E2=86=92=20Lat/Lon=20(OSM=20convention)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirror of AZ-794 (inventory z/x/y rename). RequestRegionRequest.cs renames C# props Latitude→Lat / Longitude→Lon and adds [JsonPropertyName("lat"/"lon")] so the wire format is unambiguous under the AZ-795 strict-parsing stack (UnmappedMemberHandling.Disallow → legacy {"latitude":..,"longitude":..} now returns HTTP 400 instead of silently coercing). Updates all in-repo consumers: API handler (Program.cs), integration tests (Models.cs, RegionTests.cs, IdempotentPostTests.cs, SecurityTests.cs), the performance harness (run-performance-tests.sh PT-03/04/05/07), and module docs (common_dtos.md, api_program.md; system-flows.md F2 already used lat/lon). New RegionFieldRenameTests.cs covers AC-4 both directions (new format → 200, legacy format → 400). Smoke green; no regressions. region-request.md contract doc not bumped here — AZ-808 publishes v1.0.0 directly with the post-rename names per AZ-812 coordination clause. Batch 01 of cycle 8. PASS_WITH_WARNINGS (one Low DRY finding for follow-up test-helper consolidation; details in _docs/03_implementation/reviews/batch_01_cycle8_review.md). Co-authored-by: Cursor --- SatelliteProvider.Api/Program.cs | 4 +- .../DTO/RequestRegionRequest.cs | 7 +- .../IdempotentPostTests.cs | 4 +- SatelliteProvider.IntegrationTests/Models.cs | 9 +- SatelliteProvider.IntegrationTests/Program.cs | 2 + .../RegionFieldRenameTests.cs | 111 ++++++++++++++++++ .../RegionTests.cs | 4 +- .../SecurityTests.cs | 2 +- _docs/02_document/modules/api_program.md | 4 +- _docs/02_document/modules/common_dtos.md | 8 +- .../AZ-812_region_field_rename_to_osm.md | 0 .../batch_01_cycle8_report.md | 51 ++++++++ .../reviews/batch_01_cycle8_review.md | 70 +++++++++++ scripts/run-performance-tests.sh | 10 +- 14 files changed, 268 insertions(+), 18 deletions(-) create mode 100644 SatelliteProvider.IntegrationTests/RegionFieldRenameTests.cs rename _docs/02_tasks/{todo => done}/AZ-812_region_field_rename_to_osm.md (100%) create mode 100644 _docs/03_implementation/batch_01_cycle8_report.md create mode 100644 _docs/03_implementation/reviews/batch_01_cycle8_review.md diff --git a/SatelliteProvider.Api/Program.cs b/SatelliteProvider.Api/Program.cs index 6d3865f..e919b2c 100644 --- a/SatelliteProvider.Api/Program.cs +++ b/SatelliteProvider.Api/Program.cs @@ -348,8 +348,8 @@ async Task RequestRegion([FromBody] RequestRegionRequest request, IRegi var status = await regionService.RequestRegionAsync( request.Id, - request.Latitude, - request.Longitude, + request.Lat, + request.Lon, request.SizeMeters, request.ZoomLevel, request.StitchTiles); diff --git a/SatelliteProvider.Common/DTO/RequestRegionRequest.cs b/SatelliteProvider.Common/DTO/RequestRegionRequest.cs index 401bcd1..8e18de7 100644 --- a/SatelliteProvider.Common/DTO/RequestRegionRequest.cs +++ b/SatelliteProvider.Common/DTO/RequestRegionRequest.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace SatelliteProvider.Common.DTO; @@ -8,10 +9,12 @@ public record RequestRegionRequest public Guid Id { get; set; } [Required] - public double Latitude { get; set; } + [JsonPropertyName("lat")] + public double Lat { get; set; } [Required] - public double Longitude { get; set; } + [JsonPropertyName("lon")] + public double Lon { get; set; } [Required] public double SizeMeters { get; set; } diff --git a/SatelliteProvider.IntegrationTests/IdempotentPostTests.cs b/SatelliteProvider.IntegrationTests/IdempotentPostTests.cs index 4acac8b..31f6ea8 100644 --- a/SatelliteProvider.IntegrationTests/IdempotentPostTests.cs +++ b/SatelliteProvider.IntegrationTests/IdempotentPostTests.cs @@ -30,8 +30,8 @@ public static class IdempotentPostTests var body = JsonSerializer.Serialize(new { id = regionId, - latitude = 47.4617, - longitude = 37.6470, + lat = 47.4617, + lon = 37.6470, sizeMeters = 200, zoomLevel = 18, stitchTiles = false, diff --git a/SatelliteProvider.IntegrationTests/Models.cs b/SatelliteProvider.IntegrationTests/Models.cs index 19b3a55..4e594fb 100644 --- a/SatelliteProvider.IntegrationTests/Models.cs +++ b/SatelliteProvider.IntegrationTests/Models.cs @@ -17,8 +17,13 @@ public record DownloadTileResponse public record RequestRegionRequest { public Guid Id { get; set; } - public double Latitude { get; set; } - public double Longitude { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("lat")] + public double Lat { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("lon")] + public double Lon { get; set; } + public double SizeMeters { get; set; } public int ZoomLevel { get; set; } public bool StitchTiles { get; set; } = false; diff --git a/SatelliteProvider.IntegrationTests/Program.cs b/SatelliteProvider.IntegrationTests/Program.cs index d3686bf..216d89f 100644 --- a/SatelliteProvider.IntegrationTests/Program.cs +++ b/SatelliteProvider.IntegrationTests/Program.cs @@ -140,6 +140,7 @@ class Program await IdempotentPostTests.RunAll(httpClient); await TileInventoryTests.RunAll(httpClient); await TileInventoryValidationTests.RunAll(httpClient); + await RegionFieldRenameTests.RunAll(httpClient); await LeafletPathIndexOnlyTests.RunAll(connectionString); await MigrationTests.RunAll(); } @@ -164,6 +165,7 @@ class Program await IdempotentPostTests.RunAll(httpClient); await TileInventoryTests.RunAll(httpClient); await TileInventoryValidationTests.RunAll(httpClient); + await RegionFieldRenameTests.RunAll(httpClient); await LeafletPathIndexOnlyTests.RunAll(connectionString); await MigrationTests.RunAll(); } diff --git a/SatelliteProvider.IntegrationTests/RegionFieldRenameTests.cs b/SatelliteProvider.IntegrationTests/RegionFieldRenameTests.cs new file mode 100644 index 0000000..a8410d3 --- /dev/null +++ b/SatelliteProvider.IntegrationTests/RegionFieldRenameTests.cs @@ -0,0 +1,111 @@ +using System.Net.Http.Json; +using System.Text; +using System.Text.Json; + +namespace SatelliteProvider.IntegrationTests; + +// AZ-812: wire-format rename for POST /api/satellite/request. +// `RequestRegionRequest` now uses `lat`/`lon` (OSM convention) on the wire, +// replacing the previous verbose `latitude`/`longitude`. The strict-parsing +// infrastructure landed by AZ-795 (UnmappedMemberHandling.Disallow + +// GlobalExceptionHandler) means the old wire format must now be rejected +// explicitly, not silently coerced. AC-4 from the AZ-812 task spec. +public static class RegionFieldRenameTests +{ + private const string RegionPath = "/api/satellite/request"; + + public static async Task RunAll(HttpClient httpClient) + { + RouteTestHelpers.PrintTestHeader("Test: Region endpoint OSM field-name rename (AZ-812)"); + + await NewLatLonFormat_Returns200(httpClient); + await OldLatitudeLongitudeFormat_Returns400(httpClient); + + Console.WriteLine("✓ Region field-rename tests: PASSED"); + } + + private static async Task NewLatLonFormat_Returns200(HttpClient httpClient) + { + Console.WriteLine(); + Console.WriteLine("AZ-812 AC-4 (positive): new {lat,lon} wire format → HTTP 200"); + + // Arrange + var regionId = Guid.NewGuid(); + var body = $"{{\"id\":\"{regionId}\",\"lat\":47.461747,\"lon\":37.647063,\"sizeMeters\":200,\"zoomLevel\":18,\"stitchTiles\":false}}"; + + // Act + var response = await PostJsonAsync(httpClient, body); + var status = (int)response.StatusCode; + var responseBody = await response.Content.ReadAsStringAsync(); + + // Assert + if (status != 200) + { + throw new Exception($"AZ-812 AC-4 positive: expected HTTP 200 for {{lat,lon}} body, got {status}. Body: {responseBody}"); + } + + Console.WriteLine(" ✓ {lat,lon} body accepted with HTTP 200"); + } + + private static async Task OldLatitudeLongitudeFormat_Returns400(HttpClient httpClient) + { + Console.WriteLine(); + Console.WriteLine("AZ-812 AC-4 (negative): legacy {latitude,longitude} wire format → HTTP 400 (UnmappedMemberHandling.Disallow)"); + + // Arrange — exact pre-AZ-812 wire format; must now fail explicitly instead + // of silently mapping to the renamed Lat/Lon properties. + var regionId = Guid.NewGuid(); + var body = $"{{\"id\":\"{regionId}\",\"latitude\":47.461747,\"longitude\":37.647063,\"sizeMeters\":200,\"zoomLevel\":18,\"stitchTiles\":false}}"; + + // Act + var response = await PostJsonAsync(httpClient, body); + var problem = await ProblemDetailsAssertions.ReadProblemDetailsAsync(response, "AZ-812 legacy field names"); + + // Assert + ProblemDetailsAssertions.AssertValidationProblem(problem, expectedStatus: 400, label: "AZ-812 legacy field names"); + AssertErrorsContainsMention(problem, expectedMention: "latitude", label: "AZ-812 legacy field names"); + + Console.WriteLine(" ✓ Legacy {latitude,longitude} body rejected with HTTP 400; errors map names the unknown field"); + } + + private static Task PostJsonAsync(HttpClient httpClient, string body) + { + var content = new StringContent(body, Encoding.UTF8, "application/json"); + return httpClient.PostAsync(RegionPath, content); + } + + private static void AssertErrorsContainsMention(JsonElement problem, string expectedMention, string label) + { + if (!problem.TryGetProperty("errors", out var errorsEl) || errorsEl.ValueKind != JsonValueKind.Object) + { + throw new Exception($"{label}: expected 'errors' object in ProblemDetails body."); + } + + var found = false; + foreach (var prop in errorsEl.EnumerateObject()) + { + if (prop.Name.Contains(expectedMention, StringComparison.OrdinalIgnoreCase)) + { + found = true; + break; + } + + foreach (var msg in prop.Value.EnumerateArray()) + { + if (msg.GetString()?.Contains(expectedMention, StringComparison.OrdinalIgnoreCase) == true) + { + found = true; + break; + } + } + + if (found) break; + } + + if (!found) + { + var paths = string.Join(", ", errorsEl.EnumerateObject().Select(p => p.Name)); + throw new Exception($"{label}: expected '{expectedMention}' to appear in errors keys or messages. Available paths: {paths}."); + } + } +} diff --git a/SatelliteProvider.IntegrationTests/RegionTests.cs b/SatelliteProvider.IntegrationTests/RegionTests.cs index 5ae6c30..afd776f 100644 --- a/SatelliteProvider.IntegrationTests/RegionTests.cs +++ b/SatelliteProvider.IntegrationTests/RegionTests.cs @@ -84,8 +84,8 @@ public static class RegionTests var requestRegion = new RequestRegionRequest { Id = regionId, - Latitude = latitude, - Longitude = longitude, + Lat = latitude, + Lon = longitude, SizeMeters = sizeMeters, ZoomLevel = zoomLevel, StitchTiles = stitchTiles diff --git a/SatelliteProvider.IntegrationTests/SecurityTests.cs b/SatelliteProvider.IntegrationTests/SecurityTests.cs index 787f8f3..b2a317d 100644 --- a/SatelliteProvider.IntegrationTests/SecurityTests.cs +++ b/SatelliteProvider.IntegrationTests/SecurityTests.cs @@ -66,7 +66,7 @@ public static class SecurityTests Console.WriteLine("SEC-03: Oversized region request (sizeMeters beyond allowed cap)"); var regionId = Guid.NewGuid(); - var body = $"{{\"id\":\"{regionId}\",\"latitude\":47.461747,\"longitude\":37.647063,\"sizeMeters\":1000000,\"zoomLevel\":18,\"stitchTiles\":false}}"; + var body = $"{{\"id\":\"{regionId}\",\"lat\":47.461747,\"lon\":37.647063,\"sizeMeters\":1000000,\"zoomLevel\":18,\"stitchTiles\":false}}"; var content = new StringContent(body, Encoding.UTF8, "application/json"); var response = await httpClient.PostAsync("/api/satellite/request", content); var status = (int)response.StatusCode; diff --git a/_docs/02_document/modules/api_program.md b/_docs/02_document/modules/api_program.md index ebce749..bf1956b 100644 --- a/_docs/02_document/modules/api_program.md +++ b/_docs/02_document/modules/api_program.md @@ -21,9 +21,11 @@ Application entry point. Configures DI container, sets up middleware, defines mi ### Local Records (defined in Program.cs) - `GetSatelliteTilesResponse`, `SatelliteTile` — MGRS response stubs - `DownloadTileResponse` — tile download response -- `RequestRegionRequest` — region request body - `ParameterDescriptionFilter` — Swagger operation filter +### Common/DTO (region API) +- `RequestRegionRequest` — `POST /api/satellite/request` body. Moved out of Program.cs by AZ-369. Fields: `Id` (Guid), `Lat`/`Lon` (double, JSON `lat`/`lon` per AZ-812 cycle 8 OSM rename), `SizeMeters`, `ZoomLevel` (int, default 18), `StitchTiles` (bool, default false). + ### Api/DTOs (AZ-488) - `UavTileBatchUploadRequest` — multipart envelope with `metadata` (JSON string) and `files` (`IFormFileCollection`) diff --git a/_docs/02_document/modules/common_dtos.md b/_docs/02_document/modules/common_dtos.md index 7091b5f..47bfab3 100644 --- a/_docs/02_document/modules/common_dtos.md +++ b/_docs/02_document/modules/common_dtos.md @@ -33,8 +33,14 @@ Metadata about a stored tile (mirrors `TileEntity` but without DB-specific conce - `Version` (int?), `FilePath` (string) - `CreatedAt`, `UpdatedAt` (DateTime) +### RequestRegionRequest (renamed by AZ-812 cycle 8 — OSM convention) +API request body for `POST /api/satellite/request` (region enqueue). Defined in `SatelliteProvider.Common/DTO/RequestRegionRequest.cs`. Moved out of `Program.cs` by AZ-369. +- `Id` (Guid), `Lat` (double, JSON: `"lat"`), `Lon` (double, JSON: `"lon"`), `SizeMeters` (double) +- `ZoomLevel` (int, default 18), `StitchTiles` (bool, default false) +- AZ-812 renamed C# props `Latitude/Longitude` → `Lat/Lon` and added `[JsonPropertyName("lat")]` / `[JsonPropertyName("lon")]` to make the wire format unambiguous. With `JsonSerializerOptions.UnmappedMemberHandling.Disallow` active (AZ-795), the old `latitude`/`longitude` wire shape now returns HTTP 400. + ### RegionRequest -Queue message for async region processing. +Internal queue message for async region processing (not a wire-format DTO — exchanged between the API handler and `RegionProcessingService` background worker via `IRegionRequestQueue`). Distinct from `RequestRegionRequest` above; intentionally kept on `Latitude`/`Longitude` because the queue is in-process only. - `Id` (Guid), `Latitude`, `Longitude` (double), `SizeMeters` (double) - `ZoomLevel` (int), `StitchTiles` (bool) diff --git a/_docs/02_tasks/todo/AZ-812_region_field_rename_to_osm.md b/_docs/02_tasks/done/AZ-812_region_field_rename_to_osm.md similarity index 100% rename from _docs/02_tasks/todo/AZ-812_region_field_rename_to_osm.md rename to _docs/02_tasks/done/AZ-812_region_field_rename_to_osm.md diff --git a/_docs/03_implementation/batch_01_cycle8_report.md b/_docs/03_implementation/batch_01_cycle8_report.md new file mode 100644 index 0000000..f6e70c7 --- /dev/null +++ b/_docs/03_implementation/batch_01_cycle8_report.md @@ -0,0 +1,51 @@ +# Batch Report + +**Batch**: 01 (cycle 8) +**Tasks**: AZ-812 (Region API field rename Latitude/Longitude → Lat/Lon, OSM convention) +**Date**: 2026-05-22 + +## Task Results + +| Task | Status | Files Modified | Tests | AC Coverage | Issues | +|------|--------|---------------|-------|-------------|--------| +| AZ-812_region_field_rename_to_osm | Done | 11 files (1 new) | smoke pass (mode=smoke, exit 0) | 6/6 ACs covered | 1 Low (DRY in test helper) | + +## AC Test Coverage: All covered (6/6) + +| AC | Coverage | +|----|----------| +| AC-1 | DTO `RequestRegionRequest` uses `Lat`/`Lon` + `[JsonPropertyName("lat"/"lon")]` — verified by reading `SatelliteProvider.Common/DTO/RequestRegionRequest.cs`. | +| AC-2 | Wire format `{"lat":..,"lon":..}` end-to-end — exercised by `RegionFieldRenameTests.NewLatLonFormat_Returns200`, `RegionTests.RunRegionProcessingTest_*` (200m/400m/500m), `IdempotentPostTests`, `SecurityTests`, `scripts/run-performance-tests.sh` PT-03..PT-07. | +| AC-3 | `RegionTests.cs` happy-path tests pass against the new wire format — confirmed by smoke (`RegionTests.RunRegionProcessingTest_200m_Zoom18` green). | +| AC-4 | Old `{"latitude":..,"longitude":..}` returns HTTP 400 with `UnmappedMemberHandling.Disallow` — `RegionFieldRenameTests.OldLatitudeLongitudeFormat_Returns400` exercises this; smoke green. New `{"lat":..,"lon":..}` returns HTTP 200 — `RegionFieldRenameTests.NewLatLonFormat_Returns200`; smoke green. | +| AC-5 | Docs updated: `_docs/02_document/modules/common_dtos.md` (added `RequestRegionRequest` section, disambiguated `RegionRequest` as internal queue type), `_docs/02_document/modules/api_program.md` (relocated `RequestRegionRequest` from Local Records to `Common/DTO`), `_docs/02_document/system-flows.md::F2` (verified — already used `lat, lon`). | +| AC-6 | `_docs/02_document/contracts/api/region-request.md` does NOT yet exist — AZ-808 (region validator, queued for Batch 2) will publish v1.0.0 directly with the post-rename `lat`/`lon` names per the spec's coordination clause. No version bump needed here. | + +## Code Review Verdict: PASS_WITH_WARNINGS +See `_docs/03_implementation/reviews/batch_01_cycle8_review.md` for the single Low finding (test-helper DRY). + +## Auto-Fix Attempts: 0 +## Stuck Agents: None + +## Files Modified + +| Path | Kind | +|------|------| +| `SatelliteProvider.Common/DTO/RequestRegionRequest.cs` | DTO rename + JsonPropertyName | +| `SatelliteProvider.Api/Program.cs` | Handler property access | +| `SatelliteProvider.IntegrationTests/Models.cs` | Test-side DTO mirror | +| `SatelliteProvider.IntegrationTests/RegionTests.cs` | Happy-path uses Lat/Lon | +| `SatelliteProvider.IntegrationTests/IdempotentPostTests.cs` | JSON payload lat/lon | +| `SatelliteProvider.IntegrationTests/SecurityTests.cs` | JSON payload lat/lon | +| `SatelliteProvider.IntegrationTests/RegionFieldRenameTests.cs` | **NEW** — AC-4 positive + negative | +| `SatelliteProvider.IntegrationTests/Program.cs` | Wire RegionFieldRenameTests into smoke + full suites | +| `scripts/run-performance-tests.sh` | PT-03/04/05/07 JSON bodies → lat/lon | +| `_docs/02_document/modules/common_dtos.md` | Documentation | +| `_docs/02_document/modules/api_program.md` | Documentation | + +## Tracker + +- AZ-812: To Do → **In Progress** (set at Batch 1 start) → **In Testing** (set at Batch 1 commit, post-smoke). + +## Next Batch +Batch 2: AZ-811 + AZ-808 — lat/lon GET endpoint validator (AZ-811) + region-request validator (AZ-808). AZ-808's contract doc `region-request.md` will be published at v1.0.0 with `lat`/`lon` per AZ-812's coordination clause. diff --git a/_docs/03_implementation/reviews/batch_01_cycle8_review.md b/_docs/03_implementation/reviews/batch_01_cycle8_review.md new file mode 100644 index 0000000..c12d08d --- /dev/null +++ b/_docs/03_implementation/reviews/batch_01_cycle8_review.md @@ -0,0 +1,70 @@ +# Code Review Report + +**Batch**: 01 (cycle 8) +**Tasks**: AZ-812 (Region API field rename Latitude/Longitude → Lat/Lon, OSM convention) +**Date**: 2026-05-22 +**Verdict**: PASS_WITH_WARNINGS + +## Findings + +| # | Severity | Category | File:Line | Title | +|---|----------|----------|-----------|-------| +| 1 | Low | Maintainability | `SatelliteProvider.IntegrationTests/RegionFieldRenameTests.cs:79` | `AssertErrorsContainsMention` copy-pasted from `TileInventoryValidationTests.cs` | + +### Finding Details + +**F1: `AssertErrorsContainsMention` copy-pasted from `TileInventoryValidationTests.cs`** (Low / Maintainability) +- Location: `SatelliteProvider.IntegrationTests/RegionFieldRenameTests.cs:79-110` (and the prior copy at `TileInventoryValidationTests.cs:396-428`) +- Description: The new `RegionFieldRenameTests.cs` copies the private `AssertErrorsContainsMention` helper verbatim from `TileInventoryValidationTests.cs`. Both helpers walk the same `errors` object and assert a mention exists in either keys or messages. +- Suggestion: Promote the helper to `ProblemDetailsAssertions.cs` (the natural home for cross-test ProblemDetails assertions) and migrate both tests in a small follow-up cleanup. NOT done in this batch — touching the cycle-7 file is out of AZ-812 scope. Tracked as future hygiene, not a regression. +- Task: AZ-812 + +## Phase Summary + +| Phase | Outcome | +|-------|---------| +| 1. Context Loading | Read AZ-812 spec + `_docs/02_document/module-layout.md`. Scope is the Region POST DTO rename. | +| 2. Spec Compliance | AC-1 ✓ (DTO has Lat/Lon + JsonPropertyName), AC-2 ✓ (wire format end-to-end with `lat`/`lon`), AC-3 ✓ (`RegionTests.cs` happy-path updated), AC-4 ✓ (`RegionFieldRenameTests` validates both directions, smoke green), AC-5 ✓ (common_dtos.md, api_program.md, system-flows.md updated/verified), AC-6 ✓ (`region-request.md` does not yet exist — AZ-808 will publish v1.0.0 directly with new names per spec coordination). No Spec-Gap. | +| 3. Code Quality | Mechanical DTO rename, clean. One DRY violation (F1) — Low severity. | +| 4. Security | No SQL injection, no hardcoded secrets, no sensitive data in logs. New test uses GUID + test coordinates only. | +| 5. Performance | No perf impact (field rename). No N+1, no blocking I/O. | +| 6. Cross-Task Consistency | Single-task batch — N/A. | +| 7. Architecture Compliance | DTO in `SatelliteProvider.Common/DTO/` (Common layer, importable by all). Test in `SatelliteProvider.IntegrationTests/` (test layer). No layering violations, no cycles, no Public-API bypasses, no ADR violations. | + +## Files Reviewed + +- `SatelliteProvider.Common/DTO/RequestRegionRequest.cs` (DTO rename + `[JsonPropertyName]` attrs) +- `SatelliteProvider.Api/Program.cs` (handler property access updated) +- `SatelliteProvider.IntegrationTests/Models.cs` (test-side DTO mirror updated) +- `SatelliteProvider.IntegrationTests/RegionTests.cs` (happy-path uses new property names) +- `SatelliteProvider.IntegrationTests/IdempotentPostTests.cs` (JSON payload `lat`/`lon`) +- `SatelliteProvider.IntegrationTests/SecurityTests.cs` (JSON payload `lat`/`lon`) +- `SatelliteProvider.IntegrationTests/RegionFieldRenameTests.cs` (new — AZ-812 AC-4 coverage) +- `SatelliteProvider.IntegrationTests/Program.cs` (smoke + full suite wired to call `RegionFieldRenameTests.RunAll`) +- `scripts/run-performance-tests.sh` (PT-03/04/05/07 JSON bodies updated to `lat`/`lon`) +- `_docs/02_document/modules/common_dtos.md` (RequestRegionRequest section added; RegionRequest disambiguated as internal queue type) +- `_docs/02_document/modules/api_program.md` (RequestRegionRequest moved from Local Records to Common/DTO section) + +## Test Evidence + +`./scripts/run-tests.sh --smoke` (`integration-tests` container exit code 0): + +``` +Test: Region endpoint OSM field-name rename (AZ-812) +==================================================== + +AZ-812 AC-4 (positive): new {lat,lon} wire format → HTTP 200 + ✓ {lat,lon} body accepted with HTTP 200 + +AZ-812 AC-4 (negative): legacy {latitude,longitude} wire format → HTTP 400 (UnmappedMemberHandling.Disallow) + ✓ Legacy {latitude,longitude} body rejected with HTTP 400; errors map names the unknown field +✓ Region field-rename tests: PASSED +``` + +No regressions: cycle-7 inventory validation suite, idempotent POST, security, route, tile, leaflet path, and migrations 012/013/014 all green in the same smoke run. + +## Verdict Logic + +- No Critical, no High, no Medium findings. +- 1 Low finding (DRY in test helpers) — does not block. +- **PASS_WITH_WARNINGS**. diff --git a/scripts/run-performance-tests.sh b/scripts/run-performance-tests.sh index d442f07..9d2df71 100755 --- a/scripts/run-performance-tests.sh +++ b/scripts/run-performance-tests.sh @@ -197,7 +197,7 @@ fi echo "" echo "PT-03: Region Processing 200m / zoom 18 (threshold: 60000ms)" PT03_ID=$(uuidgen | tr '[:upper:]' '[:lower:]') -PT03_BODY="{\"id\":\"$PT03_ID\",\"latitude\":47.461747,\"longitude\":37.647063,\"sizeMeters\":200,\"zoomLevel\":18,\"stitchTiles\":false}" +PT03_BODY="{\"id\":\"$PT03_ID\",\"lat\":47.461747,\"lon\":37.647063,\"sizeMeters\":200,\"zoomLevel\":18,\"stitchTiles\":false}" START=$(date +%s%N) HTTP_CODE=$(curl "${CURL_OPTS[@]}" -s -o /dev/null -w "%{http_code}" -X POST -H "Content-Type: application/json" -H "$AUTH_HEADER" -d "$PT03_BODY" "$API_URL/api/satellite/request") if [[ "$HTTP_CODE" == "200" || "$HTTP_CODE" == "202" ]]; then @@ -218,7 +218,7 @@ fi echo "" echo "PT-04: Region Processing 500m / zoom 18 + stitch (threshold: 120000ms)" PT04_ID=$(uuidgen | tr '[:upper:]' '[:lower:]') -PT04_BODY="{\"id\":\"$PT04_ID\",\"latitude\":47.461747,\"longitude\":37.647063,\"sizeMeters\":500,\"zoomLevel\":18,\"stitchTiles\":true}" +PT04_BODY="{\"id\":\"$PT04_ID\",\"lat\":47.461747,\"lon\":37.647063,\"sizeMeters\":500,\"zoomLevel\":18,\"stitchTiles\":true}" START=$(date +%s%N) HTTP_CODE=$(curl "${CURL_OPTS[@]}" -s -o /dev/null -w "%{http_code}" -X POST -H "Content-Type: application/json" -H "$AUTH_HEADER" -d "$PT04_BODY" "$API_URL/api/satellite/request") if [[ "$HTTP_CODE" == "200" || "$HTTP_CODE" == "202" ]]; then @@ -245,7 +245,7 @@ for i in 1 2 3 4 5; do PT05_IDS+=("$rid") LAT=$(awk "BEGIN { printf \"%.6f\", 47.461747 + 0.001 * $i }") LON=$(awk "BEGIN { printf \"%.6f\", 37.647063 + 0.001 * $i }") - BODY="{\"id\":\"$rid\",\"latitude\":$LAT,\"longitude\":$LON,\"sizeMeters\":200,\"zoomLevel\":18,\"stitchTiles\":false}" + BODY="{\"id\":\"$rid\",\"lat\":$LAT,\"lon\":$LON,\"sizeMeters\":200,\"zoomLevel\":18,\"stitchTiles\":false}" HTTP_CODE=$(curl "${CURL_OPTS[@]}" -s -o /dev/null -w "%{http_code}" -X POST -H "Content-Type: application/json" -H "$AUTH_HEADER" -d "$BODY" "$API_URL/api/satellite/request") if [[ "$HTTP_CODE" != "200" && "$HTTP_CODE" != "202" ]]; then echo " ✗ PT-05: enqueue $i HTTP $HTTP_CODE (expected 200/202)" @@ -303,7 +303,7 @@ for ((i=0; i