using ZB.MOM.WW.Overview.Polling;
namespace ZB.MOM.WW.Overview.Tests;
///
/// The store is the cache-first seam: a page reads it and never probes. These tests pin that it
/// always has something to serve and that subscribers hear about replacements.
///
public class OverviewSnapshotStoreTests
{
private static OverviewSnapshot SnapshotAt(int second) =>
new(new DateTimeOffset(2026, 7, 24, 12, 0, second, TimeSpan.Zero), []);
[Fact]
public void Current_BeforeAnyPublish_IsEmptyNotNull()
{
// A page can render before the poller's first sweep finishes; a null here would be a
// NullReferenceException on the landing page of a monitoring dashboard.
var store = new OverviewSnapshotStore();
Assert.NotNull(store.Current);
Assert.Empty(store.Current.Applications);
}
[Fact]
public void Publish_ReplacesCurrent()
{
var store = new OverviewSnapshotStore();
var snapshot = SnapshotAt(1);
store.Publish(snapshot);
Assert.Same(snapshot, store.Current);
}
[Fact]
public void Publish_NotifiesSubscribersWithTheNewSnapshot()
{
var store = new OverviewSnapshotStore();
OverviewSnapshot? received = null;
store.Changed += s => received = s;
var snapshot = SnapshotAt(2);
store.Publish(snapshot);
Assert.Same(snapshot, received);
}
[Fact]
public void Publish_AfterUnsubscribe_DoesNotNotify()
{
// Blazor circuits come and go; a handler that outlives its component would call
// StateHasChanged on a disposed renderer on every sweep.
var store = new OverviewSnapshotStore();
var calls = 0;
void Handler(OverviewSnapshot _) => calls++;
store.Changed += Handler;
store.Publish(SnapshotAt(1));
store.Changed -= Handler;
store.Publish(SnapshotAt(2));
Assert.Equal(1, calls);
}
[Fact]
public void Publish_Null_Throws()
{
Assert.Throws(() => new OverviewSnapshotStore().Publish(null!));
}
[Fact]
public async Task Current_ReadDuringPublish_ObservesOneWholeSnapshot()
{
// The tearing guarantee: a reader always sees a complete graph, never a half-swapped one.
var store = new OverviewSnapshotStore();
var a = SnapshotAt(1);
var b = SnapshotAt(2);
store.Publish(a);
using var stop = new CancellationTokenSource();
var reader = Task.Run(() =>
{
while (!stop.IsCancellationRequested)
{
var seen = store.Current;
Assert.True(ReferenceEquals(seen, a) || ReferenceEquals(seen, b));
}
});
for (var i = 0; i < 1_000; i++)
store.Publish(i % 2 == 0 ? b : a);
await stop.CancelAsync();
await reader;
}
}