30a9d5f094
1. RewriteEndpointUrl keeps the advertised port when the configured URL names none (Uri.Port -1 would have sent the session to 4840); port-less + IPv6 tests. 2. The trailing re-read repairs any (value, status) difference that is not older than the last delivered value — Good->Bad/Uncertain included; a read without a source timestamp counts as newer; an equal source time with different content is published (repairs the replay guard's by-design equal-timestamp drop). 3. One driver-wide delivery lock makes decide+publish atomic across live notifications, the recovery read and the trailing read, so an older value can never land after a newer one (deterministic race test via a seam). 4. Known gap documented: a ConnectionLost raised while the host is not Connected is swallowed and that session falls back to the watchdog. 5. Only a SERVER-reported subscription loss raises ConnectionLost; the publishing-stall heuristic stays with the #527 watchdog. 6. Post-recovery timers run on Task.Delay(…, Clock, ct) with a session-work CTS cancelled on session release and dispose. 7. Replay summary at Information only when something was dropped (Debug otherwise); a re-arm reports the superseded window's drops and counts afresh. 8. DriverInstanceActor: AttachHostStatusSource doc comment restored; Reconnecting swallows a stray DisconnectObserved (test). 9. Stall wire test: 120 s session timeout, 3000-cycle subscription lifetime, one further bad keep-alive tick, 60 s headroom on the post-recovery waits (5/5 green).
70 lines
2.8 KiB
C#
70 lines
2.8 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests;
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// <c>CoreClientUtils.SelectEndpoint</c> does.
|
|
/// </summary>
|
|
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_configured_url_without_a_port_keeps_the_advertised_port()
|
|
{
|
|
// Review of 3b88056f: Uri.Port is -1 for a port-less opc.tcp URL; copying it would drop the advertised port and
|
|
// the SDK would fall back to 4840.
|
|
OpcUaClientDriver.RewriteEndpointUrl("opc.tcp://internal-host:62541/Server", "opc.tcp://gateway/Server")
|
|
.ShouldBe("opc.tcp://gateway:62541/Server");
|
|
}
|
|
|
|
[Fact]
|
|
public void An_IPv6_configured_host_is_bracketed()
|
|
{
|
|
OpcUaClientDriver.RewriteEndpointUrl("opc.tcp://internal-host:4840/OtOpcUa", "opc.tcp://[fd00::1]:54840/OtOpcUa")
|
|
.ShouldBe("opc.tcp://[fd00::1]:54840/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");
|
|
}
|
|
}
|