mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-25 11:50:03 +08:00
Cover the data channel Service forwarders on the client sessions
The advisory patch coverage gate reported 60.30% against a 75% floor. Most of
the shortfall is structural rather than untested code: Opc.Ua.Bindings.Quic is
29.8% of the changed source lines and 60 of the 316 data channel tests skip on
the CI runner because msquic is not installed there, so that assembly reads
close to zero in the merged report while measuring 80.65% locally, where QUIC
runs.
Two genuine gaps were in that list and are closed here. ManagedSession and
RedundantClientSession each forward the three data channel Services to the
session they wrap, and neither forwarder was exercised: the existing
integration test drives ISession directly, so it never passes through either
facade. A dropped forwarder would be invisible until a caller found the
Service silently doing nothing, and on the redundant facade it would leave a
channel opened against a session the caller can no longer reach after failover.
Both fixtures already assert this obligation for every other Service, so the
new cases join the table-driven passthrough suite rather than standing alone.
The [Experimental("DataChannels")] opt-in is scoped to the new cases with a
pragma rather than switched on for the whole test project, so the attribute
keeps its meaning everywhere else.
Opc.Ua.Client.Tests 2126 pass, Opc.Ua.Redundancy.Client.Tests 123 pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5d026b47-265d-4c97-8715-26e207dfa03c
This commit is contained in:
@@ -579,6 +579,28 @@ namespace Opc.Ua.Client.Tests.ManagedSession
|
||||
yield return Case<CancelRequest, CancelResponse>(
|
||||
"Cancel",
|
||||
m => m.CancelAsync(null, 0, default).AsTask());
|
||||
|
||||
// The data channel Services are marked [Experimental("DataChannels")]
|
||||
// so consumers opt in deliberately. This fixture asserts that every
|
||||
// Service on the managed session reaches the inner one, and that
|
||||
// obligation applies to an experimental Service as much as a stable
|
||||
// one, so the opt-in is scoped to these three cases rather than
|
||||
// switched on for the whole project.
|
||||
#pragma warning disable DataChannels
|
||||
yield return Case<OpenDataChannelRequest, OpenDataChannelResponse>(
|
||||
"OpenDataChannel",
|
||||
m => m.OpenDataChannelAsync(null,
|
||||
new NodeId(1u), 0, 0,
|
||||
new DataChannelParametersDataType(), default).AsTask());
|
||||
yield return Case<ModifyDataChannelRequest, ModifyDataChannelResponse>(
|
||||
"ModifyDataChannel",
|
||||
m => m.ModifyDataChannelAsync(null, 1,
|
||||
new DataChannelParametersDataType(), default).AsTask());
|
||||
yield return Case<CloseDataChannelRequest, CloseDataChannelResponse>(
|
||||
"CloseDataChannel",
|
||||
m => m.CloseDataChannelAsync(null, 1,
|
||||
StatusCodes.Good, false, default).AsTask());
|
||||
#pragma warning restore DataChannels
|
||||
}
|
||||
|
||||
private static TestCaseData Case<TRequest, TResponse>(
|
||||
|
||||
@@ -659,6 +659,65 @@ namespace Opc.Ua.Client.Redundancy.Tests
|
||||
Assert.That(await facade.CancelAsync(null, 1, ct).ConfigureAwait(false), Is.SameAs(cancel));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The data channel Services delegate to the current session like every
|
||||
/// other Service on the facade. A redundant client that silently dropped
|
||||
/// them would leave a channel opened against a session the caller can no
|
||||
/// longer reach after a failover.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task LeaderDataChannelServiceCallsDelegateToCurrentSessionAsync()
|
||||
{
|
||||
var session = new Mock<ISession>();
|
||||
RedundantClientSession facade = CreateLeaderFacade(session);
|
||||
CancellationToken ct = CancellationToken.None;
|
||||
|
||||
var opened = new OpenDataChannelResponse();
|
||||
var modified = new ModifyDataChannelResponse();
|
||||
var closed = new CloseDataChannelResponse();
|
||||
|
||||
// Scoped opt-in: the Services carry [Experimental("DataChannels")],
|
||||
// and the facade's delegation obligation applies to them regardless.
|
||||
#pragma warning disable DataChannels
|
||||
session.Setup(s => s.OpenDataChannelAsync(
|
||||
It.IsAny<RequestHeader>(),
|
||||
It.IsAny<NodeId>(),
|
||||
It.IsAny<uint>(),
|
||||
It.IsAny<ulong>(),
|
||||
It.IsAny<DataChannelParametersDataType>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Returns(new ValueTask<OpenDataChannelResponse>(opened));
|
||||
session.Setup(s => s.ModifyDataChannelAsync(
|
||||
It.IsAny<RequestHeader>(),
|
||||
It.IsAny<uint>(),
|
||||
It.IsAny<DataChannelParametersDataType>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Returns(new ValueTask<ModifyDataChannelResponse>(modified));
|
||||
session.Setup(s => s.CloseDataChannelAsync(
|
||||
It.IsAny<RequestHeader>(),
|
||||
It.IsAny<uint>(),
|
||||
It.IsAny<StatusCode>(),
|
||||
It.IsAny<bool>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Returns(new ValueTask<CloseDataChannelResponse>(closed));
|
||||
|
||||
Assert.Multiple(async () =>
|
||||
{
|
||||
Assert.That(
|
||||
await facade.OpenDataChannelAsync(null, new NodeId(1u), 0, 0, null, ct)
|
||||
.ConfigureAwait(false),
|
||||
Is.SameAs(opened));
|
||||
Assert.That(
|
||||
await facade.ModifyDataChannelAsync(null, 1, null, ct).ConfigureAwait(false),
|
||||
Is.SameAs(modified));
|
||||
Assert.That(
|
||||
await facade.CloseDataChannelAsync(null, 1, StatusCodes.Good, false, ct)
|
||||
.ConfigureAwait(false),
|
||||
Is.SameAs(closed));
|
||||
});
|
||||
#pragma warning restore DataChannels
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task LeaderConvenienceMethodsDelegateToCurrentSessionAsync()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user