test: pass TestContext.Current.CancellationToken at xUnit1051 call sites (analyzer code fix, 451 sites) (lmxopcua#725)

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
Joseph Doherty
2026-11-09 13:19:34 -05:00
parent 5db01053e8
commit 1d7a4b7a53
77 changed files with 452 additions and 457 deletions
@@ -21,9 +21,9 @@ public class AlarmsCommandTests
using var console = TestConsoleHelper.CreateConsole();
var task = Task.Run(async () => { await command.ExecuteAsync(console); });
var task = Task.Run(async () => { await command.ExecuteAsync(console); }, TestContext.Current.CancellationToken);
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10));
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
console.RequestCancellation();
await task;
@@ -46,9 +46,9 @@ public class AlarmsCommandTests
using var console = TestConsoleHelper.CreateConsole();
var task = Task.Run(async () => { await command.ExecuteAsync(console); });
var task = Task.Run(async () => { await command.ExecuteAsync(console); }, TestContext.Current.CancellationToken);
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10));
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
console.RequestCancellation();
await task;
@@ -71,9 +71,9 @@ public class AlarmsCommandTests
using var console = TestConsoleHelper.CreateConsole();
var task = Task.Run(async () => { await command.ExecuteAsync(console); });
var task = Task.Run(async () => { await command.ExecuteAsync(console); }, TestContext.Current.CancellationToken);
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10));
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
console.RequestCancellation();
await task;
@@ -91,13 +91,13 @@ public class AlarmsCommandTests
var command = new AlarmsCommand(factory) { Url = "opc.tcp://localhost:4840" };
using var console = TestConsoleHelper.CreateConsole();
var task = Task.Run(async () => { await command.ExecuteAsync(console); });
var task = Task.Run(async () => { await command.ExecuteAsync(console); }, TestContext.Current.CancellationToken);
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10));
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
fakeService.RaiseAlarmEvent(new Client.Shared.Models.AlarmEventArgs(
"src", "cond", 500, "msg", true, true, false, DateTime.UtcNow,
eventId: [0xAB, 0x01, 0xFF], conditionNodeId: "ns=2;s=Plant/cond"));
await Task.Delay(500);
await Task.Delay(500, TestContext.Current.CancellationToken);
console.RequestCancellation();
await task;
@@ -123,9 +123,9 @@ public class AlarmsCommandTests
using var console = TestConsoleHelper.CreateConsole();
var task = Task.Run(async () => { await command.ExecuteAsync(console); });
var task = Task.Run(async () => { await command.ExecuteAsync(console); }, TestContext.Current.CancellationToken);
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10));
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
console.RequestCancellation();
await task;
@@ -146,9 +146,9 @@ public class AlarmsCommandTests
using var console = TestConsoleHelper.CreateConsole();
var task = Task.Run(async () => { await command.ExecuteAsync(console); });
var task = Task.Run(async () => { await command.ExecuteAsync(console); }, TestContext.Current.CancellationToken);
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10));
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
console.RequestCancellation();
await task;
@@ -168,9 +168,9 @@ public class AlarmsCommandTests
using var console = TestConsoleHelper.CreateConsole();
var task = Task.Run(async () => { await command.ExecuteAsync(console); });
var task = Task.Run(async () => { await command.ExecuteAsync(console); }, TestContext.Current.CancellationToken);
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10));
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
console.RequestCancellation();
await task;
@@ -49,9 +49,9 @@ public class EventHandlerLifecycleTests
using var console = TestConsoleHelper.CreateConsole();
var task = Task.Run(async () => await command.ExecuteAsync(console));
var task = Task.Run(async () => await command.ExecuteAsync(console), TestContext.Current.CancellationToken);
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10));
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
console.RequestCancellation();
await task;
@@ -55,7 +55,7 @@ public class SubscribeCommandSummaryTests
};
using var console = TestConsoleHelper.CreateConsole();
var runTask = Task.Run(async () => await command.ExecuteAsync(console));
var runTask = Task.Run(async () => await command.ExecuteAsync(console), TestContext.Current.CancellationToken);
// Wait until the subscription is registered by the fake.
await WaitForAsync(() => fakeService.SubscribeCalls.Count == 1);
@@ -86,7 +86,7 @@ public class SubscribeCommandSummaryTests
};
using var console = TestConsoleHelper.CreateConsole();
var runTask = Task.Run(async () => await command.ExecuteAsync(console));
var runTask = Task.Run(async () => await command.ExecuteAsync(console), TestContext.Current.CancellationToken);
await WaitForAsync(() => fakeService.SubscribeCalls.Count == 1);
@@ -143,7 +143,7 @@ public class SubscribeCommandSummaryTests
};
using var console = TestConsoleHelper.CreateConsole();
var runTask = Task.Run(async () => await command.ExecuteAsync(console));
var runTask = Task.Run(async () => await command.ExecuteAsync(console), TestContext.Current.CancellationToken);
await WaitForAsync(() => fakeService.SubscribeCalls.Count == 1);
fakeService.RaiseDataChanged(
@@ -180,7 +180,7 @@ public class SubscribeCommandSummaryTests
await command.ExecuteAsync(console);
File.Exists(tempFile).ShouldBeTrue();
var contents = await File.ReadAllTextAsync(tempFile);
var contents = await File.ReadAllTextAsync(tempFile, TestContext.Current.CancellationToken);
contents.ShouldContain("SUMMARY");
contents.ShouldContain("Total subscribed: 1");
}
@@ -24,10 +24,10 @@ public class SubscribeCommandTests
// The subscribe command waits for cancellation. We need to cancel it.
// Use the console's cancellation to trigger stop.
var task = Task.Run(async () => { await command.ExecuteAsync(console); });
var task = Task.Run(async () => { await command.ExecuteAsync(console); }, TestContext.Current.CancellationToken);
// Wait until the command has actually subscribed (deterministic — no fixed-sleep race), then cancel
await fakeService.SubscribeInvoked.WaitAsync(TimeSpan.FromSeconds(10));
await fakeService.SubscribeInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
console.RequestCancellation();
await task;
@@ -50,9 +50,9 @@ public class SubscribeCommandTests
using var console = TestConsoleHelper.CreateConsole();
var task = Task.Run(async () => { await command.ExecuteAsync(console); });
var task = Task.Run(async () => { await command.ExecuteAsync(console); }, TestContext.Current.CancellationToken);
await fakeService.SubscribeInvoked.WaitAsync(TimeSpan.FromSeconds(10));
await fakeService.SubscribeInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
console.RequestCancellation();
await task;
@@ -73,9 +73,9 @@ public class SubscribeCommandTests
using var console = TestConsoleHelper.CreateConsole();
var task = Task.Run(async () => { await command.ExecuteAsync(console); });
var task = Task.Run(async () => { await command.ExecuteAsync(console); }, TestContext.Current.CancellationToken);
await fakeService.SubscribeInvoked.WaitAsync(TimeSpan.FromSeconds(10));
await fakeService.SubscribeInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
console.RequestCancellation();
await task;
@@ -98,9 +98,9 @@ public class SubscribeCommandTests
using var console = TestConsoleHelper.CreateConsole();
var task = Task.Run(async () => { await command.ExecuteAsync(console); });
var task = Task.Run(async () => { await command.ExecuteAsync(console); }, TestContext.Current.CancellationToken);
await fakeService.SubscribeInvoked.WaitAsync(TimeSpan.FromSeconds(10));
await fakeService.SubscribeInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
console.RequestCancellation();
await task;
@@ -58,12 +58,12 @@ public sealed class FailoverEpochTests : IDisposable
_factory.EnqueueSession(userSession); // dequeued by the user's Connect (the failover is still held)
_factory.EnqueueSession(failoverSession); // dequeued by the failover once released
await _service.DisconnectAsync();
await _service.ConnectAsync(Settings());
await _service.DisconnectAsync(TestContext.Current.CancellationToken);
await _service.ConnectAsync(Settings(), TestContext.Current.CancellationToken);
lock (_states) _states.Clear();
gate.SetResult();
await failover.WaitAsync(Wait);
await failover.WaitAsync(Wait, TestContext.Current.CancellationToken);
failoverSession.Disposed.ShouldBeTrue();
userSession.Disposed.ShouldBeFalse();
@@ -79,14 +79,14 @@ public sealed class FailoverEpochTests : IDisposable
var (_, failover, gate) = await StartHeldFailoverAsync();
_factory.EnqueueSession(new FakeSessionAdapter());
await _service.DisconnectAsync();
await _service.ConnectAsync(Settings());
await _service.DisconnectAsync(TestContext.Current.CancellationToken);
await _service.ConnectAsync(Settings(), TestContext.Current.CancellationToken);
lock (_states) _states.Clear();
var createsBefore = _factory.CreateCallCount;
_factory.ThrowOnCreate = true;
gate.SetResult();
await failover.WaitAsync(Wait);
await failover.WaitAsync(Wait, TestContext.Current.CancellationToken);
_service.IsConnected.ShouldBeTrue();
lock (_states) _states.ShouldBeEmpty();
@@ -94,8 +94,8 @@ public sealed class FailoverEpochTests : IDisposable
_factory.CreateCallCount.ShouldBe(createsBefore);
// And the user can still disconnect / reconnect normally afterwards.
_factory.ThrowOnCreate = false;
await _service.DisconnectAsync();
await _service.ConnectAsync(Settings());
await _service.DisconnectAsync(TestContext.Current.CancellationToken);
await _service.ConnectAsync(Settings(), TestContext.Current.CancellationToken);
_service.IsConnected.ShouldBeTrue();
}
@@ -107,7 +107,7 @@ public sealed class FailoverEpochTests : IDisposable
_factory.EnqueueSession(new FakeSessionAdapter());
gate.SetResult();
await failover.WaitAsync(Wait);
await failover.WaitAsync(Wait, TestContext.Current.CancellationToken);
_service.IsConnected.ShouldBeTrue();
_service.CurrentConnectionInfo!.EndpointUrl.ShouldBe(Backup);
@@ -123,15 +123,15 @@ public sealed class FailoverEpochTests : IDisposable
{
var s1 = new FakeSessionAdapter();
_factory.EnqueueSession(s1);
await _service.ConnectAsync(Settings());
await _service.DisconnectAsync();
await _service.ConnectAsync(Settings(), TestContext.Current.CancellationToken);
await _service.DisconnectAsync(TestContext.Current.CancellationToken);
_factory.EnqueueSession(new FakeSessionAdapter());
await _service.ConnectAsync(Settings());
await _service.ConnectAsync(Settings(), TestContext.Current.CancellationToken);
var creates = _factory.CreateCallCount;
lock (_states) _states.Clear();
s1.SimulateKeepAlive(false);
if (_service.LastFailover is { } t) await t.WaitAsync(Wait);
if (_service.LastFailover is { } t) await t.WaitAsync(Wait, TestContext.Current.CancellationToken);
_factory.CreateCallCount.ShouldBe(creates);
lock (_states) _states.ShouldBeEmpty();
@@ -150,7 +150,7 @@ public sealed class FailoverEpochTests : IDisposable
failover.IsCompleted.ShouldBeFalse();
_factory.EnqueueSession(new FakeSessionAdapter());
gate.SetResult();
await failover.WaitAsync(Wait);
await failover.WaitAsync(Wait, TestContext.Current.CancellationToken);
}
/// <summary>
@@ -163,15 +163,15 @@ public sealed class FailoverEpochTests : IDisposable
{
var sub = new FakeSubscriptionAdapter();
_factory.EnqueueSession(new FakeSessionAdapter { NextSubscription = sub });
await _service.ConnectAsync(Settings());
await _service.ConnectAsync(Settings(), TestContext.Current.CancellationToken);
var node = new Opc.Ua.NodeId("ns=2;s=X");
await _service.SubscribeAsync(node);
await _service.SubscribeAsync(node, ct: TestContext.Current.CancellationToken);
sub.ThrowOnRemove = true;
await Should.ThrowAsync<Opc.Ua.ServiceResultException>(() => _service.UnsubscribeAsync(node));
sub.ThrowOnRemove = false;
await _service.SubscribeAsync(node);
await _service.SubscribeAsync(node, ct: TestContext.Current.CancellationToken);
sub.AddDataChangeCount.ShouldBe(2);
}
}
@@ -47,7 +47,7 @@ public class OpcUaClientServiceTests : IDisposable
[Fact]
public async Task ConnectAsync_ValidSettings_ReturnsConnectionInfo()
{
var info = await _service.ConnectAsync(ValidSettings());
var info = await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
info.ShouldNotBeNull();
info.EndpointUrl.ShouldBe("opc.tcp://localhost:4840");
@@ -84,7 +84,7 @@ public class OpcUaClientServiceTests : IDisposable
};
_sessionFactory.EnqueueSession(session);
var info = await _service.ConnectAsync(ValidSettings());
var info = await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
info.ServerName.ShouldBe("MyServer");
info.SecurityMode.ShouldBe("Sign");
@@ -102,7 +102,7 @@ public class OpcUaClientServiceTests : IDisposable
var events = new List<ConnectionStateChangedEventArgs>();
_service.ConnectionStateChanged += (_, e) => events.Add(e);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
events.Count.ShouldBe(2);
events[0].OldState.ShouldBe(ConnectionState.Disconnected);
@@ -137,7 +137,7 @@ public class OpcUaClientServiceTests : IDisposable
settings.Username = "admin";
settings.Password = "secret";
await _service.ConnectAsync(settings);
await _service.ConnectAsync(settings, TestContext.Current.CancellationToken);
_configFactory.LastSettings!.Username.ShouldBe("admin");
_configFactory.LastSettings!.Password.ShouldBe("secret");
@@ -151,10 +151,10 @@ public class OpcUaClientServiceTests : IDisposable
[Fact]
public async Task DisconnectAsync_WhenConnected_ClosesSession()
{
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var session = _sessionFactory.CreatedSessions[0];
await _service.DisconnectAsync();
await _service.DisconnectAsync(TestContext.Current.CancellationToken);
session.Closed.ShouldBeTrue();
_service.IsConnected.ShouldBeFalse();
@@ -167,7 +167,7 @@ public class OpcUaClientServiceTests : IDisposable
[Fact]
public async Task DisconnectAsync_WhenNotConnected_IsIdempotent()
{
await _service.DisconnectAsync(); // Should not throw
await _service.DisconnectAsync(TestContext.Current.CancellationToken); // Should not throw
_service.IsConnected.ShouldBeFalse();
}
@@ -177,9 +177,9 @@ public class OpcUaClientServiceTests : IDisposable
[Fact]
public async Task DisconnectAsync_CalledTwice_IsIdempotent()
{
await _service.ConnectAsync(ValidSettings());
await _service.DisconnectAsync();
await _service.DisconnectAsync(); // Should not throw
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
await _service.DisconnectAsync(TestContext.Current.CancellationToken);
await _service.DisconnectAsync(TestContext.Current.CancellationToken); // Should not throw
}
// --- Read tests ---
@@ -195,9 +195,9 @@ public class OpcUaClientServiceTests : IDisposable
ReadResponse = new DataValue(new Variant(42), StatusCodes.Good)
};
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var result = await _service.ReadValueAsync(new NodeId("ns=2;s=MyNode"));
var result = await _service.ReadValueAsync(new NodeId("ns=2;s=MyNode"), TestContext.Current.CancellationToken);
result.Value.ShouldBe(42);
session.ReadCount.ShouldBe(1);
@@ -221,7 +221,7 @@ public class OpcUaClientServiceTests : IDisposable
{
var session = new FakeSessionAdapter { ThrowOnRead = true };
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
await Should.ThrowAsync<ServiceResultException>(() =>
_service.ReadValueAsync(new NodeId("ns=2;s=MyNode")));
@@ -241,9 +241,9 @@ public class OpcUaClientServiceTests : IDisposable
WriteResponse = StatusCodes.Good
};
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var result = await _service.WriteValueAsync(new NodeId("ns=2;s=MyNode"), 42);
var result = await _service.WriteValueAsync(new NodeId("ns=2;s=MyNode"), 42, TestContext.Current.CancellationToken);
result.ShouldBe(StatusCodes.Good);
session.WriteCount.ShouldBe(1);
@@ -260,9 +260,9 @@ public class OpcUaClientServiceTests : IDisposable
ReadResponse = new DataValue(new Variant(0), StatusCodes.Good) // int type
};
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
await _service.WriteValueAsync(new NodeId("ns=2;s=MyNode"), "42");
await _service.WriteValueAsync(new NodeId("ns=2;s=MyNode"), "42", TestContext.Current.CancellationToken);
session.WriteCount.ShouldBe(1);
session.ReadCount.ShouldBe(1); // Read for type inference
@@ -276,9 +276,9 @@ public class OpcUaClientServiceTests : IDisposable
{
var session = new FakeSessionAdapter();
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
await _service.WriteValueAsync(new NodeId("ns=2;s=MyNode"), 42);
await _service.WriteValueAsync(new NodeId("ns=2;s=MyNode"), 42, TestContext.Current.CancellationToken);
session.WriteCount.ShouldBe(1);
session.ReadCount.ShouldBe(0); // No read for non-string values
@@ -306,7 +306,7 @@ public class OpcUaClientServiceTests : IDisposable
ReadResponse = new DataValue(StatusCodes.BadNodeIdUnknown) // Bad status, null Value
};
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var ex = await Should.ThrowAsync<InvalidOperationException>(() =>
_service.WriteValueAsync(new NodeId("ns=2;s=MyNode"), "42"));
@@ -326,7 +326,7 @@ public class OpcUaClientServiceTests : IDisposable
ReadResponse = new DataValue(StatusCodes.BadNotReadable)
};
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var ex = await Should.ThrowAsync<InvalidOperationException>(() =>
_service.WriteValueAsync(new NodeId("ns=2;s=MyNode"), "42"));
@@ -355,9 +355,9 @@ public class OpcUaClientServiceTests : IDisposable
]
};
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var results = await _service.BrowseAsync();
var results = await _service.BrowseAsync(ct: TestContext.Current.CancellationToken);
results.Count.ShouldBe(1);
results[0].DisplayName.ShouldBe("Child1");
@@ -376,9 +376,9 @@ public class OpcUaClientServiceTests : IDisposable
BrowseResponse = []
};
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
await _service.BrowseAsync();
await _service.BrowseAsync(ct: TestContext.Current.CancellationToken);
session.BrowseCount.ShouldBe(1);
}
@@ -416,9 +416,9 @@ public class OpcUaClientServiceTests : IDisposable
]
};
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var results = await _service.BrowseAsync();
var results = await _service.BrowseAsync(ct: TestContext.Current.CancellationToken);
results.Select(r => r.HasChildren).ShouldBe([true, true, false]);
session.BrowseCount.ShouldBe(1);
@@ -455,9 +455,9 @@ public class OpcUaClientServiceTests : IDisposable
BrowseNextContinuationPoint = null
};
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var results = await _service.BrowseAsync();
var results = await _service.BrowseAsync(ct: TestContext.Current.CancellationToken);
results.Count.ShouldBe(2);
session.BrowseNextCount.ShouldBe(1);
@@ -482,9 +482,9 @@ public class OpcUaClientServiceTests : IDisposable
{
var session = new FakeSessionAdapter();
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
await _service.SubscribeAsync(new NodeId("ns=2;s=MyNode"), 500);
await _service.SubscribeAsync(new NodeId("ns=2;s=MyNode"), 500, TestContext.Current.CancellationToken);
session.CreatedSubscriptions.Count.ShouldBe(1);
session.CreatedSubscriptions[0].AddDataChangeCount.ShouldBe(1);
@@ -498,10 +498,10 @@ public class OpcUaClientServiceTests : IDisposable
{
var session = new FakeSessionAdapter();
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
await _service.SubscribeAsync(new NodeId("ns=2;s=MyNode"));
await _service.SubscribeAsync(new NodeId("ns=2;s=MyNode")); // duplicate
await _service.SubscribeAsync(new NodeId("ns=2;s=MyNode"), ct: TestContext.Current.CancellationToken);
await _service.SubscribeAsync(new NodeId("ns=2;s=MyNode"), ct: TestContext.Current.CancellationToken); // duplicate
session.CreatedSubscriptions[0].AddDataChangeCount.ShouldBe(1);
}
@@ -515,12 +515,12 @@ public class OpcUaClientServiceTests : IDisposable
var fakeSub = new FakeSubscriptionAdapter();
var session = new FakeSessionAdapter { NextSubscription = fakeSub };
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
DataChangedEventArgs? received = null;
_service.DataChanged += (_, e) => received = e;
await _service.SubscribeAsync(new NodeId("ns=2;s=MyNode"), 500);
await _service.SubscribeAsync(new NodeId("ns=2;s=MyNode"), 500, TestContext.Current.CancellationToken);
// Simulate data change
var handle = fakeSub.ActiveHandles.First();
@@ -540,10 +540,10 @@ public class OpcUaClientServiceTests : IDisposable
var fakeSub = new FakeSubscriptionAdapter();
var session = new FakeSessionAdapter { NextSubscription = fakeSub };
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
await _service.SubscribeAsync(new NodeId("ns=2;s=MyNode"));
await _service.UnsubscribeAsync(new NodeId("ns=2;s=MyNode"));
await _service.SubscribeAsync(new NodeId("ns=2;s=MyNode"), ct: TestContext.Current.CancellationToken);
await _service.UnsubscribeAsync(new NodeId("ns=2;s=MyNode"), TestContext.Current.CancellationToken);
fakeSub.RemoveCount.ShouldBe(1);
}
@@ -556,9 +556,9 @@ public class OpcUaClientServiceTests : IDisposable
{
var session = new FakeSessionAdapter();
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
await _service.UnsubscribeAsync(new NodeId("ns=2;s=NotSubscribed"));
await _service.UnsubscribeAsync(new NodeId("ns=2;s=NotSubscribed"), TestContext.Current.CancellationToken);
// Should not throw
}
@@ -582,9 +582,9 @@ public class OpcUaClientServiceTests : IDisposable
{
var session = new FakeSessionAdapter();
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
await _service.SubscribeAlarmsAsync();
await _service.SubscribeAlarmsAsync(ct: TestContext.Current.CancellationToken);
session.CreatedSubscriptions.Count.ShouldBe(1);
session.CreatedSubscriptions[0].AddEventCount.ShouldBe(1);
@@ -598,10 +598,10 @@ public class OpcUaClientServiceTests : IDisposable
{
var session = new FakeSessionAdapter();
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
await _service.SubscribeAlarmsAsync();
await _service.SubscribeAlarmsAsync(); // duplicate
await _service.SubscribeAlarmsAsync(ct: TestContext.Current.CancellationToken);
await _service.SubscribeAlarmsAsync(ct: TestContext.Current.CancellationToken); // duplicate
session.CreatedSubscriptions.Count.ShouldBe(1);
}
@@ -615,12 +615,12 @@ public class OpcUaClientServiceTests : IDisposable
var fakeSub = new FakeSubscriptionAdapter();
var session = new FakeSessionAdapter { NextSubscription = fakeSub };
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
AlarmEventArgs? received = null;
_service.AlarmEvent += (_, e) => received = e;
await _service.SubscribeAlarmsAsync();
await _service.SubscribeAlarmsAsync(ct: TestContext.Current.CancellationToken);
// Simulate alarm event with proper field count
var handle = fakeSub.ActiveHandles.First();
@@ -663,10 +663,10 @@ public class OpcUaClientServiceTests : IDisposable
var fakeSub = new FakeSubscriptionAdapter();
var session = new FakeSessionAdapter { NextSubscription = fakeSub };
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
await _service.SubscribeAlarmsAsync();
await _service.UnsubscribeAlarmsAsync();
await _service.SubscribeAlarmsAsync(ct: TestContext.Current.CancellationToken);
await _service.UnsubscribeAlarmsAsync(TestContext.Current.CancellationToken);
fakeSub.Deleted.ShouldBeTrue();
}
@@ -679,9 +679,9 @@ public class OpcUaClientServiceTests : IDisposable
{
var session = new FakeSessionAdapter();
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
await _service.UnsubscribeAlarmsAsync(); // Should not throw
await _service.UnsubscribeAlarmsAsync(TestContext.Current.CancellationToken); // Should not throw
}
/// <summary>
@@ -693,10 +693,10 @@ public class OpcUaClientServiceTests : IDisposable
var fakeSub = new FakeSubscriptionAdapter();
var session = new FakeSessionAdapter { NextSubscription = fakeSub };
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
await _service.SubscribeAlarmsAsync();
await _service.RequestConditionRefreshAsync();
await _service.SubscribeAlarmsAsync(ct: TestContext.Current.CancellationToken);
await _service.RequestConditionRefreshAsync(TestContext.Current.CancellationToken);
fakeSub.ConditionRefreshCalled.ShouldBeTrue();
}
@@ -709,7 +709,7 @@ public class OpcUaClientServiceTests : IDisposable
{
var session = new FakeSessionAdapter();
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
await Should.ThrowAsync<InvalidOperationException>(() =>
_service.RequestConditionRefreshAsync());
@@ -740,10 +740,9 @@ public class OpcUaClientServiceTests : IDisposable
};
var session = new FakeSessionAdapter { HistoryReadRawResponse = expectedValues };
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var results = await _service.HistoryReadRawAsync(
new NodeId("ns=2;s=Temp"), DateTime.UtcNow.AddHours(-1), DateTime.UtcNow);
var results = await _service.HistoryReadRawAsync(new NodeId("ns=2;s=Temp"), DateTime.UtcNow.AddHours(-1), DateTime.UtcNow, ct: TestContext.Current.CancellationToken);
results.Count.ShouldBe(2);
session.HistoryReadRawCount.ShouldBe(1);
@@ -767,7 +766,7 @@ public class OpcUaClientServiceTests : IDisposable
{
var session = new FakeSessionAdapter { ThrowOnHistoryReadRaw = true };
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
await Should.ThrowAsync<ServiceResultException>(() =>
_service.HistoryReadRawAsync(new NodeId("ns=2;s=Temp"), DateTime.UtcNow.AddHours(-1), DateTime.UtcNow));
@@ -785,11 +784,9 @@ public class OpcUaClientServiceTests : IDisposable
};
var session = new FakeSessionAdapter { HistoryReadAggregateResponse = expectedValues };
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var results = await _service.HistoryReadAggregateAsync(
new NodeId("ns=2;s=Temp"), DateTime.UtcNow.AddHours(-1), DateTime.UtcNow,
AggregateType.Average);
var results = await _service.HistoryReadAggregateAsync(new NodeId("ns=2;s=Temp"), DateTime.UtcNow.AddHours(-1), DateTime.UtcNow, AggregateType.Average, ct: TestContext.Current.CancellationToken);
results.Count.ShouldBe(1);
session.HistoryReadAggregateCount.ShouldBe(1);
@@ -815,7 +812,7 @@ public class OpcUaClientServiceTests : IDisposable
{
var session = new FakeSessionAdapter { ThrowOnHistoryReadAggregate = true };
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
await Should.ThrowAsync<ServiceResultException>(() =>
_service.HistoryReadAggregateAsync(
@@ -847,9 +844,9 @@ public class OpcUaClientServiceTests : IDisposable
}
};
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var info = await _service.GetRedundancyInfoAsync();
var info = await _service.GetRedundancyInfoAsync(TestContext.Current.CancellationToken);
info.Mode.ShouldBe("Warm");
info.ServiceLevel.ShouldBe((byte)200);
@@ -877,9 +874,9 @@ public class OpcUaClientServiceTests : IDisposable
}
};
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var info = await _service.GetRedundancyInfoAsync();
var info = await _service.GetRedundancyInfoAsync(TestContext.Current.CancellationToken);
info.Mode.ShouldBe("None");
info.ServiceLevel.ShouldBe((byte)100);
@@ -917,9 +914,9 @@ public class OpcUaClientServiceTests : IDisposable
}
};
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var info = await _service.GetRedundancyInfoAsync();
var info = await _service.GetRedundancyInfoAsync(TestContext.Current.CancellationToken);
info.Mode.ShouldBe("Warm");
info.ServiceLevel.ShouldBe((byte)200);
@@ -944,9 +941,9 @@ public class OpcUaClientServiceTests : IDisposable
}
};
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var info = await _service.GetRedundancyInfoAsync();
var info = await _service.GetRedundancyInfoAsync(TestContext.Current.CancellationToken);
info.Mode.ShouldBe("None");
info.ServiceLevel.ShouldBe((byte)0);
@@ -964,12 +961,12 @@ public class OpcUaClientServiceTests : IDisposable
var fakeSub = new FakeSubscriptionAdapter();
var session = new FakeSessionAdapter { NextSubscription = fakeSub };
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
AlarmEventArgs? received = null;
_service.AlarmEvent += (_, e) => received = e;
await _service.SubscribeAlarmsAsync();
await _service.SubscribeAlarmsAsync(ct: TestContext.Current.CancellationToken);
var handle = fakeSub.ActiveHandles.First();
// Only 3 fields — EventId, EventType, SourceName — fewer than the old < 6 threshold
@@ -998,12 +995,12 @@ public class OpcUaClientServiceTests : IDisposable
var fakeSub = new FakeSubscriptionAdapter();
var session = new FakeSessionAdapter { NextSubscription = fakeSub };
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var eventCount = 0;
_service.AlarmEvent += (_, _) => eventCount++;
await _service.SubscribeAlarmsAsync();
await _service.SubscribeAlarmsAsync(ct: TestContext.Current.CancellationToken);
var handle = fakeSub.ActiveHandles.First();
fakeSub.SimulateEvent(handle, new EventFieldList { EventFields = [] });
@@ -1022,9 +1019,9 @@ public class OpcUaClientServiceTests : IDisposable
{
var session = new FakeSessionAdapter();
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var result = await _service.AcknowledgeAlarmAsync("ns=2;s=Cond", new byte[] { 1, 2 }, "acked");
var result = await _service.AcknowledgeAlarmAsync("ns=2;s=Cond", new byte[] { 1, 2 }, "acked", TestContext.Current.CancellationToken);
result.ShouldBe(StatusCodes.Good);
session.CallMethodCount.ShouldBe(1);
@@ -1045,9 +1042,9 @@ public class OpcUaClientServiceTests : IDisposable
StatusCodes.BadConditionAlreadyEnabled, "already acked")
};
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var result = await _service.AcknowledgeAlarmAsync("ns=2;s=Cond", new byte[] { 1, 2 }, "acked");
var result = await _service.AcknowledgeAlarmAsync("ns=2;s=Cond", new byte[] { 1, 2 }, "acked", TestContext.Current.CancellationToken);
StatusCode.IsBad(result).ShouldBeTrue();
result.Code.ShouldBe(StatusCodes.BadConditionAlreadyEnabled);
@@ -1062,10 +1059,10 @@ public class OpcUaClientServiceTests : IDisposable
{
var session = new FakeSessionAdapter();
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
await _service.AcknowledgeAlarmAsync("ns=2;s=Plant/Alarm1", new byte[] { 1, 2 }, "acked");
await _service.ConfirmAlarmAsync("ns=2;s=Plant/Alarm1", new byte[] { 1, 2 }, "confirmed");
await _service.AcknowledgeAlarmAsync("ns=2;s=Plant/Alarm1", new byte[] { 1, 2 }, "acked", TestContext.Current.CancellationToken);
await _service.ConfirmAlarmAsync("ns=2;s=Plant/Alarm1", new byte[] { 1, 2 }, "confirmed", TestContext.Current.CancellationToken);
session.CallMethodObjectIds.ShouldAllBe(id => id == NodeId.Parse("ns=2;s=Plant/Alarm1"));
session.CallMethodMethodIds[0].ShouldBe(MethodIds.AcknowledgeableConditionType_Acknowledge);
@@ -1083,9 +1080,9 @@ public class OpcUaClientServiceTests : IDisposable
{
var session = new FakeSessionAdapter();
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var result = await _service.ConfirmAlarmAsync("ns=2;s=Cond", new byte[] { 1, 2 }, "confirmed");
var result = await _service.ConfirmAlarmAsync("ns=2;s=Cond", new byte[] { 1, 2 }, "confirmed", TestContext.Current.CancellationToken);
result.ShouldBe(StatusCodes.Good);
session.CallMethodCount.ShouldBe(1);
@@ -1104,9 +1101,9 @@ public class OpcUaClientServiceTests : IDisposable
StatusCodes.BadConditionAlreadyEnabled, "already confirmed")
};
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var result = await _service.ConfirmAlarmAsync("ns=2;s=Cond", new byte[] { 1, 2 }, "confirmed");
var result = await _service.ConfirmAlarmAsync("ns=2;s=Cond", new byte[] { 1, 2 }, "confirmed", TestContext.Current.CancellationToken);
StatusCode.IsBad(result).ShouldBeTrue();
result.Code.ShouldBe(StatusCodes.BadConditionAlreadyEnabled);
@@ -1122,9 +1119,9 @@ public class OpcUaClientServiceTests : IDisposable
{
var session = new FakeSessionAdapter();
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var result = await _service.ShelveAlarmAsync("ns=2;s=Cond", ShelveKind.OneShot);
var result = await _service.ShelveAlarmAsync("ns=2;s=Cond", ShelveKind.OneShot, ct: TestContext.Current.CancellationToken);
result.ShouldBe(StatusCodes.Good);
session.CallMethodCount.ShouldBe(1);
@@ -1142,10 +1139,10 @@ public class OpcUaClientServiceTests : IDisposable
{
var session = new FakeSessionAdapter();
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
const double durationSeconds = 30.0;
var result = await _service.ShelveAlarmAsync("ns=2;s=Cond", ShelveKind.Timed, durationSeconds);
var result = await _service.ShelveAlarmAsync("ns=2;s=Cond", ShelveKind.Timed, durationSeconds, TestContext.Current.CancellationToken);
result.ShouldBe(StatusCodes.Good);
session.CallMethodCount.ShouldBe(1);
@@ -1161,9 +1158,9 @@ public class OpcUaClientServiceTests : IDisposable
{
var session = new FakeSessionAdapter();
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var result = await _service.ShelveAlarmAsync("ns=2;s=Cond", ShelveKind.Unshelve);
var result = await _service.ShelveAlarmAsync("ns=2;s=Cond", ShelveKind.Unshelve, ct: TestContext.Current.CancellationToken);
result.ShouldBe(StatusCodes.Good);
session.CallMethodCount.ShouldBe(1);
@@ -1185,9 +1182,9 @@ public class OpcUaClientServiceTests : IDisposable
StatusCodes.BadConditionAlreadyShelved, "already shelved")
};
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var result = await _service.ShelveAlarmAsync("ns=2;s=Cond", ShelveKind.OneShot);
var result = await _service.ShelveAlarmAsync("ns=2;s=Cond", ShelveKind.OneShot, ct: TestContext.Current.CancellationToken);
StatusCode.IsBad(result).ShouldBeTrue();
result.Code.ShouldBe(StatusCodes.BadConditionAlreadyShelved);
@@ -1204,9 +1201,9 @@ public class OpcUaClientServiceTests : IDisposable
{
var session = new FakeSessionAdapter();
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var result = await _service.EnableAsync("ns=2;s=Cond");
var result = await _service.EnableAsync("ns=2;s=Cond", TestContext.Current.CancellationToken);
result.ShouldBe(StatusCodes.Good);
session.CallMethodCount.ShouldBe(1);
@@ -1224,9 +1221,9 @@ public class OpcUaClientServiceTests : IDisposable
{
var session = new FakeSessionAdapter();
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var result = await _service.DisableAsync("ns=2;s=Cond");
var result = await _service.DisableAsync("ns=2;s=Cond", TestContext.Current.CancellationToken);
result.ShouldBe(StatusCodes.Good);
session.CallMethodCount.ShouldBe(1);
@@ -1248,9 +1245,9 @@ public class OpcUaClientServiceTests : IDisposable
StatusCodes.BadConditionAlreadyEnabled, "already enabled")
};
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var result = await _service.EnableAsync("ns=2;s=Cond");
var result = await _service.EnableAsync("ns=2;s=Cond", TestContext.Current.CancellationToken);
StatusCode.IsBad(result).ShouldBeTrue();
result.Code.ShouldBe(StatusCodes.BadConditionAlreadyEnabled);
@@ -1269,9 +1266,9 @@ public class OpcUaClientServiceTests : IDisposable
StatusCodes.BadConditionAlreadyDisabled, "already disabled")
};
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var result = await _service.DisableAsync("ns=2;s=Cond");
var result = await _service.DisableAsync("ns=2;s=Cond", TestContext.Current.CancellationToken);
StatusCode.IsBad(result).ShouldBeTrue();
result.Code.ShouldBe(StatusCodes.BadConditionAlreadyDisabled);
@@ -1290,12 +1287,12 @@ public class OpcUaClientServiceTests : IDisposable
var fakeSub = new FakeSubscriptionAdapter();
var session = new FakeSessionAdapter { NextSubscription = fakeSub };
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
AlarmEventArgs? received = null;
_service.AlarmEvent += (_, e) => received = e;
await _service.SubscribeAlarmsAsync();
await _service.SubscribeAlarmsAsync(ct: TestContext.Current.CancellationToken);
var handle = fakeSub.ActiveHandles.First();
var fields = new EventFieldList
@@ -1348,13 +1345,13 @@ public class OpcUaClientServiceTests : IDisposable
var stateChanges = new List<ConnectionStateChangedEventArgs>();
_service.ConnectionStateChanged += (_, e) => stateChanges.Add(e);
await _service.ConnectAsync(settings);
await _service.ConnectAsync(settings, TestContext.Current.CancellationToken);
// Simulate keep-alive failure
session1.SimulateKeepAlive(false);
// Give async failover time to complete
await Task.Delay(200);
await Task.Delay(200, TestContext.Current.CancellationToken);
// Should have reconnected
stateChanges.ShouldContain(e => e.NewState == ConnectionState.Reconnecting);
@@ -1380,9 +1377,9 @@ public class OpcUaClientServiceTests : IDisposable
var settings = ValidSettings("opc.tcp://primary:4840");
settings.FailoverUrls = ["opc.tcp://backup:4840"];
await _service.ConnectAsync(settings);
await _service.ConnectAsync(settings, TestContext.Current.CancellationToken);
session1.SimulateKeepAlive(false);
await Task.Delay(200);
await Task.Delay(200, TestContext.Current.CancellationToken);
_service.CurrentConnectionInfo!.EndpointUrl.ShouldBe("opc.tcp://backup:4840");
_service.CurrentConnectionInfo.ServerName.ShouldBe("BackupServer");
@@ -1397,12 +1394,12 @@ public class OpcUaClientServiceTests : IDisposable
var session1 = new FakeSessionAdapter();
_sessionFactory.EnqueueSession(session1);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
// After the first session, make factory fail
_sessionFactory.ThrowOnCreate = true;
session1.SimulateKeepAlive(false);
await Task.Delay(200);
await Task.Delay(200, TestContext.Current.CancellationToken);
_service.IsConnected.ShouldBeFalse();
}
@@ -1423,7 +1420,7 @@ public class OpcUaClientServiceTests : IDisposable
var settings = ValidSettings("opc.tcp://primary:4840");
settings.FailoverUrls = ["opc.tcp://backup:4840"];
await _service.ConnectAsync(settings);
await _service.ConnectAsync(settings, TestContext.Current.CancellationToken);
var createCountAfterConnect = _sessionFactory.CreateCallCount; // 1
// Hold the failover's session creation open so it stays in-flight.
@@ -1439,7 +1436,7 @@ public class OpcUaClientServiceTests : IDisposable
// Release the gate so the in-flight failover completes.
gate.SetResult();
await Task.Delay(200);
await Task.Delay(200, TestContext.Current.CancellationToken);
// Exactly one extra session created by the single failover loop.
_sessionFactory.CreateCallCount.ShouldBe(createCountAfterConnect + 1);
@@ -1455,7 +1452,7 @@ public class OpcUaClientServiceTests : IDisposable
{
var session1 = new FakeSessionAdapter();
_sessionFactory.EnqueueSession(session1);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
await Should.ThrowAsync<InvalidOperationException>(() => _service.ConnectAsync(ValidSettings()));
@@ -1463,8 +1460,8 @@ public class OpcUaClientServiceTests : IDisposable
session1.Disposed.ShouldBeFalse();
_service.IsConnected.ShouldBeTrue();
await _service.DisconnectAsync();
await _service.ConnectAsync(ValidSettings());
await _service.DisconnectAsync(TestContext.Current.CancellationToken);
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
_sessionFactory.CreateCallCount.ShouldBe(2);
_service.IsConnected.ShouldBeTrue();
}
@@ -1484,26 +1481,26 @@ public class OpcUaClientServiceTests : IDisposable
_sessionFactory.EnqueueSession(session2);
var settings = ValidSettings("opc.tcp://primary:4840");
settings.FailoverUrls = ["opc.tcp://backup:4840"];
await _service.ConnectAsync(settings);
await _service.ConnectAsync(settings, TestContext.Current.CancellationToken);
var gate = new TaskCompletionSource();
session1.CreateSubscriptionGate = gate;
var subscribe = _service.SubscribeAsync(new NodeId("ns=2;s=Racer"), 1000);
await session1.CreateSubscriptionEntered.Task.WaitAsync(TimeSpan.FromSeconds(10));
var subscribe = _service.SubscribeAsync(new NodeId("ns=2;s=Racer"), 1000, TestContext.Current.CancellationToken);
await session1.CreateSubscriptionEntered.Task.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
session1.SimulateKeepAlive(false);
var failover = _service.LastFailover.ShouldNotBeNull();
gate.SetResult();
await subscribe.WaitAsync(TimeSpan.FromSeconds(10));
await failover.WaitAsync(TimeSpan.FromSeconds(10));
await subscribe.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
await failover.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
_service.CurrentConnectionInfo!.EndpointUrl.ShouldBe("opc.tcp://backup:4840");
var replayed = session2.CreatedSubscriptions.ShouldHaveSingleItem();
replayed.AddDataChangeCount.ShouldBe(1);
// And it is the live subscription: unsubscribing removes it from session2's subscription, not session1's.
await _service.UnsubscribeAsync(new NodeId("ns=2;s=Racer"));
await _service.UnsubscribeAsync(new NodeId("ns=2;s=Racer"), TestContext.Current.CancellationToken);
replayed.RemoveCount.ShouldBe(1);
replayed.ActiveHandles.ShouldBeEmpty();
}
@@ -1522,16 +1519,16 @@ public class OpcUaClientServiceTests : IDisposable
_sessionFactory.EnqueueSession(session2);
var settings = ValidSettings("opc.tcp://primary:4840");
settings.FailoverUrls = ["opc.tcp://backup:4840"];
await _service.ConnectAsync(settings);
await _service.ConnectAsync(settings, TestContext.Current.CancellationToken);
var gate = new TaskCompletionSource();
_sessionFactory.CreateGate = gate;
session1.SimulateKeepAlive(false);
var failover = _service.LastFailover.ShouldNotBeNull();
await _service.DisconnectAsync();
await _service.DisconnectAsync(TestContext.Current.CancellationToken);
gate.SetResult();
await failover.WaitAsync(TimeSpan.FromSeconds(10));
await failover.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
_service.IsConnected.ShouldBeFalse();
session2.Disposed.ShouldBeTrue();
@@ -1547,7 +1544,7 @@ public class OpcUaClientServiceTests : IDisposable
var fakeSub = new FakeSubscriptionAdapter();
var session = new FakeSessionAdapter { NextSubscription = fakeSub };
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
var tasks = new List<Task>();
for (var i = 0; i < 50; i++)
@@ -1574,7 +1571,7 @@ public class OpcUaClientServiceTests : IDisposable
{
var session = new FakeSessionAdapter();
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
_service.Dispose();
@@ -1645,14 +1642,14 @@ public class OpcUaClientServiceTests : IDisposable
// Second CreateSubscription call should return the alarm fake.
// We wire it by overriding the factory queue after the first subscribe.
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
// Subscribe data (uses dataFakeSub from session.NextSubscription).
await _service.SubscribeAsync(new NodeId("ns=2;s=Node1"));
await _service.SubscribeAsync(new NodeId("ns=2;s=Node1"), ct: TestContext.Current.CancellationToken);
// Now set NextSubscription on the session so SubscribeAlarmsAsync gets the alarm fake.
session.NextSubscription = alarmFakeSub;
await _service.SubscribeAlarmsAsync();
await _service.SubscribeAlarmsAsync(ct: TestContext.Current.CancellationToken);
// Both subscriptions are now active. Disconnect with a cancellable token.
using var cts = new CancellationTokenSource();
@@ -1681,7 +1678,7 @@ public class OpcUaClientServiceTests : IDisposable
var fakeSub = new FakeSubscriptionAdapter();
var session = new FakeSessionAdapter { NextSubscription = fakeSub };
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
// Fire many concurrent SubscribeAlarmsAsync calls — only one should "win".
var tasks = Enumerable.Range(0, 20).Select(_ =>
@@ -1706,9 +1703,9 @@ public class OpcUaClientServiceTests : IDisposable
var fakeSub = new FakeSubscriptionAdapter();
var session = new FakeSessionAdapter { NextSubscription = fakeSub };
_sessionFactory.EnqueueSession(session);
await _service.ConnectAsync(ValidSettings());
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
await _service.SubscribeAlarmsAsync();
await _service.SubscribeAlarmsAsync(ct: TestContext.Current.CancellationToken);
using var cts = new CancellationTokenSource();
await _service.UnsubscribeAlarmsAsync(cts.Token);
@@ -106,7 +106,7 @@ public class BrowseTreeViewModelTests
parent.IsExpanded = true;
// Allow async operation to complete
await Task.Delay(50);
await Task.Delay(50, TestContext.Current.CancellationToken);
_service.BrowseCallCount.ShouldBe(initialBrowseCount + 1);
parent.Children.Count.ShouldBe(1);
@@ -131,13 +131,13 @@ public class BrowseTreeViewModelTests
var parent = _vm.RootNodes[0];
parent.IsExpanded = true;
await Task.Delay(50);
await Task.Delay(50, TestContext.Current.CancellationToken);
var browseCountAfterFirst = _service.BrowseCallCount;
parent.IsExpanded = false;
parent.IsExpanded = true;
await Task.Delay(50);
await Task.Delay(50, TestContext.Current.CancellationToken);
_service.BrowseCallCount.ShouldBe(browseCountAfterFirst);
}
@@ -157,7 +157,7 @@ public class BrowseTreeViewModelTests
var parent = _vm.RootNodes[0];
parent.IsExpanded = true;
await Task.Delay(50);
await Task.Delay(50, TestContext.Current.CancellationToken);
// After completion, IsLoading should be false
parent.IsLoading.ShouldBeFalse();