feat(alarms): EquipmentTagPlan.Alarm parsed byte-parity from TagConfig (Phase B WS-2)
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.OpcUaServer;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests;
|
||||
|
||||
public class ExtractTagAlarmTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("{\"FullName\":\"X.Y\"}", false, null, 0)]
|
||||
[InlineData("{\"FullName\":\"X.Y\",\"alarm\":{}}", true, "AlarmCondition", 500)]
|
||||
[InlineData("{\"FullName\":\"X.Y\",\"alarm\":{\"alarmType\":\"OffNormalAlarm\",\"severity\":700}}", true, "OffNormalAlarm", 700)]
|
||||
[InlineData("not json", false, null, 0)]
|
||||
[InlineData("{\"FullName\":\"X.Y\",\"alarm\":\"oops\"}", false, null, 0)]
|
||||
public void ExtractTagAlarm_parses_or_returns_null(string cfg, bool present, string? type, int sev)
|
||||
{
|
||||
var info = Phase7Composer.ExtractTagAlarm(cfg);
|
||||
if (!present) { info.ShouldBeNull(); return; }
|
||||
info!.AlarmType.ShouldBe(type);
|
||||
info.Severity.ShouldBe(sev);
|
||||
}
|
||||
}
|
||||
@@ -143,7 +143,7 @@ public sealed class Phase7ApplierHierarchyTests : IDisposable
|
||||
{
|
||||
EquipmentTags = new[]
|
||||
{
|
||||
new EquipmentTagPlan("tag-1", "eq-1", "drv", FolderPath: "", Name: "Speed", DataType: "Float", FullName: "40001", Writable: false),
|
||||
new EquipmentTagPlan("tag-1", "eq-1", "drv", FolderPath: "", Name: "Speed", DataType: "Float", FullName: "40001", Writable: false, Alarm: null),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ public sealed class Phase7ApplierTests
|
||||
{
|
||||
EquipmentTags = new[]
|
||||
{
|
||||
new EquipmentTagPlan("tag-1", "eq-1", "drv", FolderPath: "", Name: "Speed", DataType: "Float", FullName: "40001", Writable: true),
|
||||
new EquipmentTagPlan("tag-1", "eq-1", "drv", FolderPath: "", Name: "Speed", DataType: "Float", FullName: "40001", Writable: true, Alarm: null),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -158,7 +158,7 @@ public sealed class Phase7ApplierTests
|
||||
{
|
||||
EquipmentTags = new[]
|
||||
{
|
||||
new EquipmentTagPlan("tag-2", "eq-1", "drv", FolderPath: "Diagnostics", Name: "Temp", DataType: "Float", FullName: "40002", Writable: false),
|
||||
new EquipmentTagPlan("tag-2", "eq-1", "drv", FolderPath: "Diagnostics", Name: "Temp", DataType: "Float", FullName: "40002", Writable: false, Alarm: null),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -185,8 +185,8 @@ public sealed class Phase7ApplierTests
|
||||
{
|
||||
EquipmentTags = new[]
|
||||
{
|
||||
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),
|
||||
new EquipmentTagPlan("tag-a", "eq-1", "drv-1", FolderPath: "", Name: "Speed", DataType: "Float", FullName: "40001", Writable: false, Alarm: null),
|
||||
new EquipmentTagPlan("tag-b", "eq-2", "drv-2", FolderPath: "", Name: "Speed", DataType: "Float", FullName: "40001", Writable: false, Alarm: null),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -243,8 +243,8 @@ public sealed class Phase7ApplierTests
|
||||
{
|
||||
EquipmentTags = new[]
|
||||
{
|
||||
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),
|
||||
new EquipmentTagPlan("tag-flat", "eq-1", "drv", FolderPath: "", Name: "Speed", DataType: "Float", FullName: "40001", Writable: false, Alarm: null),
|
||||
new EquipmentTagPlan("tag-nested", "eq-1", "drv", FolderPath: "Diagnostics", Name: "Temp", DataType: "Float", FullName: "40002", Writable: false, Alarm: null),
|
||||
},
|
||||
EquipmentVirtualTags = new[]
|
||||
{
|
||||
@@ -338,7 +338,7 @@ public sealed class Phase7ApplierTests
|
||||
{
|
||||
AddedEquipmentTags = new[]
|
||||
{
|
||||
new EquipmentTagPlan("tag-1", "eq-1", "drv", FolderPath: "", Name: "Speed", DataType: "Float", FullName: "40001", Writable: false),
|
||||
new EquipmentTagPlan("tag-1", "eq-1", "drv", FolderPath: "", Name: "Speed", DataType: "Float", FullName: "40001", Writable: false, Alarm: null),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ public sealed class Phase7PlannerTests
|
||||
{
|
||||
EquipmentTags = new[]
|
||||
{
|
||||
new EquipmentTagPlan("tag-1", "eq-1", "drv", FolderPath: "", Name: "Speed", DataType: "Float", FullName: "40001", Writable: false),
|
||||
new EquipmentTagPlan("tag-1", "eq-1", "drv", FolderPath: "", Name: "Speed", DataType: "Float", FullName: "40001", Writable: false, Alarm: null),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
+13
-1
@@ -241,6 +241,8 @@ public sealed class DeploymentArtifactAliasParityTests
|
||||
};
|
||||
|
||||
// The Galaxy equipment tag — FullName is the Galaxy ref "tag_name.AttributeName".
|
||||
// It also carries a native-alarm intent in TagConfig.alarm, so this draft proves the
|
||||
// optional Alarm field is parsed byte-identically on both producers (Phase B WS-2).
|
||||
var galaxyTag = new Tag
|
||||
{
|
||||
TagId = "tag-galaxy",
|
||||
@@ -250,7 +252,7 @@ public sealed class DeploymentArtifactAliasParityTests
|
||||
Name = "DownloadPath",
|
||||
DataType = "String",
|
||||
AccessLevel = TagAccessLevel.Read,
|
||||
TagConfig = "{\"FullName\":\"DelmiaReceiver_001.DownloadPath\"}",
|
||||
TagConfig = "{\"FullName\":\"DelmiaReceiver_001.DownloadPath\",\"alarm\":{\"alarmType\":\"OffNormalAlarm\",\"severity\":700}}",
|
||||
};
|
||||
// A non-Galaxy (Modbus) equipment tag — proves the parity holds across drivers, not just Galaxy.
|
||||
var modbusTag = new Tag
|
||||
@@ -315,6 +317,7 @@ public sealed class DeploymentArtifactAliasParityTests
|
||||
d.DataType.ShouldBe(x.DataType);
|
||||
d.FullName.ShouldBe(x.FullName);
|
||||
d.Writable.ShouldBe(x.Writable);
|
||||
d.Alarm.ShouldBe(x.Alarm); // EquipmentTagAlarmInfo is a positional record ⇒ value equality
|
||||
}
|
||||
|
||||
var galaxyPlan = decoded.EquipmentTags.Single(t => t.TagId == "tag-galaxy");
|
||||
@@ -323,6 +326,15 @@ public sealed class DeploymentArtifactAliasParityTests
|
||||
galaxyPlan.DriverInstanceId.ShouldBe("drv-galaxy");
|
||||
galaxyPlan.FolderPath.ShouldBe(string.Empty); // null FolderPath coalesced identically on both sides
|
||||
|
||||
// The native-alarm intent in the Galaxy tag's TagConfig.alarm is parsed byte-identically on both
|
||||
// producers (Phase B WS-2). The Modbus tag has no alarm object ⇒ null Alarm on both sides.
|
||||
galaxyPlan.Alarm.ShouldNotBeNull();
|
||||
galaxyPlan.Alarm!.AlarmType.ShouldBe("OffNormalAlarm");
|
||||
galaxyPlan.Alarm.Severity.ShouldBe(700);
|
||||
composed.EquipmentTags.Single(t => t.TagId == "tag-galaxy").Alarm.ShouldBe(galaxyPlan.Alarm);
|
||||
decoded.EquipmentTags.Single(t => t.TagId == "tag-modbus").Alarm.ShouldBeNull();
|
||||
composed.EquipmentTags.Single(t => t.TagId == "tag-modbus").Alarm.ShouldBeNull();
|
||||
|
||||
// Writability flows from Tag.AccessLevel: the Galaxy tag is Read (read-only node), the Modbus
|
||||
// tag is ReadWrite (writable node). Both producers must derive the same Writable flag, and the
|
||||
// SequenceEqual above already proves they agree element-wise.
|
||||
|
||||
Reference in New Issue
Block a user