mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-21 08:51:13 +00:00
[AZ-812] Region API: rename Latitude/Longitude → Lat/Lon (OSM convention)
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 <cursoragent@cursor.com>
This commit is contained in:
@@ -348,8 +348,8 @@ async Task<IResult> RequestRegion([FromBody] RequestRegionRequest request, IRegi
|
|||||||
|
|
||||||
var status = await regionService.RequestRegionAsync(
|
var status = await regionService.RequestRegionAsync(
|
||||||
request.Id,
|
request.Id,
|
||||||
request.Latitude,
|
request.Lat,
|
||||||
request.Longitude,
|
request.Lon,
|
||||||
request.SizeMeters,
|
request.SizeMeters,
|
||||||
request.ZoomLevel,
|
request.ZoomLevel,
|
||||||
request.StitchTiles);
|
request.StitchTiles);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace SatelliteProvider.Common.DTO;
|
namespace SatelliteProvider.Common.DTO;
|
||||||
|
|
||||||
@@ -8,10 +9,12 @@ public record RequestRegionRequest
|
|||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
public double Latitude { get; set; }
|
[JsonPropertyName("lat")]
|
||||||
|
public double Lat { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
public double Longitude { get; set; }
|
[JsonPropertyName("lon")]
|
||||||
|
public double Lon { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
public double SizeMeters { get; set; }
|
public double SizeMeters { get; set; }
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ public static class IdempotentPostTests
|
|||||||
var body = JsonSerializer.Serialize(new
|
var body = JsonSerializer.Serialize(new
|
||||||
{
|
{
|
||||||
id = regionId,
|
id = regionId,
|
||||||
latitude = 47.4617,
|
lat = 47.4617,
|
||||||
longitude = 37.6470,
|
lon = 37.6470,
|
||||||
sizeMeters = 200,
|
sizeMeters = 200,
|
||||||
zoomLevel = 18,
|
zoomLevel = 18,
|
||||||
stitchTiles = false,
|
stitchTiles = false,
|
||||||
|
|||||||
@@ -17,8 +17,13 @@ public record DownloadTileResponse
|
|||||||
public record RequestRegionRequest
|
public record RequestRegionRequest
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
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 double SizeMeters { get; set; }
|
||||||
public int ZoomLevel { get; set; }
|
public int ZoomLevel { get; set; }
|
||||||
public bool StitchTiles { get; set; } = false;
|
public bool StitchTiles { get; set; } = false;
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ class Program
|
|||||||
await IdempotentPostTests.RunAll(httpClient);
|
await IdempotentPostTests.RunAll(httpClient);
|
||||||
await TileInventoryTests.RunAll(httpClient);
|
await TileInventoryTests.RunAll(httpClient);
|
||||||
await TileInventoryValidationTests.RunAll(httpClient);
|
await TileInventoryValidationTests.RunAll(httpClient);
|
||||||
|
await RegionFieldRenameTests.RunAll(httpClient);
|
||||||
await LeafletPathIndexOnlyTests.RunAll(connectionString);
|
await LeafletPathIndexOnlyTests.RunAll(connectionString);
|
||||||
await MigrationTests.RunAll();
|
await MigrationTests.RunAll();
|
||||||
}
|
}
|
||||||
@@ -164,6 +165,7 @@ class Program
|
|||||||
await IdempotentPostTests.RunAll(httpClient);
|
await IdempotentPostTests.RunAll(httpClient);
|
||||||
await TileInventoryTests.RunAll(httpClient);
|
await TileInventoryTests.RunAll(httpClient);
|
||||||
await TileInventoryValidationTests.RunAll(httpClient);
|
await TileInventoryValidationTests.RunAll(httpClient);
|
||||||
|
await RegionFieldRenameTests.RunAll(httpClient);
|
||||||
await LeafletPathIndexOnlyTests.RunAll(connectionString);
|
await LeafletPathIndexOnlyTests.RunAll(connectionString);
|
||||||
await MigrationTests.RunAll();
|
await MigrationTests.RunAll();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<HttpResponseMessage> 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}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -84,8 +84,8 @@ public static class RegionTests
|
|||||||
var requestRegion = new RequestRegionRequest
|
var requestRegion = new RequestRegionRequest
|
||||||
{
|
{
|
||||||
Id = regionId,
|
Id = regionId,
|
||||||
Latitude = latitude,
|
Lat = latitude,
|
||||||
Longitude = longitude,
|
Lon = longitude,
|
||||||
SizeMeters = sizeMeters,
|
SizeMeters = sizeMeters,
|
||||||
ZoomLevel = zoomLevel,
|
ZoomLevel = zoomLevel,
|
||||||
StitchTiles = stitchTiles
|
StitchTiles = stitchTiles
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public static class SecurityTests
|
|||||||
Console.WriteLine("SEC-03: Oversized region request (sizeMeters beyond allowed cap)");
|
Console.WriteLine("SEC-03: Oversized region request (sizeMeters beyond allowed cap)");
|
||||||
|
|
||||||
var regionId = Guid.NewGuid();
|
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 content = new StringContent(body, Encoding.UTF8, "application/json");
|
||||||
var response = await httpClient.PostAsync("/api/satellite/request", content);
|
var response = await httpClient.PostAsync("/api/satellite/request", content);
|
||||||
var status = (int)response.StatusCode;
|
var status = (int)response.StatusCode;
|
||||||
|
|||||||
@@ -21,9 +21,11 @@ Application entry point. Configures DI container, sets up middleware, defines mi
|
|||||||
### Local Records (defined in Program.cs)
|
### Local Records (defined in Program.cs)
|
||||||
- `GetSatelliteTilesResponse`, `SatelliteTile` — MGRS response stubs
|
- `GetSatelliteTilesResponse`, `SatelliteTile` — MGRS response stubs
|
||||||
- `DownloadTileResponse` — tile download response
|
- `DownloadTileResponse` — tile download response
|
||||||
- `RequestRegionRequest` — region request body
|
|
||||||
- `ParameterDescriptionFilter` — Swagger operation filter
|
- `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)
|
### Api/DTOs (AZ-488)
|
||||||
- `UavTileBatchUploadRequest` — multipart envelope with `metadata` (JSON string) and `files` (`IFormFileCollection`)
|
- `UavTileBatchUploadRequest` — multipart envelope with `metadata` (JSON string) and `files` (`IFormFileCollection`)
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,14 @@ Metadata about a stored tile (mirrors `TileEntity` but without DB-specific conce
|
|||||||
- `Version` (int?), `FilePath` (string)
|
- `Version` (int?), `FilePath` (string)
|
||||||
- `CreatedAt`, `UpdatedAt` (DateTime)
|
- `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
|
### 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)
|
- `Id` (Guid), `Latitude`, `Longitude` (double), `SizeMeters` (double)
|
||||||
- `ZoomLevel` (int), `StitchTiles` (bool)
|
- `ZoomLevel` (int), `StitchTiles` (bool)
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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**.
|
||||||
@@ -197,7 +197,7 @@ fi
|
|||||||
echo ""
|
echo ""
|
||||||
echo "PT-03: Region Processing 200m / zoom 18 (threshold: 60000ms)"
|
echo "PT-03: Region Processing 200m / zoom 18 (threshold: 60000ms)"
|
||||||
PT03_ID=$(uuidgen | tr '[:upper:]' '[:lower:]')
|
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)
|
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")
|
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
|
if [[ "$HTTP_CODE" == "200" || "$HTTP_CODE" == "202" ]]; then
|
||||||
@@ -218,7 +218,7 @@ fi
|
|||||||
echo ""
|
echo ""
|
||||||
echo "PT-04: Region Processing 500m / zoom 18 + stitch (threshold: 120000ms)"
|
echo "PT-04: Region Processing 500m / zoom 18 + stitch (threshold: 120000ms)"
|
||||||
PT04_ID=$(uuidgen | tr '[:upper:]' '[:lower:]')
|
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)
|
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")
|
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
|
if [[ "$HTTP_CODE" == "200" || "$HTTP_CODE" == "202" ]]; then
|
||||||
@@ -245,7 +245,7 @@ for i in 1 2 3 4 5; do
|
|||||||
PT05_IDS+=("$rid")
|
PT05_IDS+=("$rid")
|
||||||
LAT=$(awk "BEGIN { printf \"%.6f\", 47.461747 + 0.001 * $i }")
|
LAT=$(awk "BEGIN { printf \"%.6f\", 47.461747 + 0.001 * $i }")
|
||||||
LON=$(awk "BEGIN { printf \"%.6f\", 37.647063 + 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")
|
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
|
if [[ "$HTTP_CODE" != "200" && "$HTTP_CODE" != "202" ]]; then
|
||||||
echo " ✗ PT-05: enqueue $i HTTP $HTTP_CODE (expected 200/202)"
|
echo " ✗ PT-05: enqueue $i HTTP $HTTP_CODE (expected 200/202)"
|
||||||
@@ -303,7 +303,7 @@ for ((i=0; i<PERF_REPEAT_COUNT; i++)); do
|
|||||||
rid=$(uuidgen | tr '[:upper:]' '[:lower:]')
|
rid=$(uuidgen | tr '[:upper:]' '[:lower:]')
|
||||||
lat=$(awk -v base="$PT07_BASE_LAT" -v idx="$i" 'BEGIN { printf "%.6f", base + 0.002 * idx }')
|
lat=$(awk -v base="$PT07_BASE_LAT" -v idx="$i" 'BEGIN { printf "%.6f", base + 0.002 * idx }')
|
||||||
lon=$(awk -v base="$PT07_BASE_LON" -v idx="$i" 'BEGIN { printf "%.6f", base + 0.002 * idx }')
|
lon=$(awk -v base="$PT07_BASE_LON" -v idx="$i" 'BEGIN { printf "%.6f", base + 0.002 * idx }')
|
||||||
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}"
|
||||||
start=$(date +%s%N)
|
start=$(date +%s%N)
|
||||||
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")
|
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 [[ "$code" != "200" && "$code" != "202" ]]; then
|
if [[ "$code" != "200" && "$code" != "202" ]]; then
|
||||||
@@ -327,7 +327,7 @@ for ((i=0; i<PERF_REPEAT_COUNT; i++)); do
|
|||||||
rid=$(uuidgen | tr '[:upper:]' '[:lower:]')
|
rid=$(uuidgen | tr '[:upper:]' '[:lower:]')
|
||||||
lat=$(awk -v base="$PT07_BASE_LAT" -v idx="$i" 'BEGIN { printf "%.6f", base + 0.002 * idx }')
|
lat=$(awk -v base="$PT07_BASE_LAT" -v idx="$i" 'BEGIN { printf "%.6f", base + 0.002 * idx }')
|
||||||
lon=$(awk -v base="$PT07_BASE_LON" -v idx="$i" 'BEGIN { printf "%.6f", base + 0.002 * idx }')
|
lon=$(awk -v base="$PT07_BASE_LON" -v idx="$i" 'BEGIN { printf "%.6f", base + 0.002 * idx }')
|
||||||
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}"
|
||||||
start=$(date +%s%N)
|
start=$(date +%s%N)
|
||||||
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")
|
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 [[ "$code" != "200" && "$code" != "202" ]]; then
|
if [[ "$code" != "200" && "$code" != "202" ]]; then
|
||||||
|
|||||||
Reference in New Issue
Block a user