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 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 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 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", "source", "captured_at as CapturedAt", "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; } }