Files
lmxopcua/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Drivers/TestConnectBudgetTests.cs
T
Joseph Doherty 4b594ec525 fix(adminui): Test Connect honours the configured probe timeout; drain its clear timer (R04 L2, G1-F5, F-FU2)
DriverTestConnectButton always sent a fixed 10 s, and the Galaxy/OpcUaClient
probes run under min(caller, configured), so ProbeTimeoutSeconds - documented
on every driver as the Test Connect timeout - could shorten a probe but never
lengthen it (Galaxy's 30 s default was silently 10 s). TestConnectBudget reads
the root ProbeTimeoutSeconds from the merged config at click time, clamped to
the actor's [1, 60] s, falling back to TimeoutSeconds. DeviceModal (the only
host) needs no change. The Galaxy/OpcUaClient form hints are reworded.

The 30 s result-clear timer moves from System.Timers.Timer + an async void
Elapsed lambda to System.Threading.Timer, drained in DisposeAsync, with a
generation so a superseded fire cannot clear a newer result.

Refs #745
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
2026-10-22 17:45:39 -04:00

56 lines
2.3 KiB
C#

using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers;
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Drivers;
/// <summary>
/// G1-F5 / F-FU2: Test Connect honours the driver's configured <c>ProbeTimeoutSeconds</c> (so it can
/// lengthen the budget, not only shorten it), aligned across Galaxy and OpcUaClient, and the button
/// drains its clear timer (R04 L2).
/// </summary>
[Trait("Category", "Unit")]
public sealed class TestConnectBudgetTests
{
[Theory]
[InlineData("{\"ProbeTimeoutSeconds\":30}", 30)] // Galaxy default — was silently 10 s
[InlineData("{\"probeTimeoutSeconds\":15}", 15)] // OpcUaClient default, camelCase
[InlineData("{\"ProbeTimeoutSeconds\":\"25\"}", 25)]
[InlineData("{\"ProbeTimeoutSeconds\":2.5}", 3)]
[InlineData("{\"ProbeTimeoutSeconds\":600}", 60)] // clamped to the actor's max
public void A_configured_timeout_is_the_budget(string json, int expected)
=> TestConnectBudget.Resolve(json).ShouldBe(expected);
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData("{}")]
[InlineData("{\"ProbeTimeoutSeconds\":0}")]
[InlineData("{\"ProbeTimeoutSeconds\":-5}")]
[InlineData("{\"ProbeTimeoutSeconds\":null}")]
[InlineData("not json")]
[InlineData("[1,2]")]
[InlineData("{\"Nested\":{\"ProbeTimeoutSeconds\":40}}")] // only the root key counts
public void No_valid_configured_timeout_falls_back(string? json)
=> TestConnectBudget.Resolve(json, fallbackSeconds: 10).ShouldBe(10);
[Fact]
public void The_fallback_is_clamped_too()
{
TestConnectBudget.Resolve("{}", fallbackSeconds: 0).ShouldBe(TestConnectBudget.MinSeconds);
TestConnectBudget.Resolve("{}", fallbackSeconds: 999).ShouldBe(TestConnectBudget.MaxSeconds);
}
[Fact]
public void Button_resolves_its_budget_from_the_config_and_drains_a_threading_timer()
{
var src = AdminUiSource.Read("Components", "Shared", "Drivers", "DriverTestConnectButton.razor");
src.ShouldContain("TestConnectBudget.Resolve(json, TimeoutSeconds)");
src.ShouldContain("@implements IAsyncDisposable");
src.ShouldContain("System.Threading.Timer");
src.ShouldNotContain("System.Timers.Timer");
src.ShouldNotContain("async (_, _)");
}
}