test(galaxyrepo): projector + cache tests; dispose semaphores; pack 0.1.0
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using ZB.MOM.WW.GalaxyRepository;
|
||||
|
||||
namespace ZB.MOM.WW.GalaxyRepository.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory <see cref="IGalaxyRepository"/> 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.
|
||||
/// </summary>
|
||||
internal sealed class FakeGalaxyRepository : IGalaxyRepository
|
||||
{
|
||||
private readonly IReadOnlyList<GalaxyHierarchyRow> _hierarchy;
|
||||
private readonly IReadOnlyList<GalaxyAttributeRow> _attributes;
|
||||
|
||||
public FakeGalaxyRepository(
|
||||
IReadOnlyList<GalaxyHierarchyRow> hierarchy,
|
||||
IReadOnlyList<GalaxyAttributeRow> attributes,
|
||||
DateTime? deployTime)
|
||||
{
|
||||
_hierarchy = hierarchy;
|
||||
_attributes = attributes;
|
||||
DeployTime = deployTime;
|
||||
}
|
||||
|
||||
/// <summary>The deploy time returned by <see cref="GetLastDeployTimeAsync"/>; mutate to simulate a redeploy.</summary>
|
||||
public DateTime? DeployTime { get; set; }
|
||||
|
||||
/// <summary>When set, every query throws this exception (simulates an unreachable database).</summary>
|
||||
public Exception? ThrowOnQuery { get; set; }
|
||||
|
||||
public int HierarchyReadCount { get; private set; }
|
||||
|
||||
public int AttributeReadCount { get; private set; }
|
||||
|
||||
public Task<bool> TestConnectionAsync(CancellationToken ct = default) =>
|
||||
ThrowOnQuery is null ? Task.FromResult(true) : throw ThrowOnQuery;
|
||||
|
||||
public Task<DateTime?> GetLastDeployTimeAsync(CancellationToken ct = default)
|
||||
{
|
||||
if (ThrowOnQuery is not null)
|
||||
{
|
||||
throw ThrowOnQuery;
|
||||
}
|
||||
|
||||
return Task.FromResult(DeployTime);
|
||||
}
|
||||
|
||||
public Task<List<GalaxyHierarchyRow>> GetHierarchyAsync(CancellationToken ct = default)
|
||||
{
|
||||
if (ThrowOnQuery is not null)
|
||||
{
|
||||
throw ThrowOnQuery;
|
||||
}
|
||||
|
||||
HierarchyReadCount++;
|
||||
return Task.FromResult(_hierarchy.ToList());
|
||||
}
|
||||
|
||||
public Task<List<GalaxyAttributeRow>> GetAttributesAsync(CancellationToken ct = default)
|
||||
{
|
||||
if (ThrowOnQuery is not null)
|
||||
{
|
||||
throw ThrowOnQuery;
|
||||
}
|
||||
|
||||
AttributeReadCount++;
|
||||
return Task.FromResult(_attributes.ToList());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Records published deploy events so tests can assert publication.</summary>
|
||||
internal sealed class RecordingDeployNotifier : IGalaxyDeployNotifier
|
||||
{
|
||||
public List<GalaxyDeployEventInfo> Published { get; } = [];
|
||||
|
||||
public GalaxyDeployEventInfo? Latest { get; private set; }
|
||||
|
||||
public void Publish(GalaxyDeployEventInfo info)
|
||||
{
|
||||
Published.Add(info);
|
||||
Latest = info;
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<GalaxyDeployEventInfo> SubscribeAsync(
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
if (Latest is { } latest)
|
||||
{
|
||||
yield return latest;
|
||||
}
|
||||
|
||||
await Task.CompletedTask.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In-memory <see cref="IGalaxyHierarchySnapshotStore"/>. Pre-seed <see cref="Snapshot"/>
|
||||
/// to exercise the restore path; reads <see cref="SaveAsync"/> back to assert persistence.
|
||||
/// </summary>
|
||||
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<GalaxyHierarchySnapshot?> TryLoadAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
LoadCount++;
|
||||
return Task.FromResult(Snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="TimeProvider"/> whose UTC clock is fixed (and advanceable) so the cache's
|
||||
/// staleness projection (which fires after a 5-minute threshold) is deterministic.
|
||||
/// </summary>
|
||||
internal sealed class StubTimeProvider(DateTimeOffset start) : TimeProvider
|
||||
{
|
||||
private DateTimeOffset _now = start;
|
||||
|
||||
public override DateTimeOffset GetUtcNow() => _now;
|
||||
|
||||
public void Advance(TimeSpan delta) => _now += delta;
|
||||
}
|
||||
Reference in New Issue
Block a user