513fa1aa16
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
114 lines
4.5 KiB
C#
114 lines
4.5 KiB
C#
using Opc.Ua;
|
|
using ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Adapters;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests.Adapters;
|
|
|
|
/// <summary>
|
|
/// Covers <see cref="RealOpcUaClient.RewriteEndpointHostForReachability"/> — the fix for OPC UA
|
|
/// servers (e.g. OtOpcUa v3) that advertise an unroutable base address (a 0.0.0.0 wildcard bind or
|
|
/// an internal container/NAT hostname). The session must dial the host the operator reached, not
|
|
/// the advertised one, while preserving the advertised scheme + path.
|
|
/// </summary>
|
|
public class RealOpcUaClientEndpointRewriteTests
|
|
{
|
|
private static string? Rewrite(string advertised, string discovery)
|
|
{
|
|
var ep = new EndpointDescription(advertised);
|
|
RealOpcUaClient.RewriteEndpointHostForReachability(ep, discovery);
|
|
return ep.EndpointUrl;
|
|
}
|
|
|
|
/// <summary>Verifies that a 0.0.0.0 advertisement is rewritten to the reachable host while preserving the path.</summary>
|
|
[Fact]
|
|
public void Rewrites_0_0_0_0_advertisement_to_the_reachable_host_preserving_path()
|
|
{
|
|
Assert.Equal(
|
|
"opc.tcp://otopcua-dev-site-a-1-1:4840/OtOpcUa",
|
|
Rewrite("opc.tcp://0.0.0.0:4840/OtOpcUa", "opc.tcp://otopcua-dev-site-a-1-1:4840/OtOpcUa"));
|
|
}
|
|
|
|
/// <summary>Verifies that an internal NAT hostname is rewritten to the reachable host.</summary>
|
|
[Fact]
|
|
public void Rewrites_internal_nat_hostname_to_the_reachable_host()
|
|
{
|
|
Assert.Equal(
|
|
"opc.tcp://plc.example.com:4840/OtOpcUa",
|
|
Rewrite("opc.tcp://be5667fab9da:4840/OtOpcUa", "opc.tcp://plc.example.com:4840/OtOpcUa"));
|
|
}
|
|
|
|
/// <summary>Verifies that the port is also rewritten when the advertised port differs.</summary>
|
|
[Fact]
|
|
public void Rewrites_port_too_when_the_advertised_port_differs()
|
|
{
|
|
Assert.Equal(
|
|
"opc.tcp://gateway:51210/OtOpcUa",
|
|
Rewrite("opc.tcp://0.0.0.0:4840/OtOpcUa", "opc.tcp://gateway:51210/OtOpcUa"));
|
|
}
|
|
|
|
/// <summary>Verifies that an already reachable advertisement is left untouched.</summary>
|
|
[Fact]
|
|
public void Leaves_an_already_reachable_advertisement_untouched()
|
|
{
|
|
// opc-plc advertises its real container name — must not be rewritten.
|
|
Assert.Equal(
|
|
"opc.tcp://scadabridge-opcua:50000",
|
|
Rewrite("opc.tcp://scadabridge-opcua:50000", "opc.tcp://scadabridge-opcua:50000"));
|
|
}
|
|
|
|
/// <summary>Verifies that an authority-only advertisement is handled without appending a trailing slash.</summary>
|
|
[Fact]
|
|
public void Handles_authority_only_advertisement_without_appending_a_trailing_slash()
|
|
{
|
|
Assert.Equal(
|
|
"opc.tcp://host-b:4840",
|
|
Rewrite("opc.tcp://0.0.0.0:4840", "opc.tcp://host-b:4840"));
|
|
}
|
|
|
|
/// <summary>Verifies that an IPv6 reachable host is re-wrapped in brackets.</summary>
|
|
[Fact]
|
|
public void Re_wraps_an_ipv6_reachable_host_in_brackets()
|
|
{
|
|
Assert.Equal(
|
|
"opc.tcp://[::1]:4840/OtOpcUa",
|
|
Rewrite("opc.tcp://0.0.0.0:4840/OtOpcUa", "opc.tcp://[::1]:4840/OtOpcUa"));
|
|
}
|
|
|
|
/// <summary>Verifies that no port is emitted when neither URL carries one.</summary>
|
|
[Fact]
|
|
public void Emits_no_port_when_neither_url_carries_one()
|
|
{
|
|
// opc.tcp has no default port, so Uri.Port is -1 on both — must not emit ":-1".
|
|
Assert.Equal(
|
|
"opc.tcp://myhost/UaServer",
|
|
Rewrite("opc.tcp://0.0.0.0/UaServer", "opc.tcp://myhost/UaServer"));
|
|
}
|
|
|
|
/// <summary>Verifies that the reachable port is used when the advertisement omits one.</summary>
|
|
[Fact]
|
|
public void Uses_the_reachable_port_when_the_advertisement_omits_one()
|
|
{
|
|
Assert.Equal(
|
|
"opc.tcp://myhost:4840/UaServer",
|
|
Rewrite("opc.tcp://0.0.0.0/UaServer", "opc.tcp://myhost:4840/UaServer"));
|
|
}
|
|
|
|
/// <summary>Verifies that the endpoint is left unchanged when the discovery URL is unusable.</summary>
|
|
/// <param name="discovery">The discovery.</param>
|
|
[Theory]
|
|
[InlineData("")]
|
|
[InlineData(" ")]
|
|
[InlineData("not-a-uri")]
|
|
public void Leaves_endpoint_unchanged_when_discovery_url_is_unusable(string discovery)
|
|
{
|
|
Assert.Equal("opc.tcp://0.0.0.0:4840/OtOpcUa", Rewrite("opc.tcp://0.0.0.0:4840/OtOpcUa", discovery));
|
|
}
|
|
|
|
/// <summary>Verifies that a null endpoint is a no-op.</summary>
|
|
[Fact]
|
|
public void Null_endpoint_is_a_no_op()
|
|
{
|
|
// Must not throw.
|
|
RealOpcUaClient.RewriteEndpointHostForReachability(null, "opc.tcp://host:4840");
|
|
}
|
|
}
|