mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-22 13:11:14 +00:00
[AZ-794] [AZ-795] [AZ-796] Strict input validation + z/x/y rename
AZ-794: rename inventory wire fields tileZoom/tileX/tileY -> z/x/y to match the slippy-map URL convention. Contract bumped to v2.0.0. AZ-795: shared validation infrastructure -- FluentValidation + ValidationEndpointFilter + GlobalValidatorConfig (camelCase paths). GlobalExceptionHandler now converts JsonException (UnmappedMember + JsonRequired) into RFC 7807 ValidationProblemDetails. JSON layer hardened with UnmappedMemberHandling.Disallow + camelCase naming policy. New error-shape.md contract. AZ-796: InventoryRequestValidator covers 9 rules (XOR tiles vs locationHashes, cap 1000, z 0..22, x/y in slippy bounds, hash length/charset). 16 unit tests + 16 integration tests + a manual curl probe script. Adjacent fixes uncovered by the new strict layer: - IdempotentPostTests RoutePoint payload corrected to lat/lon (the DTO has used JsonPropertyName for ages; previously silently ignored under PascalCase fallback). - TileInventoryTests slippy x/y reduced to fit z=18 bounds. - docker-compose.yml host port for Postgres moved 5432 -> 5433 to avoid sibling-project conflict; appsettings.Development + README + AGENTS + architecture + containerization docs aligned. New coderule (suite + repo): API consumer-facing OpenAPI descriptions must not contain task IDs, contract filenames, or version-bump history -- internal change tracking belongs in commits/contract docs/changelogs. Existing offending descriptions in Program.cs cleaned up. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -72,6 +72,45 @@ public class GlobalExceptionHandlerTests
|
||||
"BadHttpRequestException is a client error and must not be ERROR-logged as a server failure");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryHandleAsync_DeserializationFailure_WritesValidationProblemDetailsWithJsonPath_AZ795()
|
||||
{
|
||||
// Arrange
|
||||
var loggerMock = new Mock<ILogger<GlobalExceptionHandler>>();
|
||||
var handler = new GlobalExceptionHandler(loggerMock.Object);
|
||||
var httpContext = new DefaultHttpContext { TraceIdentifier = "trace-AZ795" };
|
||||
httpContext.Response.Body = new MemoryStream();
|
||||
var jsonInner = new JsonException(
|
||||
"The JSON property 'foo' could not be mapped to any .NET member contained in type 'TileInventoryRequest'.",
|
||||
"$.tiles[0].foo",
|
||||
lineNumber: null,
|
||||
bytePositionInLine: null);
|
||||
var bindFailure = new BadHttpRequestException(
|
||||
"Failed to read parameter \"TileInventoryRequest request\" from request body.",
|
||||
StatusCodes.Status400BadRequest,
|
||||
jsonInner);
|
||||
|
||||
// Act
|
||||
var handled = await handler.TryHandleAsync(httpContext, bindFailure, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
handled.Should().BeTrue();
|
||||
httpContext.Response.StatusCode.Should().Be(StatusCodes.Status400BadRequest);
|
||||
httpContext.Response.ContentType.Should().Contain("application/problem+json");
|
||||
|
||||
httpContext.Response.Body.Position = 0;
|
||||
using var doc = JsonDocument.Parse(httpContext.Response.Body);
|
||||
var root = doc.RootElement;
|
||||
|
||||
root.GetProperty("status").GetInt32().Should().Be(400);
|
||||
root.GetProperty("title").GetString().Should().Be("One or more validation errors occurred.");
|
||||
root.GetProperty("type").GetString().Should().Be("https://tools.ietf.org/html/rfc7231#section-6.5.1");
|
||||
root.GetProperty("errors")
|
||||
.GetProperty("tiles[0].foo")[0]
|
||||
.GetString()
|
||||
.Should().Contain("could not be mapped");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryHandleAsync_LogsFullExceptionWithCorrelationId_AC2()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user