Files
satellite-provider/_docs/02_document/tests/blackbox-tests.md
T
Oleksandr Bezdieniezhnykh 98cdcd17c1 [AZ-487] [AZ-488] docs: cycle 2 test-spec sync
Append cycle 2 entries to test-spec artifacts (cycle-update mode):

* security-tests.md: SEC-05..SEC-09 (AZ-487 JWT 401/403/parity)
  + SEC-10..SEC-11 (AZ-488 permission + reject-detail leak hygiene).
* blackbox-tests.md: BT-13..BT-17 (UAV happy / mixed / multi-source
  coexistence / same-source UPSERT / rule-ordering) + BT-18 (existing
  endpoints parity with Bearer token).
* resource-limit-tests.md: RL-05..RL-07 (MaxBatchSize, per-item MaxBytes,
  Kestrel/Form envelope cap).
* performance-tests.md: untouched (PT-08 already landed with AZ-488 as
  Deferred — see _docs/_process_leftovers/2026-05-11_perf-pt07-harness).
* traceability-matrix.md: append AC rows for AZ-487 AC-1..AC-8 and
  AZ-488 AC-1..AC-10 + AC-7a..AC-7e; annotate "No authentication"
  restriction as superseded by AZ-487+AZ-488; add NFR rows (perf,
  security, reliability, compatibility) for both tasks; refresh totals
  (78 tests; 47/47 ACs; 8/8 restrictions).

Coverage shape: AZ-487 AC-7 (Swagger Authorize) and the perf NFRs are
recorded but not actively measured this commit (manual UI smoke +
deferred PT-08 harness, respectively).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 00:00:14 +03:00

