Files
satellite-provider/SatelliteProvider.Tests/RepositoryRefactorTests.cs
T
Oleksandr Bezdieniezhnykh 6099d1c86b
ci/woodpecker/push/01-test Pipeline was successful
ci/woodpecker/push/02-build-push Pipeline was successful
[AZ-376] [AZ-378] [AZ-379] [AZ-380] Repo cleanup: dead code, logger discipline, ColumnList consts
Batch 23 of refactor 03-code-quality-refactoring (4 tasks, 5 SP):

- AZ-376 (C23): Delete unused FindExistingTileAsync from
  ITileRepository / TileRepository. No callers; method also took the
  obsolete `version` arg removed by C06/AZ-357.
- AZ-378 (C25): Repository _logger discipline.
  TileRepository.GetTilesByRegionAsync now emits LogWarning when the
  query exceeds SlowQueryThresholdMs (500 ms). RegionRepository and
  RouteRepository drop the unused ILogger<TRepo> field, parameter, and
  using; Program.cs DI registrations updated.
- AZ-379 (C26): Extract `private const string ColumnList` per repo
  (TileRepository, RegionRepository, RouteRepository); SELECTs use
  $@"SELECT {ColumnList} FROM ..." (C# 10+ const interpolation).
  INSERT/UPDATE/DELETE unchanged; route_points single-site SELECT left
  inline.
- AZ-380 (C27): Delete dead alias GeoUtils.CalculatePolygonDiagonalDistance.

Tests: +9 new (RepositoryRefactorTests x8, GeoUtilsRefactorTests x1)
covering each AC via reflection / file-content assertions; pattern
mirrors ToolingConfigurationTests (b22) and AcceptanceCriteriaRT2Tests
(b19). Unit suite 181 -> 190, all green. dotnet format clean.

Code review: PASS_WITH_WARNINGS (3 Low findings, all informational or
out-of-scope for this batch). See
_docs/03_implementation/reviews/batch_23_review.md.

Cumulative review counter 2/3; next K=3 review fires after batch 24.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 04:57:49 +03:00

196 lines
8.0 KiB
C#

using System.Reflection;
using FluentAssertions;
using SatelliteProvider.DataAccess.Repositories;
namespace SatelliteProvider.Tests;
public class RepositoryRefactorTests
{
[Fact]
public void TileRepository_DoesNotExposeFindExistingTileAsync_AZ376_AC1()
{
// Arrange
var interfaceType = typeof(ITileRepository);
var concreteType = typeof(TileRepository);
// Assert
interfaceType.GetMethod("FindExistingTileAsync").Should().BeNull(
"AZ-376 deletes the unused FindExistingTileAsync method from ITileRepository");
concreteType.GetMethod("FindExistingTileAsync").Should().BeNull(
"AZ-376 deletes the unused FindExistingTileAsync implementation from TileRepository");
}
[Fact]
public void TileRepository_KeepsAndUsesLogger_AZ378_AC1()
{
// Arrange
var path = LocateRepoFile(Path.Combine("SatelliteProvider.DataAccess", "Repositories", "TileRepository.cs"));
path.Should().NotBeNull();
var content = File.ReadAllText(path!);
// Assert
content.Should().Contain("ILogger<TileRepository>",
"TileRepository keeps the logger field per AZ-378 recommended split");
content.Should().Contain("_logger.LogWarning",
"AZ-378 AC-1 requires the kept logger to actually be used (slow-query warning)");
content.Should().Contain("SlowQueryThresholdMs",
"Slow-query threshold is a named const per the task spec");
}
[Fact]
public void RegionRepository_HasNoUnusedLoggerParameter_AZ378_AC2()
{
// Arrange
var ctors = typeof(RegionRepository).GetConstructors(BindingFlags.Public | BindingFlags.Instance);
// Assert
ctors.Should().HaveCount(1);
ctors[0].GetParameters().Should().OnlyContain(p => p.ParameterType == typeof(string),
"AZ-378 removes the unused ILogger<RegionRepository> parameter; only the connection string remains");
}
[Fact]
public void RouteRepository_HasNoUnusedLoggerParameter_AZ378_AC2()
{
// Arrange
var ctors = typeof(RouteRepository).GetConstructors(BindingFlags.Public | BindingFlags.Instance);
// Assert
ctors.Should().HaveCount(1);
ctors[0].GetParameters().Should().OnlyContain(p => p.ParameterType == typeof(string),
"AZ-378 removes the unused ILogger<RouteRepository> parameter; only the connection string remains");
}
[Fact]
public void TileRepository_DefinesColumnListConstantOnce_AZ379_AC1()
{
// Arrange
var path = LocateRepoFile(Path.Combine("SatelliteProvider.DataAccess", "Repositories", "TileRepository.cs"));
path.Should().NotBeNull();
var content = File.ReadAllText(path!);
// Assert
CountOccurrences(content, "private const string ColumnList").Should().Be(1,
"AZ-379 AC-1 requires exactly one ColumnList constant per repository");
CountOccurrences(content, "{ColumnList}").Should().BeGreaterThanOrEqualTo(3,
"TileRepository has at least 3 SELECTs that should reuse {ColumnList} (GetById, GetByTileCoordinates, GetTilesByRegion)");
}
[Fact]
public void RegionRepository_DefinesColumnListConstantOnce_AZ379_AC1()
{
// Arrange
var path = LocateRepoFile(Path.Combine("SatelliteProvider.DataAccess", "Repositories", "RegionRepository.cs"));
path.Should().NotBeNull();
var content = File.ReadAllText(path!);
// Assert
CountOccurrences(content, "private const string ColumnList").Should().Be(1);
CountOccurrences(content, "{ColumnList}").Should().BeGreaterThanOrEqualTo(2,
"RegionRepository has 2 SELECTs that should reuse {ColumnList} (GetById, GetByStatus)");
}
[Fact]
public void RouteRepository_DefinesColumnListConstantOnce_AZ379_AC1()
{
// Arrange
var path = LocateRepoFile(Path.Combine("SatelliteProvider.DataAccess", "Repositories", "RouteRepository.cs"));
path.Should().NotBeNull();
var content = File.ReadAllText(path!);
// Assert
CountOccurrences(content, "private const string ColumnList").Should().Be(1);
CountOccurrences(content, "{ColumnList}").Should().BeGreaterThanOrEqualTo(2,
"RouteRepository has 2 routes-table SELECTs that should reuse {ColumnList} (GetById, GetRoutesWithPendingMaps)");
}
[Fact]
public void RepositoryColumnLists_ContainExpectedColumns_AZ379_AC2()
{
// Arrange
var tilePath = LocateRepoFile(Path.Combine("SatelliteProvider.DataAccess", "Repositories", "TileRepository.cs"));
var regionPath = LocateRepoFile(Path.Combine("SatelliteProvider.DataAccess", "Repositories", "RegionRepository.cs"));
var routePath = LocateRepoFile(Path.Combine("SatelliteProvider.DataAccess", "Repositories", "RouteRepository.cs"));
tilePath.Should().NotBeNull();
regionPath.Should().NotBeNull();
routePath.Should().NotBeNull();
var tileContent = File.ReadAllText(tilePath!);
var regionContent = File.ReadAllText(regionPath!);
var routeContent = File.ReadAllText(routePath!);
var tileColumns = new[]
{
"id", "tile_zoom as TileZoom", "tile_x as TileX", "tile_y as TileY",
"latitude", "longitude",
"tile_size_meters as TileSizeMeters", "tile_size_pixels as TileSizePixels",
"image_type as ImageType", "maps_version as MapsVersion", "version",
"file_path as FilePath", "created_at as CreatedAt", "updated_at as UpdatedAt"
};
var regionColumns = new[]
{
"id", "latitude", "longitude", "size_meters as SizeMeters",
"zoom_level as ZoomLevel", "status",
"csv_file_path as CsvFilePath", "summary_file_path as SummaryFilePath",
"tiles_downloaded as TilesDownloaded", "tiles_reused as TilesReused",
"stitch_tiles as StitchTiles",
"created_at as CreatedAt", "updated_at as UpdatedAt"
};
var routeColumns = new[]
{
"id", "name", "description", "region_size_meters as RegionSizeMeters",
"zoom_level as ZoomLevel", "total_distance_meters as TotalDistanceMeters",
"total_points as TotalPoints", "request_maps as RequestMaps",
"maps_ready as MapsReady", "create_tiles_zip as CreateTilesZip",
"csv_file_path as CsvFilePath",
"summary_file_path as SummaryFilePath", "stitched_image_path as StitchedImagePath",
"tiles_zip_path as TilesZipPath",
"created_at as CreatedAt", "updated_at as UpdatedAt"
};
// Assert
foreach (var column in tileColumns)
{
tileContent.Should().Contain(column,
$"TileRepository ColumnList must include '{column}' to keep generated SQL semantically identical");
}
foreach (var column in regionColumns)
{
regionContent.Should().Contain(column,
$"RegionRepository ColumnList must include '{column}' to keep generated SQL semantically identical");
}
foreach (var column in routeColumns)
{
routeContent.Should().Contain(column,
$"RouteRepository ColumnList must include '{column}' to keep generated SQL semantically identical");
}
}
private static int CountOccurrences(string haystack, string needle)
{
var count = 0;
var index = 0;
while ((index = haystack.IndexOf(needle, index, StringComparison.Ordinal)) != -1)
{
count++;
index += needle.Length;
}
return count;
}
private static string? LocateRepoFile(string relativePath)
{
var dir = new DirectoryInfo(Directory.GetCurrentDirectory());
while (dir is not null)
{
var candidate = Path.Combine(dir.FullName, relativePath);
if (File.Exists(candidate))
{
return candidate;
}
dir = dir.Parent;
}
return null;
}
}