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;
}