merge WP2 M1 fix (AddReference Organizes seam) into v3/batch4-address-space
This commit is contained in:
+1
@@ -189,5 +189,6 @@ public sealed class AddressSpaceApplierFailureSurfaceTests
|
||||
}
|
||||
|
||||
public void RaiseNodesAddedModelChange(string affectedNodeId, AddressSpaceRealm realm = AddressSpaceRealm.Uns) { }
|
||||
public void AddReference(string sourceNodeId, AddressSpaceRealm sourceRealm, string targetNodeId, AddressSpaceRealm targetRealm, string referenceType = "Organizes") { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,5 +338,6 @@ public sealed class AddressSpaceApplierHierarchyTests : IDisposable
|
||||
public void RebuildAddressSpace() { }
|
||||
/// <summary>Announces a NodeAdded model-change (stub implementation for testing).</summary>
|
||||
public void RaiseNodesAddedModelChange(string affectedNodeId, AddressSpaceRealm realm = AddressSpaceRealm.Uns) { }
|
||||
public void AddReference(string sourceNodeId, AddressSpaceRealm sourceRealm, string targetNodeId, AddressSpaceRealm targetRealm, string referenceType = "Organizes") { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2307,6 +2307,7 @@ public sealed class AddressSpaceApplierTests
|
||||
/// <summary>Records a NodeAdded model-change announcement.</summary>
|
||||
/// <param name="affectedNodeId">The node under which discovered nodes were added.</param>
|
||||
public void RaiseNodesAddedModelChange(string affectedNodeId, AddressSpaceRealm realm = AddressSpaceRealm.Uns) => ModelChangeQueue.Enqueue(affectedNodeId);
|
||||
public void AddReference(string sourceNodeId, AddressSpaceRealm sourceRealm, string targetNodeId, AddressSpaceRealm targetRealm, string referenceType = "Organizes") { }
|
||||
}
|
||||
|
||||
/// <summary>A recording sink that does NOT implement <see cref="ISurgicalAddressSpaceSink"/> — used to
|
||||
@@ -2331,6 +2332,7 @@ public sealed class AddressSpaceApplierTests
|
||||
public void RebuildAddressSpace() => Interlocked.Increment(ref RebuildCalls);
|
||||
/// <summary>No-op NodeAdded model-change announcement.</summary>
|
||||
public void RaiseNodesAddedModelChange(string affectedNodeId, AddressSpaceRealm realm = AddressSpaceRealm.Uns) { }
|
||||
public void AddReference(string sourceNodeId, AddressSpaceRealm sourceRealm, string targetNodeId, AddressSpaceRealm targetRealm, string referenceType = "Organizes") { }
|
||||
}
|
||||
|
||||
private sealed class ThrowingSink : IOpcUaAddressSpaceSink
|
||||
@@ -2379,5 +2381,6 @@ public sealed class AddressSpaceApplierTests
|
||||
public void RebuildAddressSpace() { }
|
||||
/// <summary>No-op NodeAdded model-change announcement.</summary>
|
||||
public void RaiseNodesAddedModelChange(string affectedNodeId, AddressSpaceRealm realm = AddressSpaceRealm.Uns) { }
|
||||
public void AddReference(string sourceNodeId, AddressSpaceRealm sourceRealm, string targetNodeId, AddressSpaceRealm targetRealm, string referenceType = "Organizes") { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,6 +249,7 @@ public sealed class DeferredAddressSpaceSinkTests
|
||||
public void RebuildAddressSpace() => CallQueue.Enqueue("RB");
|
||||
/// <inheritdoc />
|
||||
public void RaiseNodesAddedModelChange(string affectedNodeId, AddressSpaceRealm realm = AddressSpaceRealm.Uns) => CallQueue.Enqueue($"NA:{affectedNodeId}");
|
||||
public void AddReference(string sourceNodeId, AddressSpaceRealm sourceRealm, string targetNodeId, AddressSpaceRealm targetRealm, string referenceType = "Organizes") { }
|
||||
}
|
||||
|
||||
private sealed class SurgicalRecordingSink : IOpcUaAddressSpaceSink, ISurgicalAddressSpaceSink
|
||||
@@ -298,5 +299,6 @@ public sealed class DeferredAddressSpaceSinkTests
|
||||
public void RebuildAddressSpace() { }
|
||||
/// <inheritdoc />
|
||||
public void RaiseNodesAddedModelChange(string affectedNodeId, AddressSpaceRealm realm = AddressSpaceRealm.Uns) { }
|
||||
public void AddReference(string sourceNodeId, AddressSpaceRealm sourceRealm, string targetNodeId, AddressSpaceRealm targetRealm, string referenceType = "Organizes") { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,67 @@ public sealed class SdkAddressSpaceSinkTests : IDisposable
|
||||
await host.DisposeAsync();
|
||||
}
|
||||
|
||||
/// <summary>B4-WP2 M1: AddReference wires the cross-tree Organizes edge from a UNS reference variable
|
||||
/// (source, realm=Uns) to its backing Raw node (target, realm=Raw), bidirectionally, so browse resolves
|
||||
/// both ways — and is idempotent.</summary>
|
||||
[Fact]
|
||||
public async Task AddReference_wires_Organizes_edge_from_UNS_variable_to_Raw_node()
|
||||
{
|
||||
var (host, server) = await BootAsync();
|
||||
var nm = server.NodeManager!;
|
||||
var sink = new SdkAddressSpaceSink(nm);
|
||||
|
||||
// Backing Raw node (device tree) and the UNS reference variable (equipment tree).
|
||||
sink.EnsureVariable("MAIN/modbus/dev/grp/temp", parentFolderNodeId: null, displayName: "Temp",
|
||||
dataType: "Float", writable: false, realm: AddressSpaceRealm.Raw);
|
||||
sink.EnsureVariable("Area/Line/Eq/Temperature", parentFolderNodeId: null, displayName: "Temperature",
|
||||
dataType: "Float", writable: false, realm: AddressSpaceRealm.Uns);
|
||||
|
||||
sink.AddReference("Area/Line/Eq/Temperature", AddressSpaceRealm.Uns,
|
||||
"MAIN/modbus/dev/grp/temp", AddressSpaceRealm.Raw);
|
||||
|
||||
var unsVar = nm.TryGetVariable("Area/Line/Eq/Temperature", AddressSpaceRealm.Uns);
|
||||
var rawVar = nm.TryGetVariable("MAIN/modbus/dev/grp/temp", AddressSpaceRealm.Raw);
|
||||
unsVar.ShouldNotBeNull();
|
||||
rawVar.ShouldNotBeNull();
|
||||
|
||||
// Forward Organizes on the UNS variable → Raw node; inverse (OrganizedBy) on the Raw node.
|
||||
unsVar.ReferenceExists(ReferenceTypeIds.Organizes, isInverse: false, rawVar.NodeId).ShouldBeTrue();
|
||||
rawVar.ReferenceExists(ReferenceTypeIds.Organizes, isInverse: true, unsVar.NodeId).ShouldBeTrue();
|
||||
|
||||
// Idempotent: a second identical call does not duplicate the edge. The UNS variable's only
|
||||
// FORWARD Organizes ref is the one we added (its parent link manifests as an inverse ref).
|
||||
sink.AddReference("Area/Line/Eq/Temperature", AddressSpaceRealm.Uns,
|
||||
"MAIN/modbus/dev/grp/temp", AddressSpaceRealm.Raw);
|
||||
nm.GetNodeReferences("Area/Line/Eq/Temperature", AddressSpaceRealm.Uns)
|
||||
.Count(r => r.ReferenceTypeId == ReferenceTypeIds.Organizes && !r.IsInverse).ShouldBe(1);
|
||||
|
||||
await host.DisposeAsync();
|
||||
}
|
||||
|
||||
/// <summary>B4-WP2 M1: a missing endpoint makes AddReference a safe no-op (never throws, no edge added).</summary>
|
||||
[Fact]
|
||||
public async Task AddReference_with_missing_endpoint_is_a_safe_no_op()
|
||||
{
|
||||
var (host, server) = await BootAsync();
|
||||
var nm = server.NodeManager!;
|
||||
var sink = new SdkAddressSpaceSink(nm);
|
||||
|
||||
sink.EnsureVariable("Area/Line/Eq/Temperature", parentFolderNodeId: null, displayName: "Temperature",
|
||||
dataType: "Float", writable: false, realm: AddressSpaceRealm.Uns);
|
||||
|
||||
// Target does not exist ⇒ no-op, no throw.
|
||||
Should.NotThrow(() => sink.AddReference("Area/Line/Eq/Temperature", AddressSpaceRealm.Uns,
|
||||
"MAIN/does/not/exist", AddressSpaceRealm.Raw));
|
||||
|
||||
var unsVar = nm.TryGetVariable("Area/Line/Eq/Temperature", AddressSpaceRealm.Uns);
|
||||
unsVar.ShouldNotBeNull();
|
||||
nm.GetNodeReferences("Area/Line/Eq/Temperature", AddressSpaceRealm.Uns)
|
||||
.Any(r => r.ReferenceTypeId == ReferenceTypeIds.Organizes && !r.IsInverse).ShouldBeFalse();
|
||||
|
||||
await host.DisposeAsync();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that RebuildAddressSpace clears all registered variables.</summary>
|
||||
[Fact]
|
||||
public async Task RebuildAddressSpace_clears_all_registered_variables()
|
||||
|
||||
Reference in New Issue
Block a user