This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
using Ludarium.Application;
|
||||
using Ludarium.Domain;
|
||||
using Ludarium.Infrastructure;
|
||||
using Npgsql;
|
||||
using Testcontainers.PostgreSql;
|
||||
|
||||
namespace Ludarium.IntegrationTests;
|
||||
|
||||
/// <summary>
|
||||
/// What a scan concludes about the files that were not there any more.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Reconciliation compared the stored state against the name "Present" while every other query in
|
||||
/// the store reads the enum's ordinal. The comparison matched no row, so a ROM deleted from the
|
||||
/// archive stayed Present: it kept its Play control, counted towards the archive's size and never
|
||||
/// appeared as missing. Only the real database can prove the ordinal contract, so it is proven here.
|
||||
/// </remarks>
|
||||
public sealed class ArtifactReconciliationTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Container")]
|
||||
public async Task AFileThatLeftTheArchiveIsMarkedMissing()
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("LUDARIUM_RUN_CONTAINER_TESTS"), "1", StringComparison.Ordinal))
|
||||
return;
|
||||
|
||||
await using var postgres = new PostgreSqlBuilder("postgres:16.15-bookworm")
|
||||
.WithDatabase("ludarium_reconciliation_test")
|
||||
.WithUsername("ludarium")
|
||||
.WithPassword("synthetic-test-password")
|
||||
.Build();
|
||||
await postgres.StartAsync();
|
||||
await using var dataSource = NpgsqlDataSource.Create(postgres.GetConnectionString());
|
||||
var store = new PostgresStore(dataSource);
|
||||
await store.InitializeAsync(CancellationToken.None);
|
||||
|
||||
var library = LibraryRoot.Create("Reconciliation fixture",
|
||||
Path.GetFullPath(Path.Combine(Path.GetTempPath(), "ludarium-reconciliation-library")), LibraryKind.Rom) with
|
||||
{ IsReadOnly = true, IsAvailable = true };
|
||||
await store.UpsertLibraryAsync(library, CancellationToken.None);
|
||||
|
||||
var observed = "roms/nes/Kept.nes";
|
||||
var removed = "roms/nes/Deleted.nes";
|
||||
foreach (var path in new[] { observed, removed })
|
||||
await store.UpsertArtifactAsync(
|
||||
new(Guid.NewGuid(), library.Id, path, 24_592, DateTimeOffset.UtcNow.AddDays(-2),
|
||||
ArtifactState.Present, MediaType.Rom, Confidence.Deterministic, null,
|
||||
DateTimeOffset.UtcNow.AddDays(-2), DateTimeOffset.UtcNow.AddDays(-2), null, "nes"),
|
||||
null, [], CancellationToken.None);
|
||||
|
||||
var at = DateTimeOffset.UtcNow;
|
||||
var marked = await store.MarkMissingExceptAsync(library.Id, new HashSet<string>([observed]), at, CancellationToken.None);
|
||||
|
||||
Assert.Equal(1, marked);
|
||||
var gone = await store.FindByPathAsync(library.Id, removed, CancellationToken.None);
|
||||
// Reading it back through the store proves the written state is the shape the record expects.
|
||||
Assert.Equal(ArtifactState.Missing, gone!.State);
|
||||
Assert.Equal(2, gone.Version);
|
||||
var kept = await store.FindByPathAsync(library.Id, observed, CancellationToken.None);
|
||||
Assert.Equal(ArtifactState.Present, kept!.State);
|
||||
Assert.Equal(1, kept.Version);
|
||||
|
||||
// The same ordinal contract is read by the storage summary, so the archive reports the loss.
|
||||
var summary = await store.GetStorageSummaryAsync(CancellationToken.None);
|
||||
var entry = Assert.Single(summary.Libraries, item => item.LibraryId == library.Id);
|
||||
Assert.Equal(1, entry.Missing);
|
||||
|
||||
// A second scan that still cannot see the file leaves it alone rather than counting it twice.
|
||||
Assert.Equal(0, await store.MarkMissingExceptAsync(library.Id, new HashSet<string>([observed]), at, CancellationToken.None));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user