using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Driver.Mqtt.Tests; public sealed class LastValueCacheTests { // OPC UA BadWaitingForInitialData (0x80320000) — the repo-wide convention for "no value // observed yet" (mirrored by CalculationDriver, VirtualTagEngine, FOCAS, and // OtOpcUaNodeManager for freshly-materialised / not-yet-evaluated nodes). MQTT is // subscribe-first, so an unseen RawPath is in exactly that state until its first publish. private const uint BadWaitingForInitialData = 0x80320000u; [Fact] public void Read_UnseenRef_ReturnsPerRefWaitingForInitialData_DoesNotThrow() { var cache = new LastValueCache(); var snap = cache.Read("factory/oven/temp"); snap.StatusCode.ShouldBe(BadWaitingForInitialData); snap.Value.ShouldBeNull(); } [Fact] public void Update_ThenRead_ReturnsLastValue() { var cache = new LastValueCache(); var now = DateTime.UtcNow; cache.Update("factory/oven/temp", new DataValueSnapshot(42.0, 0u, now, now)); var snap = cache.Read("factory/oven/temp"); snap.Value.ShouldBe(42.0); snap.StatusCode.ShouldBe(0u); } [Fact] public void Update_OverwritesPreviousValue_ForSameRawPath() { var cache = new LastValueCache(); var t1 = DateTime.UtcNow; var t2 = t1.AddSeconds(1); cache.Update("factory/oven/temp", new DataValueSnapshot(1.0, 0u, t1, t1)); cache.Update("factory/oven/temp", new DataValueSnapshot(2.0, 0u, t2, t2)); cache.Read("factory/oven/temp").Value.ShouldBe(2.0); } [Theory] [InlineData(null)] [InlineData("")] public void Read_NullOrEmptyRawPath_DoesNotThrow_ReturnsWaitingForInitialData(string? rawPath) { var cache = new LastValueCache(); var snap = cache.Read(rawPath!); snap.StatusCode.ShouldBe(BadWaitingForInitialData); } [Fact] public void Update_NullRawPath_DoesNotThrow() { var cache = new LastValueCache(); Should.NotThrow(() => cache.Update(null!, new DataValueSnapshot(1.0, 0u, DateTime.UtcNow, DateTime.UtcNow))); } [Fact] public void Read_DistinguishesUnseenFromGenuinelyBadObservedValue() { var cache = new LastValueCache(); var now = DateTime.UtcNow; const uint badDeviceFailure = 0x808B0000u; cache.Update("factory/oven/temp", new DataValueSnapshot(null, badDeviceFailure, null, now)); var observedBad = cache.Read("factory/oven/temp"); var neverObserved = cache.Read("factory/oven/other"); observedBad.StatusCode.ShouldBe(badDeviceFailure); neverObserved.StatusCode.ShouldBe(BadWaitingForInitialData); observedBad.StatusCode.ShouldNotBe(neverObserved.StatusCode); } }