review(Driver.OpcUaClient.Browser): AttributesAsync updates LastUsedUtc

Review at HEAD 7286d320. -001: AttributesAsync now updates LastUsedUtc (IBrowseSession
contract) + test (InternalsVisibleTo+Moq added). -002 (continuation-point cancel leak)
deferred cross-cutting w/ runtime Driver.OpcUaClient.
This commit is contained in:
Joseph Doherty
2026-06-19 10:52:23 -04:00
parent 960d76ffcb
commit 2fe8e587dd
5 changed files with 122 additions and 2 deletions
@@ -1,5 +1,9 @@
using Moq;
using Opc.Ua;
using Opc.Ua.Client;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient;
using ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser;
namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser.Tests;
@@ -49,4 +53,39 @@ public sealed class OpcUaClientDriverBrowserTests
() => _sut.OpenAsync(json, TestContext.Current.CancellationToken));
ex.Message.ShouldContain("Certificate");
}
// ---- Driver.OpcUaClient.Browser-001: AttributesAsync must refresh LastUsedUtc ----
/// <summary>
/// <see cref="IBrowseSession.LastUsedUtc"/> must be updated on every call
/// including <see cref="OpcUaClientBrowseSession.AttributesAsync"/>, which the
/// <see cref="BrowseSessionReaper"/> uses for idle eviction. Before the fix
/// the method returned immediately without touching the property, causing a
/// session that only received <c>AttributesAsync</c> calls to be evicted early.
/// </summary>
[Fact]
public async Task AttributesAsync_updates_LastUsedUtc()
{
var ct = TestContext.Current.CancellationToken;
// Arrange: build a minimal OpcUaClientBrowseSession without a live server.
// AttributesAsync does not call ISession — MockBehavior.Loose is safe.
var mockSession = new Mock<ISession>(MockBehavior.Loose);
var nsMap = NamespaceMap.FromTable(new NamespaceTable());
var sut = new OpcUaClientBrowseSession(mockSession.Object, nsMap, ObjectIds.ObjectsFolder);
var before = sut.LastUsedUtc;
// Introduce a small delay so clock advances at least one tick.
await Task.Delay(5, ct);
// Act
var attrs = await sut.AttributesAsync("nsu=http://opcfoundation.org/UA/;i=85", ct);
// Assert: LastUsedUtc must have been refreshed, and the result must be empty
// (OPC UA picker treats variables as leaves, no attribute side-panel).
attrs.ShouldBeEmpty();
sut.LastUsedUtc.ShouldBeGreaterThan(before,
"AttributesAsync must refresh LastUsedUtc to satisfy the IBrowseSession contract");
}
}
@@ -12,6 +12,7 @@
<ItemGroup>
<PackageReference Include="xunit.v3"/>
<PackageReference Include="Shouldly"/>
<PackageReference Include="Moq"/>
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>