a8703c6afe
TimerTriggerScheduler was welded to the engine and has no production consumer. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
89 lines
2.8 KiB
C#
89 lines
2.8 KiB
C#
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, 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);
|
|
}
|
|
}
|