using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common; namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.QuestDb.IntegrationTests; public sealed partial class TombstoneTests { /// /// The live writer's outbox redelivers batches (at-least-once). A batch replayed after an operator's /// DeleteAtTime must not bring the deleted sample back: the writer's existence check counts a tombstone as /// present (it deliberately does not filter NOT deleted), so the redelivered row is skipped. /// [Fact] public async Task Tombstone_is_not_resurrected_by_a_writer_redelivery() { fx.SkipIfUnavailable(); var rig = UpdateTests.MakeRig(fx); var writer = new QuestDbHistorianValueWriter(rig.Conn, rig.Tags, new QuestDbWalVisibility(rig.Conn, TimeSpan.FromSeconds(5))); HistorizationValue[] batch = [ new(T0, HistorianValue.FromDouble(1), HistorianStatusCodes.Good), new(T0.AddSeconds(1), HistorianValue.FromDouble(2), HistorianStatusCodes.Good), ]; (await writer.WriteLiveValuesAsync("r", batch, default)).ShouldBeTrue(); (await rig.Updater.DeleteAtTimeAsync("r", [T0], UpdateTests.Who, default)).ShouldBe([HistorianStatusCodes.Good]); (await writer.WriteLiveValuesAsync("r", batch, default)).ShouldBeTrue(); // the redelivery (await UpdateTests.RowsAsync(rig, "r", liveOnly: true)).Select(r => r.Num).ShouldBe([2d]); var all = await UpdateTests.RowsAsync(rig, "r"); all.Count.ShouldBe(2); all[0].Deleted.ShouldBeTrue(); all[0].Num.ShouldBeNull(); } }