mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-22 02:31:14 +00:00
[AZ-284] Autodev baseline + testability refactor
Phase A baseline outputs from /autodev (Steps 1-5): - Problem & solution docs (_docs/00_problem, _docs/01_solution) - Codebase documentation (_docs/02_document) incl. architecture, module-layout, glossary, system-flows, baseline compliance scan - Test specs (blackbox, performance, resilience, security, resource, traceability matrix) - Test task decomposition (_docs/02_tasks/todo): AZ-285..AZ-290 - Testability refactor (_docs/04_refactoring/01-testability-refactoring): - TC-01 Move DownloadedTileInfoV2 + new ExistingTileInfo to Common.DTO - TC-02 Replace dead ISatelliteDownloader API with real signatures - TC-03 GoogleMapsDownloaderV2 implements ISatelliteDownloader - TC-04 TileService depends on ISatelliteDownloader (mockable) - TC-05 DI + endpoints use ISatelliteDownloader - Test runner scripts (scripts/run-tests.sh, run-performance-tests.sh) - Autodev state pointer (_docs/_autodev_state.md) Prepares the codebase for AZ-285..AZ-290 unit/integration test work. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
# 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
|
||||
@@ -0,0 +1,49 @@
|
||||
# Test Environment
|
||||
|
||||
## Infrastructure
|
||||
|
||||
| Component | Technology | Configuration |
|
||||
|-----------|-----------|---------------|
|
||||
| System Under Test | SatelliteProvider.Api (Docker container) | ASPNETCORE_ENVIRONMENT=Development |
|
||||
| Database | PostgreSQL 16 (Docker container) | Fresh DB per test run (migrations auto-applied) |
|
||||
| Test Runner | Custom console app (SatelliteProvider.IntegrationTests) | Docker container on same network |
|
||||
| Orchestration | docker-compose.tests.yml | Waits for API health before starting tests |
|
||||
|
||||
## Network Topology
|
||||
|
||||
```
|
||||
[Test Runner] --HTTP--> [API :8080] --TCP--> [PostgreSQL :5432]
|
||||
|
|
||||
+--HTTPS--> [Google Maps] (external, real)
|
||||
```
|
||||
|
||||
## External Dependencies
|
||||
|
||||
| Dependency | Strategy | Notes |
|
||||
|------------|----------|-------|
|
||||
| Google Maps tile server | Real (live) | Integration tests use real downloads; requires GOOGLE_MAPS_API_KEY |
|
||||
| PostgreSQL | Real (containerized) | Fresh database each run via migrations |
|
||||
| File system | Real (Docker volume) | ./tiles, ./ready, ./logs mounted |
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Value | Purpose |
|
||||
|----------|-------|---------|
|
||||
| API_URL | http://api:8080 | Test runner → API connection |
|
||||
| ASPNETCORE_ENVIRONMENT | Development | API config mode |
|
||||
| ConnectionStrings__DefaultConnection | Host=postgres;Port=5432;... | DB connection |
|
||||
| MapConfig__ApiKey | (from host env) | Google Maps auth |
|
||||
|
||||
## Test Execution
|
||||
|
||||
**Decision**: Docker (no hardware dependencies detected)
|
||||
**Hardware dependencies found**: None
|
||||
**Execution method**: `docker-compose -f docker-compose.yml -f docker-compose.tests.yml up --build --abort-on-container-exit`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Execution mode | Sequential (one test at a time) |
|
||||
| Timeout per test | 15 minutes (HttpClient timeout) |
|
||||
| Polling interval | 2–3 seconds |
|
||||
| Max poll attempts | 120–360 (depends on test) |
|
||||
| Startup wait | 30 retries × 2s = 60s max |
|
||||
@@ -0,0 +1,43 @@
|
||||
# Performance Test Scenarios
|
||||
|
||||
## PT-01: Single Tile Download Latency
|
||||
|
||||
**Trigger**: GET /api/satellite/tiles/latlon (uncached tile)
|
||||
**Load**: 1 request
|
||||
**Expected**: Response within 30s (includes Google Maps round-trip)
|
||||
**Pass criterion**: Response time < 30000ms; HTTP 200
|
||||
|
||||
## PT-02: Cached Tile Retrieval Latency
|
||||
|
||||
**Trigger**: GET /api/satellite/tiles/latlon (cached tile)
|
||||
**Load**: 1 request
|
||||
**Expected**: Response within 500ms (DB lookup + response)
|
||||
**Pass criterion**: Response time < 500ms; HTTP 200
|
||||
|
||||
## PT-03: Region Processing Throughput (200m)
|
||||
|
||||
**Trigger**: POST /api/satellite/request with 200m region
|
||||
**Load**: 1 region
|
||||
**Expected**: Complete processing within 60s
|
||||
**Pass criterion**: status="completed" within 60s; tiles downloaded > 0
|
||||
|
||||
## PT-04: Region Processing Throughput (500m with stitch)
|
||||
|
||||
**Trigger**: POST /api/satellite/request with 500m region + stitch
|
||||
**Load**: 1 region
|
||||
**Expected**: Complete processing within 120s (more tiles + stitching)
|
||||
**Pass criterion**: status="completed" within 120s; stitched image exists
|
||||
|
||||
## PT-05: Concurrent Region Requests
|
||||
|
||||
**Trigger**: 5 simultaneous POST /api/satellite/request (different coordinates)
|
||||
**Load**: 5 concurrent requests
|
||||
**Expected**: All queued immediately; all complete within 5 minutes
|
||||
**Pass criterion**: All 5 regions reach status="completed"; queue does not reject
|
||||
|
||||
## PT-06: Route Point Interpolation Speed
|
||||
|
||||
**Trigger**: POST /api/satellite/route with 20 points
|
||||
**Load**: 1 request
|
||||
**Expected**: Route created (with interpolation) within 5s
|
||||
**Pass criterion**: HTTP 200 response within 5000ms; totalPoints > 20
|
||||
@@ -0,0 +1,37 @@
|
||||
# Resilience Test Scenarios
|
||||
|
||||
## RS-01: API Startup with Database Ready
|
||||
|
||||
**Trigger**: Start API container after PostgreSQL is healthy
|
||||
**Observable**: API responds to HTTP requests
|
||||
**Pass criterion**: API returns non-5xx response within 60s of container start
|
||||
|
||||
## RS-02: Database Migrations on Fresh Start
|
||||
|
||||
**Trigger**: Start API against empty database
|
||||
**Observable**: All 11 migrations execute successfully
|
||||
**Pass criterion**: API starts without error; all tables exist; schemaversions table has 11 entries
|
||||
|
||||
## RS-03: Region Processing Survives Tile Download Failure
|
||||
|
||||
**Trigger**: Submit region request where some tiles may fail (rate limit / timeout)
|
||||
**Observable**: Region either completes (with partial tiles) or is marked "failed"
|
||||
**Pass criterion**: Status is either "completed" or "failed" (never stuck in "processing" indefinitely); max processing time < 300s
|
||||
|
||||
## RS-04: Queue Capacity Limit
|
||||
|
||||
**Trigger**: Submit 1001+ region requests rapidly (exceeds capacity 1000)
|
||||
**Observable**: Queue rejects overflow requests
|
||||
**Pass criterion**: First 1000 accepted; subsequent requests return error or are dropped; no crash
|
||||
|
||||
## RS-05: Concurrent Download Limit Respected
|
||||
|
||||
**Trigger**: Submit large region (many tiles) and observe download concurrency
|
||||
**Observable**: At most MaxConcurrentDownloads (4) HTTP requests to Google Maps simultaneously
|
||||
**Pass criterion**: No more than 4 concurrent outbound tile requests at any point (behavioral; requires observation or logging)
|
||||
|
||||
## RS-06: Route Processing with All Regions Completing
|
||||
|
||||
**Trigger**: Create route with requestMaps=true, wait for completion
|
||||
**Observable**: Route transitions from processing to ready
|
||||
**Pass criterion**: mapsReady=true; no regions stuck in "processing"
|
||||
@@ -0,0 +1,25 @@
|
||||
# Resource Limit Test Scenarios
|
||||
|
||||
## RL-01: ZIP File Size Limit (50 MB)
|
||||
|
||||
**Trigger**: Create route with enough tiles to approach 50 MB ZIP limit
|
||||
**Observable**: ZIP file size
|
||||
**Pass criterion**: ZIP file ≤ 50 MB; tiles included up to limit; no crash on boundary
|
||||
|
||||
## RL-02: Queue Capacity (1000)
|
||||
|
||||
**Trigger**: Submit 1000 region requests
|
||||
**Observable**: Queue accepts all 1000
|
||||
**Pass criterion**: All 1000 requests accepted and queued; no rejection until capacity reached
|
||||
|
||||
## RL-03: Concurrent Download Semaphore (4)
|
||||
|
||||
**Trigger**: Process region with many tiles
|
||||
**Observable**: Concurrent outbound HTTP connections
|
||||
**Pass criterion**: Never exceeds 4 simultaneous tile downloads (configurable via ProcessingConfig.MaxConcurrentDownloads)
|
||||
|
||||
## RL-04: Concurrent Region Processing (20)
|
||||
|
||||
**Trigger**: Queue 25 region requests
|
||||
**Observable**: Processing parallelism
|
||||
**Pass criterion**: At most 20 regions processing simultaneously (configurable via ProcessingConfig.MaxConcurrentRegions); remaining wait in queue
|
||||
@@ -0,0 +1,25 @@
|
||||
# Security Test Scenarios
|
||||
|
||||
## SEC-01: SQL Injection via Coordinate Parameters
|
||||
|
||||
**Trigger**: GET /api/satellite/tiles/latlon?Latitude=1;DROP TABLE tiles--&Longitude=1&ZoomLevel=18
|
||||
**Expected**: Request rejected or treated as invalid parameter
|
||||
**Pass criterion**: HTTP 400 or parameter parsing error; no database damage; tiles table intact
|
||||
|
||||
## SEC-02: Path Traversal in Tile Serving
|
||||
|
||||
**Trigger**: GET /tiles/18/../../../etc/passwd
|
||||
**Expected**: Request rejected; no file outside tiles directory served
|
||||
**Pass criterion**: HTTP 404 or 400; response body does not contain system file content
|
||||
|
||||
## SEC-03: Oversized Region Request
|
||||
|
||||
**Trigger**: POST /api/satellite/request with sizeMeters=999999999
|
||||
**Expected**: Either rejected or handled without resource exhaustion
|
||||
**Pass criterion**: No OOM; no infinite processing; either error response or bounded processing
|
||||
|
||||
## SEC-04: Malformed JSON in Route Request
|
||||
|
||||
**Trigger**: POST /api/satellite/route with invalid JSON body
|
||||
**Expected**: Parse error returned
|
||||
**Pass criterion**: HTTP 400; error message indicates parsing failure; no crash
|
||||
@@ -0,0 +1,30 @@
|
||||
# Test Data Management
|
||||
|
||||
## Data Sources
|
||||
|
||||
| Source | Location | Type |
|
||||
|--------|----------|------|
|
||||
| Test coordinates | `_docs/00_problem/input_data/test_coordinates.md` | Static reference data |
|
||||
| Expected results | `_docs/00_problem/input_data/expected_results/results_report.md` | Pass/fail criteria |
|
||||
| Generated tiles | ./tiles/ (Docker volume) | Runtime artifacts |
|
||||
| Output files | ./ready/ (Docker volume) | Runtime artifacts |
|
||||
|
||||
## Test Data Lifecycle
|
||||
|
||||
1. **Before test run**: Fresh PostgreSQL database (empty, migrations applied on API startup)
|
||||
2. **During test run**: Each test creates its own data (unique GUIDs for routes/regions)
|
||||
3. **After test run**: Data persists in volumes for inspection; DB data disposable
|
||||
|
||||
## Data Isolation
|
||||
|
||||
- Each test uses `Guid.NewGuid()` for region/route IDs — no conflicts between tests
|
||||
- Tests run sequentially — no concurrency conflicts
|
||||
- Tile cache is shared across tests (by design — tests tile reuse)
|
||||
|
||||
## Reference Coordinates
|
||||
|
||||
| Label | Latitude | Longitude | Use |
|
||||
|-------|----------|-----------|-----|
|
||||
| Tile/Region test point | 47.461747 | 37.647063 | Tile download, region processing |
|
||||
| Route area (start) | 48.276067 | 37.384458 | Route creation, map processing |
|
||||
| Route area (east) | 48.276067 | 37.519458 | Extended route (non-overlapping) |
|
||||
@@ -0,0 +1,53 @@
|
||||
# Traceability Matrix
|
||||
|
||||
## Acceptance Criteria → Test Mapping
|
||||
|
||||
| AC | Description | Tests | Coverage |
|
||||
|----|-------------|-------|----------|
|
||||
| T1 | Tiles cached, not re-downloaded | BT-02 | ✓ |
|
||||
| T2 | Concurrent download limit | RS-05, RL-03 | ✓ |
|
||||
| T3 | Tile stored with correct path | BT-01 | ✓ |
|
||||
| T4 | Tile metadata persisted | BT-01 | ✓ |
|
||||
| R1 | Region state transitions | BT-03, BT-04, BT-05 | ✓ |
|
||||
| R2 | CSV manifest generated | BT-03, BT-04, BT-05 | ✓ |
|
||||
| R3 | Summary file generated | BT-03, BT-04, BT-05 | ✓ |
|
||||
| R4 | Stitched image when requested | BT-05 | ✓ |
|
||||
| R5 | Stitched image valid content | BT-05 | ✓ |
|
||||
| R6 | Region processing bounded | RL-04 | ✓ |
|
||||
| RT1 | Points interpolated at ~200m | BT-06 | ✓ |
|
||||
| RT2 | Point types correctly assigned | BT-06 | ✓ |
|
||||
| RT3 | Total distance calculated | BT-06 | ✓ |
|
||||
| RT4 | Geofence filtering applied | BT-11 | ✓ |
|
||||
| RT5 | ZIP ≤ 50 MB | BT-09, RL-01 | ✓ |
|
||||
| RT6 | Route map stitched | BT-08, BT-10, BT-12 | ✓ |
|
||||
| A1 | Region request returns immediately | BT-03 | ✓ |
|
||||
| A2 | Status endpoint reflects state | BT-03, BT-07 | ✓ |
|
||||
| A3 | Route returns computed metadata | BT-06 | ✓ |
|
||||
| S1 | Migrations run on startup | RS-02 | ✓ |
|
||||
| S2 | Queue rejects when full | RS-04, RL-02 | ✓ |
|
||||
| S3 | Failed regions marked failed | RS-03 | ✓ |
|
||||
|
||||
## Restrictions → Test Mapping
|
||||
|
||||
| Restriction | Tests | Coverage |
|
||||
|-------------|-------|----------|
|
||||
| .NET 8.0 runtime | All (via Docker image) | ✓ |
|
||||
| PostgreSQL 16 | All (via docker-compose) | ✓ |
|
||||
| Single instance | PT-05 (concurrent regions on one instance) | ✓ |
|
||||
| Max 4 concurrent downloads | RS-05, RL-03 | ✓ |
|
||||
| Max 20 concurrent regions | RL-04 | ✓ |
|
||||
| Queue capacity 1000 | RS-04, RL-02 | ✓ |
|
||||
| Max ZIP 50 MB | RL-01 | ✓ |
|
||||
| No authentication | SEC-01 through SEC-04 (all requests accepted without auth) | ✓ |
|
||||
|
||||
## Coverage Summary
|
||||
|
||||
| Category | Total Tests | ACs Covered | Restrictions Covered |
|
||||
|----------|-------------|-------------|---------------------|
|
||||
| Blackbox (positive) | 12 | 19/22 | — |
|
||||
| Blackbox (negative) | 5 | — | — |
|
||||
| Performance | 6 | 2 | 1 |
|
||||
| Resilience | 6 | 4 | 3 |
|
||||
| Security | 4 | — | 1 |
|
||||
| Resource Limits | 4 | 3 | 4 |
|
||||
| **Total** | **37** | **22/22 (100%)** | **8/8 (100%)** |
|
||||
Reference in New Issue
Block a user