Files
Oleksandr Bezdieniezhnykh 1d89cd9997
ci/woodpecker/push/01-test Pipeline was successful
ci/woodpecker/push/02-build-push Pipeline was successful
[AZ-353][AZ-354][AZ-356] Refactor 03 batch 2: harden API surface
AZ-353: Centralize 500 handling via GlobalExceptionHandler /
AddProblemDetails / UseExceptionHandler. Sanitized ProblemDetails
body carries a generic title, RFC9110 type link, and the request's
TraceIdentifier as correlationId; the leaky exception message stays
server-side in the ERR log entry. Strip per-endpoint
try/catch (Exception) wrappers and the unused ILogger<Program>
parameters they served. Preserve the typed ArgumentException catch
in CreateRoute (AC-3). The handler maps BadHttpRequestException
back to its framework-supplied StatusCode so model-binding /
malformed-body failures stay 4xx instead of being promoted to 500.

AZ-354: Extract CorsConfigurationValidator (pure static helpers)
and wire it into Program.cs. Production with empty
CorsConfig:AllowedOrigins and no CorsConfig:AllowAnyOrigin opt-in
now throws InvalidOperationException at host startup. Development
keeps the permissive default but logs a warning post-build. Adds
the explicit CorsConfig:AllowAnyOrigin escape hatch.

AZ-356: GetSatelliteTilesByMgrs and UploadImage now return
Results.Problem(StatusCode 501) with ProblemDetails. Added
.ProducesProblem(501) so swagger.json documents the
not-implemented status.

Tests: SatelliteProvider.Tests now references SatelliteProvider.Api
(downward, idiomatic) so unit tests can reach the new helpers.
+9 CorsConfigurationValidator unit tests, +3
GlobalExceptionHandler unit tests, +3 StubAndErrorContractTests
integration tests (added to smoke + full suites).

58/58 unit + 5/5 smoke + 3/3 stub-contract pass.
Code review verdict: PASS.
Batch report: _docs/03_implementation/batch_08_report.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 23:52:52 +03:00

6.4 KiB

Batch Report

Batch: 8 (refactor 03-code-quality-refactoring · Phase 1 — critical defensive fixes, part 2/2) Tasks: AZ-356, AZ-354, AZ-353 Date: 2026-05-10 Total Story Points: 7 (2 + 2 + 3)

Task Results

Task Status Files Modified Tests AC Coverage Issues
AZ-356 Stub endpoints return 501 Done 1 (Program.cs handlers + OpenAPI) 2 new integration 3/3 ACs None
AZ-354 Strict CORS by default Done 1 svc helper + 1 Program.cs hook 9 new unit 4/4 ACs None
AZ-353 Sanitize 5xx via IExceptionHandler Done 1 svc class + Program.cs rewire + per-endpoint catch removal 3 new unit + 1 new integration 4/4 ACs None

Files Changed

Source

  • SatelliteProvider.Api/GlobalExceptionHandler.cs — NEW (AZ-353)
  • SatelliteProvider.Api/CorsConfigurationValidator.cs — NEW (AZ-354)
  • SatelliteProvider.Api/Program.cs — usings + CORS validator wiring + warn-after-build + ExceptionHandler/ProblemDetails registration + UseExceptionHandler middleware + 501 stubs + per-endpoint try/catch stripping (AZ-353/354/356)

Tests

  • SatelliteProvider.Tests/CorsConfigurationValidatorTests.cs — NEW (AZ-354, 9 tests)
  • SatelliteProvider.Tests/GlobalExceptionHandlerTests.cs — NEW (AZ-353, 3 tests)
  • SatelliteProvider.Tests/SatelliteProvider.Tests.csproj — added ProjectReference to Api so unit tests can reach the new classes
  • SatelliteProvider.IntegrationTests/StubAndErrorContractTests.cs — NEW (AZ-356 AC-1, AZ-353 AC-3, 3 tests)
  • SatelliteProvider.IntegrationTests/Program.cs — added StubAndErrorContractTests.RunAll to smoke + full

AC Test Coverage: All covered

