#!/usr/bin/env bash set -euo pipefail # Manual end-to-end probe for the region-request endpoint's strict validation # gate (AZ-808). Each failure call should return HTTP 400 with an # `application/problem+json` body whose `errors` map names the offending field # path. The happy path should return HTTP 200. # # Usage: # API_URL=https://localhost:8080 JWT="" ./scripts/probe_region_validation.sh # (defaults to https://localhost:8080 with a JWT minted via PerfBootstrap --mint-only) API_URL="${API_URL:-https://localhost:8080}" JWT="${JWT:-}" PATH_REGION="${API_URL%/}/api/satellite/request" if [[ -z "${JWT}" ]]; then echo "ERROR: set JWT env var to a bearer token. Mint one via:" echo " dotnet run --project SatelliteProvider.IntegrationTests -- --mint-only" exit 2 fi curl_args=(-sS -k -H "Authorization: Bearer ${JWT}" -H "Content-Type: application/json" -X POST "${PATH_REGION}") probe() { local label="$1" local body="$2" local expected_status="$3" echo "----- ${label} (expecting HTTP ${expected_status}) -----" local response response=$(curl "${curl_args[@]}" -d "${body}" -w "\nHTTP_STATUS=%{http_code}\n") echo "${response}" local actual_status actual_status=$(echo "${response}" | tail -n 1 | sed 's/HTTP_STATUS=//') if [[ "${actual_status}" != "${expected_status}" ]]; then echo "FAIL: expected HTTP ${expected_status}, got ${actual_status}" return 1 fi echo "OK: HTTP ${expected_status}" echo } # Generate a unique guid for the happy path HAPPY_ID="$(uuidgen)" probe "happy-path" "{\"id\":\"${HAPPY_ID}\",\"lat\":47.461747,\"lon\":37.647063,\"sizeMeters\":200,\"zoomLevel\":18,\"stitchTiles\":false}" 200 # Reproduces the 2026-05-22 probe that surfaced silent-Guid-coercion (pre-AZ-808) probe "missing-id" '{"lat":49.94,"lon":36.31,"sizeMeters":200,"zoomLevel":18,"stitchTiles":false}' 400 probe "zero-guid-id" '{"id":"00000000-0000-0000-0000-000000000000","lat":47.461747,"lon":37.647063,"sizeMeters":200,"zoomLevel":18,"stitchTiles":false}' 400 probe "missing-lat" "{\"id\":\"$(uuidgen)\",\"lon\":37.647063,\"sizeMeters\":200,\"zoomLevel\":18,\"stitchTiles\":false}" 400 probe "lat-out-of-range" "{\"id\":\"$(uuidgen)\",\"lat\":91,\"lon\":37.647063,\"sizeMeters\":200,\"zoomLevel\":18,\"stitchTiles\":false}" 400 probe "missing-lon" "{\"id\":\"$(uuidgen)\",\"lat\":47.461747,\"sizeMeters\":200,\"zoomLevel\":18,\"stitchTiles\":false}" 400 probe "lon-out-of-range" "{\"id\":\"$(uuidgen)\",\"lat\":47.461747,\"lon\":181,\"sizeMeters\":200,\"zoomLevel\":18,\"stitchTiles\":false}" 400 probe "missing-sizeMeters" "{\"id\":\"$(uuidgen)\",\"lat\":47.461747,\"lon\":37.647063,\"zoomLevel\":18,\"stitchTiles\":false}" 400 probe "sizeMeters-out-of-range" "{\"id\":\"$(uuidgen)\",\"lat\":47.461747,\"lon\":37.647063,\"sizeMeters\":1000000,\"zoomLevel\":18,\"stitchTiles\":false}" 400 probe "missing-zoomLevel" "{\"id\":\"$(uuidgen)\",\"lat\":47.461747,\"lon\":37.647063,\"sizeMeters\":200,\"stitchTiles\":false}" 400 probe "zoomLevel-out-of-range" "{\"id\":\"$(uuidgen)\",\"lat\":47.461747,\"lon\":37.647063,\"sizeMeters\":200,\"zoomLevel\":30,\"stitchTiles\":false}" 400 probe "missing-stitchTiles" "{\"id\":\"$(uuidgen)\",\"lat\":47.461747,\"lon\":37.647063,\"sizeMeters\":200,\"zoomLevel\":18}" 400 probe "lat-type-mismatch" "{\"id\":\"$(uuidgen)\",\"lat\":\"fifty\",\"lon\":37.647063,\"sizeMeters\":200,\"zoomLevel\":18,\"stitchTiles\":false}" 400 # Unknown root field — confirms UnmappedMemberHandling.Disallow stays active probe "unknown-root-field" "{\"id\":\"$(uuidgen)\",\"lat\":47.461747,\"lon\":37.647063,\"sizeMeters\":200,\"zoomLevel\":18,\"stitchTiles\":false,\"unknownField\":1}" 400 echo "All probes passed."