docs: backfill XML documentation across 756 files
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Adds <summary>, <param>, <typeparam>, and <inheritdoc/> tags to public members surfaced by commentchecker — resolves 5,847 of 5,869 issues (99.6%) across three /fixdocs passes.
This commit is contained in:
@@ -7,6 +7,7 @@ namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Tests;
|
||||
|
||||
public class AlarmsCommandTests
|
||||
{
|
||||
/// <summary>Verifies that Execute subscribes to alarms.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_SubscribesToAlarms()
|
||||
{
|
||||
@@ -31,6 +32,7 @@ public class AlarmsCommandTests
|
||||
fakeService.SubscribeAlarmsCalls[0].SourceNodeId.ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Execute with node passes source node ID.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_WithNode_PassesSourceNodeId()
|
||||
{
|
||||
@@ -55,6 +57,7 @@ public class AlarmsCommandTests
|
||||
fakeService.SubscribeAlarmsCalls[0].SourceNodeId!.Identifier.ShouldBe("AlarmSource");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Execute with refresh requests condition refresh.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_WithRefresh_RequestsConditionRefresh()
|
||||
{
|
||||
@@ -79,6 +82,7 @@ public class AlarmsCommandTests
|
||||
output.ShouldContain("Condition refresh requested.");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that refresh failure prints error.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_RefreshFailure_PrintsError()
|
||||
{
|
||||
@@ -105,6 +109,7 @@ public class AlarmsCommandTests
|
||||
output.ShouldContain("Condition refresh not supported:");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Execute unsubscribes on cancellation.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_UnsubscribesOnCancellation()
|
||||
{
|
||||
@@ -126,6 +131,7 @@ public class AlarmsCommandTests
|
||||
fakeService.UnsubscribeAlarmsCalled.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Execute disconnects in finally block.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_DisconnectsInFinally()
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Tests;
|
||||
|
||||
public class BrowseCommandTests
|
||||
{
|
||||
/// <summary>Verifies that Execute prints browse results correctly.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_PrintsBrowseResults()
|
||||
{
|
||||
@@ -35,6 +36,7 @@ public class BrowseCommandTests
|
||||
output.ShouldContain("[Method] Method1 (NodeId: ns=2;s=Meth1)");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Execute browses from the specified node ID.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_BrowsesFromSpecifiedNode()
|
||||
{
|
||||
@@ -57,6 +59,7 @@ public class BrowseCommandTests
|
||||
fakeService.BrowseNodeIds[0]!.Identifier.ShouldBe("StartNode");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Execute browses from null node when not specified.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_DefaultBrowsesFromNull()
|
||||
{
|
||||
@@ -77,6 +80,7 @@ public class BrowseCommandTests
|
||||
fakeService.BrowseNodeIds[0].ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Execute browses only a single level when not recursive.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_NonRecursive_BrowsesSingleLevel()
|
||||
{
|
||||
@@ -101,6 +105,7 @@ public class BrowseCommandTests
|
||||
fakeService.BrowseNodeIds.Count.ShouldBe(1);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Execute browses child nodes when recursive flag is set.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_Recursive_BrowsesChildren()
|
||||
{
|
||||
@@ -123,6 +128,7 @@ public class BrowseCommandTests
|
||||
fakeService.BrowseNodeIds.Count.ShouldBeGreaterThan(1);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Execute disconnects and disposes in the finally block.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_DisconnectsInFinally()
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Tests;
|
||||
|
||||
public class CommandBaseTests
|
||||
{
|
||||
/// <summary>Verifies that common options map to connection settings correctly.</summary>
|
||||
[Fact]
|
||||
public async Task CommonOptions_MapToConnectionSettings_Correctly()
|
||||
{
|
||||
@@ -37,6 +38,7 @@ public class CommandBaseTests
|
||||
settings.AutoAcceptCertificates.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that encrypt option maps to SignAndEncrypt.</summary>
|
||||
[Fact]
|
||||
public async Task SecurityOption_Encrypt_MapsToSignAndEncrypt()
|
||||
{
|
||||
@@ -54,6 +56,7 @@ public class CommandBaseTests
|
||||
fakeService.LastConnectionSettings!.SecurityMode.ShouldBe(SecurityMode.SignAndEncrypt);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that none option maps to None.</summary>
|
||||
[Fact]
|
||||
public async Task SecurityOption_None_MapsToNone()
|
||||
{
|
||||
@@ -71,6 +74,7 @@ public class CommandBaseTests
|
||||
fakeService.LastConnectionSettings!.SecurityMode.ShouldBe(SecurityMode.None);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that no failover URLs results in null FailoverUrls.</summary>
|
||||
[Fact]
|
||||
public async Task NoFailoverUrls_FailoverUrlsIsNull()
|
||||
{
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Tests;
|
||||
/// </summary>
|
||||
public class CommandRangeValidationTests
|
||||
{
|
||||
/// <summary>Verifies that BrowseCommand rejects negative depth values with a command exception.</summary>
|
||||
[Fact]
|
||||
public async Task BrowseCommand_NegativeDepth_ThrowsCommandException()
|
||||
{
|
||||
@@ -28,6 +29,7 @@ public class CommandRangeValidationTests
|
||||
ex.Message.ShouldContain("--depth");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that BrowseCommand rejects zero depth values with a command exception.</summary>
|
||||
[Fact]
|
||||
public async Task BrowseCommand_ZeroDepth_ThrowsCommandException()
|
||||
{
|
||||
@@ -44,6 +46,7 @@ public class CommandRangeValidationTests
|
||||
ex.Message.ShouldContain("--depth");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that SubscribeCommand rejects zero interval values with a command exception.</summary>
|
||||
[Fact]
|
||||
public async Task SubscribeCommand_ZeroInterval_ThrowsCommandException()
|
||||
{
|
||||
@@ -61,6 +64,7 @@ public class CommandRangeValidationTests
|
||||
ex.Message.ShouldContain("--interval");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that SubscribeCommand rejects negative interval values with a command exception.</summary>
|
||||
[Fact]
|
||||
public async Task SubscribeCommand_NegativeInterval_ThrowsCommandException()
|
||||
{
|
||||
@@ -77,6 +81,7 @@ public class CommandRangeValidationTests
|
||||
await Should.ThrowAsync<CommandException>(async () => await command.ExecuteAsync(console));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that SubscribeCommand in recursive mode rejects zero max depth with a command exception.</summary>
|
||||
[Fact]
|
||||
public async Task SubscribeCommand_RecursiveZeroMaxDepth_ThrowsCommandException()
|
||||
{
|
||||
@@ -96,6 +101,7 @@ public class CommandRangeValidationTests
|
||||
ex.Message.ShouldContain("--max-depth");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that SubscribeCommand rejects negative duration values with a command exception.</summary>
|
||||
[Fact]
|
||||
public async Task SubscribeCommand_NegativeDuration_ThrowsCommandException()
|
||||
{
|
||||
@@ -112,6 +118,7 @@ public class CommandRangeValidationTests
|
||||
await Should.ThrowAsync<CommandException>(async () => await command.ExecuteAsync(console));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AlarmsCommand rejects zero interval values with a command exception.</summary>
|
||||
[Fact]
|
||||
public async Task AlarmsCommand_ZeroInterval_ThrowsCommandException()
|
||||
{
|
||||
@@ -128,6 +135,7 @@ public class CommandRangeValidationTests
|
||||
ex.Message.ShouldContain("--interval");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that HistoryReadCommand rejects negative max values with a command exception.</summary>
|
||||
[Fact]
|
||||
public async Task HistoryReadCommand_NegativeMax_ThrowsCommandException()
|
||||
{
|
||||
@@ -145,6 +153,7 @@ public class CommandRangeValidationTests
|
||||
ex.Message.ShouldContain("--max");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that HistoryReadCommand rejects zero interval values with a command exception.</summary>
|
||||
[Fact]
|
||||
public async Task HistoryReadCommand_ZeroInterval_ThrowsCommandException()
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Tests;
|
||||
|
||||
public class ConnectCommandTests
|
||||
{
|
||||
/// <summary>Verifies that execute prints connection info.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_PrintsConnectionInfo()
|
||||
{
|
||||
@@ -38,6 +39,7 @@ public class ConnectCommandTests
|
||||
output.ShouldContain("Connection successful.");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that execute calls connect and disconnect.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_CallsConnectAndDisconnect()
|
||||
{
|
||||
@@ -56,6 +58,7 @@ public class ConnectCommandTests
|
||||
fakeService.DisposeCalled.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that execute disconnects on error.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_DisconnectsOnError()
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Tests;
|
||||
/// </summary>
|
||||
public class EventHandlerLifecycleTests
|
||||
{
|
||||
/// <summary>Verifies that SubscribeCommand detaches the DataChanged event handler after exit.</summary>
|
||||
[Fact]
|
||||
public async Task SubscribeCommand_AfterExit_DataChangedEventHasNoSubscribers()
|
||||
{
|
||||
@@ -35,6 +36,7 @@ public class EventHandlerLifecycleTests
|
||||
"SubscribeCommand must detach its DataChanged handler before returning.");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AlarmsCommand detaches the AlarmEvent handler after exit.</summary>
|
||||
[Fact]
|
||||
public async Task AlarmsCommand_AfterExit_AlarmEventHasNoSubscribers()
|
||||
{
|
||||
|
||||
@@ -12,27 +12,55 @@ namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Tests.Fakes;
|
||||
public sealed class FakeOpcUaClientService : IOpcUaClientService
|
||||
{
|
||||
// Track calls
|
||||
/// <summary>Gets a value indicating whether ConnectAsync was called.</summary>
|
||||
public bool ConnectCalled { get; private set; }
|
||||
|
||||
/// <summary>Gets the connection settings from the most recent ConnectAsync call.</summary>
|
||||
public ConnectionSettings? LastConnectionSettings { get; private set; }
|
||||
|
||||
/// <summary>Gets a value indicating whether DisconnectAsync was called.</summary>
|
||||
public bool DisconnectCalled { get; private set; }
|
||||
|
||||
/// <summary>Gets a value indicating whether Dispose was called.</summary>
|
||||
public bool DisposeCalled { get; private set; }
|
||||
|
||||
/// <summary>Gets the list of node IDs passed to ReadValueAsync calls.</summary>
|
||||
public List<NodeId> ReadNodeIds { get; } = [];
|
||||
|
||||
/// <summary>Gets the list of (NodeId, value) pairs passed to WriteValueAsync calls.</summary>
|
||||
public List<(NodeId NodeId, object Value)> WriteValues { get; } = [];
|
||||
|
||||
/// <summary>Gets the list of parent node IDs passed to BrowseAsync calls.</summary>
|
||||
public List<NodeId?> BrowseNodeIds { get; } = [];
|
||||
|
||||
/// <summary>Gets the list of (NodeId, intervalMs) pairs from SubscribeAsync calls.</summary>
|
||||
public List<(NodeId NodeId, int IntervalMs)> SubscribeCalls { get; } = [];
|
||||
|
||||
/// <summary>Gets the list of node IDs passed to UnsubscribeAsync calls.</summary>
|
||||
public List<NodeId> UnsubscribeCalls { get; } = [];
|
||||
|
||||
/// <summary>Gets the list of (SourceNodeId, intervalMs) pairs from SubscribeAlarmsAsync calls.</summary>
|
||||
public List<(NodeId? SourceNodeId, int IntervalMs)> SubscribeAlarmsCalls { get; } = [];
|
||||
|
||||
/// <summary>Gets a value indicating whether UnsubscribeAlarmsAsync was called.</summary>
|
||||
public bool UnsubscribeAlarmsCalled { get; private set; }
|
||||
|
||||
/// <summary>Gets a value indicating whether RequestConditionRefreshAsync was called.</summary>
|
||||
public bool RequestConditionRefreshCalled { get; private set; }
|
||||
|
||||
/// <summary>Gets the list of (NodeId, start, end, maxValues) tuples from HistoryReadRawAsync calls.</summary>
|
||||
public List<(NodeId NodeId, DateTime Start, DateTime End, int MaxValues)> HistoryReadRawCalls { get; } = [];
|
||||
|
||||
/// <summary>Gets the list of history read aggregate call parameters.</summary>
|
||||
public List<(NodeId NodeId, DateTime Start, DateTime End, AggregateType Aggregate, double IntervalMs)>
|
||||
HistoryReadAggregateCalls { get; } =
|
||||
[];
|
||||
|
||||
/// <summary>Gets a value indicating whether GetRedundancyInfoAsync was called.</summary>
|
||||
public bool GetRedundancyInfoCalled { get; private set; }
|
||||
|
||||
// Configurable results
|
||||
/// <summary>Gets or sets the connection info returned by ConnectAsync.</summary>
|
||||
public ConnectionInfo ConnectionInfoResult { get; set; } = new(
|
||||
"opc.tcp://localhost:4840",
|
||||
"TestServer",
|
||||
@@ -41,14 +69,17 @@ public sealed class FakeOpcUaClientService : IOpcUaClientService
|
||||
"session-1",
|
||||
"TestSession");
|
||||
|
||||
/// <summary>Gets or sets the data value returned by ReadValueAsync.</summary>
|
||||
public DataValue ReadValueResult { get; set; } = new(
|
||||
new Variant(42),
|
||||
StatusCodes.Good,
|
||||
DateTime.UtcNow,
|
||||
DateTime.UtcNow);
|
||||
|
||||
/// <summary>Gets or sets the status code returned by WriteValueAsync.</summary>
|
||||
public StatusCode WriteStatusCodeResult { get; set; } = StatusCodes.Good;
|
||||
|
||||
/// <summary>Gets or sets the browse results returned by BrowseAsync.</summary>
|
||||
public IReadOnlyList<BrowseResult> BrowseResults { get; set; } = new List<BrowseResult>
|
||||
{
|
||||
new("ns=2;s=Node1", "Node1", "Object", true),
|
||||
@@ -63,19 +94,30 @@ public sealed class FakeOpcUaClientService : IOpcUaClientService
|
||||
/// </summary>
|
||||
public Dictionary<string, IReadOnlyList<BrowseResult>> BrowseResultsByParent { get; } = new();
|
||||
|
||||
/// <summary>Gets or sets the history read result returned by HistoryReadRawAsync and HistoryReadAggregateAsync.</summary>
|
||||
public IReadOnlyList<DataValue> HistoryReadResult { get; set; } = new List<DataValue>
|
||||
{
|
||||
new(new Variant(10.0), StatusCodes.Good, DateTime.UtcNow.AddHours(-1), DateTime.UtcNow),
|
||||
new(new Variant(20.0), StatusCodes.Good, DateTime.UtcNow, DateTime.UtcNow)
|
||||
};
|
||||
|
||||
/// <summary>Gets or sets the redundancy info returned by GetRedundancyInfoAsync.</summary>
|
||||
public RedundancyInfo RedundancyInfoResult { get; set; } = new(
|
||||
"Warm", 200, ["urn:server1", "urn:server2"], "urn:app:test");
|
||||
|
||||
/// <summary>Gets or sets the exception thrown by ConnectAsync.</summary>
|
||||
public Exception? ConnectException { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the exception thrown by ReadValueAsync.</summary>
|
||||
public Exception? ReadException { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the exception thrown by WriteValueAsync.</summary>
|
||||
public Exception? WriteException { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the exception thrown by RequestConditionRefreshAsync.</summary>
|
||||
public Exception? ConditionRefreshException { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the exception thrown by SubscribeAsync.</summary>
|
||||
public Exception? SubscribeException { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -218,18 +260,24 @@ public sealed class FakeOpcUaClientService : IOpcUaClientService
|
||||
}
|
||||
|
||||
/// <summary>Raises the DataChanged event for testing subscribe commands.</summary>
|
||||
/// <param name="nodeId">The node ID string that changed.</param>
|
||||
/// <param name="value">The new data value.</param>
|
||||
public void RaiseDataChanged(string nodeId, DataValue value)
|
||||
{
|
||||
DataChanged?.Invoke(this, new DataChangedEventArgs(nodeId, value));
|
||||
}
|
||||
|
||||
/// <summary>Raises the AlarmEvent for testing alarm commands.</summary>
|
||||
/// <param name="args">The alarm event arguments.</param>
|
||||
public void RaiseAlarmEvent(AlarmEventArgs args)
|
||||
{
|
||||
AlarmEvent?.Invoke(this, args);
|
||||
}
|
||||
|
||||
/// <summary>Raises the ConnectionStateChanged event for testing.</summary>
|
||||
/// <param name="oldState">The previous connection state.</param>
|
||||
/// <param name="newState">The new connection state.</param>
|
||||
/// <param name="endpointUrl">The endpoint URL.</param>
|
||||
public void RaiseConnectionStateChanged(ConnectionState oldState, ConnectionState newState, string endpointUrl)
|
||||
{
|
||||
ConnectionStateChanged?.Invoke(this, new ConnectionStateChangedEventArgs(oldState, newState, endpointUrl));
|
||||
|
||||
@@ -9,11 +9,14 @@ public sealed class FakeOpcUaClientServiceFactory : IOpcUaClientServiceFactory
|
||||
{
|
||||
private readonly FakeOpcUaClientService _service;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="FakeOpcUaClientServiceFactory"/> class.</summary>
|
||||
/// <param name="service">The fake OPC UA client service to return.</param>
|
||||
public FakeOpcUaClientServiceFactory(FakeOpcUaClientService service)
|
||||
{
|
||||
_service = service;
|
||||
}
|
||||
|
||||
/// <summary>Creates and returns the fake OPC UA client service.</summary>
|
||||
public IOpcUaClientService Create()
|
||||
{
|
||||
return _service;
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Tests;
|
||||
|
||||
public class HistoryReadCommandTests
|
||||
{
|
||||
/// <summary>Verifies RawRead execution prints values.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_RawRead_PrintsValues()
|
||||
{
|
||||
@@ -42,6 +43,7 @@ public class HistoryReadCommandTests
|
||||
output.ShouldContain("2 values returned.");
|
||||
}
|
||||
|
||||
/// <summary>Verifies RawRead execution calls HistoryReadRaw.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_RawRead_CallsHistoryReadRaw()
|
||||
{
|
||||
@@ -62,6 +64,7 @@ public class HistoryReadCommandTests
|
||||
fakeService.HistoryReadRawCalls[0].NodeId.Identifier.ShouldBe("HistNode");
|
||||
}
|
||||
|
||||
/// <summary>Verifies AggregateRead execution calls HistoryReadAggregate.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_AggregateRead_CallsHistoryReadAggregate()
|
||||
{
|
||||
@@ -83,6 +86,7 @@ public class HistoryReadCommandTests
|
||||
fakeService.HistoryReadAggregateCalls[0].IntervalMs.ShouldBe(60000);
|
||||
}
|
||||
|
||||
/// <summary>Verifies AggregateRead execution prints aggregate info.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_AggregateRead_PrintsAggregateInfo()
|
||||
{
|
||||
@@ -104,6 +108,7 @@ public class HistoryReadCommandTests
|
||||
output.ShouldContain("7200000");
|
||||
}
|
||||
|
||||
/// <summary>Verifies invalid aggregate throws CommandException.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_InvalidAggregate_ThrowsCommandException()
|
||||
{
|
||||
@@ -123,6 +128,7 @@ public class HistoryReadCommandTests
|
||||
async () => await command.ExecuteAsync(console));
|
||||
}
|
||||
|
||||
/// <summary>Verifies disconnect is called in finally block.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_DisconnectsInFinally()
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Tests;
|
||||
/// </summary>
|
||||
public class InputValidationErrorsTests
|
||||
{
|
||||
/// <summary>Verifies that HistoryReadCommand with invalid start time throws CommandException.</summary>
|
||||
[Fact]
|
||||
public async Task HistoryReadCommand_InvalidStartTime_ThrowsCommandException()
|
||||
{
|
||||
@@ -30,6 +31,7 @@ public class InputValidationErrorsTests
|
||||
ex.Message.ShouldContain("--start");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that HistoryReadCommand with invalid end time throws CommandException.</summary>
|
||||
[Fact]
|
||||
public async Task HistoryReadCommand_InvalidEndTime_ThrowsCommandException()
|
||||
{
|
||||
@@ -47,6 +49,7 @@ public class InputValidationErrorsTests
|
||||
ex.Message.ShouldContain("--end");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that HistoryReadCommand with invalid aggregate throws CommandException.</summary>
|
||||
[Fact]
|
||||
public async Task HistoryReadCommand_InvalidAggregate_ThrowsCommandException()
|
||||
{
|
||||
@@ -64,6 +67,7 @@ public class InputValidationErrorsTests
|
||||
ex.Message.ShouldContain("aggregate", Case.Insensitive);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ReadCommand with invalid node ID throws CommandException.</summary>
|
||||
[Fact]
|
||||
public async Task ReadCommand_InvalidNodeId_ThrowsCommandException()
|
||||
{
|
||||
@@ -80,6 +84,7 @@ public class InputValidationErrorsTests
|
||||
ex.Message.ShouldContain("node", Case.Insensitive);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that SubscribeCommand with invalid node ID throws CommandException.</summary>
|
||||
[Fact]
|
||||
public async Task SubscribeCommand_InvalidNodeId_ThrowsCommandException()
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Tests;
|
||||
/// </summary>
|
||||
public class LoggerLifecycleTests
|
||||
{
|
||||
/// <summary>Verifies that ConfigureLogging disposes previous logger before reassigning.</summary>
|
||||
[Fact]
|
||||
public async Task ConfigureLogging_DisposesPreviousLogger_BeforeReassigning()
|
||||
{
|
||||
@@ -48,8 +49,12 @@ public class LoggerLifecycleTests
|
||||
|
||||
private sealed class DisposeTrackingSink : ILogEventSink, IDisposable
|
||||
{
|
||||
/// <summary>Gets a value indicating whether the sink has been disposed.</summary>
|
||||
public bool Disposed { get; private set; }
|
||||
/// <summary>Emits a log event.</summary>
|
||||
/// <param name="logEvent">The log event to emit.</param>
|
||||
public void Emit(LogEvent logEvent) { }
|
||||
/// <summary>Disposes the sink and marks it as disposed.</summary>
|
||||
public void Dispose() => Disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,24 +7,28 @@ namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Tests;
|
||||
|
||||
public class NodeIdParserTests
|
||||
{
|
||||
/// <summary>Verifies that Parse returns null for null input.</summary>
|
||||
[Fact]
|
||||
public void Parse_NullInput_ReturnsNull()
|
||||
{
|
||||
NodeIdParser.Parse(null).ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Parse returns null for empty string input.</summary>
|
||||
[Fact]
|
||||
public void Parse_EmptyString_ReturnsNull()
|
||||
{
|
||||
NodeIdParser.Parse("").ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Parse returns null for whitespace-only input.</summary>
|
||||
[Fact]
|
||||
public void Parse_WhitespaceOnly_ReturnsNull()
|
||||
{
|
||||
NodeIdParser.Parse(" ").ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Parse correctly parses standard NodeId format (ns=2;s=MyNode).</summary>
|
||||
[Fact]
|
||||
public void Parse_StandardStringFormat_ReturnsNodeId()
|
||||
{
|
||||
@@ -34,6 +38,7 @@ public class NodeIdParserTests
|
||||
result.Identifier.ShouldBe("MyNode");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Parse correctly parses numeric NodeId format (i=85).</summary>
|
||||
[Fact]
|
||||
public void Parse_NumericFormat_ReturnsNodeId()
|
||||
{
|
||||
@@ -42,6 +47,7 @@ public class NodeIdParserTests
|
||||
result.IdType.ShouldBe(IdType.Numeric);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Parse treats bare numeric input as namespace 0 numeric NodeId.</summary>
|
||||
[Fact]
|
||||
public void Parse_BareNumeric_ReturnsNamespace0NumericNodeId()
|
||||
{
|
||||
@@ -51,6 +57,7 @@ public class NodeIdParserTests
|
||||
result.Identifier.ShouldBe((uint)85);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Parse trims whitespace padding from input.</summary>
|
||||
[Fact]
|
||||
public void Parse_WithWhitespacePadding_Trims()
|
||||
{
|
||||
@@ -59,24 +66,28 @@ public class NodeIdParserTests
|
||||
result.Identifier.ShouldBe("MyNode");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Parse throws FormatException for invalid input.</summary>
|
||||
[Fact]
|
||||
public void Parse_InvalidFormat_ThrowsFormatException()
|
||||
{
|
||||
Should.Throw<FormatException>(() => NodeIdParser.Parse("not-a-node-id"));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ParseRequired throws ArgumentException for null input.</summary>
|
||||
[Fact]
|
||||
public void ParseRequired_NullInput_ThrowsArgumentException()
|
||||
{
|
||||
Should.Throw<ArgumentException>(() => NodeIdParser.ParseRequired(null));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ParseRequired throws ArgumentException for empty input.</summary>
|
||||
[Fact]
|
||||
public void ParseRequired_EmptyInput_ThrowsArgumentException()
|
||||
{
|
||||
Should.Throw<ArgumentException>(() => NodeIdParser.ParseRequired(""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ParseRequired returns a NodeId for valid input.</summary>
|
||||
[Fact]
|
||||
public void ParseRequired_ValidInput_ReturnsNodeId()
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Tests;
|
||||
|
||||
public class ReadCommandTests
|
||||
{
|
||||
/// <summary>Verifies that execute prints the read value.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_PrintsReadValue()
|
||||
{
|
||||
@@ -39,6 +40,7 @@ public class ReadCommandTests
|
||||
output.ShouldContain("Server Time:");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that execute calls read value with correct node ID.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_CallsReadValueWithCorrectNodeId()
|
||||
{
|
||||
@@ -57,6 +59,7 @@ public class ReadCommandTests
|
||||
fakeService.ReadNodeIds[0].Identifier.ShouldBe("MyVariable");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that execute disconnects in finally.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_DisconnectsInFinally()
|
||||
{
|
||||
@@ -75,6 +78,7 @@ public class ReadCommandTests
|
||||
fakeService.DisposeCalled.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that execute disconnects even on read error.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_DisconnectsEvenOnReadError()
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Tests;
|
||||
|
||||
public class RedundancyCommandTests
|
||||
{
|
||||
/// <summary>Verifies that Execute prints redundancy information correctly.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_PrintsRedundancyInfo()
|
||||
{
|
||||
@@ -34,6 +35,7 @@ public class RedundancyCommandTests
|
||||
output.ShouldContain("Application URI: urn:app:myserver");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Execute omits the Server URIs section when none are present.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_NoServerUris_OmitsUriSection()
|
||||
{
|
||||
@@ -58,6 +60,7 @@ public class RedundancyCommandTests
|
||||
output.ShouldContain("Application URI: urn:app:standalone");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Execute calls GetRedundancyInfo on the service.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_CallsGetRedundancyInfo()
|
||||
{
|
||||
@@ -74,6 +77,7 @@ public class RedundancyCommandTests
|
||||
fakeService.GetRedundancyInfoCalled.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Execute disconnects and disposes in the finally block.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_DisconnectsInFinally()
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Tests;
|
||||
/// </summary>
|
||||
public class SubscribeCommandSummaryTests
|
||||
{
|
||||
/// <summary>Verifies that nodes with no updates are counted separately from suspects.</summary>
|
||||
[Fact]
|
||||
public async Task Summary_NodeWithNoUpdate_IsCountedAsNeverNotAsNeverWentBad()
|
||||
{
|
||||
@@ -39,6 +40,7 @@ public class SubscribeCommandSummaryTests
|
||||
output.ShouldContain("--- Nodes that never received an update at all ---");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that nodes with only good values are counted as never went bad.</summary>
|
||||
[Fact]
|
||||
public async Task Summary_NodeReceivedOnlyGoodValues_IsCountedAsNeverWentBad()
|
||||
{
|
||||
@@ -70,6 +72,7 @@ public class SubscribeCommandSummaryTests
|
||||
output.ShouldContain("No update received at all: 0");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that nodes with bad values are counted as ever went bad.</summary>
|
||||
[Fact]
|
||||
public async Task Summary_NodeReceivedBadValue_IsCountedAsEverWentBad()
|
||||
{
|
||||
@@ -98,6 +101,7 @@ public class SubscribeCommandSummaryTests
|
||||
output.ShouldContain("NEVER went bad (suspect): 0");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that subscription auto-exits when duration expires.</summary>
|
||||
[Fact]
|
||||
public async Task Duration_ZeroOrPositive_AutoExits()
|
||||
{
|
||||
@@ -124,6 +128,7 @@ public class SubscribeCommandSummaryTests
|
||||
output.ShouldContain("==================== SUMMARY ====================");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that --quiet suppresses updates but prints summary.</summary>
|
||||
[Fact]
|
||||
public async Task Quiet_SuppressesPerUpdateOutputButPrintsSummary()
|
||||
{
|
||||
@@ -154,6 +159,7 @@ public class SubscribeCommandSummaryTests
|
||||
output.ShouldContain("==================== SUMMARY ====================");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that summary is written to disk when summary file is specified.</summary>
|
||||
[Fact]
|
||||
public async Task SummaryFile_WritesSummaryToDisk()
|
||||
{
|
||||
@@ -184,6 +190,7 @@ public class SubscribeCommandSummaryTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Verifies that recursive flag browses subtree and subscribes every variable.</summary>
|
||||
[Fact]
|
||||
public async Task Recursive_BrowsesSubtreeAndSubscribesEveryVariable()
|
||||
{
|
||||
@@ -222,6 +229,7 @@ public class SubscribeCommandSummaryTests
|
||||
output.ShouldContain("Browsing subtree of ns=2;s=Root");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that subscription failures are handled gracefully.</summary>
|
||||
[Fact]
|
||||
public async Task SubscribeFailure_PrintsFailedMessage_DoesNotCrash()
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Tests;
|
||||
|
||||
public class SubscribeCommandTests
|
||||
{
|
||||
/// <summary>Verifies that ExecuteAsync subscribes with the correct parameters.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_SubscribesWithCorrectParameters()
|
||||
{
|
||||
@@ -35,6 +36,7 @@ public class SubscribeCommandTests
|
||||
fakeService.SubscribeCalls[0].NodeId.Identifier.ShouldBe("TestVar");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ExecuteAsync unsubscribes when cancellation is requested.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_UnsubscribesOnCancellation()
|
||||
{
|
||||
@@ -57,6 +59,7 @@ public class SubscribeCommandTests
|
||||
fakeService.UnsubscribeCalls.Count.ShouldBe(1);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ExecuteAsync disconnects and disposes in a finally block.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_DisconnectsInFinally()
|
||||
{
|
||||
@@ -80,6 +83,7 @@ public class SubscribeCommandTests
|
||||
fakeService.DisposeCalled.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ExecuteAsync prints the correct subscription message.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_PrintsSubscriptionMessage()
|
||||
{
|
||||
|
||||
@@ -18,6 +18,8 @@ public static class TestConsoleHelper
|
||||
/// <summary>
|
||||
/// Reads all text written to the console's standard output.
|
||||
/// </summary>
|
||||
/// <param name="console">The fake console instance.</param>
|
||||
/// <returns>The console output text.</returns>
|
||||
public static string GetOutput(FakeInMemoryConsole console)
|
||||
{
|
||||
console.Output.Flush();
|
||||
@@ -27,6 +29,8 @@ public static class TestConsoleHelper
|
||||
/// <summary>
|
||||
/// Reads all text written to the console's standard error.
|
||||
/// </summary>
|
||||
/// <param name="console">The fake console instance.</param>
|
||||
/// <returns>The console error text.</returns>
|
||||
public static string GetError(FakeInMemoryConsole console)
|
||||
{
|
||||
console.Error.Flush();
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Tests;
|
||||
|
||||
public class WriteCommandTests
|
||||
{
|
||||
/// <summary>Verifies that write command executes successfully.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_WritesSuccessfully()
|
||||
{
|
||||
@@ -31,6 +32,7 @@ public class WriteCommandTests
|
||||
output.ShouldContain("Write successful: ns=2;s=MyVar = 100");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that write command reports failure.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_ReportsFailure()
|
||||
{
|
||||
@@ -54,6 +56,7 @@ public class WriteCommandTests
|
||||
output.ShouldContain("Write failed:");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that write command reads current value before writing.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_ReadsCurrentValueThenWrites()
|
||||
{
|
||||
@@ -79,6 +82,7 @@ public class WriteCommandTests
|
||||
fakeService.WriteValues[0].Value.ShouldBeOfType<double>();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that write command disconnects in finally block.</summary>
|
||||
[Fact]
|
||||
public async Task Execute_DisconnectsInFinally()
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace ZB.MOM.WW.OtOpcUa.Client.Shared.Tests;
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class ClientStoragePathsTests
|
||||
{
|
||||
/// <summary>Verifies that GetRoot returns the canonical folder name under LocalAppData.</summary>
|
||||
[Fact]
|
||||
public void GetRoot_ReturnsCanonicalFolderName_UnderLocalAppData()
|
||||
{
|
||||
@@ -15,6 +16,7 @@ public sealed class ClientStoragePathsTests
|
||||
root.ShouldContain(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that GetPkiPath nests PKI under root.</summary>
|
||||
[Fact]
|
||||
public void GetPkiPath_NestsPkiUnderRoot()
|
||||
{
|
||||
@@ -22,12 +24,14 @@ public sealed class ClientStoragePathsTests
|
||||
pki.ShouldEndWith(Path.Combine(ClientStoragePaths.CanonicalFolderName, "pki"));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that CanonicalFolderName is OtOpcUaClient.</summary>
|
||||
[Fact]
|
||||
public void CanonicalFolderName_IsOtOpcUaClient()
|
||||
{
|
||||
ClientStoragePaths.CanonicalFolderName.ShouldBe("OtOpcUaClient");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that LegacyFolderName is LmxOpcUaClient.</summary>
|
||||
[Fact]
|
||||
public void LegacyFolderName_IsLmxOpcUaClient()
|
||||
{
|
||||
@@ -36,6 +40,7 @@ public sealed class ClientStoragePathsTests
|
||||
ClientStoragePaths.LegacyFolderName.ShouldBe("LmxOpcUaClient");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that TryRunLegacyMigration returns false on repeat invocation.</summary>
|
||||
[Fact]
|
||||
public void TryRunLegacyMigration_Returns_False_On_Repeat_Invocation()
|
||||
{
|
||||
|
||||
+6
@@ -6,10 +6,16 @@ namespace ZB.MOM.WW.OtOpcUa.Client.Shared.Tests.Fakes;
|
||||
|
||||
internal sealed class FakeApplicationConfigurationFactory : IApplicationConfigurationFactory
|
||||
{
|
||||
/// <summary>Gets or sets a value indicating whether to throw when Create is called.</summary>
|
||||
public bool ThrowOnCreate { get; set; }
|
||||
|
||||
/// <summary>Gets the number of times CreateAsync has been called.</summary>
|
||||
public int CreateCallCount { get; private set; }
|
||||
|
||||
/// <summary>Gets the last connection settings passed to CreateAsync.</summary>
|
||||
public ConnectionSettings? LastSettings { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ApplicationConfiguration> CreateAsync(ConnectionSettings settings, CancellationToken ct)
|
||||
{
|
||||
CreateCallCount++;
|
||||
|
||||
@@ -5,10 +5,18 @@ namespace ZB.MOM.WW.OtOpcUa.Client.Shared.Tests.Fakes;
|
||||
|
||||
internal sealed class FakeEndpointDiscovery : IEndpointDiscovery
|
||||
{
|
||||
/// <summary>Gets or sets a value indicating whether SelectEndpoint should throw an exception.</summary>
|
||||
public bool ThrowOnSelect { get; set; }
|
||||
/// <summary>Gets the number of times SelectEndpoint has been called.</summary>
|
||||
public int SelectCallCount { get; private set; }
|
||||
/// <summary>Gets the last endpoint URL passed to SelectEndpoint.</summary>
|
||||
public string? LastEndpointUrl { get; private set; }
|
||||
|
||||
/// <summary>Selects an endpoint for the given configuration and URL, optionally throwing an exception.</summary>
|
||||
/// <param name="config">The application configuration.</param>
|
||||
/// <param name="endpointUrl">The endpoint URL to select.</param>
|
||||
/// <param name="requestedMode">The requested security mode.</param>
|
||||
/// <returns>The selected endpoint description.</returns>
|
||||
public EndpointDescription SelectEndpoint(ApplicationConfiguration config, string endpointUrl,
|
||||
MessageSecurityMode requestedMode)
|
||||
{
|
||||
|
||||
@@ -20,31 +20,53 @@ internal sealed class FakeSessionAdapter : ISessionAdapter
|
||||
/// Gets a value indicating whether the fake session has been disposed.
|
||||
/// </summary>
|
||||
public bool Disposed { get; private set; }
|
||||
/// <summary>Gets the number of times ReadValueAsync has been called.</summary>
|
||||
public int ReadCount { get; private set; }
|
||||
/// <summary>Gets the number of times WriteValueAsync has been called.</summary>
|
||||
public int WriteCount { get; private set; }
|
||||
/// <summary>Gets the number of times BrowseAsync has been called.</summary>
|
||||
public int BrowseCount { get; private set; }
|
||||
/// <summary>Gets the number of times BrowseNextAsync has been called.</summary>
|
||||
public int BrowseNextCount { get; private set; }
|
||||
/// <summary>Gets the number of times HasChildrenAsync has been called.</summary>
|
||||
public int HasChildrenCount { get; private set; }
|
||||
/// <summary>Gets the number of times HistoryReadRawAsync has been called.</summary>
|
||||
public int HistoryReadRawCount { get; private set; }
|
||||
/// <summary>Gets the number of times HistoryReadAggregateAsync has been called.</summary>
|
||||
public int HistoryReadAggregateCount { get; private set; }
|
||||
|
||||
// Configurable responses
|
||||
/// <summary>Gets or sets the data value returned by read operations.</summary>
|
||||
public DataValue? ReadResponse { get; set; }
|
||||
/// <summary>Gets or sets a function to generate read responses dynamically based on the node ID.</summary>
|
||||
public Func<NodeId, DataValue>? ReadResponseFunc { get; set; }
|
||||
/// <summary>Gets or sets the status code returned by write operations.</summary>
|
||||
public StatusCode WriteResponse { get; set; } = StatusCodes.Good;
|
||||
/// <summary>Gets or sets a value indicating whether read operations should throw an exception.</summary>
|
||||
public bool ThrowOnRead { get; set; }
|
||||
/// <summary>Gets or sets a value indicating whether write operations should throw an exception.</summary>
|
||||
public bool ThrowOnWrite { get; set; }
|
||||
/// <summary>Gets or sets a value indicating whether browse operations should throw an exception.</summary>
|
||||
public bool ThrowOnBrowse { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the browse references returned by browse operations.</summary>
|
||||
public ReferenceDescriptionCollection BrowseResponse { get; set; } = [];
|
||||
/// <summary>Gets or sets the continuation point for browse operations.</summary>
|
||||
public byte[]? BrowseContinuationPoint { get; set; }
|
||||
/// <summary>Gets or sets the browse references returned by browse-next operations.</summary>
|
||||
public ReferenceDescriptionCollection BrowseNextResponse { get; set; } = [];
|
||||
/// <summary>Gets or sets the continuation point for browse-next operations.</summary>
|
||||
public byte[]? BrowseNextContinuationPoint { get; set; }
|
||||
/// <summary>Gets or sets the result returned by has-children operations.</summary>
|
||||
public bool HasChildrenResponse { get; set; } = false;
|
||||
|
||||
/// <summary>Gets or sets the historical values returned by raw history-read operations.</summary>
|
||||
public List<DataValue> HistoryReadRawResponse { get; set; } = [];
|
||||
/// <summary>Gets or sets the historical values returned by aggregate history-read operations.</summary>
|
||||
public List<DataValue> HistoryReadAggregateResponse { get; set; } = [];
|
||||
/// <summary>Gets or sets a value indicating whether raw history-read operations should throw an exception.</summary>
|
||||
public bool ThrowOnHistoryReadRaw { get; set; }
|
||||
/// <summary>Gets or sets a value indicating whether aggregate history-read operations should throw an exception.</summary>
|
||||
public bool ThrowOnHistoryReadAggregate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -210,6 +232,7 @@ internal sealed class FakeSessionAdapter : ISessionAdapter
|
||||
/// <summary>
|
||||
/// Simulates a keep-alive event.
|
||||
/// </summary>
|
||||
/// <param name="isGood">Whether the keep-alive status is good.</param>
|
||||
public void SimulateKeepAlive(bool isGood)
|
||||
{
|
||||
_keepAliveCallback?.Invoke(isGood);
|
||||
|
||||
@@ -8,8 +8,11 @@ internal sealed class FakeSessionFactory : ISessionFactory
|
||||
private readonly List<FakeSessionAdapter> _createdSessions = [];
|
||||
private readonly Queue<FakeSessionAdapter> _sessions = new();
|
||||
|
||||
/// <summary>Gets the number of times CreateSessionAsync has been called.</summary>
|
||||
public int CreateCallCount { get; private set; }
|
||||
/// <summary>Gets or sets a value indicating whether CreateSessionAsync should throw.</summary>
|
||||
public bool ThrowOnCreate { get; set; }
|
||||
/// <summary>Gets the endpoint URL from the last CreateSessionAsync call.</summary>
|
||||
public string? LastEndpointUrl { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -18,8 +21,17 @@ internal sealed class FakeSessionFactory : ISessionFactory
|
||||
/// </summary>
|
||||
public TaskCompletionSource? CreateGate { get; set; }
|
||||
|
||||
/// <summary>Gets the list of sessions created via CreateSessionAsync.</summary>
|
||||
public IReadOnlyList<FakeSessionAdapter> CreatedSessions => _createdSessions;
|
||||
|
||||
/// <summary>Creates a session asynchronously (fake implementation).</summary>
|
||||
/// <param name="config">The application configuration.</param>
|
||||
/// <param name="endpoint">The endpoint description.</param>
|
||||
/// <param name="sessionName">The session name.</param>
|
||||
/// <param name="sessionTimeoutMs">The session timeout in milliseconds.</param>
|
||||
/// <param name="identity">The user identity.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>A task containing the session adapter.</returns>
|
||||
public async Task<ISessionAdapter> CreateSessionAsync(
|
||||
ApplicationConfiguration config, EndpointDescription endpoint, string sessionName,
|
||||
uint sessionTimeoutMs, UserIdentity identity, CancellationToken ct)
|
||||
@@ -54,6 +66,7 @@ internal sealed class FakeSessionFactory : ISessionFactory
|
||||
/// <summary>
|
||||
/// Enqueues a session adapter to be returned on the next call to CreateSessionAsync.
|
||||
/// </summary>
|
||||
/// <param name="session">The session adapter to enqueue.</param>
|
||||
public void EnqueueSession(FakeSessionAdapter session)
|
||||
{
|
||||
_sessions.Enqueue(session);
|
||||
|
||||
@@ -31,8 +31,13 @@ internal sealed class FakeSubscriptionAdapter : ISubscriptionAdapter
|
||||
/// Gets or sets a value indicating whether condition refresh should throw to simulate unsupported servers.
|
||||
/// </summary>
|
||||
public bool ThrowOnConditionRefresh { get; set; }
|
||||
/// <summary>Gets the number of times AddDataChangeMonitoredItemAsync was called.</summary>
|
||||
public int AddDataChangeCount { get; private set; }
|
||||
|
||||
/// <summary>Gets the number of times AddEventMonitoredItemAsync was called.</summary>
|
||||
public int AddEventCount { get; private set; }
|
||||
|
||||
/// <summary>Gets the number of times RemoveMonitoredItemAsync was called.</summary>
|
||||
public int RemoveCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -115,6 +120,8 @@ internal sealed class FakeSubscriptionAdapter : ISubscriptionAdapter
|
||||
/// <summary>
|
||||
/// Simulates a data change notification for testing.
|
||||
/// </summary>
|
||||
/// <param name="handle">The monitored item handle.</param>
|
||||
/// <param name="value">The new data value to simulate.</param>
|
||||
public void SimulateDataChange(uint handle, DataValue value)
|
||||
{
|
||||
(NodeId NodeId, Action<string, DataValue>? DataCallback, Action<EventFieldList>? EventCallback) item;
|
||||
@@ -129,6 +136,8 @@ internal sealed class FakeSubscriptionAdapter : ISubscriptionAdapter
|
||||
/// <summary>
|
||||
/// Simulates an event notification for testing.
|
||||
/// </summary>
|
||||
/// <param name="handle">The monitored item handle.</param>
|
||||
/// <param name="eventFields">The event field list to simulate.</param>
|
||||
public void SimulateEvent(uint handle, EventFieldList eventFields)
|
||||
{
|
||||
(NodeId NodeId, Action<string, DataValue>? DataCallback, Action<EventFieldList>? EventCallback) item;
|
||||
|
||||
+10
@@ -8,6 +8,8 @@ namespace ZB.MOM.WW.OtOpcUa.Client.Shared.Tests.Helpers;
|
||||
|
||||
public class AggregateTypeMapperTests
|
||||
{
|
||||
/// <summary>Verifies that ToNodeId returns a non-null NodeId for all AggregateType values.</summary>
|
||||
/// <param name="aggregate">The aggregate type to test.</param>
|
||||
[Theory]
|
||||
[InlineData(AggregateType.Average)]
|
||||
[InlineData(AggregateType.Minimum)]
|
||||
@@ -23,42 +25,49 @@ public class AggregateTypeMapperTests
|
||||
nodeId.IsNullNodeId.ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Average aggregate type maps to the correct OPC UA node ID.</summary>
|
||||
[Fact]
|
||||
public void ToNodeId_Average_MapsCorrectly()
|
||||
{
|
||||
AggregateTypeMapper.ToNodeId(AggregateType.Average).ShouldBe(ObjectIds.AggregateFunction_Average);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Minimum aggregate type maps to the correct OPC UA node ID.</summary>
|
||||
[Fact]
|
||||
public void ToNodeId_Minimum_MapsCorrectly()
|
||||
{
|
||||
AggregateTypeMapper.ToNodeId(AggregateType.Minimum).ShouldBe(ObjectIds.AggregateFunction_Minimum);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Maximum aggregate type maps to the correct OPC UA node ID.</summary>
|
||||
[Fact]
|
||||
public void ToNodeId_Maximum_MapsCorrectly()
|
||||
{
|
||||
AggregateTypeMapper.ToNodeId(AggregateType.Maximum).ShouldBe(ObjectIds.AggregateFunction_Maximum);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Count aggregate type maps to the correct OPC UA node ID.</summary>
|
||||
[Fact]
|
||||
public void ToNodeId_Count_MapsCorrectly()
|
||||
{
|
||||
AggregateTypeMapper.ToNodeId(AggregateType.Count).ShouldBe(ObjectIds.AggregateFunction_Count);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Start aggregate type maps to the correct OPC UA node ID.</summary>
|
||||
[Fact]
|
||||
public void ToNodeId_Start_MapsCorrectly()
|
||||
{
|
||||
AggregateTypeMapper.ToNodeId(AggregateType.Start).ShouldBe(ObjectIds.AggregateFunction_Start);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that End aggregate type maps to the correct OPC UA node ID.</summary>
|
||||
[Fact]
|
||||
public void ToNodeId_End_MapsCorrectly()
|
||||
{
|
||||
AggregateTypeMapper.ToNodeId(AggregateType.End).ShouldBe(ObjectIds.AggregateFunction_End);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that StandardDeviation aggregate type maps to the correct OPC UA node ID.</summary>
|
||||
[Fact]
|
||||
public void ToNodeId_StandardDeviation_MapsCorrectly()
|
||||
{
|
||||
@@ -66,6 +75,7 @@ public class AggregateTypeMapperTests
|
||||
.ShouldBe(ObjectIds.AggregateFunction_StandardDeviationPopulation);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that invalid aggregate type value throws ArgumentOutOfRangeException.</summary>
|
||||
[Fact]
|
||||
public void ToNodeId_InvalidValue_Throws()
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace ZB.MOM.WW.OtOpcUa.Client.Shared.Tests.Helpers;
|
||||
|
||||
public class FailoverUrlParserTests
|
||||
{
|
||||
/// <summary>Verifies that Parse returns only the primary URL when CSV failover list is null.</summary>
|
||||
[Fact]
|
||||
public void Parse_CsvNull_ReturnsPrimaryOnly()
|
||||
{
|
||||
@@ -13,6 +14,7 @@ public class FailoverUrlParserTests
|
||||
result.ShouldBe(["opc.tcp://primary:4840"]);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Parse returns only the primary URL when CSV failover list is empty.</summary>
|
||||
[Fact]
|
||||
public void Parse_CsvEmpty_ReturnsPrimaryOnly()
|
||||
{
|
||||
@@ -20,6 +22,7 @@ public class FailoverUrlParserTests
|
||||
result.ShouldBe(["opc.tcp://primary:4840"]);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Parse returns only the primary URL when CSV failover list is whitespace.</summary>
|
||||
[Fact]
|
||||
public void Parse_CsvWhitespace_ReturnsPrimaryOnly()
|
||||
{
|
||||
@@ -27,6 +30,7 @@ public class FailoverUrlParserTests
|
||||
result.ShouldBe(["opc.tcp://primary:4840"]);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Parse returns both primary and single failover URL.</summary>
|
||||
[Fact]
|
||||
public void Parse_SingleFailover_ReturnsBoth()
|
||||
{
|
||||
@@ -34,6 +38,7 @@ public class FailoverUrlParserTests
|
||||
result.ShouldBe(["opc.tcp://primary:4840", "opc.tcp://backup:4840"]);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Parse returns primary URL and all failover URLs from CSV list.</summary>
|
||||
[Fact]
|
||||
public void Parse_MultipleFailovers_ReturnsAll()
|
||||
{
|
||||
@@ -41,6 +46,7 @@ public class FailoverUrlParserTests
|
||||
result.ShouldBe(["opc.tcp://primary:4840", "opc.tcp://backup1:4840", "opc.tcp://backup2:4840"]);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Parse trims leading and trailing whitespace from failover URLs.</summary>
|
||||
[Fact]
|
||||
public void Parse_TrimsWhitespace()
|
||||
{
|
||||
@@ -48,6 +54,7 @@ public class FailoverUrlParserTests
|
||||
result.ShouldBe(["opc.tcp://primary:4840", "opc.tcp://backup:4840"]);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Parse deduplicates the primary URL if present in the failover list.</summary>
|
||||
[Fact]
|
||||
public void Parse_DeduplicatesPrimaryInFailoverList()
|
||||
{
|
||||
@@ -55,6 +62,7 @@ public class FailoverUrlParserTests
|
||||
result.ShouldBe(["opc.tcp://primary:4840", "opc.tcp://backup:4840"]);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Parse performs case-insensitive deduplication of URLs.</summary>
|
||||
[Fact]
|
||||
public void Parse_DeduplicatesCaseInsensitive()
|
||||
{
|
||||
@@ -62,6 +70,7 @@ public class FailoverUrlParserTests
|
||||
result.ShouldBe(["opc.tcp://Primary:4840"]);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Parse returns only the primary URL when array of failover URLs is null.</summary>
|
||||
[Fact]
|
||||
public void Parse_ArrayNull_ReturnsPrimaryOnly()
|
||||
{
|
||||
@@ -69,6 +78,7 @@ public class FailoverUrlParserTests
|
||||
result.ShouldBe(["opc.tcp://primary:4840"]);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Parse returns only the primary URL when array of failover URLs is empty.</summary>
|
||||
[Fact]
|
||||
public void Parse_ArrayEmpty_ReturnsPrimaryOnly()
|
||||
{
|
||||
@@ -76,6 +86,7 @@ public class FailoverUrlParserTests
|
||||
result.ShouldBe(["opc.tcp://primary:4840"]);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Parse returns primary URL and all failover URLs from array.</summary>
|
||||
[Fact]
|
||||
public void Parse_ArrayWithUrls_ReturnsAll()
|
||||
{
|
||||
@@ -84,6 +95,7 @@ public class FailoverUrlParserTests
|
||||
result.ShouldBe(["opc.tcp://primary:4840", "opc.tcp://backup1:4840", "opc.tcp://backup2:4840"]);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Parse deduplicates URLs from array if primary URL is present.</summary>
|
||||
[Fact]
|
||||
public void Parse_ArrayDeduplicates()
|
||||
{
|
||||
@@ -92,6 +104,7 @@ public class FailoverUrlParserTests
|
||||
result.ShouldBe(["opc.tcp://primary:4840", "opc.tcp://backup:4840"]);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Parse trims whitespace from URLs in array.</summary>
|
||||
[Fact]
|
||||
public void Parse_ArrayTrimsWhitespace()
|
||||
{
|
||||
@@ -100,6 +113,7 @@ public class FailoverUrlParserTests
|
||||
result.ShouldBe(["opc.tcp://primary:4840", "opc.tcp://backup:4840"]);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Parse skips null and empty URLs from array.</summary>
|
||||
[Fact]
|
||||
public void Parse_ArraySkipsNullAndEmpty()
|
||||
{
|
||||
|
||||
@@ -8,6 +8,9 @@ namespace ZB.MOM.WW.OtOpcUa.Client.Shared.Tests.Helpers;
|
||||
|
||||
public class SecurityModeMapperTests
|
||||
{
|
||||
/// <summary>Verifies ToMessageSecurityMode correctly maps SecurityMode values to OPC UA MessageSecurityMode.</summary>
|
||||
/// <param name="input">The SecurityMode value to map.</param>
|
||||
/// <param name="expected">The expected MessageSecurityMode result.</param>
|
||||
[Theory]
|
||||
[InlineData(SecurityMode.None, MessageSecurityMode.None)]
|
||||
[InlineData(SecurityMode.Sign, MessageSecurityMode.Sign)]
|
||||
@@ -17,6 +20,7 @@ public class SecurityModeMapperTests
|
||||
SecurityModeMapper.ToMessageSecurityMode(input).ShouldBe(expected);
|
||||
}
|
||||
|
||||
/// <summary>Verifies ToMessageSecurityMode throws on invalid SecurityMode values.</summary>
|
||||
[Fact]
|
||||
public void ToMessageSecurityMode_InvalidValue_Throws()
|
||||
{
|
||||
@@ -24,6 +28,9 @@ public class SecurityModeMapperTests
|
||||
SecurityModeMapper.ToMessageSecurityMode((SecurityMode)99));
|
||||
}
|
||||
|
||||
/// <summary>Verifies FromString correctly parses security mode strings (case-insensitive).</summary>
|
||||
/// <param name="input">The security mode string to parse.</param>
|
||||
/// <param name="expected">The expected SecurityMode result.</param>
|
||||
[Theory]
|
||||
[InlineData("none", SecurityMode.None)]
|
||||
[InlineData("None", SecurityMode.None)]
|
||||
@@ -38,18 +45,21 @@ public class SecurityModeMapperTests
|
||||
SecurityModeMapper.FromString(input).ShouldBe(expected);
|
||||
}
|
||||
|
||||
/// <summary>Verifies FromString correctly parses strings with leading and trailing whitespace.</summary>
|
||||
[Fact]
|
||||
public void FromString_WithWhitespace_ParsesCorrectly()
|
||||
{
|
||||
SecurityModeMapper.FromString(" sign ").ShouldBe(SecurityMode.Sign);
|
||||
}
|
||||
|
||||
/// <summary>Verifies FromString throws on unrecognized security mode strings.</summary>
|
||||
[Fact]
|
||||
public void FromString_UnknownValue_Throws()
|
||||
{
|
||||
Should.Throw<ArgumentException>(() => SecurityModeMapper.FromString("invalid"));
|
||||
}
|
||||
|
||||
/// <summary>Verifies FromString returns None when passed a null string.</summary>
|
||||
[Fact]
|
||||
public void FromString_Null_DefaultsToNone()
|
||||
{
|
||||
|
||||
@@ -6,96 +6,112 @@ namespace ZB.MOM.WW.OtOpcUa.Client.Shared.Tests.Helpers;
|
||||
|
||||
public class ValueConverterTests
|
||||
{
|
||||
/// <summary>Verifies that a boolean string "True" is converted to true.</summary>
|
||||
[Fact]
|
||||
public void ConvertValue_Bool_True()
|
||||
{
|
||||
ValueConverter.ConvertValue("True", true).ShouldBe(true);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a boolean string "False" is converted to false.</summary>
|
||||
[Fact]
|
||||
public void ConvertValue_Bool_False()
|
||||
{
|
||||
ValueConverter.ConvertValue("False", false).ShouldBe(false);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a byte value is converted correctly.</summary>
|
||||
[Fact]
|
||||
public void ConvertValue_Byte()
|
||||
{
|
||||
ValueConverter.ConvertValue("255", (byte)0).ShouldBe((byte)255);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a short value is converted correctly.</summary>
|
||||
[Fact]
|
||||
public void ConvertValue_Short()
|
||||
{
|
||||
ValueConverter.ConvertValue("-100", (short)0).ShouldBe((short)-100);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an unsigned short value is converted correctly.</summary>
|
||||
[Fact]
|
||||
public void ConvertValue_UShort()
|
||||
{
|
||||
ValueConverter.ConvertValue("65535", (ushort)0).ShouldBe((ushort)65535);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an integer value is converted correctly.</summary>
|
||||
[Fact]
|
||||
public void ConvertValue_Int()
|
||||
{
|
||||
ValueConverter.ConvertValue("42", 0).ShouldBe(42);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an unsigned integer value is converted correctly.</summary>
|
||||
[Fact]
|
||||
public void ConvertValue_UInt()
|
||||
{
|
||||
ValueConverter.ConvertValue("42", 0u).ShouldBe(42u);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a long value is converted correctly.</summary>
|
||||
[Fact]
|
||||
public void ConvertValue_Long()
|
||||
{
|
||||
ValueConverter.ConvertValue("9999999999", 0L).ShouldBe(9999999999L);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an unsigned long value is converted correctly.</summary>
|
||||
[Fact]
|
||||
public void ConvertValue_ULong()
|
||||
{
|
||||
ValueConverter.ConvertValue("18446744073709551615", 0UL).ShouldBe(ulong.MaxValue);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a float value is converted correctly.</summary>
|
||||
[Fact]
|
||||
public void ConvertValue_Float()
|
||||
{
|
||||
ValueConverter.ConvertValue("3.14", 0f).ShouldBe(3.14f);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a double value is converted correctly.</summary>
|
||||
[Fact]
|
||||
public void ConvertValue_Double()
|
||||
{
|
||||
ValueConverter.ConvertValue("3.14159", 0.0).ShouldBe(3.14159);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a string value is converted correctly when the current value is a string.</summary>
|
||||
[Fact]
|
||||
public void ConvertValue_String_WhenCurrentIsString()
|
||||
{
|
||||
ValueConverter.ConvertValue("hello", "").ShouldBe("hello");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a string value is converted correctly when the current value is null.</summary>
|
||||
[Fact]
|
||||
public void ConvertValue_String_WhenCurrentIsNull()
|
||||
{
|
||||
ValueConverter.ConvertValue("hello", null).ShouldBe("hello");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a string value is converted correctly when the current value is an unknown type.</summary>
|
||||
[Fact]
|
||||
public void ConvertValue_String_WhenCurrentIsUnknownType()
|
||||
{
|
||||
ValueConverter.ConvertValue("hello", new object()).ShouldBe("hello");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that converting an invalid boolean value throws a FormatException.</summary>
|
||||
[Fact]
|
||||
public void ConvertValue_InvalidBool_Throws()
|
||||
{
|
||||
Should.Throw<FormatException>(() => ValueConverter.ConvertValue("notabool", true));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that converting an invalid integer value throws a FormatException with a descriptive message.</summary>
|
||||
[Fact]
|
||||
public void ConvertValue_InvalidInt_ThrowsWithDescription()
|
||||
{
|
||||
@@ -104,6 +120,7 @@ public class ValueConverterTests
|
||||
ex.Message.ShouldContain("notanint");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an overflow during conversion throws a FormatException.</summary>
|
||||
[Fact]
|
||||
public void ConvertValue_Overflow_ThrowsFormatException()
|
||||
{
|
||||
@@ -114,6 +131,9 @@ public class ValueConverterTests
|
||||
|
||||
// --- Client.Shared-008: Boolean aliases ---
|
||||
|
||||
/// <summary>Verifies that boolean values accept numeric and word aliases.</summary>
|
||||
/// <param name="input">The input string value.</param>
|
||||
/// <param name="expected">The expected boolean value.</param>
|
||||
[Theory]
|
||||
[InlineData("1", true)]
|
||||
[InlineData("0", false)]
|
||||
@@ -130,6 +150,7 @@ public class ValueConverterTests
|
||||
ValueConverter.ConvertValue(input, true).ShouldBe(expected);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that converting an invalid boolean value throws a descriptive FormatException.</summary>
|
||||
[Fact]
|
||||
public void ConvertValue_InvalidBool_ThrowsDescriptiveFormatException()
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace ZB.MOM.WW.OtOpcUa.Client.Shared.Tests.Models;
|
||||
|
||||
public class ConnectionSettingsTests
|
||||
{
|
||||
/// <summary>Verifies that ConnectionSettings defaults are correct.</summary>
|
||||
[Fact]
|
||||
public void Defaults_AreCorrect()
|
||||
{
|
||||
@@ -24,6 +25,7 @@ public class ConnectionSettingsTests
|
||||
settings.CertificateStorePath.ShouldBe(string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that validation throws on null endpoint URL.</summary>
|
||||
[Fact]
|
||||
public void Validate_ThrowsOnNullEndpointUrl()
|
||||
{
|
||||
@@ -32,6 +34,7 @@ public class ConnectionSettingsTests
|
||||
.ParamName.ShouldBe("EndpointUrl");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that validation throws on empty endpoint URL.</summary>
|
||||
[Fact]
|
||||
public void Validate_ThrowsOnEmptyEndpointUrl()
|
||||
{
|
||||
@@ -40,6 +43,7 @@ public class ConnectionSettingsTests
|
||||
.ParamName.ShouldBe("EndpointUrl");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that validation throws on whitespace endpoint URL.</summary>
|
||||
[Fact]
|
||||
public void Validate_ThrowsOnWhitespaceEndpointUrl()
|
||||
{
|
||||
@@ -48,6 +52,7 @@ public class ConnectionSettingsTests
|
||||
.ParamName.ShouldBe("EndpointUrl");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that validation throws on zero timeout.</summary>
|
||||
[Fact]
|
||||
public void Validate_ThrowsOnZeroTimeout()
|
||||
{
|
||||
@@ -60,6 +65,7 @@ public class ConnectionSettingsTests
|
||||
.ParamName.ShouldBe("SessionTimeoutSeconds");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that validation throws on negative timeout.</summary>
|
||||
[Fact]
|
||||
public void Validate_ThrowsOnNegativeTimeout()
|
||||
{
|
||||
@@ -72,6 +78,7 @@ public class ConnectionSettingsTests
|
||||
.ParamName.ShouldBe("SessionTimeoutSeconds");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that validation throws on timeout above 3600 seconds.</summary>
|
||||
[Fact]
|
||||
public void Validate_ThrowsOnTimeoutAbove3600()
|
||||
{
|
||||
@@ -84,6 +91,7 @@ public class ConnectionSettingsTests
|
||||
.ParamName.ShouldBe("SessionTimeoutSeconds");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that validation succeeds with valid settings.</summary>
|
||||
[Fact]
|
||||
public void Validate_SucceedsWithValidSettings()
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace ZB.MOM.WW.OtOpcUa.Client.Shared.Tests.Models;
|
||||
|
||||
public class ModelConstructionTests
|
||||
{
|
||||
/// <summary>Verifies that BrowseResult constructs correctly with all properties.</summary>
|
||||
[Fact]
|
||||
public void BrowseResult_ConstructsCorrectly()
|
||||
{
|
||||
@@ -19,6 +20,7 @@ public class ModelConstructionTests
|
||||
result.HasChildren.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that BrowseResult correctly sets HasChildren to false.</summary>
|
||||
[Fact]
|
||||
public void BrowseResult_WithoutChildren()
|
||||
{
|
||||
@@ -26,6 +28,7 @@ public class ModelConstructionTests
|
||||
result.HasChildren.ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AlarmEventArgs constructs correctly with all properties.</summary>
|
||||
[Fact]
|
||||
public void AlarmEventArgs_ConstructsCorrectly()
|
||||
{
|
||||
@@ -42,6 +45,7 @@ public class ModelConstructionTests
|
||||
args.Time.ShouldBe(time);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that RedundancyInfo constructs correctly with all properties.</summary>
|
||||
[Fact]
|
||||
public void RedundancyInfo_ConstructsCorrectly()
|
||||
{
|
||||
@@ -54,6 +58,7 @@ public class ModelConstructionTests
|
||||
info.ApplicationUri.ShouldBe("urn:server1");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that RedundancyInfo handles empty URIs correctly.</summary>
|
||||
[Fact]
|
||||
public void RedundancyInfo_WithEmptyUris()
|
||||
{
|
||||
@@ -62,6 +67,7 @@ public class ModelConstructionTests
|
||||
info.ApplicationUri.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that DataChangedEventArgs constructs correctly with all properties.</summary>
|
||||
[Fact]
|
||||
public void DataChangedEventArgs_ConstructsCorrectly()
|
||||
{
|
||||
@@ -73,6 +79,7 @@ public class ModelConstructionTests
|
||||
args.Value.Value.ShouldBe(42);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ConnectionStateChangedEventArgs constructs correctly with all properties.</summary>
|
||||
[Fact]
|
||||
public void ConnectionStateChangedEventArgs_ConstructsCorrectly()
|
||||
{
|
||||
@@ -84,6 +91,7 @@ public class ModelConstructionTests
|
||||
args.EndpointUrl.ShouldBe("opc.tcp://localhost:4840");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ConnectionInfo constructs correctly with all properties.</summary>
|
||||
[Fact]
|
||||
public void ConnectionInfo_ConstructsCorrectly()
|
||||
{
|
||||
@@ -103,6 +111,7 @@ public class ModelConstructionTests
|
||||
info.SessionName.ShouldBe("TestSession");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that SecurityMode enum has the expected number of values.</summary>
|
||||
[Fact]
|
||||
public void SecurityMode_Enum_HasExpectedValues()
|
||||
{
|
||||
@@ -112,6 +121,7 @@ public class ModelConstructionTests
|
||||
((int)SecurityMode.SignAndEncrypt).ShouldBe(2);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ConnectionState enum has the expected number of values.</summary>
|
||||
[Fact]
|
||||
public void ConnectionState_Enum_HasExpectedValues()
|
||||
{
|
||||
@@ -122,6 +132,7 @@ public class ModelConstructionTests
|
||||
((int)ConnectionState.Reconnecting).ShouldBe(3);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AggregateType enum has the expected number of values.</summary>
|
||||
[Fact]
|
||||
public void AggregateType_Enum_HasExpectedValues()
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
private readonly OpcUaClientService _service;
|
||||
private readonly FakeSessionFactory _sessionFactory = new();
|
||||
|
||||
/// <summary>Initializes a new test instance with fake dependencies.</summary>
|
||||
public OpcUaClientServiceTests()
|
||||
{
|
||||
_service = new OpcUaClientService(_configFactory, _endpointDiscovery, _sessionFactory);
|
||||
|
||||
@@ -12,6 +12,7 @@ public class AlarmsViewModelTests
|
||||
private readonly FakeOpcUaClientService _service;
|
||||
private readonly AlarmsViewModel _vm;
|
||||
|
||||
/// <summary>Initializes a new test instance.</summary>
|
||||
public AlarmsViewModelTests()
|
||||
{
|
||||
_service = new FakeOpcUaClientService();
|
||||
@@ -19,6 +20,7 @@ public class AlarmsViewModelTests
|
||||
_vm = new AlarmsViewModel(_service, dispatcher);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that SubscribeCommand cannot execute when disconnected.</summary>
|
||||
[Fact]
|
||||
public void SubscribeCommand_CannotExecute_WhenDisconnected()
|
||||
{
|
||||
@@ -26,6 +28,7 @@ public class AlarmsViewModelTests
|
||||
_vm.SubscribeCommand.CanExecute(null).ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that SubscribeCommand cannot execute when already subscribed.</summary>
|
||||
[Fact]
|
||||
public void SubscribeCommand_CannotExecute_WhenAlreadySubscribed()
|
||||
{
|
||||
@@ -34,6 +37,7 @@ public class AlarmsViewModelTests
|
||||
_vm.SubscribeCommand.CanExecute(null).ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that SubscribeCommand can execute when connected and not subscribed.</summary>
|
||||
[Fact]
|
||||
public void SubscribeCommand_CanExecute_WhenConnectedAndNotSubscribed()
|
||||
{
|
||||
@@ -42,6 +46,7 @@ public class AlarmsViewModelTests
|
||||
_vm.SubscribeCommand.CanExecute(null).ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that SubscribeCommand sets IsSubscribed flag.</summary>
|
||||
[Fact]
|
||||
public async Task SubscribeCommand_SetsIsSubscribed()
|
||||
{
|
||||
@@ -53,6 +58,7 @@ public class AlarmsViewModelTests
|
||||
_service.SubscribeAlarmsCallCount.ShouldBe(1);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that UnsubscribeCommand cannot execute when not subscribed.</summary>
|
||||
[Fact]
|
||||
public void UnsubscribeCommand_CannotExecute_WhenNotSubscribed()
|
||||
{
|
||||
@@ -61,6 +67,7 @@ public class AlarmsViewModelTests
|
||||
_vm.UnsubscribeCommand.CanExecute(null).ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that UnsubscribeCommand clears IsSubscribed flag.</summary>
|
||||
[Fact]
|
||||
public async Task UnsubscribeCommand_ClearsIsSubscribed()
|
||||
{
|
||||
@@ -73,6 +80,7 @@ public class AlarmsViewModelTests
|
||||
_service.UnsubscribeAlarmsCallCount.ShouldBe(1);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that RefreshCommand calls the service.</summary>
|
||||
[Fact]
|
||||
public async Task RefreshCommand_CallsService()
|
||||
{
|
||||
@@ -84,6 +92,7 @@ public class AlarmsViewModelTests
|
||||
_service.RequestConditionRefreshCallCount.ShouldBe(1);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that RefreshCommand cannot execute when not subscribed.</summary>
|
||||
[Fact]
|
||||
public void RefreshCommand_CannotExecute_WhenNotSubscribed()
|
||||
{
|
||||
@@ -92,6 +101,7 @@ public class AlarmsViewModelTests
|
||||
_vm.RefreshCommand.CanExecute(null).ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that alarm events are added to the collection.</summary>
|
||||
[Fact]
|
||||
public void AlarmEvent_AddsToCollection()
|
||||
{
|
||||
@@ -108,6 +118,7 @@ public class AlarmsViewModelTests
|
||||
_vm.AlarmEvents[0].Message.ShouldBe("Temperature high");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Clear resets the view model state.</summary>
|
||||
[Fact]
|
||||
public void Clear_ResetsState()
|
||||
{
|
||||
@@ -120,6 +131,7 @@ public class AlarmsViewModelTests
|
||||
_vm.IsSubscribed.ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Teardown unregisters the event handler.</summary>
|
||||
[Fact]
|
||||
public void Teardown_UnhooksEventHandler()
|
||||
{
|
||||
@@ -133,6 +145,7 @@ public class AlarmsViewModelTests
|
||||
_vm.AlarmEvents.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the default polling interval is 1000ms.</summary>
|
||||
[Fact]
|
||||
public void DefaultInterval_Is1000()
|
||||
{
|
||||
|
||||
@@ -7,12 +7,14 @@ using BrowseResult = ZB.MOM.WW.OtOpcUa.Client.Shared.Models.BrowseResult;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Client.UI.Tests;
|
||||
|
||||
/// <summary>Tests for the BrowseTreeViewModel class.</summary>
|
||||
public class BrowseTreeViewModelTests
|
||||
{
|
||||
private readonly SynchronousUiDispatcher _dispatcher;
|
||||
private readonly FakeOpcUaClientService _service;
|
||||
private readonly BrowseTreeViewModel _vm;
|
||||
|
||||
/// <summary>Initializes a new instance of the BrowseTreeViewModelTests class.</summary>
|
||||
public BrowseTreeViewModelTests()
|
||||
{
|
||||
_service = new FakeOpcUaClientService
|
||||
@@ -27,6 +29,7 @@ public class BrowseTreeViewModelTests
|
||||
_vm = new BrowseTreeViewModel(_service, _dispatcher);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that LoadRootsAsync populates root nodes.</summary>
|
||||
[Fact]
|
||||
public async Task LoadRootsAsync_PopulatesRootNodes()
|
||||
{
|
||||
@@ -37,6 +40,7 @@ public class BrowseTreeViewModelTests
|
||||
_vm.RootNodes[1].DisplayName.ShouldBe("Node2");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that LoadRootsAsync browses with null parent.</summary>
|
||||
[Fact]
|
||||
public async Task LoadRootsAsync_BrowsesWithNullParent()
|
||||
{
|
||||
@@ -46,6 +50,7 @@ public class BrowseTreeViewModelTests
|
||||
_service.LastBrowseParentNodeId.ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Clear removes all root nodes.</summary>
|
||||
[Fact]
|
||||
public void Clear_RemovesAllRootNodes()
|
||||
{
|
||||
@@ -55,6 +60,7 @@ public class BrowseTreeViewModelTests
|
||||
_vm.RootNodes.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that nodes with children have a placeholder.</summary>
|
||||
[Fact]
|
||||
public async Task LoadRootsAsync_NodeWithChildren_HasPlaceholder()
|
||||
{
|
||||
@@ -66,6 +72,7 @@ public class BrowseTreeViewModelTests
|
||||
nodeWithChildren.Children[0].IsPlaceholder.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that nodes without children have no placeholder.</summary>
|
||||
[Fact]
|
||||
public async Task LoadRootsAsync_NodeWithoutChildren_HasNoPlaceholder()
|
||||
{
|
||||
@@ -76,6 +83,7 @@ public class BrowseTreeViewModelTests
|
||||
leafNode.Children.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that first tree node expand triggers child browse.</summary>
|
||||
[Fact]
|
||||
public async Task TreeNode_FirstExpand_TriggersChildBrowse()
|
||||
{
|
||||
@@ -105,6 +113,7 @@ public class BrowseTreeViewModelTests
|
||||
parent.Children[0].DisplayName.ShouldBe("Child1");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that second tree node expand does not browse again.</summary>
|
||||
[Fact]
|
||||
public async Task TreeNode_SecondExpand_DoesNotBrowseAgain()
|
||||
{
|
||||
@@ -133,6 +142,7 @@ public class BrowseTreeViewModelTests
|
||||
_service.BrowseCallCount.ShouldBe(browseCountAfterFirst);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that IsLoading transitions during browse.</summary>
|
||||
[Fact]
|
||||
public async Task TreeNode_IsLoading_TransitionsDuringBrowse()
|
||||
{
|
||||
|
||||
@@ -84,28 +84,50 @@ public sealed class FakeOpcUaClientService : IOpcUaClientService
|
||||
public Exception? HistoryException { get; set; }
|
||||
|
||||
// Call tracking
|
||||
/// <summary>Gets the number of times ConnectAsync has been called.</summary>
|
||||
public int ConnectCallCount { get; private set; }
|
||||
/// <summary>Gets the number of times DisconnectAsync has been called.</summary>
|
||||
public int DisconnectCallCount { get; private set; }
|
||||
/// <summary>Gets the number of times ReadValueAsync has been called.</summary>
|
||||
public int ReadCallCount { get; private set; }
|
||||
/// <summary>Gets the number of times WriteValueAsync has been called.</summary>
|
||||
public int WriteCallCount { get; private set; }
|
||||
/// <summary>Gets the number of times BrowseAsync has been called.</summary>
|
||||
public int BrowseCallCount { get; private set; }
|
||||
/// <summary>Gets the number of times SubscribeAsync has been called.</summary>
|
||||
public int SubscribeCallCount { get; private set; }
|
||||
/// <summary>Gets the number of times UnsubscribeAsync has been called.</summary>
|
||||
public int UnsubscribeCallCount { get; private set; }
|
||||
/// <summary>Gets the number of times SubscribeAlarmsAsync has been called.</summary>
|
||||
public int SubscribeAlarmsCallCount { get; private set; }
|
||||
/// <summary>Gets the number of times UnsubscribeAlarmsAsync has been called.</summary>
|
||||
public int UnsubscribeAlarmsCallCount { get; private set; }
|
||||
/// <summary>Gets the number of times RequestConditionRefreshAsync has been called.</summary>
|
||||
public int RequestConditionRefreshCallCount { get; private set; }
|
||||
/// <summary>Gets the number of times HistoryReadRawAsync has been called.</summary>
|
||||
public int HistoryReadRawCallCount { get; private set; }
|
||||
/// <summary>Gets the number of times HistoryReadAggregateAsync has been called.</summary>
|
||||
public int HistoryReadAggregateCallCount { get; private set; }
|
||||
/// <summary>Gets the number of times GetRedundancyInfoAsync has been called.</summary>
|
||||
public int GetRedundancyInfoCallCount { get; private set; }
|
||||
|
||||
/// <summary>Gets the connection settings from the last ConnectAsync call.</summary>
|
||||
public ConnectionSettings? LastConnectionSettings { get; private set; }
|
||||
/// <summary>Gets the node ID from the last ReadValueAsync call.</summary>
|
||||
public NodeId? LastReadNodeId { get; private set; }
|
||||
/// <summary>Gets the node ID from the last WriteValueAsync call.</summary>
|
||||
public NodeId? LastWriteNodeId { get; private set; }
|
||||
/// <summary>Gets the value from the last WriteValueAsync call.</summary>
|
||||
public object? LastWriteValue { get; private set; }
|
||||
/// <summary>Gets the parent node ID from the last BrowseAsync call.</summary>
|
||||
public NodeId? LastBrowseParentNodeId { get; private set; }
|
||||
/// <summary>Gets the node ID from the last SubscribeAsync call.</summary>
|
||||
public NodeId? LastSubscribeNodeId { get; private set; }
|
||||
/// <summary>Gets the interval in milliseconds from the last SubscribeAsync call.</summary>
|
||||
public int LastSubscribeIntervalMs { get; private set; }
|
||||
/// <summary>Gets the node ID from the last UnsubscribeAsync call.</summary>
|
||||
public NodeId? LastUnsubscribeNodeId { get; private set; }
|
||||
/// <summary>Gets the aggregate type from the last HistoryReadAggregateAsync call.</summary>
|
||||
public AggregateType? LastAggregateType { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -225,8 +247,11 @@ public sealed class FakeOpcUaClientService : IOpcUaClientService
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the status code returned by acknowledgment operations in UI tests.</summary>
|
||||
public StatusCode AcknowledgeResult { get; set; } = StatusCodes.Good;
|
||||
/// <summary>Gets or sets the exception thrown to simulate alarm acknowledgment failures in the UI.</summary>
|
||||
public Exception? AcknowledgeException { get; set; }
|
||||
/// <summary>Gets the number of times AcknowledgeAlarmAsync has been called.</summary>
|
||||
public int AcknowledgeCallCount { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -276,6 +301,7 @@ public sealed class FakeOpcUaClientService : IOpcUaClientService
|
||||
/// <summary>
|
||||
/// Raises a simulated data-change notification so UI tests can validate live update handling.
|
||||
/// </summary>
|
||||
/// <param name="args">The data change event arguments to raise.</param>
|
||||
public void RaiseDataChanged(DataChangedEventArgs args)
|
||||
{
|
||||
DataChanged?.Invoke(this, args);
|
||||
@@ -284,6 +310,7 @@ public sealed class FakeOpcUaClientService : IOpcUaClientService
|
||||
/// <summary>
|
||||
/// Raises a simulated alarm event so UI tests can validate alarm-list behavior.
|
||||
/// </summary>
|
||||
/// <param name="args">The alarm event arguments to raise.</param>
|
||||
public void RaiseAlarmEvent(AlarmEventArgs args)
|
||||
{
|
||||
AlarmEvent?.Invoke(this, args);
|
||||
@@ -292,6 +319,7 @@ public sealed class FakeOpcUaClientService : IOpcUaClientService
|
||||
/// <summary>
|
||||
/// Raises a simulated connection-state transition so UI tests can validate status presentation and failover behavior.
|
||||
/// </summary>
|
||||
/// <param name="args">The connection state changed event arguments to raise.</param>
|
||||
public void RaiseConnectionStateChanged(ConnectionStateChangedEventArgs args)
|
||||
{
|
||||
ConnectionStateChanged?.Invoke(this, args);
|
||||
|
||||
@@ -9,11 +9,15 @@ public sealed class FakeOpcUaClientServiceFactory : IOpcUaClientServiceFactory
|
||||
{
|
||||
private readonly FakeOpcUaClientService _service;
|
||||
|
||||
/// <summary>Initializes a new instance of FakeOpcUaClientServiceFactory.</summary>
|
||||
/// <param name="service">The fake OPC UA client service to return.</param>
|
||||
public FakeOpcUaClientServiceFactory(FakeOpcUaClientService service)
|
||||
{
|
||||
_service = service;
|
||||
}
|
||||
|
||||
/// <summary>Creates an OPC UA client service instance.</summary>
|
||||
/// <returns>The preconfigured fake OPC UA client service.</returns>
|
||||
public IOpcUaClientService Create()
|
||||
{
|
||||
return _service;
|
||||
|
||||
@@ -4,17 +4,24 @@ namespace ZB.MOM.WW.OtOpcUa.Client.UI.Tests.Fakes;
|
||||
|
||||
public sealed class FakeSettingsService : ISettingsService
|
||||
{
|
||||
/// <summary>Gets or sets the settings held by this fake service.</summary>
|
||||
public UserSettings Settings { get; set; } = new();
|
||||
/// <summary>Gets the number of times Load has been called.</summary>
|
||||
public int LoadCallCount { get; private set; }
|
||||
/// <summary>Gets the number of times Save has been called.</summary>
|
||||
public int SaveCallCount { get; private set; }
|
||||
/// <summary>Gets the last settings that were saved.</summary>
|
||||
public UserSettings? LastSaved { get; private set; }
|
||||
|
||||
/// <summary>Loads and returns the current settings.</summary>
|
||||
public UserSettings Load()
|
||||
{
|
||||
LoadCallCount++;
|
||||
return Settings;
|
||||
}
|
||||
|
||||
/// <summary>Saves the specified settings.</summary>
|
||||
/// <param name="settings">The settings to save.</param>
|
||||
public void Save(UserSettings settings)
|
||||
{
|
||||
SaveCallCount++;
|
||||
|
||||
@@ -13,6 +13,7 @@ public class HistoryViewModelTests
|
||||
private readonly FakeOpcUaClientService _service;
|
||||
private readonly HistoryViewModel _vm;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="HistoryViewModelTests"/> class.</summary>
|
||||
public HistoryViewModelTests()
|
||||
{
|
||||
_service = new FakeOpcUaClientService
|
||||
@@ -31,6 +32,7 @@ public class HistoryViewModelTests
|
||||
_vm = new HistoryViewModel(_service, dispatcher);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the read history command cannot execute when disconnected.</summary>
|
||||
[Fact]
|
||||
public void ReadHistoryCommand_CannotExecute_WhenDisconnected()
|
||||
{
|
||||
@@ -39,6 +41,7 @@ public class HistoryViewModelTests
|
||||
_vm.ReadHistoryCommand.CanExecute(null).ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the read history command cannot execute when no node is selected.</summary>
|
||||
[Fact]
|
||||
public void ReadHistoryCommand_CannotExecute_WhenNoNodeSelected()
|
||||
{
|
||||
@@ -47,6 +50,7 @@ public class HistoryViewModelTests
|
||||
_vm.ReadHistoryCommand.CanExecute(null).ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the read history command can execute when connected and a node is selected.</summary>
|
||||
[Fact]
|
||||
public void ReadHistoryCommand_CanExecute_WhenConnectedAndNodeSelected()
|
||||
{
|
||||
@@ -55,6 +59,7 @@ public class HistoryViewModelTests
|
||||
_vm.ReadHistoryCommand.CanExecute(null).ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a raw history read populates results correctly.</summary>
|
||||
[Fact]
|
||||
public async Task ReadHistoryCommand_Raw_PopulatesResults()
|
||||
{
|
||||
@@ -71,6 +76,7 @@ public class HistoryViewModelTests
|
||||
_service.HistoryReadAggregateCallCount.ShouldBe(0);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an aggregate history read populates results correctly.</summary>
|
||||
[Fact]
|
||||
public async Task ReadHistoryCommand_Aggregate_PopulatesResults()
|
||||
{
|
||||
@@ -87,6 +93,7 @@ public class HistoryViewModelTests
|
||||
_service.HistoryReadRawCallCount.ShouldBe(0);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the read history command clears previous results before loading new ones.</summary>
|
||||
[Fact]
|
||||
public async Task ReadHistoryCommand_ClearsResultsBefore()
|
||||
{
|
||||
@@ -99,6 +106,7 @@ public class HistoryViewModelTests
|
||||
_vm.Results.ShouldNotContain(r => r.Value == "old");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the loading state is false after the read history command completes.</summary>
|
||||
[Fact]
|
||||
public async Task ReadHistoryCommand_IsLoading_FalseAfterComplete()
|
||||
{
|
||||
@@ -110,6 +118,7 @@ public class HistoryViewModelTests
|
||||
_vm.IsLoading.ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that default values are initialized correctly.</summary>
|
||||
[Fact]
|
||||
public void DefaultValues_AreCorrect()
|
||||
{
|
||||
@@ -119,6 +128,7 @@ public class HistoryViewModelTests
|
||||
_vm.IsAggregateRead.ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that IsAggregateRead returns true when an aggregate type is selected.</summary>
|
||||
[Fact]
|
||||
public void IsAggregateRead_TrueWhenAggregateSelected()
|
||||
{
|
||||
@@ -126,6 +136,7 @@ public class HistoryViewModelTests
|
||||
_vm.IsAggregateRead.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the aggregate types collection contains null for raw reads.</summary>
|
||||
[Fact]
|
||||
public void AggregateTypes_ContainsNullForRaw()
|
||||
{
|
||||
@@ -133,6 +144,7 @@ public class HistoryViewModelTests
|
||||
_vm.AggregateTypes.Count.ShouldBe(8); // null + 7 enum values
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the Clear method resets the view model state.</summary>
|
||||
[Fact]
|
||||
public void Clear_ResetsState()
|
||||
{
|
||||
@@ -145,6 +157,7 @@ public class HistoryViewModelTests
|
||||
_vm.SelectedNodeId.ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that read history command errors are displayed in the results.</summary>
|
||||
[Fact]
|
||||
public async Task ReadHistoryCommand_Error_ShowsErrorInResults()
|
||||
{
|
||||
|
||||
@@ -18,6 +18,7 @@ public class MainWindowViewModelTests
|
||||
private readonly FakeSettingsService _settingsService;
|
||||
private readonly MainWindowViewModel _vm;
|
||||
|
||||
/// <summary>Initializes test fixtures with default client and settings services.</summary>
|
||||
public MainWindowViewModelTests()
|
||||
{
|
||||
_service = new FakeOpcUaClientService
|
||||
|
||||
@@ -12,6 +12,7 @@ public class ReadWriteViewModelTests
|
||||
private readonly FakeOpcUaClientService _service;
|
||||
private readonly ReadWriteViewModel _vm;
|
||||
|
||||
/// <summary>Initializes a new instance of the ReadWriteViewModelTests class.</summary>
|
||||
public ReadWriteViewModelTests()
|
||||
{
|
||||
_service = new FakeOpcUaClientService
|
||||
@@ -22,6 +23,7 @@ public class ReadWriteViewModelTests
|
||||
_vm = new ReadWriteViewModel(_service, dispatcher);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the read command cannot execute when disconnected.</summary>
|
||||
[Fact]
|
||||
public void ReadCommand_CannotExecute_WhenDisconnected()
|
||||
{
|
||||
@@ -30,6 +32,7 @@ public class ReadWriteViewModelTests
|
||||
_vm.ReadCommand.CanExecute(null).ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the read command cannot execute when no node is selected.</summary>
|
||||
[Fact]
|
||||
public void ReadCommand_CannotExecute_WhenNoNodeSelected()
|
||||
{
|
||||
@@ -38,6 +41,7 @@ public class ReadWriteViewModelTests
|
||||
_vm.ReadCommand.CanExecute(null).ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the read command can execute when connected and a node is selected.</summary>
|
||||
[Fact]
|
||||
public void ReadCommand_CanExecute_WhenConnectedAndNodeSelected()
|
||||
{
|
||||
@@ -46,6 +50,7 @@ public class ReadWriteViewModelTests
|
||||
_vm.ReadCommand.CanExecute(null).ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the read command updates value and status.</summary>
|
||||
[Fact]
|
||||
public async Task ReadCommand_UpdatesValueAndStatus()
|
||||
{
|
||||
@@ -63,6 +68,7 @@ public class ReadWriteViewModelTests
|
||||
(_service.ReadCallCount - countBefore).ShouldBe(1);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that auto-read fires on selection change when connected.</summary>
|
||||
[Fact]
|
||||
public void AutoRead_OnSelectionChange_WhenConnected()
|
||||
{
|
||||
@@ -74,6 +80,7 @@ public class ReadWriteViewModelTests
|
||||
_service.ReadCallCount.ShouldBeGreaterThanOrEqualTo(1);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that null selection does not call the service.</summary>
|
||||
[Fact]
|
||||
public void NullSelection_DoesNotCallService()
|
||||
{
|
||||
@@ -83,6 +90,7 @@ public class ReadWriteViewModelTests
|
||||
_service.ReadCallCount.ShouldBe(0);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the write command updates write status.</summary>
|
||||
[Fact]
|
||||
public async Task WriteCommand_UpdatesWriteStatus()
|
||||
{
|
||||
@@ -100,6 +108,7 @@ public class ReadWriteViewModelTests
|
||||
_service.LastWriteValue.ShouldBe("NewValue");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the write command cannot execute when disconnected.</summary>
|
||||
[Fact]
|
||||
public void WriteCommand_CannotExecute_WhenDisconnected()
|
||||
{
|
||||
@@ -108,6 +117,7 @@ public class ReadWriteViewModelTests
|
||||
_vm.WriteCommand.CanExecute(null).ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that read command error sets error status.</summary>
|
||||
[Fact]
|
||||
public async Task ReadCommand_Error_SetsErrorStatus()
|
||||
{
|
||||
@@ -121,6 +131,7 @@ public class ReadWriteViewModelTests
|
||||
_vm.CurrentStatus.ShouldContain("Error");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that clear resets all properties.</summary>
|
||||
[Fact]
|
||||
public void Clear_ResetsAllProperties()
|
||||
{
|
||||
@@ -140,6 +151,7 @@ public class ReadWriteViewModelTests
|
||||
_vm.WriteStatus.ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that IsNodeSelected tracks the selected node ID.</summary>
|
||||
[Fact]
|
||||
public void IsNodeSelected_TracksSelectedNodeId()
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@ public class SubscriptionsViewModelTests
|
||||
private readonly FakeOpcUaClientService _service;
|
||||
private readonly SubscriptionsViewModel _vm;
|
||||
|
||||
/// <summary>Initializes test instance with fake services.</summary>
|
||||
public SubscriptionsViewModelTests()
|
||||
{
|
||||
_service = new FakeOpcUaClientService();
|
||||
@@ -21,6 +22,7 @@ public class SubscriptionsViewModelTests
|
||||
_vm = new SubscriptionsViewModel(_service, dispatcher);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AddSubscriptionCommand cannot execute when disconnected.</summary>
|
||||
[Fact]
|
||||
public void AddSubscriptionCommand_CannotExecute_WhenDisconnected()
|
||||
{
|
||||
@@ -29,6 +31,7 @@ public class SubscriptionsViewModelTests
|
||||
_vm.AddSubscriptionCommand.CanExecute(null).ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AddSubscriptionCommand cannot execute without a node ID.</summary>
|
||||
[Fact]
|
||||
public void AddSubscriptionCommand_CannotExecute_WhenNoNodeId()
|
||||
{
|
||||
@@ -37,6 +40,7 @@ public class SubscriptionsViewModelTests
|
||||
_vm.AddSubscriptionCommand.CanExecute(null).ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AddSubscriptionCommand adds a new subscription to the active list.</summary>
|
||||
[Fact]
|
||||
public async Task AddSubscriptionCommand_AddsItem()
|
||||
{
|
||||
@@ -53,6 +57,7 @@ public class SubscriptionsViewModelTests
|
||||
_service.SubscribeCallCount.ShouldBe(1);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that RemoveSubscriptionCommand removes selected subscription.</summary>
|
||||
[Fact]
|
||||
public async Task RemoveSubscriptionCommand_RemovesItem()
|
||||
{
|
||||
@@ -68,6 +73,7 @@ public class SubscriptionsViewModelTests
|
||||
_service.UnsubscribeCallCount.ShouldBe(1);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that RemoveSubscriptionCommand cannot execute without selection.</summary>
|
||||
[Fact]
|
||||
public void RemoveSubscriptionCommand_CannotExecute_WhenNoSelection()
|
||||
{
|
||||
@@ -76,6 +82,7 @@ public class SubscriptionsViewModelTests
|
||||
_vm.RemoveSubscriptionCommand.CanExecute(null).ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that DataChanged event updates the matching subscription row.</summary>
|
||||
[Fact]
|
||||
public async Task DataChanged_UpdatesMatchingRow()
|
||||
{
|
||||
@@ -90,6 +97,7 @@ public class SubscriptionsViewModelTests
|
||||
_vm.ActiveSubscriptions[0].Status.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that DataChanged event does not update non-matching subscription rows.</summary>
|
||||
[Fact]
|
||||
public async Task DataChanged_DoesNotUpdateNonMatchingRow()
|
||||
{
|
||||
@@ -103,6 +111,7 @@ public class SubscriptionsViewModelTests
|
||||
_vm.ActiveSubscriptions[0].Value.ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Clear removes all subscriptions.</summary>
|
||||
[Fact]
|
||||
public void Clear_RemovesAllSubscriptions()
|
||||
{
|
||||
@@ -115,6 +124,7 @@ public class SubscriptionsViewModelTests
|
||||
_vm.SubscriptionCount.ShouldBe(0);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Teardown unregisters the event handler.</summary>
|
||||
[Fact]
|
||||
public void Teardown_UnhooksEventHandler()
|
||||
{
|
||||
@@ -128,12 +138,14 @@ public class SubscriptionsViewModelTests
|
||||
_vm.ActiveSubscriptions[0].Value.ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that default interval is 1000 milliseconds.</summary>
|
||||
[Fact]
|
||||
public void DefaultInterval_Is1000()
|
||||
{
|
||||
_vm.NewInterval.ShouldBe(1000);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AddSubscriptionForNodeAsync adds a subscription.</summary>
|
||||
[Fact]
|
||||
public async Task AddSubscriptionForNodeAsync_AddsSubscription()
|
||||
{
|
||||
@@ -147,6 +159,7 @@ public class SubscriptionsViewModelTests
|
||||
_service.SubscribeCallCount.ShouldBe(1);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AddSubscriptionForNodeAsync skips duplicate subscriptions.</summary>
|
||||
[Fact]
|
||||
public async Task AddSubscriptionForNodeAsync_SkipsDuplicate()
|
||||
{
|
||||
@@ -159,6 +172,7 @@ public class SubscriptionsViewModelTests
|
||||
_service.SubscribeCallCount.ShouldBe(1);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AddSubscriptionForNodeAsync does nothing when disconnected.</summary>
|
||||
[Fact]
|
||||
public async Task AddSubscriptionForNodeAsync_DoesNothing_WhenDisconnected()
|
||||
{
|
||||
@@ -170,6 +184,7 @@ public class SubscriptionsViewModelTests
|
||||
_service.SubscribeCallCount.ShouldBe(0);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that GetSubscribedNodeIds returns all active subscription node IDs.</summary>
|
||||
[Fact]
|
||||
public async Task GetSubscribedNodeIds_ReturnsActiveNodeIds()
|
||||
{
|
||||
@@ -184,6 +199,7 @@ public class SubscriptionsViewModelTests
|
||||
ids.ShouldContain("ns=2;s=Node2");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that RestoreSubscriptionsAsync subscribes to all provided node IDs.</summary>
|
||||
[Fact]
|
||||
public async Task RestoreSubscriptionsAsync_SubscribesAllNodes()
|
||||
{
|
||||
@@ -195,6 +211,7 @@ public class SubscriptionsViewModelTests
|
||||
_service.SubscribeCallCount.ShouldBe(2);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ValidateAndWriteAsync returns true on successful write.</summary>
|
||||
[Fact]
|
||||
public async Task ValidateAndWriteAsync_SuccessReturnsTrue()
|
||||
{
|
||||
@@ -209,6 +226,7 @@ public class SubscriptionsViewModelTests
|
||||
_service.WriteCallCount.ShouldBe(1);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ValidateAndWriteAsync returns false when value parsing fails.</summary>
|
||||
[Fact]
|
||||
public async Task ValidateAndWriteAsync_ParseFailureReturnsFalse()
|
||||
{
|
||||
@@ -223,6 +241,7 @@ public class SubscriptionsViewModelTests
|
||||
_service.WriteCallCount.ShouldBe(0);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ValidateAndWriteAsync returns false when write fails.</summary>
|
||||
[Fact]
|
||||
public async Task ValidateAndWriteAsync_WriteFailureReturnsFalse()
|
||||
{
|
||||
@@ -236,6 +255,7 @@ public class SubscriptionsViewModelTests
|
||||
message.ShouldContain("Access denied");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ValidateAndWriteAsync returns false when status is bad.</summary>
|
||||
[Fact]
|
||||
public async Task ValidateAndWriteAsync_BadStatusReturnsFalse()
|
||||
{
|
||||
@@ -249,6 +269,7 @@ public class SubscriptionsViewModelTests
|
||||
message.ShouldContain("Write failed");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AddSubscriptionRecursiveAsync subscribes a variable directly.</summary>
|
||||
[Fact]
|
||||
public async Task AddSubscriptionRecursiveAsync_SubscribesVariableDirectly()
|
||||
{
|
||||
@@ -260,6 +281,7 @@ public class SubscriptionsViewModelTests
|
||||
_vm.ActiveSubscriptions[0].NodeId.ShouldBe("ns=2;s=Var1");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AddSubscriptionRecursiveAsync browses objects and subscribes variable children.</summary>
|
||||
[Fact]
|
||||
public async Task AddSubscriptionRecursiveAsync_BrowsesObjectAndSubscribesVariableChildren()
|
||||
{
|
||||
@@ -311,6 +333,7 @@ public class SubscriptionsViewModelTests
|
||||
_vm.StatusMessage.ShouldContain("Bad node id");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AddSubscriptionRecursiveAsync recurses through nested objects.</summary>
|
||||
[Fact]
|
||||
public async Task AddSubscriptionRecursiveAsync_RecursesNestedObjects()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user