feat(opcua): pure Phase7Composer + purity tests (side-effects tracked as F14)
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
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<Equipment>(),
|
||||
driverInstances: Array.Empty<DriverInstance>(),
|
||||
scriptedAlarms: Array.Empty<ScriptedAlarm>());
|
||||
|
||||
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<T> 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<DriverInstance>(), Array.Empty<ScriptedAlarm>());
|
||||
|
||||
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",
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user