1badc10445
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
72 lines
3.4 KiB
C#
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);
|
|
}
|
|
}
|