c1fe7fbc4a
Browse renders the Galaxy hierarchy tree from IGalaxyHierarchyCache: expandable areas/objects with attribute name, data type and the alarm/historized flags, plus a name/reference filter. Right-click or double-click an attribute to add it to a subscription panel that polls live value, quality and source timestamp every two seconds. Alarms lists the worker's currently-active alarm set via IAlarmRpcDispatcher, defaulting to unacknowledged Active alarms with filters for acknowledged alarms, area, severity range and text. It is read-only and warns when alarm auto-subscribe is disabled. Both tabs read live MXAccess data through a new singleton DashboardLiveDataService that owns one shared, lazily-opened gateway session (one worker) for the whole dashboard, re-opened transparently if it faults or its lease expires. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
130 lines
4.6 KiB
C#
130 lines
4.6 KiB
C#
using MxGateway.Contracts.Proto;
|
|
using MxGateway.Contracts.Proto.Galaxy;
|
|
using MxGateway.Server.Dashboard;
|
|
|
|
namespace MxGateway.Tests.Gateway.Dashboard;
|
|
|
|
/// <summary>
|
|
/// Unit tests for the pure projection/formatting helpers behind the
|
|
/// dashboard Browse and Alarms tabs.
|
|
/// </summary>
|
|
public sealed class DashboardBrowseAndAlarmModelTests
|
|
{
|
|
[Fact]
|
|
public void BuildTree_LinksChildrenToParents_AndPromotesOrphansToRoots()
|
|
{
|
|
GalaxyObject area = new() { GobjectId = 1, BrowseName = "AreaA", IsArea = true, ParentGobjectId = 0 };
|
|
GalaxyObject child = new() { GobjectId = 2, BrowseName = "Pump01", ParentGobjectId = 1 };
|
|
GalaxyObject orphan = new() { GobjectId = 3, BrowseName = "Lost", ParentGobjectId = 99 };
|
|
|
|
IReadOnlyList<DashboardBrowseNode> roots = DashboardBrowseTreeBuilder.Build([area, child, orphan]);
|
|
|
|
// The area and the orphan (its parent id is absent) are both roots.
|
|
Assert.Equal(2, roots.Count);
|
|
DashboardBrowseNode areaNode = Assert.Single(roots, node => node.Object.GobjectId == 1);
|
|
Assert.Single(areaNode.Children);
|
|
Assert.Equal(2, areaNode.Children[0].Object.GobjectId);
|
|
Assert.Contains(roots, node => node.Object.GobjectId == 3);
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildTree_SortsAreasBeforeObjects()
|
|
{
|
|
GalaxyObject instance = new() { GobjectId = 1, BrowseName = "Zeta", IsArea = false };
|
|
GalaxyObject areaB = new() { GobjectId = 2, BrowseName = "Beta", IsArea = true };
|
|
|
|
IReadOnlyList<DashboardBrowseNode> roots = DashboardBrowseTreeBuilder.Build([instance, areaB]);
|
|
|
|
Assert.Equal(2, roots.Count);
|
|
Assert.True(roots[0].IsArea);
|
|
Assert.Equal("Beta", roots[0].DisplayName);
|
|
Assert.False(roots[1].IsArea);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(true, "true")]
|
|
[InlineData(false, "false")]
|
|
public void FormatValue_FormatsBooleans(bool input, string expected)
|
|
{
|
|
MxValue value = new() { DataType = MxDataType.Boolean, BoolValue = input };
|
|
Assert.Equal(expected, DashboardMxValueFormatter.FormatValue(value));
|
|
}
|
|
|
|
[Fact]
|
|
public void FormatValue_FormatsNumbersAndStrings()
|
|
{
|
|
Assert.Equal("42", DashboardMxValueFormatter.FormatValue(new MxValue { Int32Value = 42 }));
|
|
Assert.Equal("hello", DashboardMxValueFormatter.FormatValue(new MxValue { StringValue = "hello" }));
|
|
}
|
|
|
|
[Fact]
|
|
public void FormatValue_HandlesNullPayloadAndNullReference()
|
|
{
|
|
Assert.Equal("-", DashboardMxValueFormatter.FormatValue(null));
|
|
Assert.Equal("(null)", DashboardMxValueFormatter.FormatValue(new MxValue { IsNull = true }));
|
|
}
|
|
|
|
[Fact]
|
|
public void TagValue_FromSuccessfulReadResult_MarksGoodQuality()
|
|
{
|
|
BulkReadResult result = new()
|
|
{
|
|
TagAddress = "Galaxy!Area.Tag",
|
|
WasSuccessful = true,
|
|
Quality = 192,
|
|
Value = new MxValue { DataType = MxDataType.Double, DoubleValue = 1.5 },
|
|
};
|
|
|
|
DashboardTagValue value = DashboardTagValue.FromBulkReadResult(result);
|
|
|
|
Assert.True(value.Ok);
|
|
Assert.True(value.QualityGood);
|
|
Assert.Equal("1.5", value.ValueText);
|
|
Assert.Null(value.Error);
|
|
}
|
|
|
|
[Fact]
|
|
public void TagValue_FromFailedReadResult_CarriesError()
|
|
{
|
|
BulkReadResult result = new()
|
|
{
|
|
TagAddress = "Galaxy!Area.Bad",
|
|
WasSuccessful = false,
|
|
Quality = 0,
|
|
ErrorMessage = "invalid handle",
|
|
};
|
|
|
|
DashboardTagValue value = DashboardTagValue.FromBulkReadResult(result);
|
|
|
|
Assert.False(value.Ok);
|
|
Assert.False(value.QualityGood);
|
|
Assert.Equal("invalid handle", value.Error);
|
|
}
|
|
|
|
[Fact]
|
|
public void ActiveAlarm_FromSnapshot_ParsesProviderAndAcknowledgementState()
|
|
{
|
|
ActiveAlarmSnapshot unacked = new()
|
|
{
|
|
AlarmFullReference = "Galaxy!TestArea.TestMachine_001.TestAlarm001",
|
|
Category = "TestArea",
|
|
CurrentState = AlarmConditionState.Active,
|
|
Severity = 500,
|
|
};
|
|
ActiveAlarmSnapshot acked = new()
|
|
{
|
|
AlarmFullReference = "Galaxy!TestArea.TestMachine_002.TestAlarm001",
|
|
CurrentState = AlarmConditionState.ActiveAcked,
|
|
};
|
|
|
|
DashboardActiveAlarm unackedRow = DashboardActiveAlarm.FromSnapshot(unacked);
|
|
DashboardActiveAlarm ackedRow = DashboardActiveAlarm.FromSnapshot(acked);
|
|
|
|
Assert.Equal("Galaxy", unackedRow.Provider);
|
|
Assert.Equal("TestArea", unackedRow.Area);
|
|
Assert.Equal(500, unackedRow.Severity);
|
|
Assert.True(unackedRow.IsUnacknowledged);
|
|
Assert.False(ackedRow.IsUnacknowledged);
|
|
}
|
|
}
|