perf(comms): alarms-only seed, capped buffers, at-least-once audit pull
This commit is contained in:
@@ -26,11 +26,34 @@ public class SiteAlarmLiveCacheServiceTests : TestKit
|
||||
// A mock site-wide alarm stream client whose subscription hangs until cancelled.
|
||||
private sealed class HangingClient : SiteStreamGrpcClient
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
private readonly List<Action> _connects = new();
|
||||
private readonly List<Action> _completions = new();
|
||||
|
||||
public HangingClient() : base() { }
|
||||
|
||||
/// <summary>When false the site never accepts the subscription (no connect signal).</summary>
|
||||
public bool AutoConnect { get; set; } = true;
|
||||
|
||||
/// <summary>Connect callbacks captured from every subscribe, for manual firing.</summary>
|
||||
public List<Action> Connects { get { lock (_lock) { return _connects.ToList(); } } }
|
||||
|
||||
/// <summary>Graceful-completion callbacks captured from every subscribe.</summary>
|
||||
public List<Action> Completions { get { lock (_lock) { return _completions.ToList(); } } }
|
||||
|
||||
public override Task SubscribeSiteAsync(
|
||||
string correlationId, Action<Commons.Messages.Streaming.AlarmStateChanged> onAlarmEvent,
|
||||
Action<Exception> onError, Action onCompleted, CancellationToken ct)
|
||||
Action<Exception> onError, Action onCompleted, CancellationToken ct,
|
||||
Action? onConnected = null)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (onConnected is not null) _connects.Add(onConnected);
|
||||
_completions.Add(onCompleted);
|
||||
}
|
||||
// A healthy site accepts the subscription immediately (headers flushed on
|
||||
// subscribe), which is what makes the aggregator report the stream live.
|
||||
if (AutoConnect) onConnected?.Invoke();
|
||||
var tcs = new TaskCompletionSource();
|
||||
ct.Register(() => tcs.TrySetResult());
|
||||
return tcs.Task;
|
||||
@@ -43,6 +66,9 @@ public class SiteAlarmLiveCacheServiceTests : TestKit
|
||||
private readonly HangingClient _client = new();
|
||||
public int GetOrCreateCount;
|
||||
|
||||
/// <summary>The single client this factory hands out, for connect/complete control.</summary>
|
||||
public HangingClient Client => _client;
|
||||
|
||||
public CountingFactory() : base(NullLoggerFactory.Instance) { }
|
||||
|
||||
public override SiteStreamGrpcClient GetOrCreate(string siteIdentifier, string grpcEndpoint)
|
||||
@@ -338,4 +364,43 @@ public class SiteAlarmLiveCacheServiceTests : TestKit
|
||||
Thread.Sleep(300);
|
||||
Assert.Equal(startsAfterStop, factory.GetOrCreateCount); // no self-heal restart fired
|
||||
}
|
||||
|
||||
// ── WP2.3: IsLive tracks the STREAM, not merely "a snapshot was published once" ──
|
||||
|
||||
[Fact]
|
||||
public void IsLive_StaysFalse_UntilTheSiteAcceptsTheStream()
|
||||
{
|
||||
var service = CreateService(TimeSpan.FromMilliseconds(200), out var factory);
|
||||
factory.Client.AutoConnect = false; // site never answers the subscription
|
||||
|
||||
using var sub = service.Subscribe(SiteId, () => { });
|
||||
|
||||
// The (empty) seed completes and publishes, but with no accepted stream behind it
|
||||
// the cache is not live — the page must keep polling. Pre-fix IsLive flipped true
|
||||
// here and the page grafted a never-updating snapshot over fresh poll data.
|
||||
AwaitCondition(() => service.GetCurrentAlarms(SiteId) is not null, TimeSpan.FromSeconds(5));
|
||||
Thread.Sleep(400);
|
||||
Assert.False(service.IsLive(SiteId));
|
||||
|
||||
// The site accepts it → live.
|
||||
AwaitCondition(() => factory.Client.Connects.Count >= 1, TimeSpan.FromSeconds(5));
|
||||
factory.Client.Connects[0]();
|
||||
AwaitCondition(() => service.IsLive(SiteId), TimeSpan.FromSeconds(5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsLive_DropsWhenTheStreamCompletes()
|
||||
{
|
||||
// The carried residual from Phase 1: a stream that ended gracefully (the site's 4h
|
||||
// max lifetime) left IsLive true until the next reconcile publish.
|
||||
var service = CreateService(TimeSpan.FromMilliseconds(200), out var factory);
|
||||
|
||||
using var sub = service.Subscribe(SiteId, () => { });
|
||||
AwaitCondition(() => service.IsLive(SiteId), TimeSpan.FromSeconds(5));
|
||||
|
||||
AwaitCondition(() => factory.Client.Completions.Count >= 1, TimeSpan.FromSeconds(5));
|
||||
factory.Client.Completions[0]();
|
||||
|
||||
AwaitCondition(() => !service.IsLive(SiteId), TimeSpan.FromSeconds(5));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user