|
|
|
@@ -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);
|
|
|
|
|