Task AC Test
AZ-353 AC-1 (5xx body sanitized) GlobalExceptionHandlerTests.TryHandleAsync_WritesSanitizedProblemDetailsAndReturnsTrue_AC1 (asserts response body has generic title + correlationId, not the leaky exception message)
AZ-353 AC-2 (server log has full exception + correlationId) GlobalExceptionHandlerTests.TryHandleAsync_LogsFullExceptionWithCorrelationId_AC2
AZ-353 AC-3 (4xx paths preserved) GlobalExceptionHandlerTests.TryHandleAsync_BadHttpRequestException_HonorsFrameworkStatusAndDoesNotErrorLog_AC3 (unit) + StubAndErrorContractTests.CreateRoute_InvalidPayload_Returns400_AZ353_AC3 (integration; ArgumentException → 400) + smoke SecurityTests.SEC-01..04 (regression check that 4xx framework paths still produce 4xx)
AZ-353 AC-4 (tests stay green) smoke green
AZ-354 AC-1 (Production refuses to start without origins) CorsConfigurationValidatorTests.EnsureSafeForEnvironment_ProductionWithEmptyOriginsAndNoOptIn_Throws_AC1
AZ-354 AC-2 (Development warns but starts) CorsConfigurationValidatorTests.EnsureSafeForEnvironment_NonProductionWithEmptyOrigins_DoesNotThrow_AC2 (Theory: Development/Staging/Local) + smoke (compose-managed ASPNETCORE_ENVIRONMENT=Development runs warn-after-build path; smoke is green)
AZ-354 AC-3 (explicit opt-in works) CorsConfigurationValidatorTests.EnsureSafeForEnvironment_ProductionWithExplicitAllowAnyOrigin_DoesNotThrow_AC3
AZ-354 AC-4 (tests stay green) smoke green
AZ-356 AC-1 (both stubs return 501) StubAndErrorContractTests.StubMgrs_Returns501 + StubAndErrorContractTests.StubUpload_Returns501
AZ-356 AC-2 (OpenAPI marks not-implemented) .ProducesProblem(StatusCodes.Status501NotImplemented) annotations in Program.cs; verified by inspecting the route registration
AZ-356 AC-3 (tests stay green) smoke green

Code Review Verdict: PASS

Phase 2 (Spec Compliance)

  • All ACs satisfied with executed tests.
  • BadHttpRequestException carve-out added to GlobalExceptionHandler — required by AC-3 (4xx paths preserved). Without it, the handler would have promoted SEC-01..04 framework binding errors from 400 to 500.

Phase 3 (Code Quality)

  • CorsConfigurationValidator: pure static class (no I/O, no state) — allowed under coderule.
  • GlobalExceptionHandler: instance class with injected logger; private static helper WriteClientErrorAsync is pure HTTP serialization — allowed.
  • Per-endpoint try/catch removed for 7 of 7 affected handlers; the CreateRoute typed ArgumentException catch is intentionally preserved per AC-3. Endpoint signatures lost the ILogger<Program> logger parameter where it became unused.

Phase 4 (Security)

  • application/problem+json content type set explicitly (override WriteAsJsonAsync default).
  • Sanitized 500 body removes server-internal text (validated by ..._WritesSanitizedProblemDetailsAndReturnsTrue_AC1 which uses a connection-string-shaped exception message and asserts the password literal does NOT appear in the response).
  • Strict CORS in Production is the deliverable.

Phase 5 (Performance)

  • No hot-path changes.

Phase 6 (Cross-Task Consistency)

  • All three tasks edit the same Program.cs in different sections (CORS / DI / pipeline / endpoints) without conflict.
  • The single using SatelliteProvider.Api; directive serves all three new classes.

Phase 7 (Architecture Compliance)

  • All new files live under SatelliteProvider.Api/** (Api OWNS).
  • SatelliteProvider.Tests now references SatelliteProvider.Api — this is a downward (test → top-of-stack) reference, which is idiomatic for unit-testing API-internal helpers. Tests project remains the only consumer of Api types outside Api itself.

Auto-Fix Attempts: 1 (one re-run cycle to honor BadHttpRequestException StatusCode)

Stuck Agents: None

Tracker Status (post-batch)

Task Pre-batch Post-batch (after commit)
AZ-356 In Progress → In Testing
AZ-354 In Progress → In Testing
AZ-353 In Progress → In Testing

Phase 1 Summary (Batches 7+8)

Batch Tasks SP Outcome
7 AZ-351 / AZ-352 / AZ-363 5 PASS
8 AZ-356 / AZ-354 / AZ-353 7 PASS

Phase 1 complete: 6 tasks, 12 SP, 0 blockers. All Critical-severity defensive fixes landed.

Next Batch

Batch 9 (Phase 2 — correctness): AZ-359 (consolidate RegionService catch ladder, 3 SP), AZ-357 (drop tile Version concept + new migration, 5 SP), AZ-362 (idempotent POST contract, 3 SP, depends on AZ-353 ✓). Total 11 SP.

Commit

[AZ-353][AZ-354][AZ-356] Refactor 03 batch 2: harden API surface (commit hash recorded post-commit).