Files
git.stella-ops.org/src/StellaOps.Feedser.Storage.Mongo.Tests/ExportStateManagerTests.cs
master b97fc7685a
Some checks failed
Build Test Deploy / authority-container (push) Has been cancelled
Build Test Deploy / docs (push) Has been cancelled
Build Test Deploy / deploy (push) Has been cancelled
Build Test Deploy / build-test (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Initial commit (history squashed)
2025-10-11 23:28:35 +03:00

209 lines
8.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Feedser.Storage.Mongo.Exporting;
namespace StellaOps.Feedser.Storage.Mongo.Tests;
public sealed class ExportStateManagerTests
{
[Fact]
public async Task StoreFullExportInitializesBaseline()
{
var store = new InMemoryExportStateStore();
var timeProvider = new TestTimeProvider(DateTimeOffset.Parse("2024-07-20T12:00:00Z"));
var manager = new ExportStateManager(store, timeProvider);
var record = await manager.StoreFullExportAsync(
exporterId: "export:json",
exportId: "20240720T120000Z",
exportDigest: "sha256:abcd",
cursor: "cursor-1",
targetRepository: "registry.local/json",
exporterVersion: "1.0.0",
resetBaseline: true,
manifest: Array.Empty<ExportFileRecord>(),
cancellationToken: CancellationToken.None);
Assert.Equal("export:json", record.Id);
Assert.Equal("20240720T120000Z", record.BaseExportId);
Assert.Equal("sha256:abcd", record.BaseDigest);
Assert.Equal("sha256:abcd", record.LastFullDigest);
Assert.Null(record.LastDeltaDigest);
Assert.Equal("cursor-1", record.ExportCursor);
Assert.Equal("registry.local/json", record.TargetRepository);
Assert.Equal("1.0.0", record.ExporterVersion);
Assert.Equal(timeProvider.Now, record.UpdatedAt);
}
[Fact]
public async Task StoreFullExport_ResetBaselineOverridesExisting()
{
var store = new InMemoryExportStateStore();
var timeProvider = new TestTimeProvider(DateTimeOffset.Parse("2024-07-20T12:00:00Z"));
var manager = new ExportStateManager(store, timeProvider);
await manager.StoreFullExportAsync(
exporterId: "export:json",
exportId: "20240720T120000Z",
exportDigest: "sha256:base",
cursor: "cursor-base",
targetRepository: null,
exporterVersion: "1.0.0",
resetBaseline: true,
manifest: Array.Empty<ExportFileRecord>(),
cancellationToken: CancellationToken.None);
timeProvider.Advance(TimeSpan.FromMinutes(5));
var withoutReset = await manager.StoreFullExportAsync(
exporterId: "export:json",
exportId: "20240720T120500Z",
exportDigest: "sha256:new",
cursor: "cursor-new",
targetRepository: null,
exporterVersion: "1.0.1",
resetBaseline: false,
manifest: Array.Empty<ExportFileRecord>(),
cancellationToken: CancellationToken.None);
Assert.Equal("20240720T120000Z", withoutReset.BaseExportId);
Assert.Equal("sha256:base", withoutReset.BaseDigest);
Assert.Equal("sha256:new", withoutReset.LastFullDigest);
Assert.Equal("cursor-new", withoutReset.ExportCursor);
Assert.Equal(timeProvider.Now, withoutReset.UpdatedAt);
timeProvider.Advance(TimeSpan.FromMinutes(5));
var reset = await manager.StoreFullExportAsync(
exporterId: "export:json",
exportId: "20240720T121000Z",
exportDigest: "sha256:final",
cursor: "cursor-final",
targetRepository: null,
exporterVersion: "1.0.2",
resetBaseline: true,
manifest: Array.Empty<ExportFileRecord>(),
cancellationToken: CancellationToken.None);
Assert.Equal("20240720T121000Z", reset.BaseExportId);
Assert.Equal("sha256:final", reset.BaseDigest);
Assert.Equal("sha256:final", reset.LastFullDigest);
Assert.Null(reset.LastDeltaDigest);
Assert.Equal("cursor-final", reset.ExportCursor);
Assert.Equal(timeProvider.Now, reset.UpdatedAt);
}
[Fact]
public async Task StoreFullExport_ResetsBaselineWhenRepositoryChanges()
{
var store = new InMemoryExportStateStore();
var timeProvider = new TestTimeProvider(DateTimeOffset.Parse("2024-07-21T08:00:00Z"));
var manager = new ExportStateManager(store, timeProvider);
await manager.StoreFullExportAsync(
exporterId: "export:json",
exportId: "20240721T080000Z",
exportDigest: "sha256:base",
cursor: "cursor-base",
targetRepository: "registry/v1/json",
exporterVersion: "1.0.0",
resetBaseline: true,
manifest: Array.Empty<ExportFileRecord>(),
cancellationToken: CancellationToken.None);
timeProvider.Advance(TimeSpan.FromMinutes(10));
var updated = await manager.StoreFullExportAsync(
exporterId: "export:json",
exportId: "20240721T081000Z",
exportDigest: "sha256:new",
cursor: "cursor-new",
targetRepository: "registry/v2/json",
exporterVersion: "1.1.0",
resetBaseline: false,
manifest: Array.Empty<ExportFileRecord>(),
cancellationToken: CancellationToken.None);
Assert.Equal("20240721T081000Z", updated.BaseExportId);
Assert.Equal("sha256:new", updated.BaseDigest);
Assert.Equal("sha256:new", updated.LastFullDigest);
Assert.Equal("registry/v2/json", updated.TargetRepository);
}
[Fact]
public async Task StoreDeltaExportRequiresBaseline()
{
var store = new InMemoryExportStateStore();
var manager = new ExportStateManager(store);
await Assert.ThrowsAsync<InvalidOperationException>(() => manager.StoreDeltaExportAsync(
exporterId: "export:json",
deltaDigest: "sha256:def",
cursor: null,
exporterVersion: "1.0.1",
manifest: Array.Empty<ExportFileRecord>(),
cancellationToken: CancellationToken.None));
}
[Fact]
public async Task StoreDeltaExportUpdatesExistingState()
{
var store = new InMemoryExportStateStore();
var timeProvider = new TestTimeProvider(DateTimeOffset.Parse("2024-07-20T12:00:00Z"));
var manager = new ExportStateManager(store, timeProvider);
await manager.StoreFullExportAsync(
exporterId: "export:json",
exportId: "20240720T120000Z",
exportDigest: "sha256:abcd",
cursor: "cursor-1",
targetRepository: null,
exporterVersion: "1.0.0",
resetBaseline: true,
manifest: Array.Empty<ExportFileRecord>(),
cancellationToken: CancellationToken.None);
timeProvider.Advance(TimeSpan.FromMinutes(10));
var delta = await manager.StoreDeltaExportAsync(
exporterId: "export:json",
deltaDigest: "sha256:ef01",
cursor: "cursor-2",
exporterVersion: "1.0.1",
manifest: Array.Empty<ExportFileRecord>(),
cancellationToken: CancellationToken.None);
Assert.Equal("sha256:ef01", delta.LastDeltaDigest);
Assert.Equal("cursor-2", delta.ExportCursor);
Assert.Equal("1.0.1", delta.ExporterVersion);
Assert.Equal(timeProvider.Now, delta.UpdatedAt);
Assert.Equal("sha256:abcd", delta.LastFullDigest);
}
private sealed class InMemoryExportStateStore : IExportStateStore
{
private readonly Dictionary<string, ExportStateRecord> _records = new(StringComparer.Ordinal);
public Task<ExportStateRecord?> FindAsync(string id, CancellationToken cancellationToken)
{
_records.TryGetValue(id, out var record);
return Task.FromResult<ExportStateRecord?>(record);
}
public Task<ExportStateRecord> UpsertAsync(ExportStateRecord record, CancellationToken cancellationToken)
{
_records[record.Id] = record;
return Task.FromResult(record);
}
}
private sealed class TestTimeProvider : TimeProvider
{
public TestTimeProvider(DateTimeOffset start) => Now = start;
public DateTimeOffset Now { get; private set; }
public void Advance(TimeSpan delta) => Now = Now.Add(delta);
public override DateTimeOffset GetUtcNow() => Now;
}
}