Files
lmxopcua/tests/ZB.MOM.WW.LmxOpcUa.Client.UI.Tests/MainWindowViewModelTests.cs
Joseph Doherty 41a6b66943 Apply code style formatting and restore partial modifiers on Avalonia views
Linter/formatter pass across the full codebase. Restores required partial
keyword on AXAML code-behind classes that the formatter incorrectly removed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 07:58:13 -04:00

330 lines
10 KiB
C#

using Shouldly;
using Xunit;
using ZB.MOM.WW.LmxOpcUa.Client.Shared.Models;
using ZB.MOM.WW.LmxOpcUa.Client.UI.Services;
using ZB.MOM.WW.LmxOpcUa.Client.UI.Tests.Fakes;
using ZB.MOM.WW.LmxOpcUa.Client.UI.ViewModels;
using BrowseResult = ZB.MOM.WW.LmxOpcUa.Client.Shared.Models.BrowseResult;
using ConnectionState = ZB.MOM.WW.LmxOpcUa.Client.Shared.Models.ConnectionState;
namespace ZB.MOM.WW.LmxOpcUa.Client.UI.Tests;
public class MainWindowViewModelTests
{
private readonly FakeOpcUaClientService _service;
private readonly MainWindowViewModel _vm;
public MainWindowViewModelTests()
{
_service = new FakeOpcUaClientService
{
ConnectResult = new ConnectionInfo(
"opc.tcp://localhost:4840",
"TestServer",
"None",
"http://opcfoundation.org/UA/SecurityPolicy#None",
"session-1",
"TestSession"),
BrowseResults =
[
new BrowseResult("ns=2;s=Root", "Root", "Object", true)
],
RedundancyResult = new RedundancyInfo("None", 200, ["urn:test"], "urn:test")
};
var factory = new FakeOpcUaClientServiceFactory(_service);
var dispatcher = new SynchronousUiDispatcher();
_vm = new MainWindowViewModel(factory, dispatcher);
}
[Fact]
public void DefaultState_IsDisconnected()
{
_vm.ConnectionState.ShouldBe(ConnectionState.Disconnected);
_vm.IsConnected.ShouldBeFalse();
_vm.EndpointUrl.ShouldBe("opc.tcp://localhost:4840");
_vm.StatusMessage.ShouldBe("Disconnected");
}
[Fact]
public void ConnectCommand_CanExecute_WhenDisconnected()
{
_vm.ConnectCommand.CanExecute(null).ShouldBeTrue();
}
[Fact]
public void DisconnectCommand_CannotExecute_WhenDisconnected()
{
_vm.DisconnectCommand.CanExecute(null).ShouldBeFalse();
}
[Fact]
public async Task ConnectCommand_TransitionsToConnected()
{
await _vm.ConnectCommand.ExecuteAsync(null);
_vm.ConnectionState.ShouldBe(ConnectionState.Connected);
_vm.IsConnected.ShouldBeTrue();
_service.ConnectCallCount.ShouldBe(1);
}
[Fact]
public async Task ConnectCommand_LoadsRootNodes()
{
await _vm.ConnectCommand.ExecuteAsync(null);
_vm.BrowseTree.RootNodes.Count.ShouldBe(1);
_vm.BrowseTree.RootNodes[0].DisplayName.ShouldBe("Root");
}
[Fact]
public async Task ConnectCommand_FetchesRedundancyInfo()
{
await _vm.ConnectCommand.ExecuteAsync(null);
_vm.RedundancyInfo.ShouldNotBeNull();
_vm.RedundancyInfo!.Mode.ShouldBe("None");
_vm.RedundancyInfo.ServiceLevel.ShouldBe((byte)200);
}
[Fact]
public async Task ConnectCommand_SetsSessionLabel()
{
await _vm.ConnectCommand.ExecuteAsync(null);
_vm.SessionLabel.ShouldContain("TestServer");
_vm.SessionLabel.ShouldContain("TestSession");
}
[Fact]
public async Task DisconnectCommand_TransitionsToDisconnected()
{
await _vm.ConnectCommand.ExecuteAsync(null);
await _vm.DisconnectCommand.ExecuteAsync(null);
_vm.ConnectionState.ShouldBe(ConnectionState.Disconnected);
_vm.IsConnected.ShouldBeFalse();
_service.DisconnectCallCount.ShouldBe(1);
}
[Fact]
public async Task Disconnect_ClearsStateAndChildren()
{
await _vm.ConnectCommand.ExecuteAsync(null);
await _vm.DisconnectCommand.ExecuteAsync(null);
_vm.SessionLabel.ShouldBe(string.Empty);
_vm.RedundancyInfo.ShouldBeNull();
_vm.BrowseTree.RootNodes.ShouldBeEmpty();
_vm.SubscriptionCount.ShouldBe(0);
}
[Fact]
public void ConnectionStateChangedEvent_UpdatesState()
{
_service.RaiseConnectionStateChanged(
new ConnectionStateChangedEventArgs(ConnectionState.Disconnected, ConnectionState.Reconnecting,
"opc.tcp://localhost:4840"));
_vm.ConnectionState.ShouldBe(ConnectionState.Reconnecting);
_vm.StatusMessage.ShouldBe("Reconnecting...");
}
[Fact]
public async Task SelectedTreeNode_PropagatesToChildViewModels()
{
await _vm.ConnectCommand.ExecuteAsync(null);
var node = _vm.BrowseTree.RootNodes[0];
_vm.SelectedTreeNode = node;
_vm.ReadWrite.SelectedNodeId.ShouldBe(node.NodeId);
_vm.History.SelectedNodeId.ShouldBe(node.NodeId);
}
[Fact]
public async Task ConnectCommand_PropagatesIsConnectedToChildViewModels()
{
await _vm.ConnectCommand.ExecuteAsync(null);
_vm.ReadWrite.IsConnected.ShouldBeTrue();
_vm.Subscriptions.IsConnected.ShouldBeTrue();
_vm.Alarms.IsConnected.ShouldBeTrue();
_vm.History.IsConnected.ShouldBeTrue();
}
[Fact]
public async Task DisconnectCommand_PropagatesIsConnectedFalseToChildViewModels()
{
await _vm.ConnectCommand.ExecuteAsync(null);
await _vm.DisconnectCommand.ExecuteAsync(null);
_vm.ReadWrite.IsConnected.ShouldBeFalse();
_vm.Subscriptions.IsConnected.ShouldBeFalse();
_vm.Alarms.IsConnected.ShouldBeFalse();
_vm.History.IsConnected.ShouldBeFalse();
}
[Fact]
public async Task ConnectFailure_RevertsToDisconnected()
{
_service.ConnectException = new Exception("Connection refused");
await _vm.ConnectCommand.ExecuteAsync(null);
_vm.ConnectionState.ShouldBe(ConnectionState.Disconnected);
_vm.StatusMessage.ShouldContain("Connection refused");
}
[Fact]
public void PropertyChanged_FiredForConnectionState()
{
var changed = new List<string>();
_vm.PropertyChanged += (_, e) => changed.Add(e.PropertyName!);
_service.RaiseConnectionStateChanged(
new ConnectionStateChangedEventArgs(ConnectionState.Disconnected, ConnectionState.Connected,
"opc.tcp://localhost:4840"));
changed.ShouldContain(nameof(MainWindowViewModel.ConnectionState));
changed.ShouldContain(nameof(MainWindowViewModel.IsConnected));
}
[Fact]
public void DefaultState_HasCorrectAdvancedSettings()
{
_vm.FailoverUrls.ShouldBeNull();
_vm.SessionTimeoutSeconds.ShouldBe(60);
_vm.AutoAcceptCertificates.ShouldBeTrue();
_vm.CertificateStorePath.ShouldContain("LmxOpcUaClient");
_vm.CertificateStorePath.ShouldContain("pki");
}
[Fact]
public async Task ConnectCommand_MapsFailoverUrlsToSettings()
{
_vm.FailoverUrls = "opc.tcp://backup1:4840, opc.tcp://backup2:4840";
await _vm.ConnectCommand.ExecuteAsync(null);
_service.LastConnectionSettings.ShouldNotBeNull();
_service.LastConnectionSettings!.FailoverUrls.ShouldNotBeNull();
_service.LastConnectionSettings.FailoverUrls!.Length.ShouldBe(2);
_service.LastConnectionSettings.FailoverUrls[0].ShouldBe("opc.tcp://backup1:4840");
_service.LastConnectionSettings.FailoverUrls[1].ShouldBe("opc.tcp://backup2:4840");
}
[Fact]
public async Task ConnectCommand_MapsEmptyFailoverUrlsToNull()
{
_vm.FailoverUrls = "";
await _vm.ConnectCommand.ExecuteAsync(null);
_service.LastConnectionSettings.ShouldNotBeNull();
_service.LastConnectionSettings!.FailoverUrls.ShouldBeNull();
}
[Fact]
public async Task ConnectCommand_MapsSessionTimeoutToSettings()
{
_vm.SessionTimeoutSeconds = 120;
await _vm.ConnectCommand.ExecuteAsync(null);
_service.LastConnectionSettings.ShouldNotBeNull();
_service.LastConnectionSettings!.SessionTimeoutSeconds.ShouldBe(120);
}
[Fact]
public async Task ConnectCommand_MapsAutoAcceptCertificatesToSettings()
{
_vm.AutoAcceptCertificates = false;
await _vm.ConnectCommand.ExecuteAsync(null);
_service.LastConnectionSettings.ShouldNotBeNull();
_service.LastConnectionSettings!.AutoAcceptCertificates.ShouldBeFalse();
}
[Fact]
public async Task ConnectCommand_MapsCertificateStorePathToSettings()
{
_vm.CertificateStorePath = "/custom/pki/path";
await _vm.ConnectCommand.ExecuteAsync(null);
_service.LastConnectionSettings.ShouldNotBeNull();
_service.LastConnectionSettings!.CertificateStorePath.ShouldBe("/custom/pki/path");
}
[Fact]
public async Task SubscribeSelectedNodesCommand_SubscribesAndSwitchesToTab()
{
await _vm.ConnectCommand.ExecuteAsync(null);
var node1 = _vm.BrowseTree.RootNodes[0];
_vm.SelectedTreeNodes.Add(node1);
await _vm.SubscribeSelectedNodesCommand.ExecuteAsync(null);
_vm.Subscriptions.ActiveSubscriptions.Count.ShouldBe(1);
_vm.Subscriptions.ActiveSubscriptions[0].NodeId.ShouldBe(node1.NodeId);
_vm.SelectedTabIndex.ShouldBe(1);
}
[Fact]
public async Task SubscribeSelectedNodesCommand_DoesNothing_WhenNoSelection()
{
await _vm.ConnectCommand.ExecuteAsync(null);
await _vm.SubscribeSelectedNodesCommand.ExecuteAsync(null);
_vm.Subscriptions.ActiveSubscriptions.ShouldBeEmpty();
}
[Fact]
public async Task ViewHistoryForSelectedNodeCommand_SetsNodeAndSwitchesToTab()
{
await _vm.ConnectCommand.ExecuteAsync(null);
var node = _vm.BrowseTree.RootNodes[0];
_vm.SelectedTreeNodes.Add(node);
_vm.ViewHistoryForSelectedNodeCommand.Execute(null);
_vm.History.SelectedNodeId.ShouldBe(node.NodeId);
_vm.SelectedTabIndex.ShouldBe(3);
}
[Fact]
public async Task UpdateHistoryEnabledForSelection_TrueForVariableNode()
{
await _vm.ConnectCommand.ExecuteAsync(null);
var variableNode = new TreeNodeViewModel(
"ns=2;s=Var1", "Var1", "Variable", false, _service, new SynchronousUiDispatcher());
_vm.SelectedTreeNodes.Add(variableNode);
_vm.UpdateHistoryEnabledForSelection();
_vm.IsHistoryEnabledForSelection.ShouldBeTrue();
}
[Fact]
public async Task UpdateHistoryEnabledForSelection_FalseForObjectNode()
{
await _vm.ConnectCommand.ExecuteAsync(null);
var objectNode = new TreeNodeViewModel(
"ns=2;s=Obj1", "Obj1", "Object", true, _service, new SynchronousUiDispatcher());
_vm.SelectedTreeNodes.Add(objectNode);
_vm.UpdateHistoryEnabledForSelection();
_vm.IsHistoryEnabledForSelection.ShouldBeFalse();
}
[Fact]
public void UpdateHistoryEnabledForSelection_FalseWhenDisconnected()
{
_vm.UpdateHistoryEnabledForSelection();
_vm.IsHistoryEnabledForSelection.ShouldBeFalse();
}
}