using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using Shouldly;
using Xunit;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests;
///
/// Review 2026-10-21 G-6: ProbeTimeoutSeconds was authored and compared for equivalence but
/// never consumed — the probe ran on the caller's budget alone. It now caps the probe deadline
/// (it can shorten the caller's budget, never extend it).
///
[Trait("Category", "Unit")]
public sealed class GalaxyDriverProbeTimeoutTests
{
/// The effective deadline is the shorter of the caller's budget and the configured cap.
[Theory]
[InlineData(10, 30, 10)] // caller shorter → caller wins
[InlineData(10, 2, 2)] // config shorter → config caps
[InlineData(10, 0, 10)] // unset config → caller
[InlineData(10, -1, 10)] // nonsense config → caller
[InlineData(0, 5, 1)] // no caller budget → 1 s floor beats a longer config
[InlineData(0, 0, 1)] // nothing → 1 s floor
public void EffectiveTimeout_is_the_shorter_of_caller_and_config(int callerSeconds, int configSeconds, int expectedSeconds)
=> GalaxyDriverProbe.EffectiveTimeout(TimeSpan.FromSeconds(callerSeconds), configSeconds)
.ShouldBe(TimeSpan.FromSeconds(expectedSeconds));
///
/// Against a port that accepts TCP but never speaks gRPC, a 1 s configured cap returns long
/// before the caller's 20 s budget. The gRPC handshake itself does the waiting here, so this
/// exercises the real deadline path, not the pure helper.
///
[Fact]
public async Task Configured_cap_shortens_the_probe_against_a_silent_listener()
{
var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
try
{
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
var config = $$"""
{
"Gateway": { "Endpoint": "http://127.0.0.1:{{port}}", "ApiKeySecretRef": "dev:k", "UseTls": false },
"MxAccess": { "ClientName": "probe-timeout" },
"Repository": {},
"Reconnect": {},
"ProbeTimeoutSeconds": 1
}
""";
var sw = Stopwatch.StartNew();
var result = await new GalaxyDriverProbe().ProbeAsync(config, TimeSpan.FromSeconds(20), TestContext.Current.CancellationToken);
sw.Stop();
result.Ok.ShouldBeFalse();
sw.Elapsed.ShouldBeLessThan(TimeSpan.FromSeconds(10), $"the 1 s configured cap must bound the probe (message: {result.Message})");
result.Message.ShouldNotBeNull();
(result.Message!.Contains("timed out", StringComparison.OrdinalIgnoreCase)
|| result.Message.Contains("DeadlineExceeded", StringComparison.OrdinalIgnoreCase)
|| result.Message.Contains("handshake failed", StringComparison.OrdinalIgnoreCase))
.ShouldBeTrue($"unexpected message: '{result.Message}'");
}
finally
{
listener.Stop();
}
}
}