fccffd0a0f
- Endpoint rewrite: the driver now connects to the configured host AND port (scheme match, advertised path kept) instead of the server-advertised URL — fixes NAT/port-map upstreams and lets an in-test proxy carry the session (SDK SelectEndpoint rule). - TcpStallProxy + OpcUaClientDriverStallWireTests: the in-place blip (#670) is now wire-tested on loopback — one Bad, frozen read stamp, same session, identical value republished Good, a post-recovery change arrives, no source-time regression after the SDK's late backlog flush. - Defect found by that test, fixed: a change published right after a stall rode a Publish response the SDK timed out and never republished, leaving the node Good on the pre-change value indefinitely. New trailing reconcile read (Timeout + KeepAliveInterval after recovery) publishes any Good value newer than the last accepted one, through the replay guard's mark. - IDriverConnectionLossSource (Core.Abstractions): opt-in definitive-loss signal. DriverInstanceActor attaches in PreStart / detaches in PostStop and turns a raise into a disconnect only while Connected. OpcUaClient raises once per session on keep-alive BadSessionIdInvalid/BadSessionClosed/BadSessionNotActivated/ BadServerHalted (measured: the SDK never re-opens a halted channel) or a dropped subscription; ambiguous codes stay with the #527 watchdog. - Replay guard: one Information summary per recovery when the window closes ("dropped N late notifications across M refs"), with a timer so quiet subscriptions log it too. - ReconnectPeriod deleted (options, AdminUI form, docs, tests); stored configs that carry reconnectPeriod still parse in the factory, probe and browser. - Docs: OpcUaClient.md, DriverLifecycle.md, driver-specs.md, CLAUDE.md.
54 lines
2.1 KiB
C#
54 lines
2.1 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_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");
|
|
}
|
|
}
|