using Shouldly; using Xunit; namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests; /// /// Gitea #676 — the driver connects to the endpoint URL the server ADVERTISES in GetEndpoints. A server behind NAT, /// a port map or a proxy advertises an address the client cannot reach (or one that bypasses the proxy), so the /// driver must rewrite host AND port to the configured URL when the scheme matches — what the SDK's own /// CoreClientUtils.SelectEndpoint does. /// public sealed class OpcUaClientEndpointRewriteTests { [Fact] public void Host_and_port_of_the_advertised_url_are_replaced_by_the_configured_ones() { OpcUaClientDriver.RewriteEndpointUrl("opc.tcp://internal-host:4840/OtOpcUa", "opc.tcp://10.1.2.3:54840/OtOpcUa") .ShouldBe("opc.tcp://10.1.2.3:54840/OtOpcUa"); } [Fact] public void The_advertised_path_is_kept() { // The path is the server's to choose (several endpoints may share a host:port); only the address is ours. OpcUaClientDriver.RewriteEndpointUrl("opc.tcp://plc-a:4840/Server/Ua", "opc.tcp://gateway:6000/") .ShouldBe("opc.tcp://gateway:6000/Server/Ua"); } [Fact] public void Only_the_port_differs_still_rewrites() { // The in-process stall proxy shape: same host, the configured port is the proxy's. OpcUaClientDriver.RewriteEndpointUrl("opc.tcp://127.0.0.1:4840/OtOpcUa", "opc.tcp://127.0.0.1:61000/OtOpcUa") .ShouldBe("opc.tcp://127.0.0.1:61000/OtOpcUa"); } [Fact] public void A_different_scheme_is_left_alone() { // An https endpoint advertised next to an opc.tcp one is a different transport, not a NAT artefact. OpcUaClientDriver.RewriteEndpointUrl("https://srv:443/ua", "opc.tcp://srv:4840/ua") .ShouldBe("https://srv:443/ua"); } [Theory] [InlineData(null)] [InlineData("")] [InlineData("not a url")] public void An_unusable_advertised_url_falls_back_to_the_configured_one(string? advertised) { OpcUaClientDriver.RewriteEndpointUrl(advertised, "opc.tcp://srv:4840/ua").ShouldBe("opc.tcp://srv:4840/ua"); } }