[AZ-351][AZ-352][AZ-363] Refactor 03 batch 1: critical defensive fixes
ci/woodpecker/push/01-test Pipeline was successful
ci/woodpecker/push/02-build-push Pipeline was successful

AZ-351: Resolve ILogger<DatabaseMigrator> directly from DI in
Program.cs instead of casting ILogger<Program> (which always
returned null). Migrator now logs through Serilog at startup.

AZ-352: Drop empty catch in
RouteProcessingService.ExtractTileCoordinatesFromFilename. Convert
the method from private static to internal instance so it can use
the existing _logger (per coderule: side-effecting code must not be
static). Add typed null-guard via ArgumentNullException.ThrowIfNull
so unexpected exceptions propagate. Adds InternalsVisibleTo on the
RouteManagement csproj for SatelliteProvider.Tests, plus 4 unit
tests in RouteProcessingServiceTests.cs covering AC-1 (valid /
malformed / non-numeric) and AC-2 (null path propagation).

AZ-363: Delete _totalEnqueued / _totalDequeued fields and the two
non-atomic ++ writes in RegionRequestQueue. Fields were write-only
dead code and a thread-safety hazard.

Tests: 44/44 unit + 5/5 smoke (scripts/run-tests.sh --smoke).
Code review verdict: PASS, 0 findings, 0 auto-fix attempts.
Batch report: _docs/03_implementation/batch_07_report.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-10 23:34:17 +03:00
parent ff030a9521
commit de4d4fa760
11 changed files with 201 additions and 26 deletions
@@ -0,0 +1,55 @@
# Refactor: fix null ILogger passed to DatabaseMigrator at startup
**Task**: AZ-351_refactor_fix_null_logger_migrator
**Name**: Fix null logger to DatabaseMigrator
**Description**: Resolve `ILogger<DatabaseMigrator>` directly from DI instead of casting `ILogger<Program>`, which always returns null.
**Complexity**: 2 points
**Dependencies**: None
**Component**: Api
**Tracker**: AZ-351
**Epic**: AZ-350
## Problem
`SatelliteProvider.Api/Program.cs:82-83` does `app.Services.GetRequiredService<ILogger<Program>>() as ILogger<DatabaseMigrator>`. The cast between unrelated generic instantiations always returns null. `DatabaseMigrator` runs with a null logger, so any migration failure path that depends on logging is silent.
## Outcome
- `DatabaseMigrator` receives a real `ILogger<DatabaseMigrator>` instance from DI.
- Migration log entries appear in startup output and persist to log sinks.
- 37 unit + 5 smoke tests stay green.
## Scope
### Included
- Replace the `as` cast with `app.Services.GetRequiredService<ILogger<DatabaseMigrator>>()`.
- Confirm `DatabaseMigrator`'s constructor logs at least one entry on success and one on failure (already present, just verify).
### Excluded
- Changing `DatabaseMigrator`'s logging strategy or log levels.
- Adding migration-related metrics or observability beyond what already exists.
## Acceptance Criteria
**AC-1: Migrator receives a real logger**
Given the post-refactor `Program.cs`
When the host starts
Then `DatabaseMigrator` is constructed with a non-null `ILogger<DatabaseMigrator>` (verifiable by a unit test or by inspecting startup logs).
**AC-2: Tests stay green**
Given the post-refactor build
When `scripts/run-tests.sh --smoke` runs
Then all 37 unit + 5 smoke scenarios pass.
## Constraints
- No DI graph reorder.
- No public API change.
## Risks & Mitigation
**Risk 1: hidden assumption that logger may be null**
- *Risk*: `DatabaseMigrator` may have a defensive null-check that masked the bug.
- *Mitigation*: keep the null-check during this change; remove it in a follow-up only after we confirm via tests that the live logger is always provided.
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C01).
@@ -0,0 +1,60 @@
# Refactor: replace empty catch in ExtractTileCoordinatesFromFilename
**Task**: AZ-352_refactor_replace_empty_catch_extract_tile_coords
**Name**: Remove silent empty catch in tile-coord parser
**Description**: Replace the empty `catch { }` in `RouteProcessingService.ExtractTileCoordinatesFromFilename` with a typed catch + warning log.
**Complexity**: 2 points
**Dependencies**: None
**Component**: Services.RouteManagement
**Tracker**: AZ-352
**Epic**: AZ-350
## Problem
`SatelliteProvider.Services.RouteManagement/RouteProcessingService.cs:610-630` swallows every parse/IO exception with `catch { }` and returns `(-1, -1)`. Callers treat this as "tile not stitchable" — the tile silently disappears from the route map. Direct violation of `coderule.mdc` ("Never suppress errors silently").
## Outcome
- Malformed filenames produce a visible warning log entry.
- Unexpected exception types propagate up the call stack instead of being swallowed.
- 37 unit + 5 smoke tests stay green.
## Scope
### Included
- Replace `catch { }` with `catch (FormatException) { ... } catch (ArgumentException) { ... }` plus warning log via the existing `_logger`.
- Let any other exception propagate.
- Add a unit test that feeds a malformed filename and asserts on the warning log entry.
### Excluded
- Changing the filename format or the writer side (`StorageConfig.GetTileFilePath`).
- Changing the `(-1, -1)` sentinel — that lives until C13 reorganizes the parser/writer pairing.
## Acceptance Criteria
**AC-1: Malformed filename logs a warning**
Given a file that does not match the `tile_{ts}_{x}_{y}.jpg` pattern
When `ExtractTileCoordinatesFromFilename` is called on it
Then the function returns `(-1, -1)` AND a warning log entry is emitted naming the file.
**AC-2: Unexpected exception propagates**
Given a hypothetical unrelated exception (e.g., `IOException`) raised inside the parser
When `ExtractTileCoordinatesFromFilename` is called
Then the exception is not swallowed and propagates to the caller.
**AC-3: Tests stay green**
Given the post-refactor build
When `scripts/run-tests.sh --smoke` runs
Then all 37 unit + 5 smoke scenarios pass.
## Constraints
- Behavior for valid filenames must be byte-identical.
## Risks & Mitigation
**Risk 1: existing callers may rely on the swallow-all behavior**
- *Risk*: another path in `RouteProcessingService` may pass arbitrary file lists where IO errors are expected.
- *Mitigation*: grep all callers; if any expects swallow-all, add explicit handling at that call site.
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C02).
@@ -0,0 +1,52 @@
# Refactor: delete write-only counters in RegionRequestQueue
**Task**: AZ-363_refactor_delete_writeonly_counters
**Name**: Remove non-atomic write-only counters
**Description**: Delete `_totalEnqueued` and `_totalDequeued` fields plus the two `++` lines in `RegionRequestQueue`.
**Complexity**: 1 point
**Dependencies**: None
**Component**: Services.RegionProcessing
**Tracker**: AZ-363
**Epic**: AZ-350
## Problem
`SatelliteProvider.Services.RegionProcessing/RegionRequestQueue.cs:12-13, 28, 38` uses `_totalEnqueued++` and `_totalDequeued++` on `int` fields. The increments are not atomic under concurrent producers/consumers, and the fields are never read anywhere in the codebase. Telemetry-via-`++` is both a thread-safety bug and dead code.
## Outcome
- The two fields and the two `++` lines are removed.
- 37 unit + 5 smoke tests stay green.
## Scope
### Included
- Delete the field declarations.
- Delete the `++` lines from `Enqueue` / `Dequeue`.
### Excluded
- Adding proper `Meter`/`Counter<long>` telemetry (deferred to a future observability ticket).
## Acceptance Criteria
**AC-1: Fields and increments removed**
Given the post-refactor `RegionRequestQueue.cs`
When grepped for `_totalEnqueued` or `_totalDequeued`
Then zero matches.
**AC-2: Tests stay green**
Given the post-refactor build
When `scripts/run-tests.sh --smoke` runs
Then all 37 unit + 5 smoke scenarios pass.
## Constraints
- No public API change (fields are private).
## Risks & Mitigation
**Risk 1: future telemetry need**
- *Risk*: someone may want enqueue/dequeue counts later.
- *Mitigation*: that's a separate, properly-implemented (atomic, read-out) ticket.
Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C10).