mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-21 13:21:14 +00:00
6099d1c86b
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>
196 lines
8.0 KiB
C#
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;
|
|
}
|
|
}
|