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; /// Soft-delete behaviour. The writer-redelivery case lives in T7 (TombstoneTests.Redelivery.cs). [Collection(QuestDbCollection.Name)] public sealed partial class TombstoneTests(QuestDbFixture fx) { private static readonly DateTime T0 = new(2026, 3, 1, 12, 0, 0, DateTimeKind.Utc); [Fact] public async Task Delete_then_raw_read_hides_the_row_and_leaves_a_NULL_valued_tombstone() { fx.SkipIfUnavailable(); var rig = UpdateTests.MakeRig(fx); await rig.Updater.UpdateDataAsync("t", HistoryUpdateMode.Insert, [UpdateTests.V(T0, 1), UpdateTests.V(T0.AddSeconds(1), 2)], UpdateTests.Who, default); (await rig.Updater.DeleteAtTimeAsync("t", [T0], UpdateTests.Who, default)).ShouldBe([HistorianStatusCodes.Good]); (await UpdateTests.RowsAsync(rig, "t", liveOnly: true)).Select(r => r.Num).ShouldBe([2d]); var all = await UpdateTests.RowsAsync(rig, "t"); all.Count.ShouldBe(2); // the tombstone replaced the row in place (dedup upsert) var tomb = all[0]; tomb.Deleted.ShouldBeTrue(); tomb.Num.ShouldBeNull(); tomb.Kind.ShouldBe((byte)0); tomb.Status.ShouldBe((long)HistorianStatusCodes.BadNoData); } [Fact] public async Task Insert_over_a_tombstone_reports_GoodEntryInserted_and_the_row_is_visible() { fx.SkipIfUnavailable(); var rig = UpdateTests.MakeRig(fx); await rig.Updater.UpdateDataAsync("t", HistoryUpdateMode.Insert, [UpdateTests.V(T0, 1)], UpdateTests.Who, default); await rig.Updater.DeleteAtTimeAsync("t", [T0], UpdateTests.Who, default); (await rig.Updater.UpdateDataAsync("t", HistoryUpdateMode.Replace, [UpdateTests.V(T0, 5)], UpdateTests.Who, default)) .ShouldBe([HistorianStatusCodes.BadNoEntryExists]); // a tombstone is no entry for Replace (await rig.Updater.UpdateDataAsync("t", HistoryUpdateMode.Insert, [UpdateTests.V(T0, 9)], UpdateTests.Who, default)) .ShouldBe([HistorianStatusCodes.GoodEntryInserted]); var live = await UpdateTests.RowsAsync(rig, "t", liveOnly: true); live.Count.ShouldBe(1); live[0].Num.ShouldBe(9d); live[0].Deleted.ShouldBeFalse(); } }