[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:
Oleksandr Bezdieniezhnykh
2026-05-10 04:44:08 +03:00
parent 25a644a9bf
commit b0fffa6d42
68 changed files with 4192 additions and 11 deletions
@@ -0,0 +1,65 @@
# Module: Services/GoogleMapsDownloaderV2
## Purpose
Downloads satellite imagery tiles from Google Maps. Handles session token management, concurrent download throttling, retry logic with exponential backoff, and tile deduplication.
## Public Interface
### GoogleMapsDownloaderV2
- Constructor: `GoogleMapsDownloaderV2(ILogger, IOptions<MapConfig>, IOptions<StorageConfig>, IOptions<ProcessingConfig>, IHttpClientFactory)`
- `DownloadSingleTileAsync(double lat, double lon, int zoomLevel, CancellationToken) → Task<DownloadedTileInfoV2>`: downloads one tile at specified coordinates. Validates zoom level, creates session token, downloads image, saves to disk.
- `GetTilesWithMetadataAsync(GeoPoint center, double radiusM, int zoomLevel, IEnumerable<TileEntity> existingTiles, CancellationToken) → Task<List<DownloadedTileInfoV2>>`: downloads all tiles in a bounding box, skipping those already present in `existingTiles`. Manages session token rotation.
### DownloadedTileInfoV2 (record)
- `X`, `Y` (int), `ZoomLevel` (int), `CenterLatitude`, `CenterLongitude` (double), `FilePath` (string), `TileSizeMeters` (double)
### RateLimitException (exception)
Custom exception thrown when Google Maps returns 429 Too Many Requests and retries are exhausted.
## Internal Logic
- **Allowed zoom levels**: 15, 16, 17, 18, 19 — throws `ArgumentException` for others
- **URL template**: `https://mt{server}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}&token={token}`
- **Session tokens**: obtained via `https://tile.googleapis.com/v1/createSession?key={apiKey}`, rotated every `SessionTokenReuseCount` tiles (default: 100)
- **Concurrency control**: `SemaphoreSlim` limits parallel downloads to `MaxConcurrentDownloads` (default: 4)
- **Deduplication**: `ConcurrentDictionary<string, Task<DownloadedTileInfoV2>>` (`_activeDownloads`) prevents duplicate concurrent downloads of the same tile
- **Retry logic**: exponential backoff (1s base, 30s max, 5 retries) for 429 and 5xx errors. Cancellation and auth errors (401, 403) propagate immediately.
- **Server selection**: `(x + y) % 4` distributes requests across `mt0``mt3`; single-tile downloads always use `mt0`
- **Delay between requests**: configurable via `ProcessingConfig.DelayBetweenRequestsMs`
- **Tile size calculation**: `CalculateTileSizeInMeters` uses Earth circumference × cos(latitude) / (2^zoom × 256)
## Dependencies
- `SatelliteProvider.Common.Configs` — MapConfig, StorageConfig, ProcessingConfig
- `SatelliteProvider.Common.DTO` — GeoPoint
- `SatelliteProvider.Common.Utils` — GeoUtils
- `SatelliteProvider.DataAccess.Models` — TileEntity (for existingTiles parameter)
- NuGet: `Newtonsoft.Json`, `Microsoft.Extensions.Http`, `Microsoft.Extensions.Options`
## Consumers
- `TileService``GetTilesWithMetadataAsync`
- `Program.cs` (ServeTile, GetTileByLatLon) — `DownloadSingleTileAsync`
## Data Models
Produces `DownloadedTileInfoV2` records; accepts `TileEntity` for cache checks.
## Configuration
| Config | Key | Used For |
|--------|-----|----------|
| MapConfig | ApiKey | Session token requests |
| StorageConfig | TilesDirectory | File save paths |
| ProcessingConfig | MaxConcurrentDownloads | SemaphoreSlim capacity |
| ProcessingConfig | DelayBetweenRequestsMs | Throttle delay |
| ProcessingConfig | SessionTokenReuseCount | Token rotation threshold |
## External Integrations
| Integration | Protocol | Details |
|-------------|----------|---------|
| Google Maps Tile API | HTTPS | `mt*.google.com/vt/lyrs=s` for tiles |
| Google Maps Session API | HTTPS | `tile.googleapis.com/v1/createSession` |
| File system | Local FS | Writes JPEG tiles to `StorageConfig.TilesDirectory` |
## Security
- API key transmitted over HTTPS to Google endpoints
- User-Agent spoofs a Chrome browser to match expected Google Maps client
## Tests
No dedicated unit tests (the test file `GoogleMapsDownloaderTests.cs` contains only a dummy test).