513fa1aa16
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
219 lines
9.8 KiB
C#
219 lines
9.8 KiB
C#
using ZB.MOM.WW.ScadaBridge.CentralUI.Services;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Services;
|
|
|
|
public class BindingGroupBuilderTests
|
|
{
|
|
private static BindingRow Row(string canonical, string? dflt, int? conn, string? ovr) =>
|
|
new(canonical, dflt, conn, ovr);
|
|
|
|
/// <summary>Verifies that groups list direct attributes first, then modules in name order.</summary>
|
|
[Fact]
|
|
public void Groups_DirectAttributesFirst_ThenModules_InNameOrder()
|
|
{
|
|
var rows = new[]
|
|
{
|
|
Row("RightMESReceiver.MoveInFlag", "MoveInFlag", 1, "MESReceiver_022.MoveInFlag"),
|
|
Row("ChlorineFlow", "ChlorineFlow", 1, "Z28061.ChlorineFlow"),
|
|
Row("LeftMESReceiver.MoveInFlag", "MoveInFlag", 1, "MESReceiver_023.MoveInFlag"),
|
|
};
|
|
var groups = BindingGroupBuilder.Build(rows);
|
|
Assert.Equal(new[] { BindingGroupBuilder.InstanceGroupKey, "LeftMESReceiver", "RightMESReceiver" },
|
|
groups.Select(g => g.Key));
|
|
}
|
|
|
|
/// <summary>Verifies that a nested module folds into its first path segment and keeps the nested tail.</summary>
|
|
[Fact]
|
|
public void NestedModule_FoldsIntoFirstSegment_AndKeepsNestedTail()
|
|
{
|
|
var rows = new[]
|
|
{
|
|
Row("LeftReactorSide.AIR_SVC", "AIR_SVC", 1, "Left_002.AIR_SVC"),
|
|
Row("LeftReactorSide.LeakTest.DeltaVac", "DeltaVac", 1, "Left_002.LeakTest.DeltaVac"),
|
|
};
|
|
var g = Assert.Single(BindingGroupBuilder.Build(rows));
|
|
Assert.Equal("LeftReactorSide", g.Key);
|
|
Assert.Equal(new[] { "AIR_SVC", "LeakTest.DeltaVac" }, g.Members.Select(m => m.MemberTail));
|
|
Assert.Equal(new[] { "LeakTest" }, g.NestedModules);
|
|
Assert.Equal("Left_002", g.InferredObject);
|
|
}
|
|
|
|
/// <summary>Verifies that the inferred object is the nested object path.</summary>
|
|
[Fact]
|
|
public void InferredObject_NestedObjectPath()
|
|
{
|
|
var rows = new[]
|
|
{
|
|
Row("LeftDelmiaReceiver.ReadyFlag", "ReadyFlag", 1, "Left_002.DelmiaReceiver.ReadyFlag"),
|
|
Row("LeftDelmiaReceiver.PartNumber", "PartNumber", 1, "Left_002.DelmiaReceiver.PartNumber"),
|
|
};
|
|
Assert.Equal("Left_002.DelmiaReceiver", Assert.Single(BindingGroupBuilder.Build(rows)).InferredObject);
|
|
}
|
|
|
|
/// <summary>Verifies that the inferred object is null and the state is mixed when members disagree.</summary>
|
|
[Fact]
|
|
public void InferredObject_NullWhenMixed_AndStateMixed()
|
|
{
|
|
var rows = new[]
|
|
{
|
|
Row("M.A", "A", 1, "Obj1.A"),
|
|
Row("M.B", "B", 1, "Obj2.B"),
|
|
};
|
|
var g = Assert.Single(BindingGroupBuilder.Build(rows));
|
|
Assert.Null(g.InferredObject);
|
|
Assert.True(g.IsObjectMixed);
|
|
}
|
|
|
|
/// <summary>Verifies that the inferred object is null and the group is not mixed when there are no overrides.</summary>
|
|
[Fact]
|
|
public void InferredObject_NullWhenNoOverrides_NotMixed()
|
|
{
|
|
var rows = new[] { Row("M.A", "A", 1, null), Row("M.B", "B", 1, null) };
|
|
var g = Assert.Single(BindingGroupBuilder.Build(rows));
|
|
Assert.Null(g.InferredObject);
|
|
Assert.False(g.IsObjectMixed);
|
|
}
|
|
|
|
/// <summary>Verifies that an instance group infers its object from the overrides.</summary>
|
|
[Fact]
|
|
public void InstanceGroup_InfersObjectFromOverrides()
|
|
{
|
|
var rows = new[] { Row("ChlorineFlow", "ChlorineFlow", 1, "Z28061.ChlorineFlow"), Row("MachineID", "MachineID", 1, "Z28061.MachineID") };
|
|
var g = Assert.Single(BindingGroupBuilder.Build(rows));
|
|
Assert.Equal(BindingGroupBuilder.InstanceGroupKey, g.Key);
|
|
Assert.Equal("Z28061", g.InferredObject);
|
|
}
|
|
|
|
/// <summary>Verifies that the unbound count and the connection state are computed correctly.</summary>
|
|
[Fact]
|
|
public void Counts_UnboundAndConnectionState()
|
|
{
|
|
var rows = new[]
|
|
{
|
|
Row("M.A", "A", 1, "O.A"),
|
|
Row("M.B", "B", null, null),
|
|
Row("M.C", "C", 2, "O.C"),
|
|
};
|
|
var g = Assert.Single(BindingGroupBuilder.Build(rows));
|
|
Assert.Equal(3, g.Members.Count);
|
|
Assert.Equal(1, g.UnboundCount);
|
|
Assert.Equal(BindingConnectionState.Mixed, g.ConnectionState);
|
|
Assert.Null(g.CommonConnectionId);
|
|
}
|
|
|
|
/// <summary>Verifies that the connection state is single when all members agree.</summary>
|
|
[Fact]
|
|
public void ConnectionState_SingleWhenAllAgree()
|
|
{
|
|
var rows = new[] { Row("M.A", "A", 1, null), Row("M.B", "B", 1, null) };
|
|
var g = Assert.Single(BindingGroupBuilder.Build(rows));
|
|
Assert.Equal(BindingConnectionState.Single, g.ConnectionState);
|
|
Assert.Equal(1, g.CommonConnectionId);
|
|
}
|
|
|
|
/// <summary>Verifies that a member override that does not end with the tail counts as mixed.</summary>
|
|
[Fact]
|
|
public void MemberTail_OverrideThatDoesNotEndWithTail_CountsAsMixed()
|
|
{
|
|
// An operator typed a reference that does not end in the member tail —
|
|
// the prefix cannot be inferred for that row, so the group is mixed.
|
|
var rows = new[] { Row("M.A", "A", 1, "Obj.A"), Row("M.B", "B", 1, "Obj.Other") };
|
|
Assert.True(Assert.Single(BindingGroupBuilder.Build(rows)).IsObjectMixed);
|
|
}
|
|
|
|
/// <summary>Verifies that applying an object rewrites every member and sets the connection.</summary>
|
|
[Fact]
|
|
public void ApplyObject_RewritesEveryMember_AndSetsConnection()
|
|
{
|
|
var g = Assert.Single(BindingGroupBuilder.Build(new[]
|
|
{
|
|
Row("RightMESReceiver.MoveInFlag", "MoveInFlag", 1, "MESReceiver_024.MoveInFlag"),
|
|
Row("RightMESReceiver.ScanStateCmd", "ScanStateCmd", null, null),
|
|
}));
|
|
var changes = BindingGroupBuilder.ApplyObject(g, connectionId: 1, objectPath: " MESReceiver_022. ");
|
|
Assert.Equal(2, changes.Count);
|
|
Assert.Equal(("RightMESReceiver.MoveInFlag", 1, "MESReceiver_022.MoveInFlag"), (changes[0].CanonicalName, changes[0].ConnectionId, changes[0].Override));
|
|
Assert.Equal(("RightMESReceiver.ScanStateCmd", 1, "MESReceiver_022.ScanStateCmd"), (changes[1].CanonicalName, changes[1].ConnectionId, changes[1].Override));
|
|
}
|
|
|
|
/// <summary>Verifies that applying an object with a nested tail keeps the nesting.</summary>
|
|
[Fact]
|
|
public void ApplyObject_NestedTail_KeepsNesting()
|
|
{
|
|
var g = Assert.Single(BindingGroupBuilder.Build(new[] { Row("LeftReactorSide.LeakTest.DeltaVac", "DeltaVac", 1, null) }));
|
|
var c = Assert.Single(BindingGroupBuilder.ApplyObject(g, 1, "Left_002"));
|
|
Assert.Equal("Left_002.LeakTest.DeltaVac", c.Override);
|
|
}
|
|
|
|
/// <summary>Verifies that applying an empty object clears the overrides and keeps the connection.</summary>
|
|
[Fact]
|
|
public void ApplyObject_EmptyObject_ClearsOverrides_KeepsConnection()
|
|
{
|
|
var g = Assert.Single(BindingGroupBuilder.Build(new[] { Row("M.A", "A", 1, "Obj.A") }));
|
|
var c = Assert.Single(BindingGroupBuilder.ApplyObject(g, 2, ""));
|
|
Assert.Null(c.Override);
|
|
Assert.Equal(2, c.ConnectionId);
|
|
}
|
|
|
|
// ── Review fixes 2026-08-20 ──
|
|
|
|
/// <summary>Verifies that applying a null connection keeps each member's current connection.</summary>
|
|
[Fact]
|
|
public void ApplyObject_NullConnection_KeepsEachMembersCurrentConnection()
|
|
{
|
|
// A header reading "— mixed —"/"— none —" (0) must not unbind the group:
|
|
// Save is an upsert, so a removal could never persist anyway.
|
|
var g = Assert.Single(BindingGroupBuilder.Build(new[]
|
|
{
|
|
Row("M.A", "A", 1, "Old.A"),
|
|
Row("M.B", "B", 2, "Old.B"),
|
|
Row("M.C", "C", null, null),
|
|
}));
|
|
var changes = BindingGroupBuilder.ApplyObject(g, connectionId: null, objectPath: "New");
|
|
Assert.Equal(new int?[] { 1, 2, null }, changes.Select(c => c.ConnectionId));
|
|
Assert.All(changes, c => Assert.StartsWith("New.", c.Override!));
|
|
}
|
|
|
|
/// <summary>Verifies that the inferred object is mixed when some members are still on the bare default.</summary>
|
|
[Fact]
|
|
public void InferredObject_MixedWhenSomeMembersStillOnBareDefault()
|
|
{
|
|
// M.B has no override and its default ("B") is not "<prefix>.B" — the gate
|
|
// will reject it, so the group must read mixed rather than unified on "O".
|
|
var rows = new[] { Row("M.A", "A", 1, "O.A"), Row("M.B", "B", 1, null) };
|
|
var g = Assert.Single(BindingGroupBuilder.Build(rows));
|
|
Assert.Null(g.InferredObject);
|
|
Assert.True(g.IsObjectMixed);
|
|
}
|
|
|
|
/// <summary>Verifies that the inferred object uses the effective reference, so a default that carries the prefix is not mixed.</summary>
|
|
[Fact]
|
|
public void InferredObject_UsesEffectiveReference_DefaultThatCarriesPrefixIsNotMixed()
|
|
{
|
|
var rows = new[] { Row("M.A", "A", 1, "O.A"), Row("M.B", "B", 1, null) with { DefaultReference = "O.B" } };
|
|
var g = Assert.Single(BindingGroupBuilder.Build(rows));
|
|
Assert.Equal("O", g.InferredObject);
|
|
Assert.False(g.IsObjectMixed);
|
|
}
|
|
|
|
/// <summary>Verifies that with no overrides the object is inferred from the defaults when they share a prefix.</summary>
|
|
[Fact]
|
|
public void InferredObject_NoOverrides_InfersFromDefaultsWhenTheyShareAPrefix()
|
|
{
|
|
var rows = new[] { Row("A", "Z28061.A", 1, null), Row("B", "Z28061.B", 1, null) };
|
|
var g = Assert.Single(BindingGroupBuilder.Build(rows));
|
|
Assert.Equal("Z28061", g.InferredObject);
|
|
Assert.False(g.IsObjectMixed);
|
|
}
|
|
|
|
/// <summary>Verifies that a group is unbound when its connection id is not a known connection.</summary>
|
|
[Fact]
|
|
public void IsUnbound_WhenConnectionIdIsNotAKnownConnection()
|
|
{
|
|
var rows = new[] { Row("M.A", "A", 99, "O.A") };
|
|
var g = Assert.Single(BindingGroupBuilder.Build(rows, knownConnectionIds: new[] { 1, 2 }));
|
|
Assert.Equal(1, g.UnboundCount);
|
|
Assert.Equal(BindingConnectionState.None, g.ConnectionState);
|
|
}
|
|
}
|