using System.Runtime.CompilerServices; using ZB.MOM.WW.GalaxyRepository; namespace ZB.MOM.WW.GalaxyRepository.Tests; /// /// In-memory returning canned rowsets. Counts the heavy /// hierarchy/attribute reads so tests can assert deploy-gated skips, and can be flipped to /// throw so the failure path is exercisable. /// internal sealed class FakeGalaxyRepository : IGalaxyRepository { private readonly IReadOnlyList _hierarchy; private readonly IReadOnlyList _attributes; public FakeGalaxyRepository( IReadOnlyList hierarchy, IReadOnlyList attributes, DateTime? deployTime) { _hierarchy = hierarchy; _attributes = attributes; DeployTime = deployTime; } /// The deploy time returned by ; mutate to simulate a redeploy. public DateTime? DeployTime { get; set; } /// When set, every query throws this exception (simulates an unreachable database). public Exception? ThrowOnQuery { get; set; } public int HierarchyReadCount { get; private set; } public int AttributeReadCount { get; private set; } public Task TestConnectionAsync(CancellationToken ct = default) => ThrowOnQuery is null ? Task.FromResult(true) : throw ThrowOnQuery; public Task GetLastDeployTimeAsync(CancellationToken ct = default) { if (ThrowOnQuery is not null) { throw ThrowOnQuery; } return Task.FromResult(DeployTime); } public Task> GetHierarchyAsync(CancellationToken ct = default) { if (ThrowOnQuery is not null) { throw ThrowOnQuery; } HierarchyReadCount++; return Task.FromResult(_hierarchy.ToList()); } public Task> GetAttributesAsync(CancellationToken ct = default) { if (ThrowOnQuery is not null) { throw ThrowOnQuery; } AttributeReadCount++; return Task.FromResult(_attributes.ToList()); } } /// Records published deploy events so tests can assert publication. internal sealed class RecordingDeployNotifier : IGalaxyDeployNotifier { public List Published { get; } = []; public GalaxyDeployEventInfo? Latest { get; private set; } public void Publish(GalaxyDeployEventInfo info) { Published.Add(info); Latest = info; } public async IAsyncEnumerable SubscribeAsync( [EnumeratorCancellation] CancellationToken cancellationToken) { if (Latest is { } latest) { yield return latest; } await Task.CompletedTask.ConfigureAwait(false); } } /// /// In-memory . Pre-seed /// to exercise the restore path; reads back to assert persistence. /// internal sealed class FakeSnapshotStore : IGalaxyHierarchySnapshotStore { public GalaxyHierarchySnapshot? Snapshot { get; set; } public int SaveCount { get; private set; } public int LoadCount { get; private set; } public Task SaveAsync(GalaxyHierarchySnapshot snapshot, CancellationToken cancellationToken) { SaveCount++; Snapshot = snapshot; return Task.CompletedTask; } public Task TryLoadAsync(CancellationToken cancellationToken) { LoadCount++; return Task.FromResult(Snapshot); } } /// /// A whose UTC clock is fixed (and advanceable) so the cache's /// staleness projection (which fires after a 5-minute threshold) is deterministic. /// internal sealed class StubTimeProvider(DateTimeOffset start) : TimeProvider { private DateTimeOffset _now = start; public override DateTimeOffset GetUtcNow() => _now; public void Advance(TimeSpan delta) => _now += delta; }