test(otopcua): assert exact discovered NodeId in the e2e
This commit is contained in:
+27
-25
@@ -74,6 +74,14 @@ public sealed class DiscoveryInjectionEndToEndTests : RuntimeActorTestBase
|
||||
private const string FixedTreeRef = "10.0.0.5:8193/Identity/SeriesNumber";
|
||||
private const string FixedTreeDisplayName = "SeriesNumber";
|
||||
|
||||
// The DETERMINISTIC NodeId the chain must place the FixedTree node at: EQ-1 (the bound equipment root) +
|
||||
// the COLLAPSED folder path. The mapper's device-folder collapse drops the single shared device-host
|
||||
// segment ("10.0.0.5:8193"), so FolderPathSegments ["FOCAS","10.0.0.5:8193","Identity"] + browse
|
||||
// "SeriesNumber" → "EQ-1/FOCAS/Identity/SeriesNumber" (per EquipmentNodeIds.Variable). Asserting this
|
||||
// EXACT NodeId closes the loop on the collapse rule — a prefix/StartsWith check would still pass if the
|
||||
// collapse broke (e.g. "EQ-1/FOCAS/10.0.0.5:8193/Identity/SeriesNumber").
|
||||
private const string ExpectedFixedTreeNodeId = "EQ-1/FOCAS/Identity/SeriesNumber";
|
||||
|
||||
private static DiscoveredNode[] FixedTreeNodes() => new[]
|
||||
{
|
||||
new DiscoveredNode(
|
||||
@@ -112,27 +120,25 @@ public sealed class DiscoveryInjectionEndToEndTests : RuntimeActorTestBase
|
||||
// Driver reports its captured FixedTree (the faithful Task-7/8 seam).
|
||||
host.Tell(new DriverInstanceActor.DiscoveredNodesReady("d1", FixedTreeNodes()));
|
||||
|
||||
// (a) The discovered variable was materialised through the REAL applier onto the sink, under EQ-1.
|
||||
string fixedTreeNodeId = null!;
|
||||
// (a) The discovered variable was materialised through the REAL applier onto the sink, at the EXACT
|
||||
// collapsed NodeId under the bound equipment root (proves the mapper's device-folder collapse).
|
||||
AwaitAssert(() =>
|
||||
{
|
||||
var v = sink.Variables.SingleOrDefault(x => x.DisplayName == FixedTreeDisplayName);
|
||||
v.NodeId.ShouldNotBeNull(); // EnsureVariable recorded for the FixedTree node
|
||||
v.NodeId.ShouldStartWith("EQ-1"); // grafted UNDER the bound equipment root
|
||||
v.DataType.ShouldBe("String"); // mapper carried the driver type through to the sink
|
||||
v.Writable.ShouldBeFalse(); // discovered nodes are read-only
|
||||
fixedTreeNodeId = v.NodeId;
|
||||
sink.ModelChanges.ShouldContain("EQ-1"); // NodeAdded announced under the equipment
|
||||
v.NodeId.ShouldBe(ExpectedFixedTreeNodeId); // EnsureVariable at the exact collapsed NodeId under EQ-1
|
||||
v.DataType.ShouldBe("String"); // mapper carried the driver type through to the sink
|
||||
v.Writable.ShouldBeFalse(); // discovered nodes are read-only
|
||||
sink.ModelChanges.ShouldContain("EQ-1"); // NodeAdded announced under the equipment
|
||||
}, duration: Timeout);
|
||||
|
||||
// (b) A value published for the FixedTree ref routes to the mapped NodeId and lands Good — the live
|
||||
// (b) A value published for the FixedTree ref routes to THAT exact NodeId and lands Good — the live
|
||||
// value flowed end-to-end (host routing map → publish actor → applier-backing sink WriteValue).
|
||||
host.Tell(new DriverInstanceActor.AttributeValuePublished("d1", FixedTreeRef, "SN-12345", OpcUaQuality.Good, Ts));
|
||||
|
||||
AwaitAssert(() =>
|
||||
{
|
||||
var write = sink.Values.SingleOrDefault(x => x.NodeId == fixedTreeNodeId);
|
||||
write.NodeId.ShouldBe(fixedTreeNodeId);
|
||||
var write = sink.Values.SingleOrDefault(x => x.NodeId == ExpectedFixedTreeNodeId);
|
||||
write.NodeId.ShouldBe(ExpectedFixedTreeNodeId);
|
||||
write.Value.ShouldBe("SN-12345");
|
||||
write.Quality.ShouldBe(OpcUaQuality.Good);
|
||||
write.Ts.ShouldBe(Ts);
|
||||
@@ -159,22 +165,18 @@ public sealed class DiscoveryInjectionEndToEndTests : RuntimeActorTestBase
|
||||
|
||||
host.Tell(new DriverInstanceActor.DiscoveredNodesReady("d1", FixedTreeNodes()));
|
||||
|
||||
// First injection: capture the mapped NodeId once it has materialised on the sink.
|
||||
string fixedTreeNodeId = null!;
|
||||
AwaitAssert(() =>
|
||||
{
|
||||
var v = sink.Variables.SingleOrDefault(x => x.DisplayName == FixedTreeDisplayName);
|
||||
v.NodeId.ShouldNotBeNull();
|
||||
fixedTreeNodeId = v.NodeId;
|
||||
}, duration: Timeout);
|
||||
// First injection: the FixedTree node materialises at the EXACT collapsed NodeId under EQ-1.
|
||||
AwaitAssert(
|
||||
() => sink.Variables.ShouldContain(x => x.NodeId == ExpectedFixedTreeNodeId && x.DisplayName == FixedTreeDisplayName),
|
||||
duration: Timeout);
|
||||
|
||||
// First value flows Good (pre-redeploy baseline).
|
||||
host.Tell(new DriverInstanceActor.AttributeValuePublished("d1", FixedTreeRef, "SN-AAA", OpcUaQuality.Good, Ts));
|
||||
AwaitAssert(
|
||||
() => sink.Values.ShouldContain(x => x.NodeId == fixedTreeNodeId && Equals(x.Value, "SN-AAA") && x.Quality == OpcUaQuality.Good),
|
||||
() => sink.Values.ShouldContain(x => x.NodeId == ExpectedFixedTreeNodeId && Equals(x.Value, "SN-AAA") && x.Quality == OpcUaQuality.Good),
|
||||
duration: Timeout);
|
||||
|
||||
var ensureVarCountBefore = sink.Variables.Count(x => x.NodeId == fixedTreeNodeId);
|
||||
var ensureVarCountBefore = sink.Variables.Count(x => x.NodeId == ExpectedFixedTreeNodeId);
|
||||
|
||||
// Apply a SECOND deployment (new revision, SAME d1 → EQ-1 binding) — re-runs PushDesiredSubscriptions
|
||||
// (clears + rebuilds the routing maps) then the Task-8 tail re-applies the cached discovered plan.
|
||||
@@ -183,17 +185,17 @@ public sealed class DiscoveryInjectionEndToEndTests : RuntimeActorTestBase
|
||||
host.Tell(new DispatchDeployment(deploymentId2, RevB, CorrelationId.NewId()));
|
||||
coordinator.ExpectMsg<ApplyAck>(Timeout).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||
|
||||
// (a) The cached discovered plan was RE-MATERIALISED at the SAME NodeId after the redeploy rebuild.
|
||||
// (a) The cached discovered plan was RE-MATERIALISED at the SAME exact NodeId after the redeploy rebuild.
|
||||
AwaitAssert(
|
||||
() => sink.Variables.Count(x => x.NodeId == fixedTreeNodeId).ShouldBeGreaterThan(ensureVarCountBefore),
|
||||
() => sink.Variables.Count(x => x.NodeId == ExpectedFixedTreeNodeId).ShouldBeGreaterThan(ensureVarCountBefore),
|
||||
duration: Timeout);
|
||||
|
||||
// (b) A value published AFTER the redeploy STILL routes to the mapped NodeId and lands Good — the
|
||||
// (b) A value published AFTER the redeploy STILL routes to the exact NodeId and lands Good — the
|
||||
// live-value routing map was rebuilt by the re-apply (not lost when PushDesiredSubscriptions cleared it).
|
||||
var tsAfter = Ts.AddSeconds(5);
|
||||
host.Tell(new DriverInstanceActor.AttributeValuePublished("d1", FixedTreeRef, "SN-BBB", OpcUaQuality.Good, tsAfter));
|
||||
AwaitAssert(
|
||||
() => sink.Values.ShouldContain(x => x.NodeId == fixedTreeNodeId && Equals(x.Value, "SN-BBB") && x.Quality == OpcUaQuality.Good),
|
||||
() => sink.Values.ShouldContain(x => x.NodeId == ExpectedFixedTreeNodeId && Equals(x.Value, "SN-BBB") && x.Quality == OpcUaQuality.Good),
|
||||
duration: Timeout);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user