test(opcuaclient.browser): unit + opc-plc live coverage
This commit is contained in:
@@ -99,6 +99,7 @@
|
|||||||
<Project Path="tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests.csproj" />
|
<Project Path="tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests.csproj" />
|
||||||
<Project Path="tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests.csproj" />
|
<Project Path="tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests.csproj" />
|
||||||
<Project Path="tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests.csproj" />
|
<Project Path="tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests.csproj" />
|
||||||
|
<Project Path="tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser.Tests/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser.Tests.csproj" />
|
||||||
</Folder>
|
</Folder>
|
||||||
<Folder Name="/tests/Drivers/Driver CLIs/">
|
<Folder Name="/tests/Drivers/Driver CLIs/">
|
||||||
<Project Path="tests/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.Cli.Common.Tests/ZB.MOM.WW.OtOpcUa.Driver.Cli.Common.Tests.csproj" />
|
<Project Path="tests/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.Cli.Common.Tests/ZB.MOM.WW.OtOpcUa.Driver.Cli.Common.Tests.csproj" />
|
||||||
|
|||||||
+76
@@ -0,0 +1,76 @@
|
|||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Commons.Browsing;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Live-server tests against the opc-plc Docker fixture. Bring up with
|
||||||
|
/// `lmxopcua-fix up opcuaclient` from PowerShell before running. These tests
|
||||||
|
/// drive the internal <c>OpcUaClientBrowseSession</c> through its public factory
|
||||||
|
/// <see cref="OpcUaClientDriverBrowser"/> so no InternalsVisibleTo is needed.
|
||||||
|
/// Filter out with <c>--filter "Category!=RequiresOpcPlc"</c> when the fixture
|
||||||
|
/// is unreachable.
|
||||||
|
/// </summary>
|
||||||
|
[Trait("Category", "RequiresOpcPlc")]
|
||||||
|
public sealed class OpcUaClientBrowseSessionTests
|
||||||
|
{
|
||||||
|
private static string Endpoint =>
|
||||||
|
Environment.GetEnvironmentVariable("OPCUA_SIM_ENDPOINT") ?? "opc.tcp://10.100.0.35:50000";
|
||||||
|
|
||||||
|
// Enum values use their numeric ordinal because the browser uses default
|
||||||
|
// System.Text.Json with no JsonStringEnumConverter. SecurityPolicy.None,
|
||||||
|
// SecurityMode.None, OpcUaAuthType.Anonymous are all index 0, so omitting them
|
||||||
|
// also works — keeping them explicit for documentation value.
|
||||||
|
private static string ConfigJson => $$"""
|
||||||
|
{
|
||||||
|
"EndpointUrl":"{{Endpoint}}",
|
||||||
|
"SecurityPolicy":0,
|
||||||
|
"SecurityMode":0,
|
||||||
|
"AuthType":0,
|
||||||
|
"SessionTimeout":"00:01:00",
|
||||||
|
"Timeout":"00:00:10",
|
||||||
|
"PerEndpointConnectTimeout":"00:00:10"
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
/// <summary>RootAsync should surface at least one node under ObjectsFolder
|
||||||
|
/// (opc-plc exposes a non-empty top level).</summary>
|
||||||
|
[Fact]
|
||||||
|
public async Task RootAsync_returns_at_least_one_node()
|
||||||
|
{
|
||||||
|
var ct = TestContext.Current.CancellationToken;
|
||||||
|
var browser = new OpcUaClientDriverBrowser();
|
||||||
|
await using var session = await browser.OpenAsync(ConfigJson, ct);
|
||||||
|
var roots = await session.RootAsync(ct);
|
||||||
|
roots.Count.ShouldBeGreaterThan(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>ExpandAsync should accept a stable NodeId returned by RootAsync
|
||||||
|
/// and round-trip through the live namespace table.</summary>
|
||||||
|
[Fact]
|
||||||
|
public async Task ExpandAsync_round_trips_stable_NodeId()
|
||||||
|
{
|
||||||
|
var ct = TestContext.Current.CancellationToken;
|
||||||
|
var browser = new OpcUaClientDriverBrowser();
|
||||||
|
await using var session = await browser.OpenAsync(ConfigJson, ct);
|
||||||
|
var roots = await session.RootAsync(ct);
|
||||||
|
var folder = roots.FirstOrDefault(n => n.Kind == BrowseNodeKind.Folder);
|
||||||
|
folder.ShouldNotBeNull("expected at least one Folder under ObjectsFolder");
|
||||||
|
var children = await session.ExpandAsync(folder!.NodeId, ct);
|
||||||
|
children.ShouldNotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>The OPC UA picker treats variables as terminal leaves, so
|
||||||
|
/// AttributesAsync must always be empty for this driver.</summary>
|
||||||
|
[Fact]
|
||||||
|
public async Task AttributesAsync_is_empty_for_opcuaclient()
|
||||||
|
{
|
||||||
|
var ct = TestContext.Current.CancellationToken;
|
||||||
|
var browser = new OpcUaClientDriverBrowser();
|
||||||
|
await using var session = await browser.OpenAsync(ConfigJson, ct);
|
||||||
|
var attrs = await session.AttributesAsync("nsu=http://opcfoundation.org/UA/;i=85", ct);
|
||||||
|
attrs.ShouldBeEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unit-only coverage of <see cref="OpcUaClientDriverBrowser"/>'s pre-connect
|
||||||
|
/// validation. These tests do not require a live OPC UA endpoint and are safe to
|
||||||
|
/// run without the opc-plc Docker fixture.
|
||||||
|
/// </summary>
|
||||||
|
[Trait("Category", "Unit")]
|
||||||
|
public sealed class OpcUaClientDriverBrowserTests
|
||||||
|
{
|
||||||
|
private readonly OpcUaClientDriverBrowser _sut = new();
|
||||||
|
|
||||||
|
/// <summary>The DriverType key must match the AdminUI's persisted value.</summary>
|
||||||
|
[Fact]
|
||||||
|
public void DriverType_is_OpcUaClient() => _sut.DriverType.ShouldBe("OpcUaClient");
|
||||||
|
|
||||||
|
/// <summary>An empty endpoint must fail fast with a clear EndpointUrl-mentioning message.</summary>
|
||||||
|
[Fact]
|
||||||
|
public async Task OpenAsync_with_empty_endpoint_throws()
|
||||||
|
{
|
||||||
|
var json = """{"EndpointUrl":"","EndpointUrls":[]}""";
|
||||||
|
var ex = await Should.ThrowAsync<InvalidOperationException>(
|
||||||
|
() => _sut.OpenAsync(json, TestContext.Current.CancellationToken));
|
||||||
|
ex.Message.ShouldContain("EndpointUrl");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>A JSON literal that deserializes to null must fail fast.</summary>
|
||||||
|
[Fact]
|
||||||
|
public async Task OpenAsync_with_null_json_throws()
|
||||||
|
{
|
||||||
|
var ex = await Should.ThrowAsync<InvalidOperationException>(
|
||||||
|
() => _sut.OpenAsync("null", TestContext.Current.CancellationToken));
|
||||||
|
ex.Message.ShouldContain("null");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Certificate auth is not supported by the browser; the failure message
|
||||||
|
/// must say so explicitly rather than surfacing a downstream COM/SDK error.
|
||||||
|
/// <c>OpcUaAuthType.Certificate</c> serializes as the numeric value 2 under the
|
||||||
|
/// browser's default System.Text.Json options (no string-enum converter).</summary>
|
||||||
|
[Fact]
|
||||||
|
public async Task OpenAsync_with_certificate_auth_throws_clear_message()
|
||||||
|
{
|
||||||
|
var json = """{"EndpointUrl":"opc.tcp://127.0.0.1:1","AuthType":2}""";
|
||||||
|
var ex = await Should.ThrowAsync<InvalidOperationException>(
|
||||||
|
() => _sut.OpenAsync(json, TestContext.Current.CancellationToken));
|
||||||
|
ex.Message.ShouldContain("Certificate");
|
||||||
|
}
|
||||||
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
<RootNamespace>ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser.Tests</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="xunit.v3"/>
|
||||||
|
<PackageReference Include="Shouldly"/>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\src\Drivers\ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser\ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser.csproj"/>
|
||||||
|
<ProjectReference Include="..\..\..\src\Drivers\ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Contracts\ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Contracts.csproj"/>
|
||||||
|
<ProjectReference Include="..\..\..\src\Core\ZB.MOM.WW.OtOpcUa.Commons\ZB.MOM.WW.OtOpcUa.Commons.csproj"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<NuGetAuditSuppress Include="https://github.com/advisories/GHSA-37gx-xxp4-5rgx"/>
|
||||||
|
<NuGetAuditSuppress Include="https://github.com/advisories/GHSA-w3x6-4m5h-cxqf"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
Reference in New Issue
Block a user