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
				
			
		
			
				
	
	
		
			209 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			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;
 | |
|     }
 | |
| }
 |