Files
lmxopcua/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/NativeAlarmEditorModelTests.cs
T
Joseph Doherty 6951faa08f feat(adminui): typed native-alarm section in the raw tag modal (#642)
NativeAlarmModel: IsAlarm=false now removes the alarm key; advisory Warnings, driver gating, order-safe Edit.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 16:52:55 -04:00

107 lines
4.2 KiB
C#

using System.Text.Json.Nodes;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;
/// <summary>Gitea #642 — the shared NativeAlarmEditor's pure-model behaviour: remove-on-off, composition
/// with the per-driver editors' models, validation parity with the deploy gate, driver-type gating.</summary>
public sealed class NativeAlarmEditorModelTests
{
[Fact]
public void ToJson_with_IsAlarm_false_removes_the_alarm_key_and_keeps_the_rest()
{
var m = NativeAlarmModel.FromJson("""{"FullName":"X.Y","alarm":{"alarmType":"LimitAlarm","severity":300,"extra":1}}""");
m.IsAlarm = false;
var o = JsonNode.Parse(m.ToJson())!.AsObject();
o.ContainsKey("alarm").ShouldBeFalse();
o["FullName"]!.GetValue<string>().ShouldBe("X.Y");
}
[Fact]
public void Toggle_off_then_on_again_starts_from_defaults()
{
var off = NativeAlarmModel.Edit("""{"alarm":{"severity":300}}""", m => m.IsAlarm = false);
var on = NativeAlarmModel.Edit(off, m => m.IsAlarm = true);
var back = NativeAlarmModel.FromJson(on);
back.IsAlarm.ShouldBeTrue();
back.Severity.ShouldBe(500);
back.AlarmType.ShouldBe("AlarmCondition");
}
[Fact]
public void Edit_preserves_unknown_keys_at_both_levels()
{
var json = NativeAlarmModel.Edit("""{"FullName":"A","zz":1,"alarm":{"alarmType":"OffNormalAlarm","custom":"k"}}""",
m => m.Severity = 900);
var o = JsonNode.Parse(json)!.AsObject();
o["zz"]!.GetValue<int>().ShouldBe(1);
o["alarm"]!["custom"]!.GetValue<string>().ShouldBe("k");
o["alarm"]!["severity"]!.GetValue<int>().ShouldBe(900);
}
[Fact]
public void Alarm_and_mtconnect_driver_editor_compose_in_either_order()
{
const string start = """{"fullName":"Xact"}""";
// alarm first, then driver editor (driver model re-parses the alarm-bearing JSON)
var a1 = NativeAlarmModel.Edit(start, m => { m.IsAlarm = true; m.Severity = 800; });
var d1 = MTConnectTagConfigModel.FromJson(a1);
d1.FullName = "Yact";
var r1 = d1.ToJson();
// driver editor first, then alarm
var d2 = MTConnectTagConfigModel.FromJson(start);
d2.FullName = "Yact";
var r2 = NativeAlarmModel.Edit(d2.ToJson(), m => { m.IsAlarm = true; m.Severity = 800; });
foreach (var r in new[] { r1, r2 })
{
var o = JsonNode.Parse(r)!.AsObject();
o["fullName"]!.GetValue<string>().ShouldBe("Yact");
o["alarm"]!["severity"]!.GetValue<int>().ShouldBe(800);
}
}
[Theory]
[InlineData("Modbus")]
[InlineData("S7")]
[InlineData("AbCip")]
[InlineData("AbLegacy")]
[InlineData("TwinCAT")]
[InlineData("FOCAS")]
[InlineData("OpcUaClient")]
[InlineData("Sql")]
[InlineData("Mqtt")]
[InlineData("MTConnect")]
public void Every_typed_driver_model_preserves_an_alarm_object(string driverType)
{
const string json = """{"alarm":{"alarmType":"LimitAlarm","severity":123}}""";
var round = TagConfigRoundTrip.Through(driverType, json);
NativeAlarmModel.FromJson(round).Severity.ShouldBe(123);
}
[Fact]
public void Validate_is_not_stricter_than_the_deploy_gate()
{
// The gate/parser accepts any severity (clamped 1..1000 at publish) and any alarmType string.
var m = NativeAlarmModel.FromJson("""{"alarm":{"alarmType":"Whatever","severity":5000}}""");
m.Validate().ShouldBeNull();
m.Warnings().ShouldNotBeEmpty(); // advisory only
}
[Fact]
public void SupportsNativeAlarms_covers_exactly_the_IAlarmSource_drivers()
{
foreach (var t in new[] { DriverTypeNames.MTConnect, DriverTypeNames.AbCip, DriverTypeNames.FOCAS,
DriverTypeNames.OpcUaClient, DriverTypeNames.Galaxy })
NativeAlarmModel.SupportsNativeAlarms(t).ShouldBeTrue(t);
foreach (var t in new[] { DriverTypeNames.Modbus, DriverTypeNames.S7, DriverTypeNames.Mqtt, null })
NativeAlarmModel.SupportsNativeAlarms(t).ShouldBeFalse(t);
}
}