Commit Graph

4 Commits

Author SHA1 Message Date
Oleksandr Bezdieniezhnykh 490902c80a [AZ-810] Strict validation for POST /api/satellite/upload metadata
Adds the per-endpoint child of AZ-795 ("Strict Input Validation Epic")
for the UAV upload multipart endpoint. Three new validators land under
SatelliteProvider.Api/Validators/:

- UavTileBatchMetadataPayloadValidator: items NotNull + NotEmpty +
  count <= MaxBatchSize + RuleForEach dispatching to the per-item
  validator.
- UavTileMetadataValidator: lat / lon / tileZoom range, tileSizeMeters
  > 0, capturedAt within [now - MaxAgeDays, now + future-skew]; uses an
  injectable TimeProvider so unit tests can drive a fixed clock.
- UavUploadValidationFilter: IEndpointFilter that reads the multipart
  `metadata` form field, deserializes it with the strict global
  JsonSerializerOptions (so UnmappedMemberHandling.Disallow +
  [JsonRequired] from AZ-795 are honored), runs the FluentValidation
  chain, and enforces the cross-field `items.Count == files.Count`
  envelope rule. FluentValidation errors are prefixed with `metadata.`
  so wire keys look like `errors["metadata.items[0].latitude"]`.

[JsonRequired] is added to every non-optional axis on
UavTileMetadata and UavTileBatchMetadataPayload; FlightId stays
nullable per AZ-503 anonymous-flight semantics.

Coverage: 13 unit tests + 16 integration tests + 1 curl probe script
exercise the happy path and every failure mode. All 9 ACs covered;
no regression in AZ-488 UavUploadTests payloads (traced against the
new rules).

Documentation: uav-tile-upload.md bumped v1.1.0 -> v1.2.0 with the
new validation rules section + 400-shape examples + changelog entry.
api_program.md updated to describe the three new validators + filter
+ the AddTransient<UavUploadValidationFilter>() DI registration.

Reports: batch_04_cycle8_report.md + reviews/batch_04_cycle8_review.md
record the PASS_WITH_WARNINGS verdict (2 Low DRY-in-tests findings:
FixedTimeProvider duplication crossed the cycle-2 "promote to shared"
threshold; PostBatch helper duplicated between two integration
suites). Both deferred to follow-up PBIs.

Task spec archived: _docs/02_tasks/todo/AZ-810... -> done/.
Jira: AZ-810 transitioned In Progress -> In Testing.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 13:32:19 +03:00
Oleksandr Bezdieniezhnykh 5e056b2334 [AZ-809] Strict validation for POST /api/satellite/route
Third concrete child of AZ-795 (cycle 8 batch 3). FluentValidation +
[JsonRequired] + UnmappedMemberHandling.Disallow combine to reject every
malformed payload at the API boundary with RFC 7807 ValidationProblemDetails.

Validators (SatelliteProvider.Api/Validators/, all new)
- CreateRouteRequestValidator: id non-empty, name/description length,
  regionSizeMeters/zoomLevel ranges, points count [2, 500], cross-field
  createTilesZip => requestMaps. Chains RoutePointValidator (per-point)
  and GeofencePolygonValidator (per-polygon, guarded by When(Geofences != null)).
  OverridePropertyName("geofences.polygons") on the geofences chain so
  FluentValidation's default leaf-only key policy doesn't drop the parent
  path on deep expressions like req.Geofences!.Polygons.
- RoutePointValidator: lat/lon ranges; OverridePropertyName("lat"/"lon")
  chained AFTER InclusiveBetween (the extension is defined on
  IRuleBuilderOptions<T, TProperty>, so the generic type is only
  inferable after the first concrete rule) so error keys match the
  wire format (`points[i].lat`) rather than the C# property name
  (`points[i].latitude`).
- GeofencePolygonValidator: per-corner range checks via private nested
  GeoCornerValidator; cross-field NW.Lat > SE.Lat and NW.Lon < SE.Lon
  invariants emit at errors["geofences.polygons[i].northWest"].

DTOs (SatelliteProvider.Common/DTO/, [JsonRequired] additions only)
- CreateRouteRequest: id, name, regionSizeMeters, zoomLevel, points,
  requestMaps, createTilesZip
- RoutePoint: Latitude, Longitude
- GeofencePolygon: NorthWest, SouthEast; Geofences: Polygons
- GeoPoint: Lat, Lon

Tests
- Unit: 26 methods total — 16 in CreateRouteRequestValidatorTests, 6 in
  GeofencePolygonValidatorTests, 4 in RoutePointValidatorTests. Each
  RuleFor/RuleForEach chain has at least one positive + one negative case.
- Integration: CreateRouteValidationTests.cs — 16 methods (happy + 15
  failure modes) wired into smoke + full suites. Covers empty body,
  missing/zero id, empty name, out-of-range regionSizeMeters/zoomLevel,
  points count < 2, per-point lat/lon out-of-range, geofence invariants,
  missing requestMaps, cross-field createTilesZip, unknown root field,
  nested type mismatch.
- Manual probe: scripts/probe_route_validation.sh curl-exercises every
  failure mode end-to-end + happy path.

Docs
- New contract _docs/02_document/contracts/api/route-creation.md v1.0.0
  with nested DTO chain, invariants, per-field test cases table, and
  advisories on the legacy service-layer RouteValidator + the
  input/output RoutePoint vs RoutePointDto naming asymmetry.
- system-flows.md F4 sequence diagram extended with the validation-filter
  branch; preconditions + error scenarios reference the new contract.
- modules/api_program.md: CreateRoute handler section added; Api/Validators
  bumped to AZ-808/AZ-809/AZ-811.
- modules/common_dtos.md: DTO descriptions updated with [JsonRequired]
  annotations and constraint summaries.
- tests/blackbox-tests.md BT-06/BT-N03/BT-N04/BT-N05 align with the new
  wire format and named error keys.
- tests/security-tests.md SEC-04 references GlobalExceptionHandler's
  JsonException branch + AZ-353 correlationId.
- _docs/03_implementation/batch_03_cycle8_report.md + reviews/batch_03_cycle8_review.md
  (PASS_WITH_NOTES — F1 Low: OverridePropertyName documented inline,
  F2 + F3 Info: pre-existing advisories for follow-up).

Smoke green (mode=smoke, exit 0). AZ-809 transitioned to In Testing on Jira.
Task file moved to _docs/02_tasks/done/.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 17:49:48 +03:00
Oleksandr Bezdieniezhnykh 34ee1e0b83 [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>
2026-05-22 16:29:41 +03:00
Oleksandr Bezdieniezhnykh 865dfdb3b9 [AZ-794] [AZ-795] [AZ-796] Strict input validation + z/x/y rename
ci/woodpecker/push/01-test Pipeline was successful
ci/woodpecker/push/02-build-push Pipeline was successful
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>
2026-05-22 10:02:02 +03:00