[AZ-357] Refactor C06: drop tile Version concept; cumulative review batches 7-9
ci/woodpecker/push/01-test Pipeline was successful
ci/woodpecker/push/02-build-push Pipeline was successful

AZ-357 — eliminate year-based tile cache expiry (LF-1):
- Migration 012: drop 5-col unique index, dedupe by (lat,lon,zoom,
  size) keeping max(updated_at), add new 4-col unique index, make
  version column nullable + drop default. Column itself preserved
  per coderule (column drops require explicit confirmation; tracked
  in AZ-373 / C20).
- TileEntity.Version, TileMetadata.Version, DownloadTileResponse.
  Version: int -> int? (HTTP shape preserved; field still in JSON).
- TileService.DownloadAndStoreTilesAsync: drop currentVersion year
  computation and the .Where(t => t.Version == currentVersion)
  cache filter. BuildTileEntity: drop year arg; write Version=null.
- TileRepository: ON CONFLICT now 4-col; lookup queries
  ORDER BY updated_at DESC instead of version DESC.
- Tests: replace inverted BT02b with positive AZ357_AC1
  (prior-year cached tile is reused). Add BuildTileEntity_
  DoesNotPopulateVersion_AZ357 to enforce the no-write contract.
- 69 unit + 5 smoke + 3 stub-contract integration tests pass.

Cumulative code review (batches 7-9, 7 tasks): VERDICT=PASS.
Report at _docs/03_implementation/reviews/batch_09_review.md.
Zero Critical/High/Medium/Low findings. Architecture baseline
remains clean.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-11 00:20:47 +03:00
parent 5a28f67d33
commit 581dff206e
12 changed files with 306 additions and 41 deletions
+39 -12
View File
@@ -128,13 +128,13 @@ public class TileServiceTests
}
[Fact]
public async Task DownloadAndStoreTilesAsync_IgnoresStaleVersionCachedTiles_BT02b()
public async Task DownloadAndStoreTilesAsync_TreatsCachedTileFromPriorYearAsFresh_AZ357_AC1()
{
// Arrange
var downloader = new Mock<ISatelliteDownloader>();
var tileRepo = new Mock<ITileRepository>();
var stale = new List<TileEntity>
var priorYearCached = new List<TileEntity>
{
new()
{
@@ -143,13 +143,15 @@ public class TileServiceTests
Latitude = TestCoordinates.TileLat,
Longitude = TestCoordinates.TileLon,
Version = DateTime.UtcNow.Year - 1,
FilePath = "tiles/18/0/0/old.jpg",
FilePath = "tiles/18/0/0/cached_prior_year.jpg",
TileSizePixels = 256,
ImageType = "jpg",
},
};
tileRepo
.Setup(r => r.GetTilesByRegionAsync(
It.IsAny<double>(), It.IsAny<double>(), It.IsAny<double>(), It.IsAny<int>()))
.ReturnsAsync(stale);
.ReturnsAsync(priorYearCached);
IEnumerable<ExistingTileInfo>? capturedExisting = null;
downloader
@@ -161,10 +163,7 @@ public class TileServiceTests
It.IsAny<CancellationToken>()))
.Callback<GeoPoint, double, int, IEnumerable<ExistingTileInfo>, CancellationToken>(
(_, _, _, e, _) => capturedExisting = e.ToList())
.ReturnsAsync(new List<DownloadedTileInfoV2>
{
MakeDownloaded(123, 456, TestCoordinates.DefaultZoom, TestCoordinates.TileLat, TestCoordinates.TileLon),
});
.ReturnsAsync(new List<DownloadedTileInfoV2>());
var service = BuildService(downloader, tileRepo);
@@ -173,10 +172,38 @@ public class TileServiceTests
TestCoordinates.TileLat, TestCoordinates.TileLon, 200, TestCoordinates.DefaultZoom);
// Assert
capturedExisting.Should().BeEmpty(
"stale-version tiles must not be passed to the downloader as 'already have it'");
result.Should().HaveCount(1, "only the freshly-downloaded tile is in the result");
tileRepo.Verify(r => r.InsertAsync(It.IsAny<TileEntity>()), Times.Once);
capturedExisting.Should().NotBeNull();
capturedExisting!.Should().ContainSingle(
"after AZ-357 the version column no longer gates the cache; the prior-year row is reusable");
result.Should().HaveCount(1);
result[0].Id.Should().Be(priorYearCached[0].Id);
tileRepo.Verify(r => r.InsertAsync(It.IsAny<TileEntity>()), Times.Never,
"cached tile from prior year must not be re-inserted");
}
[Fact]
public void BuildTileEntity_DoesNotPopulateVersion_AZ357()
{
// Arrange
var downloader = new Mock<ISatelliteDownloader>();
var tileRepo = new Mock<ITileRepository>();
TileEntity? captured = null;
tileRepo
.Setup(r => r.InsertAsync(It.IsAny<TileEntity>()))
.Callback<TileEntity>(e => captured = e)
.ReturnsAsync(Guid.NewGuid());
downloader
.Setup(d => d.DownloadSingleTileAsync(It.IsAny<double>(), It.IsAny<double>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new DownloadedTileInfoV2(1, 2, 18, 47.46, 37.65, "tiles/18/1/2.jpg", 100.0));
var service = BuildService(downloader, tileRepo);
// Act
_ = service.DownloadAndStoreSingleTileAsync(47.46, 37.65, 18).GetAwaiter().GetResult();
// Assert
captured.Should().NotBeNull();
captured!.Version.Should().BeNull("AZ-357: new code never writes the deprecated year-based version");
}
[Fact]