524 lines
21 KiB
C#
524 lines
21 KiB
C#
using ScadaLink.Commons.Entities.Instances;
|
|
using ScadaLink.Commons.Entities.Sites;
|
|
using ScadaLink.Commons.Entities.Templates;
|
|
using ScadaLink.Commons.Types.Enums;
|
|
using ScadaLink.TemplateEngine.Flattening;
|
|
|
|
namespace ScadaLink.TemplateEngine.Tests.Flattening;
|
|
|
|
public class FlatteningServiceTests
|
|
{
|
|
private readonly FlatteningService _sut = new();
|
|
|
|
private static Instance CreateInstance(string name = "TestInstance", int templateId = 1, int siteId = 1) =>
|
|
new(name) { Id = 1, TemplateId = templateId, SiteId = siteId };
|
|
|
|
private static Template CreateTemplate(int id, string name, int? parentId = null)
|
|
{
|
|
var t = new Template(name) { Id = id, ParentTemplateId = parentId };
|
|
return t;
|
|
}
|
|
|
|
[Fact]
|
|
public void Flatten_EmptyTemplateChain_ReturnsFailure()
|
|
{
|
|
var instance = CreateInstance();
|
|
var result = _sut.Flatten(
|
|
instance,
|
|
[],
|
|
new Dictionary<int, IReadOnlyList<TemplateComposition>>(),
|
|
new Dictionary<int, IReadOnlyList<Template>>(),
|
|
new Dictionary<int, DataConnection>());
|
|
|
|
Assert.True(result.IsFailure);
|
|
Assert.Contains("empty", result.Error, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
[Fact]
|
|
public void Flatten_SingleTemplate_ResolvesAttributes()
|
|
{
|
|
var template = CreateTemplate(1, "Base");
|
|
template.Attributes.Add(new TemplateAttribute("Temperature") { DataType = DataType.Double, Value = "25.0" });
|
|
template.Attributes.Add(new TemplateAttribute("Status") { DataType = DataType.String, Value = "OK" });
|
|
|
|
var instance = CreateInstance();
|
|
var result = _sut.Flatten(
|
|
instance,
|
|
[template],
|
|
new Dictionary<int, IReadOnlyList<TemplateComposition>>(),
|
|
new Dictionary<int, IReadOnlyList<Template>>(),
|
|
new Dictionary<int, DataConnection>());
|
|
|
|
Assert.True(result.IsSuccess);
|
|
Assert.Equal(2, result.Value.Attributes.Count);
|
|
Assert.Equal("Temperature", result.Value.Attributes[1].CanonicalName); // Sorted
|
|
Assert.Equal("25.0", result.Value.Attributes[1].Value);
|
|
Assert.Equal("Status", result.Value.Attributes[0].CanonicalName);
|
|
}
|
|
|
|
[Fact]
|
|
public void Flatten_InheritanceChain_DerivedOverridesBase()
|
|
{
|
|
var baseTemplate = CreateTemplate(2, "Base");
|
|
baseTemplate.Attributes.Add(new TemplateAttribute("Speed") { DataType = DataType.Double, Value = "100.0" });
|
|
baseTemplate.Attributes.Add(new TemplateAttribute("BaseOnly") { DataType = DataType.String, Value = "base" });
|
|
|
|
var childTemplate = CreateTemplate(1, "Child", parentId: 2);
|
|
childTemplate.Attributes.Add(new TemplateAttribute("Speed") { DataType = DataType.Double, Value = "200.0" });
|
|
childTemplate.Attributes.Add(new TemplateAttribute("ChildOnly") { DataType = DataType.Int32, Value = "42" });
|
|
|
|
// Chain: [child, base] — most-derived first
|
|
var instance = CreateInstance();
|
|
var result = _sut.Flatten(
|
|
instance,
|
|
[childTemplate, baseTemplate],
|
|
new Dictionary<int, IReadOnlyList<TemplateComposition>>(),
|
|
new Dictionary<int, IReadOnlyList<Template>>(),
|
|
new Dictionary<int, DataConnection>());
|
|
|
|
Assert.True(result.IsSuccess);
|
|
Assert.Equal(3, result.Value.Attributes.Count);
|
|
|
|
var speed = result.Value.Attributes.First(a => a.CanonicalName == "Speed");
|
|
Assert.Equal("200.0", speed.Value); // Child's value wins
|
|
}
|
|
|
|
[Fact]
|
|
public void Flatten_LockedAttribute_NotOverriddenByDerived()
|
|
{
|
|
var baseTemplate = CreateTemplate(2, "Base");
|
|
baseTemplate.Attributes.Add(new TemplateAttribute("Locked") { DataType = DataType.String, Value = "locked", IsLocked = true });
|
|
|
|
var childTemplate = CreateTemplate(1, "Child", parentId: 2);
|
|
childTemplate.Attributes.Add(new TemplateAttribute("Locked") { DataType = DataType.String, Value = "overridden" });
|
|
|
|
var instance = CreateInstance();
|
|
var result = _sut.Flatten(
|
|
instance,
|
|
[childTemplate, baseTemplate],
|
|
new Dictionary<int, IReadOnlyList<TemplateComposition>>(),
|
|
new Dictionary<int, IReadOnlyList<Template>>(),
|
|
new Dictionary<int, DataConnection>());
|
|
|
|
Assert.True(result.IsSuccess);
|
|
var locked = result.Value.Attributes.First(a => a.CanonicalName == "Locked");
|
|
Assert.Equal("locked", locked.Value); // Base locked value preserved
|
|
}
|
|
|
|
[Fact]
|
|
public void Flatten_InstanceOverride_AppliedToUnlockedAttribute()
|
|
{
|
|
var template = CreateTemplate(1, "Base");
|
|
template.Attributes.Add(new TemplateAttribute("Threshold") { DataType = DataType.Double, Value = "50.0" });
|
|
|
|
var instance = CreateInstance();
|
|
instance.AttributeOverrides.Add(new InstanceAttributeOverride("Threshold") { OverrideValue = "75.0" });
|
|
|
|
var result = _sut.Flatten(
|
|
instance,
|
|
[template],
|
|
new Dictionary<int, IReadOnlyList<TemplateComposition>>(),
|
|
new Dictionary<int, IReadOnlyList<Template>>(),
|
|
new Dictionary<int, DataConnection>());
|
|
|
|
Assert.True(result.IsSuccess);
|
|
var attr = result.Value.Attributes.First(a => a.CanonicalName == "Threshold");
|
|
Assert.Equal("75.0", attr.Value);
|
|
Assert.Equal("Override", attr.Source);
|
|
}
|
|
|
|
[Fact]
|
|
public void Flatten_InstanceOverride_SkippedForLockedAttribute()
|
|
{
|
|
var template = CreateTemplate(1, "Base");
|
|
template.Attributes.Add(new TemplateAttribute("Locked") { DataType = DataType.String, Value = "original", IsLocked = true });
|
|
|
|
var instance = CreateInstance();
|
|
instance.AttributeOverrides.Add(new InstanceAttributeOverride("Locked") { OverrideValue = "changed" });
|
|
|
|
var result = _sut.Flatten(
|
|
instance,
|
|
[template],
|
|
new Dictionary<int, IReadOnlyList<TemplateComposition>>(),
|
|
new Dictionary<int, IReadOnlyList<Template>>(),
|
|
new Dictionary<int, DataConnection>());
|
|
|
|
Assert.True(result.IsSuccess);
|
|
var attr = result.Value.Attributes.First(a => a.CanonicalName == "Locked");
|
|
Assert.Equal("original", attr.Value); // Lock honored
|
|
}
|
|
|
|
[Fact]
|
|
public void Flatten_ComposedModule_PathQualifiedNames()
|
|
{
|
|
var composedTemplate = CreateTemplate(2, "Pump");
|
|
composedTemplate.Attributes.Add(new TemplateAttribute("RPM") { DataType = DataType.Double, Value = "1500" });
|
|
composedTemplate.Scripts.Add(new TemplateScript("Start", "// start") { Id = 10 });
|
|
|
|
var parentTemplate = CreateTemplate(1, "Station");
|
|
parentTemplate.Attributes.Add(new TemplateAttribute("StationName") { DataType = DataType.String, Value = "S1" });
|
|
|
|
var compositions = new Dictionary<int, IReadOnlyList<TemplateComposition>>
|
|
{
|
|
[1] = new List<TemplateComposition>
|
|
{
|
|
new("MainPump") { ComposedTemplateId = 2 }
|
|
}
|
|
};
|
|
|
|
var composedChains = new Dictionary<int, IReadOnlyList<Template>>
|
|
{
|
|
[2] = [composedTemplate]
|
|
};
|
|
|
|
var instance = CreateInstance();
|
|
var result = _sut.Flatten(
|
|
instance,
|
|
[parentTemplate],
|
|
compositions,
|
|
composedChains,
|
|
new Dictionary<int, DataConnection>());
|
|
|
|
Assert.True(result.IsSuccess);
|
|
Assert.Contains(result.Value.Attributes, a => a.CanonicalName == "MainPump.RPM");
|
|
Assert.Contains(result.Value.Attributes, a => a.CanonicalName == "StationName");
|
|
Assert.Contains(result.Value.Scripts, s => s.CanonicalName == "MainPump.Start");
|
|
}
|
|
|
|
[Fact]
|
|
public void Flatten_ConnectionBindings_ResolvedCorrectly()
|
|
{
|
|
var template = CreateTemplate(1, "Base");
|
|
template.Attributes.Add(new TemplateAttribute("Temp")
|
|
{
|
|
DataType = DataType.Double,
|
|
DataSourceReference = "ns=2;s=Temperature"
|
|
});
|
|
|
|
var instance = CreateInstance();
|
|
instance.ConnectionBindings.Add(new InstanceConnectionBinding("Temp") { DataConnectionId = 100 });
|
|
|
|
var connections = new Dictionary<int, DataConnection>
|
|
{
|
|
[100] = new("OPC-Server1", "OpcUa", 1) { Id = 100, PrimaryConfiguration = "opc.tcp://localhost:4840" }
|
|
};
|
|
|
|
var result = _sut.Flatten(
|
|
instance,
|
|
[template],
|
|
new Dictionary<int, IReadOnlyList<TemplateComposition>>(),
|
|
new Dictionary<int, IReadOnlyList<Template>>(),
|
|
connections);
|
|
|
|
Assert.True(result.IsSuccess);
|
|
var attr = result.Value.Attributes.First(a => a.CanonicalName == "Temp");
|
|
Assert.Equal(100, attr.BoundDataConnectionId);
|
|
Assert.Equal("OPC-Server1", attr.BoundDataConnectionName);
|
|
Assert.Equal("OpcUa", attr.BoundDataConnectionProtocol);
|
|
}
|
|
|
|
[Fact]
|
|
public void Flatten_Alarms_ResolvedFromChain()
|
|
{
|
|
var template = CreateTemplate(1, "Base");
|
|
template.Alarms.Add(new TemplateAlarm("HighTemp")
|
|
{
|
|
TriggerType = AlarmTriggerType.RangeViolation,
|
|
TriggerConfiguration = "{\"attributeName\":\"Temp\",\"high\":100}",
|
|
PriorityLevel = 1
|
|
});
|
|
|
|
var instance = CreateInstance();
|
|
var result = _sut.Flatten(
|
|
instance,
|
|
[template],
|
|
new Dictionary<int, IReadOnlyList<TemplateComposition>>(),
|
|
new Dictionary<int, IReadOnlyList<Template>>(),
|
|
new Dictionary<int, DataConnection>());
|
|
|
|
Assert.True(result.IsSuccess);
|
|
Assert.Single(result.Value.Alarms);
|
|
Assert.Equal("HighTemp", result.Value.Alarms[0].CanonicalName);
|
|
Assert.Equal("RangeViolation", result.Value.Alarms[0].TriggerType);
|
|
}
|
|
|
|
[Fact]
|
|
public void Flatten_InstanceMetadata_SetCorrectly()
|
|
{
|
|
var template = CreateTemplate(1, "Base");
|
|
var instance = CreateInstance("MyInstance", templateId: 1, siteId: 5);
|
|
instance.AreaId = 3;
|
|
|
|
var result = _sut.Flatten(
|
|
instance,
|
|
[template],
|
|
new Dictionary<int, IReadOnlyList<TemplateComposition>>(),
|
|
new Dictionary<int, IReadOnlyList<Template>>(),
|
|
new Dictionary<int, DataConnection>());
|
|
|
|
Assert.True(result.IsSuccess);
|
|
Assert.Equal("MyInstance", result.Value.InstanceUniqueName);
|
|
Assert.Equal(1, result.Value.TemplateId);
|
|
Assert.Equal(5, result.Value.SiteId);
|
|
Assert.Equal(3, result.Value.AreaId);
|
|
}
|
|
|
|
[Fact]
|
|
public void Flatten_InheritedAttributeOnDerived_BaseValueWins()
|
|
{
|
|
var baseTemplate = CreateTemplate(2, "Sensor");
|
|
baseTemplate.Attributes.Add(new TemplateAttribute("SetPoint") { DataType = DataType.Double, Value = "100.0" });
|
|
|
|
var derived = CreateTemplate(1, "Pump.TempSensor", parentId: 2);
|
|
derived.Attributes.Add(new TemplateAttribute("SetPoint")
|
|
{
|
|
DataType = DataType.Double,
|
|
Value = "STALE",
|
|
IsInherited = true
|
|
});
|
|
|
|
var instance = CreateInstance();
|
|
var result = _sut.Flatten(
|
|
instance,
|
|
[derived, baseTemplate],
|
|
new Dictionary<int, IReadOnlyList<TemplateComposition>>(),
|
|
new Dictionary<int, IReadOnlyList<Template>>(),
|
|
new Dictionary<int, DataConnection>());
|
|
|
|
Assert.True(result.IsSuccess);
|
|
var setPoint = result.Value.Attributes.First(a => a.CanonicalName == "SetPoint");
|
|
Assert.Equal("100.0", setPoint.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void Flatten_OverriddenAttributeOnDerived_DerivedValueWins()
|
|
{
|
|
var baseTemplate = CreateTemplate(2, "Sensor");
|
|
baseTemplate.Attributes.Add(new TemplateAttribute("SetPoint") { DataType = DataType.Double, Value = "100.0" });
|
|
|
|
var derived = CreateTemplate(1, "Pump.TempSensor", parentId: 2);
|
|
derived.Attributes.Add(new TemplateAttribute("SetPoint")
|
|
{
|
|
DataType = DataType.Double,
|
|
Value = "150.0",
|
|
IsInherited = false
|
|
});
|
|
|
|
var instance = CreateInstance();
|
|
var result = _sut.Flatten(
|
|
instance,
|
|
[derived, baseTemplate],
|
|
new Dictionary<int, IReadOnlyList<TemplateComposition>>(),
|
|
new Dictionary<int, IReadOnlyList<Template>>(),
|
|
new Dictionary<int, DataConnection>());
|
|
|
|
Assert.True(result.IsSuccess);
|
|
var setPoint = result.Value.Attributes.First(a => a.CanonicalName == "SetPoint");
|
|
Assert.Equal("150.0", setPoint.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void Flatten_LockedInDerivedOverride_Fails()
|
|
{
|
|
var baseTemplate = CreateTemplate(2, "Sensor");
|
|
baseTemplate.Attributes.Add(new TemplateAttribute("SetPoint")
|
|
{
|
|
DataType = DataType.Double,
|
|
Value = "100.0",
|
|
LockedInDerived = true
|
|
});
|
|
|
|
var derived = CreateTemplate(1, "Pump.TempSensor", parentId: 2);
|
|
derived.Attributes.Add(new TemplateAttribute("SetPoint")
|
|
{
|
|
DataType = DataType.Double,
|
|
Value = "150.0",
|
|
IsInherited = false
|
|
});
|
|
|
|
var instance = CreateInstance();
|
|
var result = _sut.Flatten(
|
|
instance,
|
|
[derived, baseTemplate],
|
|
new Dictionary<int, IReadOnlyList<TemplateComposition>>(),
|
|
new Dictionary<int, IReadOnlyList<Template>>(),
|
|
new Dictionary<int, DataConnection>());
|
|
|
|
Assert.True(result.IsFailure);
|
|
Assert.Contains("LockedInDerived", result.Error);
|
|
Assert.Contains("SetPoint", result.Error);
|
|
}
|
|
|
|
[Fact]
|
|
public void Flatten_InheritedScriptOnDerived_BaseCodeWins()
|
|
{
|
|
var baseTemplate = CreateTemplate(2, "Sensor");
|
|
baseTemplate.Scripts.Add(new TemplateScript("Sample", "return base;"));
|
|
|
|
var derived = CreateTemplate(1, "Pump.TempSensor", parentId: 2);
|
|
derived.Scripts.Add(new TemplateScript("Sample", "stale code") { IsInherited = true });
|
|
|
|
var instance = CreateInstance();
|
|
var result = _sut.Flatten(
|
|
instance,
|
|
[derived, baseTemplate],
|
|
new Dictionary<int, IReadOnlyList<TemplateComposition>>(),
|
|
new Dictionary<int, IReadOnlyList<Template>>(),
|
|
new Dictionary<int, DataConnection>());
|
|
|
|
Assert.True(result.IsSuccess);
|
|
var script = result.Value.Scripts.First(s => s.CanonicalName == "Sample");
|
|
Assert.Equal("return base;", script.Code);
|
|
}
|
|
|
|
// ── TemplateEngine-001: deep composition nesting ───────────────────────
|
|
|
|
[Fact]
|
|
public void Flatten_ThreeLevelComposition_AttributesAlarmsScriptsAllResolved()
|
|
{
|
|
// Station composes Pump (level 1); Pump composes Motor (level 2);
|
|
// Motor composes Bearing (level 3).
|
|
var bearing = CreateTemplate(4, "Bearing");
|
|
bearing.Attributes.Add(new TemplateAttribute("Vibration") { DataType = DataType.Double, Value = "0.1" });
|
|
bearing.Alarms.Add(new TemplateAlarm("HighVibration")
|
|
{
|
|
TriggerType = AlarmTriggerType.RangeViolation,
|
|
TriggerConfiguration = "{\"attributeName\":\"Vibration\",\"high\":5}",
|
|
PriorityLevel = 1
|
|
});
|
|
bearing.Scripts.Add(new TemplateScript("MonitorBearing", "// monitor") { Id = 40 });
|
|
|
|
var motor = CreateTemplate(3, "Motor");
|
|
motor.Attributes.Add(new TemplateAttribute("Current") { DataType = DataType.Double, Value = "10" });
|
|
|
|
var pump = CreateTemplate(2, "Pump");
|
|
pump.Attributes.Add(new TemplateAttribute("RPM") { DataType = DataType.Double, Value = "1500" });
|
|
|
|
var station = CreateTemplate(1, "Station");
|
|
|
|
var compositions = new Dictionary<int, IReadOnlyList<TemplateComposition>>
|
|
{
|
|
[1] = new List<TemplateComposition> { new("MainPump") { ComposedTemplateId = 2 } },
|
|
[2] = new List<TemplateComposition> { new("DriveMotor") { ComposedTemplateId = 3 } },
|
|
[3] = new List<TemplateComposition> { new("FrontBearing") { ComposedTemplateId = 4 } },
|
|
};
|
|
var composedChains = new Dictionary<int, IReadOnlyList<Template>>
|
|
{
|
|
[2] = [pump],
|
|
[3] = [motor],
|
|
[4] = [bearing],
|
|
};
|
|
|
|
var instance = CreateInstance();
|
|
var result = _sut.Flatten(instance, [station], compositions, composedChains,
|
|
new Dictionary<int, DataConnection>());
|
|
|
|
Assert.True(result.IsSuccess);
|
|
// Level 3 attribute must be present with the full path-qualified name.
|
|
Assert.Contains(result.Value.Attributes,
|
|
a => a.CanonicalName == "MainPump.DriveMotor.FrontBearing.Vibration");
|
|
// Level 3 alarm must be present (was dropped entirely before).
|
|
Assert.Contains(result.Value.Alarms,
|
|
a => a.CanonicalName == "MainPump.DriveMotor.FrontBearing.HighVibration");
|
|
// Level 3 script must be present (was dropped entirely before).
|
|
Assert.Contains(result.Value.Scripts,
|
|
s => s.CanonicalName == "MainPump.DriveMotor.FrontBearing.MonitorBearing");
|
|
}
|
|
|
|
[Fact]
|
|
public void Flatten_NestedComposedAlarm_TriggerAttributePrefixed()
|
|
{
|
|
var bearing = CreateTemplate(4, "Bearing");
|
|
bearing.Attributes.Add(new TemplateAttribute("Vibration") { DataType = DataType.Double, Value = "0.1" });
|
|
bearing.Alarms.Add(new TemplateAlarm("HighVibration")
|
|
{
|
|
TriggerType = AlarmTriggerType.RangeViolation,
|
|
TriggerConfiguration = "{\"attributeName\":\"Vibration\",\"high\":5}",
|
|
PriorityLevel = 1
|
|
});
|
|
|
|
var motor = CreateTemplate(3, "Motor");
|
|
var pump = CreateTemplate(2, "Pump");
|
|
var station = CreateTemplate(1, "Station");
|
|
|
|
var compositions = new Dictionary<int, IReadOnlyList<TemplateComposition>>
|
|
{
|
|
[1] = new List<TemplateComposition> { new("MainPump") { ComposedTemplateId = 2 } },
|
|
[2] = new List<TemplateComposition> { new("DriveMotor") { ComposedTemplateId = 3 } },
|
|
[3] = new List<TemplateComposition> { new("FrontBearing") { ComposedTemplateId = 4 } },
|
|
};
|
|
var composedChains = new Dictionary<int, IReadOnlyList<Template>>
|
|
{
|
|
[2] = [pump], [3] = [motor], [4] = [bearing],
|
|
};
|
|
|
|
var instance = CreateInstance();
|
|
var result = _sut.Flatten(instance, [station], compositions, composedChains,
|
|
new Dictionary<int, DataConnection>());
|
|
|
|
Assert.True(result.IsSuccess);
|
|
var alarm = result.Value.Alarms.First(a => a.CanonicalName.EndsWith("HighVibration"));
|
|
// The trigger's attribute reference must carry the full nested prefix.
|
|
Assert.Contains("MainPump.DriveMotor.FrontBearing.Vibration", alarm.TriggerConfiguration);
|
|
}
|
|
|
|
// ── TemplateEngine-004: alarm on-trigger script resolution ─────────────
|
|
|
|
[Fact]
|
|
public void Flatten_AlarmOnTriggerScript_ResolvedToCanonicalName()
|
|
{
|
|
var template = CreateTemplate(1, "Base");
|
|
template.Scripts.Add(new TemplateScript("HandleAlarm", "// handle") { Id = 50 });
|
|
template.Alarms.Add(new TemplateAlarm("HighTemp")
|
|
{
|
|
TriggerType = AlarmTriggerType.RangeViolation,
|
|
TriggerConfiguration = "{\"attributeName\":\"Temp\",\"high\":100}",
|
|
PriorityLevel = 1,
|
|
OnTriggerScriptId = 50
|
|
});
|
|
|
|
var instance = CreateInstance();
|
|
var result = _sut.Flatten(instance, [template],
|
|
new Dictionary<int, IReadOnlyList<TemplateComposition>>(),
|
|
new Dictionary<int, IReadOnlyList<Template>>(),
|
|
new Dictionary<int, DataConnection>());
|
|
|
|
Assert.True(result.IsSuccess);
|
|
var alarm = result.Value.Alarms.First(a => a.CanonicalName == "HighTemp");
|
|
Assert.Equal("HandleAlarm", alarm.OnTriggerScriptCanonicalName);
|
|
}
|
|
|
|
[Fact]
|
|
public void Flatten_ComposedAlarmOnTriggerScript_ResolvedWithPrefix()
|
|
{
|
|
var composedTemplate = CreateTemplate(2, "Pump");
|
|
composedTemplate.Scripts.Add(new TemplateScript("PumpAlarmHandler", "// h") { Id = 60 });
|
|
composedTemplate.Alarms.Add(new TemplateAlarm("PumpFault")
|
|
{
|
|
TriggerType = AlarmTriggerType.ValueMatch,
|
|
TriggerConfiguration = "{\"attributeName\":\"State\",\"value\":\"FAULT\"}",
|
|
PriorityLevel = 5,
|
|
OnTriggerScriptId = 60
|
|
});
|
|
|
|
var station = CreateTemplate(1, "Station");
|
|
|
|
var compositions = new Dictionary<int, IReadOnlyList<TemplateComposition>>
|
|
{
|
|
[1] = new List<TemplateComposition> { new("MainPump") { ComposedTemplateId = 2 } }
|
|
};
|
|
var composedChains = new Dictionary<int, IReadOnlyList<Template>>
|
|
{
|
|
[2] = [composedTemplate]
|
|
};
|
|
|
|
var instance = CreateInstance();
|
|
var result = _sut.Flatten(instance, [station], compositions, composedChains,
|
|
new Dictionary<int, DataConnection>());
|
|
|
|
Assert.True(result.IsSuccess);
|
|
var alarm = result.Value.Alarms.First(a => a.CanonicalName == "MainPump.PumpFault");
|
|
Assert.Equal("MainPump.PumpAlarmHandler", alarm.OnTriggerScriptCanonicalName);
|
|
}
|
|
}
|