Files
lmxopcua/tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/DriverProbeRegistrationTests.cs
T
Joseph Doherty 1badc10445 test(v3-batch4): add Calculation to the driver-probe idempotency key set
Pre-existing stale test surfaced by Batch 4's full gate (Batch 2/3 gates were
live-/run and never ran Host.IntegrationTests): Batch 2 registered CalculationProbe
in DriverFactoryBootstrap (9 probes) but AddOtOpcUaDriverProbes_is_idempotent's
AdminUiDriverTypeKeys still listed 8, so distinctTypes(9) != Length(8). Calculation
is a real DriverTypeNames.Calculation pseudo-driver with a probe; add it and refresh
the now-stale *DriverPage.razor comment (that routed flow retired in Batch 2). Not a
Batch-4 code change — a test-correctness fix.

Claude-Session: https://claude.ai/code/session_01LVneM3eh1UtJxEisFXgmox
2026-07-16 13:22:27 -04:00

72 lines
3.4 KiB
C#

using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Host.Drivers;
namespace ZB.MOM.WW.OtOpcUa.Host.IntegrationTests;
/// <summary>
/// Guards the Test Connect wiring contract: every driver type editable in the AdminUI must have
/// a registered <see cref="IDriverProbe"/>, resolvable from the same DI container that hosts the
/// <c>admin-operations</c> cluster singleton. The singleton is role-pinned to <c>admin</c>, so on
/// a split-role deployment (the MAIN cluster's admin-only nodes) the probes must be wired by the
/// admin path — not only the driver path — or every Test Connect button returns
/// "No probe registered for driver type X".
/// </summary>
public sealed class DriverProbeRegistrationTests
{
// The canonical "all drivers" set — one entry per AdminUI typed driver page's DriverTypeKey.
// Keep in sync with the IDriverProbe registrations in
// src/Server/.../Host/Drivers/DriverFactoryBootstrap.AddOtOpcUaDriverProbes (the routed
// *DriverPage.razor flow was retired in v3 Batch 2 — the driver-type keys now live in the /raw
// modals, but the probe set is the authority for what the AdminUI can Test-connect).
private static readonly string[] AdminUiDriverTypeKeys =
[
"Modbus",
"AbCip",
"AbLegacy",
"S7",
"TwinCat", // page key; probe reports "TwinCAT" — must resolve case-insensitively
"Focas", // page key; probe reports "FOCAS" — must resolve case-insensitively
"OpcUaClient",
"GalaxyMxGateway",
"Calculation", // v3 Batch 2 pseudo-driver; CalculationProbe registered in DriverFactoryBootstrap
];
[Fact]
public void AddOtOpcUaDriverProbes_registers_a_probe_for_every_AdminUI_driver_type()
{
var services = new ServiceCollection();
services.AddOtOpcUaDriverProbes();
using var sp = services.BuildServiceProvider();
var probes = sp.GetServices<IDriverProbe>().ToList();
// No duplicate DriverType — AdminOperationsActor builds a dictionary keyed by DriverType
// (case-insensitive) and would throw on a duplicate key, crashing the singleton.
var byType = probes.ToDictionary(p => p.DriverType, StringComparer.OrdinalIgnoreCase);
foreach (var key in AdminUiDriverTypeKeys)
byType.ContainsKey(key).ShouldBeTrue($"No IDriverProbe registered for AdminUI driver type '{key}'.");
}
[Fact]
public void AddOtOpcUaDriverProbes_is_idempotent()
{
// A fused admin,driver node calls the registration from both the driver-factory path and the
// admin path. TryAddEnumerable must de-dup so the probe set stays unique (else the actor's
// ToDictionary throws).
var services = new ServiceCollection();
services.AddOtOpcUaDriverProbes();
services.AddOtOpcUaDriverProbes();
using var sp = services.BuildServiceProvider();
var probes = sp.GetServices<IDriverProbe>().ToList();
var distinctTypes = probes.Select(p => p.DriverType).Distinct(StringComparer.OrdinalIgnoreCase).Count();
probes.Count.ShouldBe(distinctTypes, "Duplicate IDriverProbe registrations — TryAddEnumerable should de-dup.");
distinctTypes.ShouldBe(AdminUiDriverTypeKeys.Length);
}
}