0f1f8ad8fa
I1: /hosts eviction keyed off the AUTHORED DriverInstances, so a driver deleted in /raw but not yet deployed - still running, still publishing - vanished every tick. The stores' sweep is now EvictUnconfiguredStale(set, olderThanUtc): an entry goes only when unconfigured AND its PublishedUtc is older than 60 s. Tests cover unconfigured+fresh kept (positive control), unconfigured+stale evicted, configured+stale kept, and the deploy hand-off. M1: Test Connect falls back to the driver's documented ProbeTimeoutSeconds default (Galaxy 30, OpcUaClient 15, ...) when the key is absent. M2: the budget never undercuts the config's root timeoutMs (+1 s). M5: RawBrowseModal compares the open generation captured before the await, so a close-then-reopen mid-open closes the stale session. M6: /alarms-historian keeps a 30 s re-poll on 'not registered' (a driver node may still be starting) instead of stopping for good. M7: /scripts source fetch failure shows in the dialog, not the error boundary. M8: the export modal maps only its preview rows; the endpoint writes via Results.Text (no second byte[] copy). M9: /hosts config queries are ordered, so SequenceEqual change detection holds. N1 tooltip, N2 CRLF-safe regex, N3 throttled Warning for subscriber faults, N5 equipment-import help text now describes RFC 4180 quoting. H3: MonacoEditor documents that themes are page-global. Refs #745 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
75 lines
3.4 KiB
C#
75 lines
3.4 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);
|
|
|
|
[Theory]
|
|
[InlineData("GalaxyMxGateway", 30)]
|
|
[InlineData("OpcUaClient", 15)]
|
|
[InlineData("Modbus", 5)]
|
|
[InlineData("Sql", 10)] // no documented default ⇒ the caller's fallback
|
|
public void An_absent_key_uses_the_drivers_documented_default(string driverType, int expected)
|
|
=> TestConnectBudget.Resolve("{}", fallbackSeconds: 10, driverType).ShouldBe(expected);
|
|
|
|
[Fact]
|
|
public void A_configured_value_beats_the_driver_default()
|
|
=> TestConnectBudget.Resolve("{\"ProbeTimeoutSeconds\":7}", 10, "GalaxyMxGateway").ShouldBe(7);
|
|
|
|
[Theory]
|
|
[InlineData("{\"ProbeTimeoutSeconds\":5,\"timeoutMs\":8000}", 9)] // M2: 8 s request + 1 s, never cut short
|
|
[InlineData("{\"ProbeTimeoutSeconds\":5,\"timeoutMs\":2000}", 5)] // positive control: a short request timeout changes nothing
|
|
[InlineData("{\"timeoutMs\":120000}", 60)] // still clamped
|
|
public void The_budget_never_undercuts_the_configs_request_timeout(string json, int expected)
|
|
=> TestConnectBudget.Resolve(json, 10, "Modbus").ShouldBe(expected);
|
|
|
|
[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, DriverType)");
|
|
src.ShouldContain("@implements IAsyncDisposable");
|
|
src.ShouldContain("System.Threading.Timer");
|
|
src.ShouldNotContain("System.Timers.Timer");
|
|
src.ShouldNotContain("async (_, _)");
|
|
}
|
|
}
|