using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Configuration.Entities; namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests; public sealed class Phase7ComposerPurityTests { [Fact] public void Empty_inputs_produce_empty_result() { var result = Phase7Composer.Compose( equipment: Array.Empty(), driverInstances: Array.Empty(), scriptedAlarms: Array.Empty()); result.EquipmentNodes.ShouldBeEmpty(); result.DriverInstancePlans.ShouldBeEmpty(); result.ScriptedAlarmPlans.ShouldBeEmpty(); } [Fact] public void Same_inputs_in_different_order_produce_structurally_equal_results() { var e1 = NewEquipment("eq-1"); var e2 = NewEquipment("eq-2"); var d1 = NewDriver("drv-1"); var d2 = NewDriver("drv-2"); var a1 = NewAlarm("a-1", "eq-1"); var a2 = NewAlarm("a-2", "eq-2"); var r1 = Phase7Composer.Compose( equipment: new[] { e1, e2 }, driverInstances: new[] { d1, d2 }, scriptedAlarms: new[] { a1, a2 }); var r2 = Phase7Composer.Compose( equipment: new[] { e2, e1 }, driverInstances: new[] { d2, d1 }, scriptedAlarms: new[] { a2, a1 }); r1.EquipmentNodes.ShouldBe(r2.EquipmentNodes); r1.DriverInstancePlans.ShouldBe(r2.DriverInstancePlans); r1.ScriptedAlarmPlans.ShouldBe(r2.ScriptedAlarmPlans); } [Fact] public void Compose_is_pure_repeated_call_returns_element_identical_output() { var equipment = new[] { NewEquipment("eq-a"), NewEquipment("eq-b") }; var drivers = new[] { NewDriver("drv-x") }; var alarms = new[] { NewAlarm("alarm-1", "eq-a") }; var r1 = Phase7Composer.Compose(equipment, drivers, alarms); var r2 = Phase7Composer.Compose(equipment, drivers, alarms); // Record equality won't help here — IReadOnlyList uses reference equality. Compare // element-wise to verify the pure-function contract. r1.EquipmentNodes.ShouldBe(r2.EquipmentNodes); r1.DriverInstancePlans.ShouldBe(r2.DriverInstancePlans); r1.ScriptedAlarmPlans.ShouldBe(r2.ScriptedAlarmPlans); } [Fact] public void Output_is_sorted_by_natural_key() { var equipment = new[] { NewEquipment("z"), NewEquipment("a"), NewEquipment("m") }; var result = Phase7Composer.Compose(equipment, Array.Empty(), Array.Empty()); result.EquipmentNodes.Select(e => e.EquipmentId) .ShouldBe(new[] { "a", "m", "z" }); } private static Equipment NewEquipment(string id) => new() { EquipmentId = id, DriverInstanceId = "drv-1", UnsLineId = "line-1", Name = id, MachineCode = id.ToUpperInvariant(), }; private static DriverInstance NewDriver(string id) => new() { DriverInstanceId = id, ClusterId = "cluster-1", NamespaceId = "ns-1", Name = id, DriverType = "Stub", DriverConfig = "{\"k\":\"v\"}", }; private static ScriptedAlarm NewAlarm(string id, string equipmentId) => new() { ScriptedAlarmId = id, EquipmentId = equipmentId, Name = id, AlarmType = "AlarmCondition", MessageTemplate = "{TagPath} alarm", PredicateScriptId = "script-1", }; }