a107a1a8b9
Register the MTConnect factory + probe with the Host, add the DriverTypeNames.MTConnect constant, and keep DriverTypeNamesGuardTests green. - DriverTypeNames: new MTConnect const + appended to All. - DriverFactoryBootstrap: MTConnectDriverFactoryExtensions.Register in Register(...) at the default Tier A (managed HttpClient/XML, in-process; Tier C is the only tier arming process-recycle, wrong here), and TryAddEnumerable(IDriverProbe -> MTConnectDriverProbe) in AddOtOpcUaDriverProbes so the probe reaches admin-only nodes (the admin-pinned Test-Connect singleton) without double-registering on a fused admin,driver node. - Host.csproj: ProjectReference to Driver.MTConnect (required to compile the Register call). - Core.Abstractions.Tests.csproj: ProjectReference to Driver.MTConnect so the guard's bin scan discovers the factory. This and the constant MUST land together -- verified RED (2/4 facts) with the constant alone. - DriverProbeRegistrationTests: MTConnect added to AdminUiDriverTypeKeys; its idempotency fact asserts distinct-probe-count and goes RED without this (and RED if TryAddEnumerable is downgraded to AddSingleton). Reconciles all four "MTConnect" literals onto the one constant: the factory's DriverTypeName, MTConnectDriver.DriverType, and MTConnectDriverProbe.DriverType now all alias DriverTypeNames.MTConnect (no circular reference -- Core.Abstractions is a leaf both driver projects already reference). This is the repo's TwinCat/Focas anti-drift convention.
73 lines
3.5 KiB
C#
73 lines
3.5 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
|
|
"MTConnect", // MTConnectProbe — read-only Agent driver; probe issues a bare GET /probe
|
|
];
|
|
|
|
[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);
|
|
}
|
|
}
|