mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-21 09:01:16 +00:00
[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>
This commit is contained in:
@@ -30,8 +30,8 @@ builder.Services.Configure<StorageConfig>(builder.Configuration.GetSection("Stor
|
|||||||
builder.Services.Configure<ProcessingConfig>(builder.Configuration.GetSection("ProcessingConfig"));
|
builder.Services.Configure<ProcessingConfig>(builder.Configuration.GetSection("ProcessingConfig"));
|
||||||
|
|
||||||
builder.Services.AddSingleton<ITileRepository>(sp => new TileRepository(connectionString, sp.GetRequiredService<ILogger<TileRepository>>()));
|
builder.Services.AddSingleton<ITileRepository>(sp => new TileRepository(connectionString, sp.GetRequiredService<ILogger<TileRepository>>()));
|
||||||
builder.Services.AddSingleton<IRegionRepository>(sp => new RegionRepository(connectionString, sp.GetRequiredService<ILogger<RegionRepository>>()));
|
builder.Services.AddSingleton<IRegionRepository>(sp => new RegionRepository(connectionString));
|
||||||
builder.Services.AddSingleton<IRouteRepository>(sp => new RouteRepository(connectionString, sp.GetRequiredService<ILogger<RouteRepository>>()));
|
builder.Services.AddSingleton<IRouteRepository>(sp => new RouteRepository(connectionString));
|
||||||
|
|
||||||
builder.Services.AddHttpClient();
|
builder.Services.AddHttpClient();
|
||||||
|
|
||||||
|
|||||||
@@ -125,9 +125,4 @@ public static class GeoUtils
|
|||||||
var centerLon = (northWest.Lon + southEast.Lon) / 2.0;
|
var centerLon = (northWest.Lon + southEast.Lon) / 2.0;
|
||||||
return new GeoPoint(centerLat, centerLon);
|
return new GeoPoint(centerLat, centerLon);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static double CalculatePolygonDiagonalDistance(GeoPoint northWest, GeoPoint southEast)
|
|
||||||
{
|
|
||||||
return CalculateDistance(northWest, southEast);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ public interface ITileRepository
|
|||||||
{
|
{
|
||||||
Task<TileEntity?> GetByIdAsync(Guid id);
|
Task<TileEntity?> GetByIdAsync(Guid id);
|
||||||
Task<TileEntity?> GetByTileCoordinatesAsync(int tileZoom, int tileX, int tileY);
|
Task<TileEntity?> GetByTileCoordinatesAsync(int tileZoom, int tileX, int tileY);
|
||||||
Task<TileEntity?> FindExistingTileAsync(double latitude, double longitude, double tileSizeMeters, int zoomLevel, int version);
|
|
||||||
Task<IEnumerable<TileEntity>> GetTilesByRegionAsync(double latitude, double longitude, double sizeMeters, int zoomLevel);
|
Task<IEnumerable<TileEntity>> GetTilesByRegionAsync(double latitude, double longitude, double sizeMeters, int zoomLevel);
|
||||||
Task<Guid> InsertAsync(TileEntity tile);
|
Task<Guid> InsertAsync(TileEntity tile);
|
||||||
Task<int> UpdateAsync(TileEntity tile);
|
Task<int> UpdateAsync(TileEntity tile);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Dapper;
|
using Dapper;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Npgsql;
|
using Npgsql;
|
||||||
using SatelliteProvider.Common.Enums;
|
using SatelliteProvider.Common.Enums;
|
||||||
using SatelliteProvider.DataAccess.Models;
|
using SatelliteProvider.DataAccess.Models;
|
||||||
@@ -8,25 +7,25 @@ namespace SatelliteProvider.DataAccess.Repositories;
|
|||||||
|
|
||||||
public class RegionRepository : IRegionRepository
|
public class RegionRepository : IRegionRepository
|
||||||
{
|
{
|
||||||
private readonly string _connectionString;
|
private const string ColumnList = @"id, latitude, longitude, size_meters as SizeMeters,
|
||||||
private readonly ILogger<RegionRepository> _logger;
|
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";
|
||||||
|
|
||||||
public RegionRepository(string connectionString, ILogger<RegionRepository> logger)
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public RegionRepository(string connectionString)
|
||||||
{
|
{
|
||||||
_connectionString = connectionString;
|
_connectionString = connectionString;
|
||||||
_logger = logger;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RegionEntity?> GetByIdAsync(Guid id)
|
public async Task<RegionEntity?> GetByIdAsync(Guid id)
|
||||||
{
|
{
|
||||||
using var connection = new NpgsqlConnection(_connectionString);
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
const string sql = @"
|
const string sql = $@"
|
||||||
SELECT id, latitude, longitude, size_meters as SizeMeters,
|
SELECT {ColumnList}
|
||||||
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
|
|
||||||
FROM regions
|
FROM regions
|
||||||
WHERE id = @Id";
|
WHERE id = @Id";
|
||||||
|
|
||||||
@@ -37,13 +36,8 @@ public class RegionRepository : IRegionRepository
|
|||||||
public async Task<IEnumerable<RegionEntity>> GetByStatusAsync(RegionStatus status)
|
public async Task<IEnumerable<RegionEntity>> GetByStatusAsync(RegionStatus status)
|
||||||
{
|
{
|
||||||
using var connection = new NpgsqlConnection(_connectionString);
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
const string sql = @"
|
const string sql = $@"
|
||||||
SELECT id, latitude, longitude, size_meters as SizeMeters,
|
SELECT {ColumnList}
|
||||||
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
|
|
||||||
FROM regions
|
FROM regions
|
||||||
WHERE status = @Status
|
WHERE status = @Status
|
||||||
ORDER BY created_at ASC";
|
ORDER BY created_at ASC";
|
||||||
@@ -96,4 +90,3 @@ public class RegionRepository : IRegionRepository
|
|||||||
return await connection.ExecuteAsync(sql, new { Id = id });
|
return await connection.ExecuteAsync(sql, new { Id = id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Dapper;
|
using Dapper;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Npgsql;
|
using Npgsql;
|
||||||
using SatelliteProvider.DataAccess.Models;
|
using SatelliteProvider.DataAccess.Models;
|
||||||
|
|
||||||
@@ -7,27 +6,27 @@ namespace SatelliteProvider.DataAccess.Repositories;
|
|||||||
|
|
||||||
public class RouteRepository : IRouteRepository
|
public class RouteRepository : IRouteRepository
|
||||||
{
|
{
|
||||||
private readonly string _connectionString;
|
private const string ColumnList = @"id, name, description, region_size_meters as RegionSizeMeters,
|
||||||
private readonly ILogger<RouteRepository> _logger;
|
|
||||||
|
|
||||||
public RouteRepository(string connectionString, ILogger<RouteRepository> logger)
|
|
||||||
{
|
|
||||||
_connectionString = connectionString;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<RouteEntity?> GetByIdAsync(Guid id)
|
|
||||||
{
|
|
||||||
using var connection = new NpgsqlConnection(_connectionString);
|
|
||||||
const string sql = @"
|
|
||||||
SELECT id, name, description, region_size_meters as RegionSizeMeters,
|
|
||||||
zoom_level as ZoomLevel, total_distance_meters as TotalDistanceMeters,
|
zoom_level as ZoomLevel, total_distance_meters as TotalDistanceMeters,
|
||||||
total_points as TotalPoints, request_maps as RequestMaps,
|
total_points as TotalPoints, request_maps as RequestMaps,
|
||||||
maps_ready as MapsReady, create_tiles_zip as CreateTilesZip,
|
maps_ready as MapsReady, create_tiles_zip as CreateTilesZip,
|
||||||
csv_file_path as CsvFilePath,
|
csv_file_path as CsvFilePath,
|
||||||
summary_file_path as SummaryFilePath, stitched_image_path as StitchedImagePath,
|
summary_file_path as SummaryFilePath, stitched_image_path as StitchedImagePath,
|
||||||
tiles_zip_path as TilesZipPath,
|
tiles_zip_path as TilesZipPath,
|
||||||
created_at as CreatedAt, updated_at as UpdatedAt
|
created_at as CreatedAt, updated_at as UpdatedAt";
|
||||||
|
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public RouteRepository(string connectionString)
|
||||||
|
{
|
||||||
|
_connectionString = connectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RouteEntity?> GetByIdAsync(Guid id)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
const string sql = $@"
|
||||||
|
SELECT {ColumnList}
|
||||||
FROM routes
|
FROM routes
|
||||||
WHERE id = @Id";
|
WHERE id = @Id";
|
||||||
|
|
||||||
@@ -146,15 +145,8 @@ public class RouteRepository : IRouteRepository
|
|||||||
public async Task<IEnumerable<RouteEntity>> GetRoutesWithPendingMapsAsync()
|
public async Task<IEnumerable<RouteEntity>> GetRoutesWithPendingMapsAsync()
|
||||||
{
|
{
|
||||||
using var connection = new NpgsqlConnection(_connectionString);
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
const string sql = @"
|
const string sql = $@"
|
||||||
SELECT id, name, description, region_size_meters as RegionSizeMeters,
|
SELECT {ColumnList}
|
||||||
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
|
|
||||||
FROM routes
|
FROM routes
|
||||||
WHERE request_maps = true AND maps_ready = false";
|
WHERE request_maps = true AND maps_ready = false";
|
||||||
|
|
||||||
@@ -185,4 +177,3 @@ public class RouteRepository : IRouteRepository
|
|||||||
return grouped;
|
return grouped;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
using Dapper;
|
using Dapper;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Npgsql;
|
using Npgsql;
|
||||||
@@ -7,6 +8,14 @@ namespace SatelliteProvider.DataAccess.Repositories;
|
|||||||
|
|
||||||
public class TileRepository : ITileRepository
|
public class TileRepository : ITileRepository
|
||||||
{
|
{
|
||||||
|
private const int SlowQueryThresholdMs = 500;
|
||||||
|
|
||||||
|
private const string ColumnList = @"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";
|
||||||
|
|
||||||
private readonly string _connectionString;
|
private readonly string _connectionString;
|
||||||
private readonly ILogger<TileRepository> _logger;
|
private readonly ILogger<TileRepository> _logger;
|
||||||
|
|
||||||
@@ -19,12 +28,8 @@ public class TileRepository : ITileRepository
|
|||||||
public async Task<TileEntity?> GetByIdAsync(Guid id)
|
public async Task<TileEntity?> GetByIdAsync(Guid id)
|
||||||
{
|
{
|
||||||
using var connection = new NpgsqlConnection(_connectionString);
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
const string sql = @"
|
const string sql = $@"
|
||||||
SELECT id, tile_zoom as TileZoom, tile_x as TileX, tile_y as TileY,
|
SELECT {ColumnList}
|
||||||
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
|
|
||||||
FROM tiles
|
FROM tiles
|
||||||
WHERE id = @Id";
|
WHERE id = @Id";
|
||||||
|
|
||||||
@@ -34,12 +39,8 @@ public class TileRepository : ITileRepository
|
|||||||
public async Task<TileEntity?> GetByTileCoordinatesAsync(int tileZoom, int tileX, int tileY)
|
public async Task<TileEntity?> GetByTileCoordinatesAsync(int tileZoom, int tileX, int tileY)
|
||||||
{
|
{
|
||||||
using var connection = new NpgsqlConnection(_connectionString);
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
const string sql = @"
|
const string sql = $@"
|
||||||
SELECT id, tile_zoom as TileZoom, tile_x as TileX, tile_y as TileY,
|
SELECT {ColumnList}
|
||||||
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
|
|
||||||
FROM tiles
|
FROM tiles
|
||||||
WHERE tile_zoom = @TileZoom AND tile_x = @TileX AND tile_y = @TileY
|
WHERE tile_zoom = @TileZoom AND tile_x = @TileX AND tile_y = @TileY
|
||||||
ORDER BY updated_at DESC
|
ORDER BY updated_at DESC
|
||||||
@@ -48,33 +49,6 @@ public class TileRepository : ITileRepository
|
|||||||
return await connection.QuerySingleOrDefaultAsync<TileEntity>(sql, new { TileZoom = tileZoom, TileX = tileX, TileY = tileY });
|
return await connection.QuerySingleOrDefaultAsync<TileEntity>(sql, new { TileZoom = tileZoom, TileX = tileX, TileY = tileY });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<TileEntity?> FindExistingTileAsync(double latitude, double longitude, double tileSizeMeters, int zoomLevel, int version)
|
|
||||||
{
|
|
||||||
using var connection = new NpgsqlConnection(_connectionString);
|
|
||||||
const string sql = @"
|
|
||||||
SELECT 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
|
|
||||||
FROM tiles
|
|
||||||
WHERE ABS(latitude - @Latitude) < 0.0001
|
|
||||||
AND ABS(longitude - @Longitude) < 0.0001
|
|
||||||
AND ABS(tile_size_meters - @TileSizeMeters) < 1
|
|
||||||
AND tile_zoom = @TileZoom
|
|
||||||
AND version = @Version
|
|
||||||
LIMIT 1";
|
|
||||||
|
|
||||||
return await connection.QuerySingleOrDefaultAsync<TileEntity>(sql, new
|
|
||||||
{
|
|
||||||
Latitude = latitude,
|
|
||||||
Longitude = longitude,
|
|
||||||
TileSizeMeters = tileSizeMeters,
|
|
||||||
TileZoom = zoomLevel,
|
|
||||||
Version = version
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<TileEntity>> GetTilesByRegionAsync(double latitude, double longitude, double sizeMeters, int zoomLevel)
|
public async Task<IEnumerable<TileEntity>> GetTilesByRegionAsync(double latitude, double longitude, double sizeMeters, int zoomLevel)
|
||||||
{
|
{
|
||||||
using var connection = new NpgsqlConnection(_connectionString);
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
@@ -90,19 +64,16 @@ public class TileRepository : ITileRepository
|
|||||||
var latRange = expandedSizeMeters / 111000.0;
|
var latRange = expandedSizeMeters / 111000.0;
|
||||||
var lonRange = expandedSizeMeters / (111000.0 * Math.Cos(latitude * Math.PI / 180.0));
|
var lonRange = expandedSizeMeters / (111000.0 * Math.Cos(latitude * Math.PI / 180.0));
|
||||||
|
|
||||||
const string sql = @"
|
const string sql = $@"
|
||||||
SELECT id, tile_zoom as TileZoom, tile_x as TileX, tile_y as TileY,
|
SELECT {ColumnList}
|
||||||
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
|
|
||||||
FROM tiles
|
FROM tiles
|
||||||
WHERE latitude BETWEEN @MinLat AND @MaxLat
|
WHERE latitude BETWEEN @MinLat AND @MaxLat
|
||||||
AND longitude BETWEEN @MinLon AND @MaxLon
|
AND longitude BETWEEN @MinLon AND @MaxLon
|
||||||
AND tile_zoom = @TileZoom
|
AND tile_zoom = @TileZoom
|
||||||
ORDER BY latitude DESC, longitude ASC, updated_at DESC";
|
ORDER BY latitude DESC, longitude ASC, updated_at DESC";
|
||||||
|
|
||||||
return await connection.QueryAsync<TileEntity>(sql, new
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
var tiles = await connection.QueryAsync<TileEntity>(sql, new
|
||||||
{
|
{
|
||||||
MinLat = latitude - latRange / 2,
|
MinLat = latitude - latRange / 2,
|
||||||
MaxLat = latitude + latRange / 2,
|
MaxLat = latitude + latRange / 2,
|
||||||
@@ -110,6 +81,16 @@ public class TileRepository : ITileRepository
|
|||||||
MaxLon = longitude + lonRange / 2,
|
MaxLon = longitude + lonRange / 2,
|
||||||
TileZoom = zoomLevel
|
TileZoom = zoomLevel
|
||||||
});
|
});
|
||||||
|
stopwatch.Stop();
|
||||||
|
|
||||||
|
if (stopwatch.ElapsedMilliseconds > SlowQueryThresholdMs)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
"Slow GetTilesByRegionAsync: {ElapsedMs} ms (threshold {ThresholdMs} ms) for lat={Latitude}, lon={Longitude}, sizeMeters={SizeMeters}, zoom={ZoomLevel}",
|
||||||
|
stopwatch.ElapsedMilliseconds, SlowQueryThresholdMs, latitude, longitude, sizeMeters, zoomLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Guid> InsertAsync(TileEntity tile)
|
public async Task<Guid> InsertAsync(TileEntity tile)
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using SatelliteProvider.Common.Utils;
|
||||||
|
|
||||||
|
namespace SatelliteProvider.Tests;
|
||||||
|
|
||||||
|
public class GeoUtilsRefactorTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void GeoUtils_DoesNotExposeCalculatePolygonDiagonalDistance_AZ380_AC1()
|
||||||
|
{
|
||||||
|
// Assert
|
||||||
|
typeof(GeoUtils).GetMethod("CalculatePolygonDiagonalDistance").Should().BeNull(
|
||||||
|
"AZ-380 deletes the dead alias method that simply forwarded to CalculateDistance");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
# Batch Report
|
||||||
|
|
||||||
|
**Batch**: 23
|
||||||
|
**Tasks**: AZ-376 (C23 — delete `FindExistingTileAsync`), AZ-378 (C25 — repo `_logger` fields cleanup), AZ-379 (C26 — repo `ColumnList` constants), AZ-380 (C27 — delete `CalculatePolygonDiagonalDistance`)
|
||||||
|
**Date**: 2026-05-11
|
||||||
|
**Run**: `03-code-quality-refactoring`
|
||||||
|
**Cycle**: 1
|
||||||
|
|
||||||
|
## Task Results
|
||||||
|
|
||||||
|
| Task | Status | Files Modified | Tests | AC Coverage | Issues |
|
||||||
|
|------|--------|----------------|-------|-------------|--------|
|
||||||
|
| AZ-376_refactor_delete_findexistingtile | Done | 2 (`ITileRepository.cs`, `TileRepository.cs`) | 1 new + build | 3/3 | None |
|
||||||
|
| AZ-378_refactor_repo_logger_fields | Done | 4 (`TileRepository.cs`, `RegionRepository.cs`, `RouteRepository.cs`, `Program.cs`) | 3 new | 3/3 | None |
|
||||||
|
| AZ-379_refactor_repo_select_columnlist | Done | 3 (`TileRepository.cs`, `RegionRepository.cs`, `RouteRepository.cs`) | 4 new | 3/3 | None blocking |
|
||||||
|
| AZ-380_refactor_delete_polygon_diagonal | Done | 1 (`GeoUtils.cs`) | 1 new | 3/3 | None |
|
||||||
|
|
||||||
|
Total: 6 source files modified + 2 new test files (`RepositoryRefactorTests.cs`, `GeoUtilsRefactorTests.cs`) + 9 new test cases.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
### AZ-376 — delete unused `FindExistingTileAsync`
|
||||||
|
- `SatelliteProvider.DataAccess/Repositories/ITileRepository.cs`: removed declaration on line 9 (no callers per grep before deletion).
|
||||||
|
- `SatelliteProvider.DataAccess/Repositories/TileRepository.cs`: removed implementation (lines 51-76).
|
||||||
|
- Removed dependency on the obsolete `version` argument that C06/AZ-357 was retiring.
|
||||||
|
|
||||||
|
### AZ-378 — repo `_logger` fields cleanup (recommended split)
|
||||||
|
- `TileRepository.cs`:
|
||||||
|
- Added `private const int SlowQueryThresholdMs = 500` (named const, easy to tune later).
|
||||||
|
- `GetTilesByRegionAsync` wraps the Dapper call in `Stopwatch.StartNew()` / `Stop()` and emits `_logger.LogWarning(...)` if elapsed > threshold. Includes structured fields (`ElapsedMs`, `ThresholdMs`, `Latitude`, `Longitude`, `SizeMeters`, `ZoomLevel`).
|
||||||
|
- Added `using System.Diagnostics`.
|
||||||
|
- `RegionRepository.cs`: removed `_logger` field, removed `ILogger<RegionRepository>` constructor parameter, removed `using Microsoft.Extensions.Logging`.
|
||||||
|
- `RouteRepository.cs`: same pattern.
|
||||||
|
- `SatelliteProvider.Api/Program.cs:33-34`: DI registrations for `IRegionRepository` and `IRouteRepository` now construct without the `ILogger<TRepo>` arg.
|
||||||
|
|
||||||
|
### AZ-379 — extract repo `ColumnList` constants
|
||||||
|
- `TileRepository.cs`: `private const string ColumnList = @"id, tile_zoom as TileZoom, ..."` interpolated into `GetByIdAsync`, `GetByTileCoordinatesAsync`, `GetTilesByRegionAsync` via `const string sql = $@"SELECT {ColumnList} FROM ..."`. INSERT/UPDATE/DELETE statements unchanged (out of scope per spec).
|
||||||
|
- `RegionRepository.cs`: same pattern; constant covers `GetByIdAsync` + `GetByStatusAsync`.
|
||||||
|
- `RouteRepository.cs`: same pattern; constant covers the two `routes`-table SELECTs (`GetByIdAsync` + `GetRoutesWithPendingMapsAsync`). `GetRoutePointsAsync` (single `route_points` SELECT) left inline — a second const would not reduce duplication.
|
||||||
|
- C# 10+ const-interpolated strings supported by net8.0 target; compile-time guarantee that the column list is identical across SELECTs.
|
||||||
|
|
||||||
|
### AZ-380 — delete dead alias `CalculatePolygonDiagonalDistance`
|
||||||
|
- `SatelliteProvider.Common/Utils/GeoUtils.cs:129-132`: removed (no callers per grep).
|
||||||
|
|
||||||
|
### Tests added
|
||||||
|
|
||||||
|
`SatelliteProvider.Tests/RepositoryRefactorTests.cs` (8 tests):
|
||||||
|
- `TileRepository_DoesNotExposeFindExistingTileAsync_AZ376_AC1` (reflection)
|
||||||
|
- `TileRepository_KeepsAndUsesLogger_AZ378_AC1` (file content)
|
||||||
|
- `RegionRepository_HasNoUnusedLoggerParameter_AZ378_AC2` (reflection)
|
||||||
|
- `RouteRepository_HasNoUnusedLoggerParameter_AZ378_AC2` (reflection)
|
||||||
|
- `TileRepository_DefinesColumnListConstantOnce_AZ379_AC1` (file content)
|
||||||
|
- `RegionRepository_DefinesColumnListConstantOnce_AZ379_AC1`
|
||||||
|
- `RouteRepository_DefinesColumnListConstantOnce_AZ379_AC1`
|
||||||
|
- `RepositoryColumnLists_ContainExpectedColumns_AZ379_AC2` (per-repo column-token assertions)
|
||||||
|
|
||||||
|
`SatelliteProvider.Tests/GeoUtilsRefactorTests.cs` (1 test):
|
||||||
|
- `GeoUtils_DoesNotExposeCalculatePolygonDiagonalDistance_AZ380_AC1` (reflection)
|
||||||
|
|
||||||
|
Same file-content / reflection assertion pattern established by `ToolingConfigurationTests` (AZ-372 b22) and `AcceptanceCriteriaRT2Tests` (AZ-370 b19).
|
||||||
|
|
||||||
|
## AC Test Coverage
|
||||||
|
|
||||||
|
| AC | Covered by |
|
||||||
|
|----|------------|
|
||||||
|
| AZ-376 AC-1 (method gone) | `TileRepository_DoesNotExposeFindExistingTileAsync_AZ376_AC1` |
|
||||||
|
| AZ-376 AC-2 (build) | Successful Docker `dotnet test` Release build |
|
||||||
|
| AZ-376 AC-3 (tests green) | 190/190 unit run |
|
||||||
|
| AZ-378 AC-1 (kept logger used) | `TileRepository_KeepsAndUsesLogger_AZ378_AC1` |
|
||||||
|
| AZ-378 AC-2 (unused loggers removed) | `*_HasNoUnusedLoggerParameter_AZ378_AC2` (×2) |
|
||||||
|
| AZ-378 AC-3 (tests green) | 190/190 unit run |
|
||||||
|
| AZ-379 AC-1 (one ColumnList per repo) | `*_DefinesColumnListConstantOnce_AZ379_AC1` (×3) |
|
||||||
|
| AZ-379 AC-2 (SQL byte-identical) | `RepositoryColumnLists_ContainExpectedColumns_AZ379_AC2` (column tokens) + structural guarantee from compile-time interpolation; smoke run handed off to test-run skill |
|
||||||
|
| AZ-379 AC-3 (tests green) | 190/190 unit run |
|
||||||
|
| AZ-380 AC-1 (method gone) | `GeoUtils_DoesNotExposeCalculatePolygonDiagonalDistance_AZ380_AC1` |
|
||||||
|
| AZ-380 AC-2 (build) | Successful Docker build |
|
||||||
|
| AZ-380 AC-3 (tests green) | 190/190 unit run |
|
||||||
|
|
||||||
|
Stale-count note carried over from b22: each task spec phrases AC-3 as "37 unit + 5 smoke". Pre-`/document` figure; current count is 190 unit. Spirit ("all tests green") satisfied.
|
||||||
|
|
||||||
|
## Test Run
|
||||||
|
|
||||||
|
| Suite | Result | Count |
|
||||||
|
|-------|--------|-------|
|
||||||
|
| Unit (`SatelliteProvider.Tests`) | All passed | 190 (was 181; +9 new tests across 2 new files) |
|
||||||
|
| `dotnet format whitespace --verify-no-changes` | Clean | — |
|
||||||
|
| Smoke integration (Docker) | Handed off to test-run skill | — |
|
||||||
|
|
||||||
|
## Code Review Verdict: PASS_WITH_WARNINGS
|
||||||
|
|
||||||
|
Three Low findings (`_docs/03_implementation/reviews/batch_23_review.md`):
|
||||||
|
- F1 (Low / Maintainability): `route_points` column list left inline (single use). Spec is explicit ("one ColumnList per repository"). No action.
|
||||||
|
- F2 (Low / Spec-Gap): Earth-related literals still in `GetTilesByRegionAsync` — explicitly the target of AZ-377 (batch 24).
|
||||||
|
- F3 (Low / Spec-Gap): Stale "37 unit + 5 smoke" count in task ACs — same as b22 F1, defer to refactor Phase 7 doc sweep.
|
||||||
|
|
||||||
|
## Auto-Fix Attempts: 0
|
||||||
|
## Stuck Agents: None
|
||||||
|
|
||||||
|
## Cumulative review counter
|
||||||
|
|
||||||
|
This is batch 2 since the last cumulative review (`cumulative_review_batches_19-21_cycle1_report.md`). Counter at 2/3; **next cumulative review fires after batch 24** (covers batches 22-24).
|
||||||
|
|
||||||
|
## Next Batch
|
||||||
|
|
||||||
|
Phase 4 continues with the remaining 2 tasks in `todo/` after this batch:
|
||||||
|
|
||||||
|
- `AZ-375` — C22 O(N) existing-tile lookup HashSet (2 SP, dep AZ-371 ✓)
|
||||||
|
- `AZ-377` — C24 consolidate Earth-geometry constants + 111000 + TileSizePixels (2 SP, dep AZ-371 ✓)
|
||||||
|
|
||||||
|
Batch 24 candidate: both tasks together (~4 SP, both touch TileDownloader / Common / DataAccess). They form the last refactor batch; the K=3 cumulative review fires immediately after. Then Phase 4 of refactor wraps up and we move to Phase 5 (Test Sync), Phase 6 (Verification), Phase 7 (Documentation), then FINAL_report.md and Step 9 (New Task).
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# Code Review Report
|
||||||
|
|
||||||
|
**Batch**: 23
|
||||||
|
**Tasks**: AZ-376 (C23), AZ-378 (C25), AZ-379 (C26), AZ-380 (C27)
|
||||||
|
**Date**: 2026-05-11
|
||||||
|
**Verdict**: PASS_WITH_WARNINGS
|
||||||
|
|
||||||
|
## Findings
|
||||||
|
|
||||||
|
| # | Severity | Category | File:Line | Title |
|
||||||
|
|---|----------|----------|-----------|-------|
|
||||||
|
| 1 | Low | Maintainability | `SatelliteProvider.DataAccess/Repositories/RouteRepository.cs:38` | `route_points` column list left inline (single use) |
|
||||||
|
| 2 | Low | Spec-Gap | `SatelliteProvider.DataAccess/Repositories/TileRepository.cs:69` | `EARTH_CIRCUMFERENCE_METERS`, `TILE_SIZE_PIXELS`, `111000.0` literals still in `GetTilesByRegionAsync` |
|
||||||
|
| 3 | Low | Spec-Gap | task ACs reference "37 unit + 5 smoke" | Stale test count in task ACs |
|
||||||
|
|
||||||
|
### Finding Details
|
||||||
|
|
||||||
|
**F1: `route_points` column list left inline (single use)** (Low / Maintainability)
|
||||||
|
- Location: `SatelliteProvider.DataAccess/Repositories/RouteRepository.cs:38-46`
|
||||||
|
- Description: `GetRoutePointsAsync` keeps its 8-column list inline rather than extracting a second const. AZ-379 task spec is explicit ("Per repository: extract the column list once" — singular) and the column list is used at exactly one site, so extracting would not reduce duplication.
|
||||||
|
- Suggestion: leave as-is. If a future SELECT also needs the route_points columns, extract `RoutePointColumnList` then.
|
||||||
|
- Task: AZ-379
|
||||||
|
|
||||||
|
**F2: Earth-related literals still in `GetTilesByRegionAsync`** (Low / Spec-Gap)
|
||||||
|
- Location: `SatelliteProvider.DataAccess/Repositories/TileRepository.cs:58-67, 90`
|
||||||
|
- Description: `EARTH_CIRCUMFERENCE_METERS`, `TILE_SIZE_PIXELS`, and `111000.0` are still local constants/literals. These are the explicit target of **AZ-377 (C24)**, which is in batch 24 and has dependency AZ-371 ✓. Out of scope for batch 23.
|
||||||
|
- Suggestion: addressed in batch 24 by AZ-377.
|
||||||
|
- Task: AZ-377 (out of scope here)
|
||||||
|
|
||||||
|
**F3: Stale test count in task ACs** (Low / Spec-Gap)
|
||||||
|
- Location: all four task spec files (`AZ-376_*.md`, `AZ-378_*.md`, `AZ-379_*.md`, `AZ-380_*.md`) — AC-3
|
||||||
|
- Description: each AC-3 quotes "37 unit + 5 smoke tests stay green". Pre-`/document`-era figure; current count is 190 unit tests after this batch (was 181 before). Same finding as F1 of batch_22_review (carried over).
|
||||||
|
- Suggestion: defer to refactor Phase 7 documentation sweep (batch 22 review already noted this).
|
||||||
|
- Task: shared
|
||||||
|
|
||||||
|
## Phase Notes
|
||||||
|
|
||||||
|
**Phase 1 — Context loading**: read `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C23, C25, C26, C27), four task specs, and the current source under review.
|
||||||
|
|
||||||
|
**Phase 2 — Spec compliance**: every AC has a corresponding test in `RepositoryRefactorTests.cs` or `GeoUtilsRefactorTests.cs`:
|
||||||
|
- AZ-376 AC-1 ↔ `TileRepository_DoesNotExposeFindExistingTileAsync_AZ376_AC1`
|
||||||
|
- AZ-376 AC-2/AC-3 ↔ build + 190/190 unit run (covers AC-2 build success and AC-3 tests-green)
|
||||||
|
- AZ-378 AC-1 ↔ `TileRepository_KeepsAndUsesLogger_AZ378_AC1`
|
||||||
|
- AZ-378 AC-2 ↔ `RegionRepository_HasNoUnusedLoggerParameter_AZ378_AC2` + `RouteRepository_HasNoUnusedLoggerParameter_AZ378_AC2`
|
||||||
|
- AZ-378 AC-3 ↔ 190/190 unit run
|
||||||
|
- AZ-379 AC-1 ↔ `*_DefinesColumnListConstantOnce_AZ379_AC1` (one per repo)
|
||||||
|
- AZ-379 AC-2 ↔ `RepositoryColumnLists_ContainExpectedColumns_AZ379_AC2` (column tokens preserved). Strict byte-for-byte SQL is structurally guaranteed by const interpolation; smoke run (deferred to test-run skill) will validate end-to-end DB interaction.
|
||||||
|
- AZ-379 AC-3 ↔ 190/190 unit run
|
||||||
|
- AZ-380 AC-1 ↔ `GeoUtils_DoesNotExposeCalculatePolygonDiagonalDistance_AZ380_AC1`
|
||||||
|
- AZ-380 AC-2/AC-3 ↔ build + 190/190 unit run
|
||||||
|
|
||||||
|
**Phase 3 — Code quality**:
|
||||||
|
- New `SlowQueryThresholdMs = 500` constant has a brief one-line use site; tunable later via config (deferred to a separate ticket if telemetry becomes formal).
|
||||||
|
- `Stopwatch.StartNew()` + log on threshold breach is the cheapest visible-only-when-slow pattern; no perf overhead in the hot path beyond a `long` comparison.
|
||||||
|
- `private const string ColumnList` placed at the top of each repository class; `$@"..."` interpolation requires C# 10+ const-interpolated strings (project targets net8.0, fully supported).
|
||||||
|
- Removed `using Microsoft.Extensions.Logging` from `RegionRepository.cs` and `RouteRepository.cs` after dropping the field.
|
||||||
|
|
||||||
|
**Phase 4 — Security quick-scan**:
|
||||||
|
- `ColumnList` interpolation is `private const string` defined in source code (not user input). Same trust boundary as the original inline `const string sql`. Dapper parameterization preserved for all actual values. No SQL-injection vector introduced.
|
||||||
|
- Slow-query log emits `latitude`, `longitude`, `sizeMeters`, `zoomLevel` — geographic coordinates, not PII; consistent with existing API logging.
|
||||||
|
- No hardcoded secrets. No new external input paths.
|
||||||
|
|
||||||
|
**Phase 5 — Performance scan**:
|
||||||
|
- Net positive: `GetTilesByRegionAsync` now self-reports slow queries. Hot path adds one `Stopwatch.GetTimestamp` start + one comparison + the existing await; negligible.
|
||||||
|
- ColumnList interpolation is compile-time; zero runtime cost.
|
||||||
|
- AZ-376 deletes a database round-trip method (no callers, but if reflection ever found it, it's gone now). Minor surface reduction.
|
||||||
|
|
||||||
|
**Phase 6 — Cross-task consistency**:
|
||||||
|
- AZ-376 (delete method) and AZ-378 (logger field) and AZ-379 (SELECT constants) all touch `TileRepository.cs`; applied in topological order avoiding interaction. Final state merges cleanly.
|
||||||
|
- DI registrations in `Program.cs` updated atomically with `RegionRepository` and `RouteRepository` constructor changes — no DI breakage.
|
||||||
|
- No conflicting patterns: all repos still use Dapper + raw SQL; constructor shape now varies (TileRepository keeps logger, others don't), justified by per-repo logger-usage decision in AZ-378.
|
||||||
|
|
||||||
|
**Phase 7 — Architecture compliance**:
|
||||||
|
- All edits stay within `SatelliteProvider.DataAccess/**` (DataAccess Owns) and `SatelliteProvider.Common/Utils/GeoUtils.cs` (Common Owns). `Program.cs` is WebApi Owns and is allowed to import DataAccess.
|
||||||
|
- No new cross-component imports. No new cyclic deps. No duplicate symbols introduced.
|
||||||
|
- `module-layout.md` Public API surface for ITileRepository changed (one method removed). All external consumers (none use the deleted method per grep) unaffected.
|
||||||
|
|
||||||
|
## Baseline Delta
|
||||||
|
|
||||||
|
`_docs/02_document/architecture_compliance_baseline.md` exists. No new Architecture findings introduced; F2 from baseline (logger fields not used) is partially **Resolved** for `RegionRepository` and `RouteRepository` (deleted) and **Carried over** as a deliberate-use case for `TileRepository`.
|
||||||
|
|
||||||
|
| Status | Finding |
|
||||||
|
|--------|---------|
|
||||||
|
| Resolved | F-baseline: `RegionRepository._logger` unused — deleted (AZ-378) |
|
||||||
|
| Resolved | F-baseline: `RouteRepository._logger` unused — deleted (AZ-378) |
|
||||||
|
| Carried over (now justified) | F-baseline: `TileRepository._logger` unused — now used by `GetTilesByRegionAsync` slow-query warning (AZ-378) |
|
||||||
|
|
||||||
|
## Verdict
|
||||||
|
|
||||||
|
**PASS_WITH_WARNINGS** — three Low findings, all informational or out-of-scope; zero Critical / High / Medium.
|
||||||
@@ -8,7 +8,7 @@ status: in_progress
|
|||||||
sub_step:
|
sub_step:
|
||||||
phase: 4
|
phase: 4
|
||||||
name: batch-loop
|
name: batch-loop
|
||||||
detail: "batch 22 complete (AZ-372); ready for batch 23"
|
detail: "batch 23 complete; cum-rev counter 2/3; ready for batch 24 (AZ-375, AZ-377)"
|
||||||
retry_count: 0
|
retry_count: 0
|
||||||
cycle: 1
|
cycle: 1
|
||||||
tracker: jira
|
tracker: jira
|
||||||
|
|||||||
Reference in New Issue
Block a user