|
|
|
@@ -131,14 +131,15 @@ public sealed class Phase7ApplierTests
|
|
|
|
|
{
|
|
|
|
|
EquipmentTags = new[]
|
|
|
|
|
{
|
|
|
|
|
new EquipmentTagPlan("tag-1", "eq-1", "drv", FolderPath: "", Name: "Speed", DataType: "Float", FullName: "40001"),
|
|
|
|
|
new EquipmentTagPlan("tag-1", "eq-1", "drv", FolderPath: "", Name: "Speed", DataType: "Float", FullName: "40001", Writable: true),
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
applier.MaterialiseEquipmentTags(composition);
|
|
|
|
|
|
|
|
|
|
sink.FolderCalls.ShouldBeEmpty(); // equipment folder already exists; no sub-folder needed
|
|
|
|
|
sink.VariableCalls.ShouldHaveSingleItem().ShouldBe(("eq-1/Speed", "eq-1", "Speed", "Float"));
|
|
|
|
|
// A ReadWrite plan threads Writable: true through the applier to the sink (the node is created CurrentReadWrite).
|
|
|
|
|
sink.VariableCalls.ShouldHaveSingleItem().ShouldBe(("eq-1/Speed", "eq-1", "Speed", "Float", true));
|
|
|
|
|
// Parity: the materialiser's NodeId is the shared EquipmentNodeIds formula (null/empty FolderPath).
|
|
|
|
|
sink.VariableCalls.Single().NodeId.ShouldBe(EquipmentNodeIds.Variable("eq-1", "", "Speed"));
|
|
|
|
|
}
|
|
|
|
@@ -157,14 +158,15 @@ public sealed class Phase7ApplierTests
|
|
|
|
|
{
|
|
|
|
|
EquipmentTags = new[]
|
|
|
|
|
{
|
|
|
|
|
new EquipmentTagPlan("tag-2", "eq-1", "drv", FolderPath: "Diagnostics", Name: "Temp", DataType: "Float", FullName: "40002"),
|
|
|
|
|
new EquipmentTagPlan("tag-2", "eq-1", "drv", FolderPath: "Diagnostics", Name: "Temp", DataType: "Float", FullName: "40002", Writable: false),
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
applier.MaterialiseEquipmentTags(composition);
|
|
|
|
|
|
|
|
|
|
sink.FolderCalls.ShouldHaveSingleItem().ShouldBe(("eq-1/Diagnostics", "eq-1", "Diagnostics"));
|
|
|
|
|
sink.VariableCalls.ShouldHaveSingleItem().ShouldBe(("eq-1/Diagnostics/Temp", "eq-1/Diagnostics", "Temp", "Float"));
|
|
|
|
|
// A Read plan threads Writable: false (the node stays CurrentRead).
|
|
|
|
|
sink.VariableCalls.ShouldHaveSingleItem().ShouldBe(("eq-1/Diagnostics/Temp", "eq-1/Diagnostics", "Temp", "Float", false));
|
|
|
|
|
// Parity: the materialiser's NodeId is the shared EquipmentNodeIds formula (with FolderPath).
|
|
|
|
|
sink.VariableCalls.Single().NodeId.ShouldBe(EquipmentNodeIds.Variable("eq-1", "Diagnostics", "Temp"));
|
|
|
|
|
}
|
|
|
|
@@ -183,16 +185,16 @@ public sealed class Phase7ApplierTests
|
|
|
|
|
{
|
|
|
|
|
EquipmentTags = new[]
|
|
|
|
|
{
|
|
|
|
|
new EquipmentTagPlan("tag-a", "eq-1", "drv-1", FolderPath: "", Name: "Speed", DataType: "Float", FullName: "40001"),
|
|
|
|
|
new EquipmentTagPlan("tag-b", "eq-2", "drv-2", FolderPath: "", Name: "Speed", DataType: "Float", FullName: "40001"),
|
|
|
|
|
new EquipmentTagPlan("tag-a", "eq-1", "drv-1", FolderPath: "", Name: "Speed", DataType: "Float", FullName: "40001", Writable: false),
|
|
|
|
|
new EquipmentTagPlan("tag-b", "eq-2", "drv-2", FolderPath: "", Name: "Speed", DataType: "Float", FullName: "40001", Writable: false),
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
applier.MaterialiseEquipmentTags(composition);
|
|
|
|
|
|
|
|
|
|
sink.VariableCalls.Count.ShouldBe(2);
|
|
|
|
|
sink.VariableCalls.ShouldContain(("eq-1/Speed", "eq-1", "Speed", "Float"));
|
|
|
|
|
sink.VariableCalls.ShouldContain(("eq-2/Speed", "eq-2", "Speed", "Float"));
|
|
|
|
|
sink.VariableCalls.ShouldContain(("eq-1/Speed", "eq-1", "Speed", "Float", false));
|
|
|
|
|
sink.VariableCalls.ShouldContain(("eq-2/Speed", "eq-2", "Speed", "Float", false));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>Verifies MaterialiseEquipmentVirtualTags creates one Variable per VirtualTag directly
|
|
|
|
@@ -218,7 +220,8 @@ public sealed class Phase7ApplierTests
|
|
|
|
|
applier.MaterialiseEquipmentVirtualTags(composition);
|
|
|
|
|
|
|
|
|
|
sink.FolderCalls.ShouldBeEmpty(); // equipment folder already exists; no sub-folder needed
|
|
|
|
|
sink.VariableCalls.ShouldHaveSingleItem().ShouldBe(("eq-1/speed-rpm", "eq-1", "speed-rpm", "Float64"));
|
|
|
|
|
// VirtualTags are computed outputs — always read-only (Writable: false).
|
|
|
|
|
sink.VariableCalls.ShouldHaveSingleItem().ShouldBe(("eq-1/speed-rpm", "eq-1", "speed-rpm", "Float64", false));
|
|
|
|
|
// Parity: the vtag materialiser's NodeId is the shared EquipmentNodeIds formula.
|
|
|
|
|
sink.VariableCalls.Single().NodeId.ShouldBe(EquipmentNodeIds.Variable("eq-1", "", "speed-rpm"));
|
|
|
|
|
}
|
|
|
|
@@ -240,8 +243,8 @@ public sealed class Phase7ApplierTests
|
|
|
|
|
{
|
|
|
|
|
EquipmentTags = new[]
|
|
|
|
|
{
|
|
|
|
|
new EquipmentTagPlan("tag-flat", "eq-1", "drv", FolderPath: "", Name: "Speed", DataType: "Float", FullName: "40001"),
|
|
|
|
|
new EquipmentTagPlan("tag-nested", "eq-1", "drv", FolderPath: "Diagnostics", Name: "Temp", DataType: "Float", FullName: "40002"),
|
|
|
|
|
new EquipmentTagPlan("tag-flat", "eq-1", "drv", FolderPath: "", Name: "Speed", DataType: "Float", FullName: "40001", Writable: false),
|
|
|
|
|
new EquipmentTagPlan("tag-nested", "eq-1", "drv", FolderPath: "Diagnostics", Name: "Temp", DataType: "Float", FullName: "40002", Writable: false),
|
|
|
|
|
},
|
|
|
|
|
EquipmentVirtualTags = new[]
|
|
|
|
|
{
|
|
|
|
@@ -286,8 +289,8 @@ public sealed class Phase7ApplierTests
|
|
|
|
|
|
|
|
|
|
sink.FolderCalls.ShouldBeEmpty();
|
|
|
|
|
sink.VariableCalls.Count.ShouldBe(2);
|
|
|
|
|
sink.VariableCalls.ShouldContain(("eq-1/speed-rpm", "eq-1", "speed-rpm", "Float64"));
|
|
|
|
|
sink.VariableCalls.ShouldContain(("eq-1/load-pct", "eq-1", "load-pct", "Float64"));
|
|
|
|
|
sink.VariableCalls.ShouldContain(("eq-1/speed-rpm", "eq-1", "speed-rpm", "Float64", false));
|
|
|
|
|
sink.VariableCalls.ShouldContain(("eq-1/load-pct", "eq-1", "load-pct", "Float64", false));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>T14 — MaterialiseScriptedAlarms materialises one condition per ENABLED alarm (keyed by
|
|
|
|
@@ -335,7 +338,7 @@ public sealed class Phase7ApplierTests
|
|
|
|
|
{
|
|
|
|
|
AddedEquipmentTags = new[]
|
|
|
|
|
{
|
|
|
|
|
new EquipmentTagPlan("tag-1", "eq-1", "drv", FolderPath: "", Name: "Speed", DataType: "Float", FullName: "40001"),
|
|
|
|
|
new EquipmentTagPlan("tag-1", "eq-1", "drv", FolderPath: "", Name: "Speed", DataType: "Float", FullName: "40001", Writable: false),
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@@ -394,7 +397,7 @@ public sealed class Phase7ApplierTests
|
|
|
|
|
/// <summary>Gets the queue of folder creation calls.</summary>
|
|
|
|
|
public ConcurrentQueue<(string NodeId, string? Parent, string DisplayName)> FolderQueue { get; } = new();
|
|
|
|
|
/// <summary>Gets the queue of variable creation calls.</summary>
|
|
|
|
|
public ConcurrentQueue<(string NodeId, string? Parent, string DisplayName, string DataType)> VariableQueue { get; } = new();
|
|
|
|
|
public ConcurrentQueue<(string NodeId, string? Parent, string DisplayName, string DataType, bool Writable)> VariableQueue { get; } = new();
|
|
|
|
|
/// <summary>Gets the queue of alarm-condition materialise calls.</summary>
|
|
|
|
|
public ConcurrentQueue<(string AlarmNodeId, string EquipmentNodeId, string DisplayName, string AlarmType, int Severity)> AlarmConditionQueue { get; } = new();
|
|
|
|
|
/// <summary>Gets the number of rebuild calls made on this sink.</summary>
|
|
|
|
@@ -405,7 +408,7 @@ public sealed class Phase7ApplierTests
|
|
|
|
|
/// <summary>Gets the list of recorded folder creation calls.</summary>
|
|
|
|
|
public List<(string NodeId, string? Parent, string DisplayName)> FolderCalls => FolderQueue.ToList();
|
|
|
|
|
/// <summary>Gets the list of recorded variable creation calls.</summary>
|
|
|
|
|
public List<(string NodeId, string? Parent, string DisplayName, string DataType)> VariableCalls => VariableQueue.ToList();
|
|
|
|
|
public List<(string NodeId, string? Parent, string DisplayName, string DataType, bool Writable)> VariableCalls => VariableQueue.ToList();
|
|
|
|
|
/// <summary>Gets the list of recorded alarm-condition materialise calls.</summary>
|
|
|
|
|
public List<(string AlarmNodeId, string EquipmentNodeId, string DisplayName, string AlarmType, int Severity)> AlarmConditionCalls => AlarmConditionQueue.ToList();
|
|
|
|
|
|
|
|
|
@@ -440,8 +443,9 @@ public sealed class Phase7ApplierTests
|
|
|
|
|
/// <param name="parentFolderNodeId">The parent folder node ID, if any.</param>
|
|
|
|
|
/// <param name="displayName">The display name for the variable.</param>
|
|
|
|
|
/// <param name="dataType">The OPC UA built-in type name.</param>
|
|
|
|
|
public void EnsureVariable(string variableNodeId, string? parentFolderNodeId, string displayName, string dataType)
|
|
|
|
|
=> VariableQueue.Enqueue((variableNodeId, parentFolderNodeId, displayName, dataType));
|
|
|
|
|
/// <param name="writable">Whether the node is created read/write.</param>
|
|
|
|
|
public void EnsureVariable(string variableNodeId, string? parentFolderNodeId, string displayName, string dataType, bool writable)
|
|
|
|
|
=> VariableQueue.Enqueue((variableNodeId, parentFolderNodeId, displayName, dataType, writable));
|
|
|
|
|
/// <summary>Records a rebuild address space call.</summary>
|
|
|
|
|
public void RebuildAddressSpace() => Interlocked.Increment(ref RebuildCalls);
|
|
|
|
|
}
|
|
|
|
@@ -485,7 +489,8 @@ public sealed class Phase7ApplierTests
|
|
|
|
|
/// <param name="parentFolderNodeId">The parent folder node ID, if any.</param>
|
|
|
|
|
/// <param name="displayName">The display name for the variable.</param>
|
|
|
|
|
/// <param name="dataType">The OPC UA built-in type name.</param>
|
|
|
|
|
public void EnsureVariable(string variableNodeId, string? parentFolderNodeId, string displayName, string dataType) { }
|
|
|
|
|
/// <param name="writable">Whether the node is created read/write.</param>
|
|
|
|
|
public void EnsureVariable(string variableNodeId, string? parentFolderNodeId, string displayName, string dataType, bool writable) { }
|
|
|
|
|
/// <summary>No-op rebuild address space call.</summary>
|
|
|
|
|
public void RebuildAddressSpace() { }
|
|
|
|
|
}
|
|
|
|
|