172 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Blackbox Test Scenarios
## BT-01: Single Tile Download
**Trigger**: GET /api/satellite/tiles/latlon?Latitude=47.461747&Longitude=37.647063&ZoomLevel=18
**Precondition**: Tile not in cache
**Expected**: HTTP 200; JSON with zoomLevel=18, tileSizePixels=256, imageType="jpg", filePath matching pattern `tiles/18/*/...`
**Pass criterion**: All fields present and correct values
## BT-02: Tile Cache Reuse
**Trigger**: Same GET as BT-01 repeated
**Precondition**: BT-01 completed (tile now cached)
**Expected**: HTTP 200; same tile ID returned; no new file created
**Pass criterion**: tile.Id matches first request's tile.Id
## BT-03: Region Request (200m, zoom 18, no stitch)
**Trigger**: POST /api/satellite/request with lat=47.461747, lon=37.647063, sizeMeters=200, zoomLevel=18, stitchTiles=false
**Expected**: HTTP 200 immediately; status transitions: pending → processing → completed
**Pass criterion**: Final status="completed"; csvFilePath non-empty; summaryFilePath non-empty; tilesDownloaded + tilesReused > 0
**Timeout**: 240s
## BT-04: Region Request (400m, zoom 17, no stitch)
**Trigger**: POST /api/satellite/request with lat=47.461747, lon=37.647063, sizeMeters=400, zoomLevel=17, stitchTiles=false
**Expected**: Same as BT-03
**Pass criterion**: Same as BT-03
**Timeout**: 240s
## BT-05: Region with Stitching (500m, zoom 18)
**Trigger**: POST /api/satellite/request with lat=47.461747, lon=37.647063, sizeMeters=500, zoomLevel=18, stitchTiles=true
**Expected**: Completes with stitched image generated
**Pass criterion**: status="completed"; stitched image file exists and size > 1024 bytes
**Timeout**: 240s
## BT-06: Simple Route Creation (2 points)
**Trigger**: POST /api/satellite/route with 2 waypoints (48.276067,37.384458) → (48.270740,37.374029), regionSize=500, zoom=18
**Expected**: Route created with interpolated intermediate points
**Pass criterion**: totalPoints > 2; every point spacing ≤ 200m; first point type="original"; last point type="original"; intermediates type="intermediate"
## BT-07: Route Retrieval by ID
**Trigger**: GET /api/satellite/route/{id} after BT-06
**Expected**: Same route returned with all points
**Pass criterion**: route.Id matches; points count matches creation response
## BT-08: Route with Map Processing
**Trigger**: POST /api/satellite/route with requestMaps=true, 2 points, regionSize=300
**Expected**: Route maps processed, stitched image and CSV created
**Pass criterion**: mapsReady=true; stitchedImagePath non-empty; csvFilePath non-empty; stitched image > 1024 bytes
**Timeout**: 180s
## BT-09: Route with Tiles ZIP
**Trigger**: POST /api/satellite/route with requestMaps=true, createTilesZip=true, 2 points
**Expected**: ZIP file created with tiles
**Pass criterion**: tilesZipPath non-empty; ZIP > 1024 bytes; ZIP entry count = unique tiles in CSV; entries start with "tiles/"; path has ≥5 parts (directory structure preserved)
**Timeout**: 180s
## BT-10: Complex Route (10 points, maps)
**Trigger**: POST /api/satellite/route with 10 waypoints, requestMaps=true, regionSize=300
**Expected**: All points interpolated; map tiles processed
**Pass criterion**: mapsReady=true; uniqueTileCount ≥ 10; stitched image > 1024 bytes
**Timeout**: 240s
## BT-11: Route with Geofences (10 points + 2 rectangles)
**Trigger**: POST /api/satellite/route with 10 waypoints + 2 geofence polygons, requestMaps=true
**Expected**: Geofence regions created and processed
**Pass criterion**: mapsReady=true; uniqueTileCount ≥ 10; stitched image > 1024 bytes; geofence regions linked to route
**Timeout**: 240s
## BT-12: Extended Route (20 points, maps)
**Trigger**: POST /api/satellite/route with 20 waypoints in separate geographic area, requestMaps=true
**Expected**: Large route processed completely
**Pass criterion**: mapsReady=true; uniqueTileCount ≥ 20; stitched image > 1024 bytes
**Timeout**: 360s
## Negative Scenarios
## BT-N01: Invalid Coordinates (out of range)
**Trigger**: GET /api/satellite/tiles/latlon?Latitude=91&Longitude=181&ZoomLevel=18
**Expected**: Error response
**Pass criterion**: HTTP 4xx or error in response body
## BT-N02: Invalid Zoom Level
**Trigger**: GET /api/satellite/tiles/latlon?Latitude=47.46&Longitude=37.64&ZoomLevel=25
**Expected**: Error response
**Pass criterion**: HTTP 4xx or error indicating invalid zoom
## BT-N03: Route with < 2 Points
**Trigger**: POST /api/satellite/route with only 1 point
**Expected**: Validation error
**Pass criterion**: HTTP 400 or validation error message
## BT-N04: Geofence with Invalid Coordinates (0,0)
**Trigger**: POST /api/satellite/route with geofence NW=(0,0) SE=(0,0)
**Expected**: Validation error
**Pass criterion**: Error message mentioning coordinates cannot be (0,0)
## BT-N05: Geofence with Inverted Corners
**Trigger**: POST /api/satellite/route with geofence NW.lat < SE.lat
**Expected**: Validation error
**Pass criterion**: Error message about northWest latitude > southEast latitude
---
## Cycle 2 — AZ-488 UAV Tile Upload (POST /api/satellite/upload)
All Cycle-2 UAV scenarios run with a JWT containing `permissions: ["GPS"]` (per AZ-487 + AZ-488). Files use the contract at `_docs/02_document/contracts/api/uav-tile-upload.md` v1.0.0; per-item correlation is by ordinal index between metadata array and `IFormFileCollection`.
## BT-13: UAV Upload — Happy-Path 1-Item Batch Persists `source='uav'`
**Trigger**: POST `/api/satellite/upload` with a 1-item batch — a 256×256 JPEG (~50 KiB), `capturedAt = now`, valid coordinates inside the test region.
**Precondition**: Empty `tiles` table for the chosen cell; valid `GPS` JWT.
**Expected**: HTTP 200; response body has `items[0].status == "accepted"` and a non-empty `tileId`; a new row exists in `tiles` with `source='uav'`, `captured_at` matching the request (UTC, ≤ 1 s drift), `file_path == 'tiles/uav/{z}/{x}/{y}.jpg'`; the file exists on disk at that path with the uploaded bytes.
**Pass criterion**: All of the above true.
**AC trace**: AZ-488 AC-1.
## BT-14: UAV Upload — 3-Item Mixed Batch Returns Per-Item Results
**Trigger**: POST `/api/satellite/upload` with a 3-item batch where item-1 is a valid 256×256 JPEG, item-2 is a 512×512 JPEG (wrong dimensions), item-3 has PNG magic bytes (wrong format).
**Precondition**: Empty `tiles` table; valid `GPS` JWT.
**Expected**: HTTP 200; `items[0].status == "accepted"` with a `tileId`; `items[1].status == "rejected"` with `rejectReason == "WRONG_DIMENSIONS"`; `items[2].status == "rejected"` with `rejectReason == "INVALID_FORMAT"`. Exactly one new row appears in `tiles` (for item-1 only). No file written for items 2 or 3.
**Pass criterion**: status array matches `[accepted, rejected, rejected]` AND reasons match exactly AND `COUNT(*) WHERE source='uav'` == 1 for the test region.
**AC trace**: AZ-488 AC-2, AC-7a, AC-7c.
## BT-15: UAV Upload — Multi-Source Coexistence with `google_maps`
**Trigger**: Pre-seed `tiles` (raw INSERT) with a `source='google_maps'` row at `(L, Ln, z=18, size_m=200)` and `captured_at = T1 = now 2h`. Then POST `/api/satellite/upload` with a UAV tile for the same cell and `capturedAt = T2 = now`.
**Precondition**: AZ-484 migration 013 applied (5-column unique index in place); valid `GPS` JWT.
**Expected**: HTTP 200; both rows exist in `tiles` after upload (no overwrite of the google_maps row); a follow-up `GetByTileCoordinatesAsync(L, Ln, 18, 200)` returns the `source='uav'` row (per AZ-484 selection rule: max `captured_at` across sources).
**Pass criterion**: `SELECT source FROM tiles WHERE ...` returns both `'google_maps'` AND `'uav'`; the repository read returns the UAV row.
**AC trace**: AZ-488 AC-3; cross-references AZ-484 AC-1 (storage Inv-3) and AZ-484 AC-2 (selection rule).
## BT-16: UAV Upload — Same-Source UPSERT Collapses to One Row
**Trigger**: POST `/api/satellite/upload` with a UAV tile for cell `(L, Ln, 18, 200)` at `capturedAt = T1 = now 30m`, then a second POST for the same cell at `capturedAt = T2 = now` with different image bytes (different `seed`).
**Precondition**: Cell is empty for `source='uav'` before T1; valid `GPS` JWT.
**Expected**: HTTP 200 for both calls. After the second call, exactly one `source='uav'` row remains for the cell with `captured_at == T2`; the JPEG at `./tiles/uav/{z}/{x}/{y}.jpg` is overwritten with the T2 bytes. Any pre-existing `source='google_maps'` row is untouched.
**Pass criterion**: `SELECT COUNT(*) FROM tiles WHERE source='uav' AND (L, Ln, 18, 200)` == 1 AND `MAX(captured_at) ≈ T2` AND on-disk JPEG checksum matches the T2 upload.
**AC trace**: AZ-488 AC-4; cross-references AZ-484 AC-3.
## BT-17: UAV Upload — Quality-Gate Rule-Ordering Determinism
**Trigger**: POST `/api/satellite/upload` with a single item that violates BOTH Rule 1 (PNG magic instead of JPEG) AND Rule 3 (512×512 dimensions). Authenticated with `GPS` permission.
**Expected**: HTTP 200; `items[0].status == "rejected"` with `rejectReason == "INVALID_FORMAT"` (Rule 1 fires first; Rule 3 never evaluated).
**Pass criterion**: rejectReason equals exactly `INVALID_FORMAT`; never `WRONG_DIMENSIONS`.
**AC trace**: AZ-488 AC-7; rule-ordering invariant from `_docs/02_document/contracts/api/uav-tile-upload.md` v1.0.0.
## Cycle 2 — AZ-487 Endpoint Parity (existing endpoints with Bearer token)
## BT-18: Existing Tile Endpoint Returns Identical Body with Valid Bearer
**Trigger**: GET `/api/satellite/tiles/latlon?Latitude=47.461747&Longitude=37.647063&ZoomLevel=18` with a valid Bearer token.
**Precondition**: Tile may or may not be cached.
**Expected**: Response body is structurally identical to BT-01 (`tileId`, `zoomLevel == 18`, `tileSizePixels == 256`, `imageType == "jpg"`, `filePath` matches `tiles/18/*/*`).
**Pass criterion**: status == 200 AND BT-01's pass criterion AND no behavioral change vs pre-AZ-487 baseline.
**AC trace**: AZ-487 AC-4 (handler unchanged); validates AZ-487 AC-8 (existing suite parity).