using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Drivers;
using ZB.MOM.WW.OtOpcUa.Runtime.Telemetry;
namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Telemetry;
///
/// Review 02 L4: 's tombstone set only ever shrank on a re-spawn of the
/// SAME id, so a driver removed for good kept its id forever. The tombstone is load-bearing (#609 — it holds
/// back the removed child's late writes), so it cannot simply be cleared; it is now timestamped and swept
/// once older than , which outlives any in-flight
/// capability call by a wide margin.
///
public sealed class TelemetryLocalHubTombstoneSweepTests
{
private static DriverHealthChanged Health(string instanceId) =>
new("cluster-a", instanceId, "Healthy", null, null, 0, DateTime.UtcNow);
private static List Drain(ITelemetrySubscription sub)
{
var items = new List();
while (sub.Reader.TryRead(out var item)) items.Add(item);
return items;
}
[Fact]
public void A_tombstone_holds_late_writes_until_the_ttl_then_is_swept()
{
var clock = new ManualTime { Now = new DateTimeOffset(2026, 10, 22, 12, 0, 0, TimeSpan.Zero) };
var hub = new TelemetryLocalHub(clock);
hub.Emit(new TelemetryItem.Health(Health("drv-a")));
hub.Evict("drv-a");
// Inside the TTL: the late write is dropped (the #609 contract) — and another eviction's sweep does
// not lift a tombstone that is still young.
clock.Now += TelemetryLocalHub.EvictionTombstoneTtl / 2;
hub.Evict("drv-b");
hub.Emit(new TelemetryItem.Health(Health("drv-a")));
using (var sub = hub.Subscribe(16))
Drain(sub).ShouldBeEmpty("a late write inside the TTL must still be held back");
// Past the TTL: the next eviction sweeps drv-a's tombstone and its writes land again.
clock.Now += TelemetryLocalHub.EvictionTombstoneTtl;
hub.Evict("drv-c");
hub.Emit(new TelemetryItem.Health(Health("drv-a")));
using (var sub = hub.Subscribe(16))
Drain(sub).OfType().Single().E.DriverInstanceId.ShouldBe("drv-a");
}
[Fact]
public void Readmit_lifts_a_tombstone_before_its_ttl()
{
var clock = new ManualTime { Now = DateTimeOffset.UtcNow };
var hub = new TelemetryLocalHub(clock);
hub.Evict("drv-a");
hub.Readmit("drv-a");
hub.Emit(new TelemetryItem.Health(Health("drv-a")));
using var sub = hub.Subscribe(16);
Drain(sub).Count.ShouldBe(1);
}
private sealed class ManualTime : TimeProvider
{
public DateTimeOffset Now { get; set; }
public override DateTimeOffset GetUtcNow() => Now;
}
}