Files
satellite-provider/SatelliteProvider.Tests/RouteProcessingServiceTests.cs
T
Oleksandr Bezdieniezhnykh de4d4fa760
ci/woodpecker/push/01-test Pipeline was successful
ci/woodpecker/push/02-build-push Pipeline was successful
[AZ-351][AZ-352][AZ-363] Refactor 03 batch 1: critical defensive fixes
AZ-351: Resolve ILogger<DatabaseMigrator> directly from DI in
Program.cs instead of casting ILogger<Program> (which always
returned null). Migrator now logs through Serilog at startup.

AZ-352: Drop empty catch in
RouteProcessingService.ExtractTileCoordinatesFromFilename. Convert
the method from private static to internal instance so it can use
the existing _logger (per coderule: side-effecting code must not be
static). Add typed null-guard via ArgumentNullException.ThrowIfNull
so unexpected exceptions propagate. Adds InternalsVisibleTo on the
RouteManagement csproj for SatelliteProvider.Tests, plus 4 unit
tests in RouteProcessingServiceTests.cs covering AC-1 (valid /
malformed / non-numeric) and AC-2 (null path propagation).

AZ-363: Delete _totalEnqueued / _totalDequeued fields and the two
non-atomic ++ writes in RegionRequestQueue. Fields were write-only
dead code and a thread-safety hazard.

Tests: 44/44 unit + 5/5 smoke (scripts/run-tests.sh --smoke).
Code review verdict: PASS, 0 findings, 0 auto-fix attempts.
Batch report: _docs/03_implementation/batch_07_report.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 23:34:17 +03:00

100 lines
3.1 KiB
C#

using FluentAssertions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
using SatelliteProvider.Common.Configs;
using SatelliteProvider.DataAccess.Repositories;
using SatelliteProvider.Services.RouteManagement;
namespace SatelliteProvider.Tests;
public class RouteProcessingServiceTests
{
private static RouteProcessingService BuildSut(out Mock<ILogger<RouteProcessingService>> loggerMock)
{
loggerMock = new Mock<ILogger<RouteProcessingService>>();
var routeRepo = new Mock<IRouteRepository>();
var regionRepo = new Mock<IRegionRepository>();
var serviceProvider = new Mock<IServiceProvider>();
var storageOptions = Options.Create(new StorageConfig());
return new RouteProcessingService(
routeRepo.Object,
regionRepo.Object,
serviceProvider.Object,
storageOptions,
loggerMock.Object);
}
private static void VerifyWarningLogged(Mock<ILogger<RouteProcessingService>> loggerMock, string substringInState)
{
loggerMock.Verify(
l => l.Log(
LogLevel.Warning,
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((state, _) => state.ToString()!.Contains(substringInState)),
It.IsAny<Exception?>(),
It.IsAny<Func<It.IsAnyType, Exception?, string>>()),
Times.AtLeastOnce);
}
[Fact]
public void ExtractTileCoordinatesFromFilename_ValidName_ReturnsParsedCoordinates_AC1()
{
// Arrange
var sut = BuildSut(out _);
// Act
var (x, y) = sut.ExtractTileCoordinatesFromFilename("/tiles/tile_1700000000_42_99.jpg");
// Assert
x.Should().Be(42);
y.Should().Be(99);
}
[Fact]
public void ExtractTileCoordinatesFromFilename_MalformedName_LogsWarningAndReturnsSentinel_AC1()
{
// Arrange
var sut = BuildSut(out var loggerMock);
const string malformed = "/tmp/not_a_tile_filename.jpg";
// Act
var (x, y) = sut.ExtractTileCoordinatesFromFilename(malformed);
// Assert
x.Should().Be(-1);
y.Should().Be(-1);
VerifyWarningLogged(loggerMock, "not_a_tile_filename");
}
[Fact]
public void ExtractTileCoordinatesFromFilename_TilePrefixWithNonNumericCoords_LogsWarningAndReturnsSentinel_AC1()
{
// Arrange
var sut = BuildSut(out var loggerMock);
const string nonNumeric = "/tiles/tile_1700000000_alpha_beta.jpg";
// Act
var (x, y) = sut.ExtractTileCoordinatesFromFilename(nonNumeric);
// Assert
x.Should().Be(-1);
y.Should().Be(-1);
VerifyWarningLogged(loggerMock, "tile_1700000000_alpha_beta");
}
[Fact]
public void ExtractTileCoordinatesFromFilename_NullPath_PropagatesArgumentNullException_AC2()
{
// Arrange
var sut = BuildSut(out _);
// Act
Action act = () => sut.ExtractTileCoordinatesFromFilename(null!);
// Assert
act.Should().Throw<ArgumentNullException>();
}
}