c9590c03d0
Default HistorizeToAveva/Retain/Enabled to the entity defaults (true) when a
field is absent/null/non-boolean so a partial blob decodes identically to the
composer's view of a default-constructed ScriptedAlarm (byte-parity), and only
call GetBoolean for a genuine true/false token. Add direct ExtractAlarmDependencyRefs
unit tests (overlap dedup + reserved {{equip}} exclusion).
215 lines
6.6 KiB
C#
215 lines
6.6 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Commons.Types;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Commons.Tests;
|
|
|
|
public class EquipmentScriptPathsTests
|
|
{
|
|
// ---- ExtractAlarmDependencyRefs (scripted-alarm dep graph; byte-parity seam) ----
|
|
|
|
[Fact]
|
|
public void ExtractAlarmDependencyRefs_predicate_reads_first_then_template_tokens_deduped()
|
|
{
|
|
// A template token identical to a predicate read appears once (predicate-first); a distinct
|
|
// template token is appended after. This dedup/order is the parity contract both the composer
|
|
// and the artifact decode rely on.
|
|
EquipmentScriptPaths
|
|
.ExtractAlarmDependencyRefs(
|
|
predicateSource: "return ctx.GetTag(\"Mach.Temp\").Value > 90;",
|
|
messageTemplate: "Temp {Mach.Temp} over {Mach.Limit}")
|
|
.ShouldBe(["Mach.Temp", "Mach.Limit"]);
|
|
}
|
|
|
|
[Fact]
|
|
public void ExtractAlarmDependencyRefs_excludes_reserved_equip_double_brace_token()
|
|
{
|
|
// The reserved {{equip}} double-brace form must NOT be picked up as a single-brace {TagPath}
|
|
// token; only the genuine {Line.Temp} token is extracted.
|
|
EquipmentScriptPaths
|
|
.ExtractAlarmDependencyRefs(
|
|
predicateSource: null,
|
|
messageTemplate: "{{equip}} too hot: {Line.Temp}")
|
|
.ShouldBe(["Line.Temp"]);
|
|
}
|
|
|
|
// ---- DeriveEquipmentBase ----
|
|
|
|
[Fact]
|
|
public void DeriveEquipmentBase_returns_shared_prefix()
|
|
{
|
|
EquipmentScriptPaths
|
|
.DeriveEquipmentBase(["TestMachine_001.A", "TestMachine_001.B"])
|
|
.ShouldBe("TestMachine_001");
|
|
}
|
|
|
|
[Fact]
|
|
public void DeriveEquipmentBase_returns_null_when_prefixes_diverge()
|
|
{
|
|
EquipmentScriptPaths
|
|
.DeriveEquipmentBase(["TestMachine_001.A", "DelmiaReceiver_001.B"])
|
|
.ShouldBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void DeriveEquipmentBase_returns_null_for_empty_sequence()
|
|
{
|
|
EquipmentScriptPaths
|
|
.DeriveEquipmentBase([])
|
|
.ShouldBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void DeriveEquipmentBase_returns_whole_value_when_no_dot()
|
|
{
|
|
EquipmentScriptPaths
|
|
.DeriveEquipmentBase(["NoDot"])
|
|
.ShouldBe("NoDot");
|
|
}
|
|
|
|
[Fact]
|
|
public void DeriveEquipmentBase_skips_null_empty_and_whitespace_entries()
|
|
{
|
|
EquipmentScriptPaths
|
|
.DeriveEquipmentBase([null, "", " ", "TestMachine_001.A"])
|
|
.ShouldBe("TestMachine_001");
|
|
}
|
|
|
|
// ---- SubstituteEquipmentToken ----
|
|
|
|
[Fact]
|
|
public void SubstituteEquipmentToken_substitutes_inside_GetTag()
|
|
{
|
|
var result = EquipmentScriptPaths.SubstituteEquipmentToken(
|
|
"ctx.GetTag(\"{{equip}}.Source\")", "TestMachine_001");
|
|
|
|
result.ShouldContain("ctx.GetTag(\"TestMachine_001.Source\")");
|
|
}
|
|
|
|
[Fact]
|
|
public void SubstituteEquipmentToken_substitutes_inside_SetVirtualTag()
|
|
{
|
|
var result = EquipmentScriptPaths.SubstituteEquipmentToken(
|
|
"ctx.SetVirtualTag(\"{{equip}}.Out\", x)", "TestMachine_001");
|
|
|
|
result.ShouldContain("ctx.SetVirtualTag(\"TestMachine_001.Out\"");
|
|
}
|
|
|
|
[Fact]
|
|
public void SubstituteEquipmentToken_leaves_comment_unchanged()
|
|
{
|
|
var source = "// uses {{equip}} here";
|
|
|
|
EquipmentScriptPaths.SubstituteEquipmentToken(source, "TestMachine_001")
|
|
.ShouldBe(source);
|
|
}
|
|
|
|
[Fact]
|
|
public void SubstituteEquipmentToken_leaves_logger_string_unchanged()
|
|
{
|
|
var source = "ctx.Logger.Information(\"{{equip}}\")";
|
|
|
|
EquipmentScriptPaths.SubstituteEquipmentToken(source, "TestMachine_001")
|
|
.ShouldBe(source);
|
|
}
|
|
|
|
[Fact]
|
|
public void SubstituteEquipmentToken_substitutes_all_occurrences()
|
|
{
|
|
var source = "ctx.GetTag(\"{{equip}}.A\"); ctx.GetTag(\"{{equip}}.B\");";
|
|
|
|
var result = EquipmentScriptPaths.SubstituteEquipmentToken(source, "TestMachine_001");
|
|
|
|
result.ShouldContain("ctx.GetTag(\"TestMachine_001.A\")");
|
|
result.ShouldContain("ctx.GetTag(\"TestMachine_001.B\")");
|
|
result.ShouldNotContain(EquipmentScriptPaths.EquipToken);
|
|
}
|
|
|
|
[Fact]
|
|
public void SubstituteEquipmentToken_is_identity_when_equipBase_is_null()
|
|
{
|
|
var source = "ctx.GetTag(\"{{equip}}.Source\")";
|
|
|
|
EquipmentScriptPaths.SubstituteEquipmentToken(source, null)
|
|
.ShouldBe(source);
|
|
}
|
|
|
|
[Fact]
|
|
public void SubstituteEquipmentToken_is_identity_when_equipBase_is_empty()
|
|
{
|
|
var source = "ctx.GetTag(\"{{equip}}.Source\")";
|
|
|
|
EquipmentScriptPaths.SubstituteEquipmentToken(source, "")
|
|
.ShouldBe(source);
|
|
}
|
|
|
|
[Fact]
|
|
public void SubstituteEquipmentToken_is_identity_when_token_absent()
|
|
{
|
|
var source = "ctx.GetTag(\"TestMachine_001.Source\")";
|
|
|
|
EquipmentScriptPaths.SubstituteEquipmentToken(source, "TestMachine_001")
|
|
.ShouldBe(source);
|
|
}
|
|
|
|
[Fact]
|
|
public void SubstituteEquipmentToken_does_not_touch_raw_string_literal()
|
|
{
|
|
// Documents the regex limitation: only "double-quote" literals are handled,
|
|
// matching the existing seam extractors. A raw-string literal is left as-is.
|
|
var source = "ctx.GetTag(\"\"\"{{equip}}.X\"\"\")";
|
|
|
|
EquipmentScriptPaths.SubstituteEquipmentToken(source, "TestMachine_001")
|
|
.ShouldBe(source);
|
|
}
|
|
|
|
// ---- ExtractDependencyRefs ----
|
|
|
|
[Fact]
|
|
public void ExtractDependencyRefs_returns_distinct_refs_in_first_seen_order()
|
|
{
|
|
var source = "ctx.GetTag(\"A\"); ctx.GetTag(\"B\"); ctx.GetTag(\"A\");";
|
|
|
|
EquipmentScriptPaths.ExtractDependencyRefs(source)
|
|
.ShouldBe(["A", "B"]);
|
|
}
|
|
|
|
[Fact]
|
|
public void ExtractDependencyRefs_excludes_SetVirtualTag_writes()
|
|
{
|
|
var source = "ctx.GetTag(\"X\"); ctx.SetVirtualTag(\"Y\", 1);";
|
|
|
|
EquipmentScriptPaths.ExtractDependencyRefs(source)
|
|
.ShouldBe(["X"]);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(null)]
|
|
[InlineData("")]
|
|
[InlineData(" ")]
|
|
public void ExtractDependencyRefs_returns_empty_for_null_or_whitespace(string? source)
|
|
{
|
|
EquipmentScriptPaths.ExtractDependencyRefs(source).ShouldBeEmpty();
|
|
}
|
|
|
|
// ---- ContainsEquipToken ----
|
|
|
|
[Fact]
|
|
public void ContainsEquipToken_true_when_token_present()
|
|
{
|
|
EquipmentScriptPaths.ContainsEquipToken("ctx.GetTag(\"{{equip}}.X\")").ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void ContainsEquipToken_false_when_token_absent()
|
|
{
|
|
EquipmentScriptPaths.ContainsEquipToken("ctx.GetTag(\"X\")").ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void ContainsEquipToken_false_for_null()
|
|
{
|
|
EquipmentScriptPaths.ContainsEquipToken(null).ShouldBeFalse();
|
|
}
|
|
}
|