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; /// /// Guards the Test Connect wiring contract: every driver type editable in the AdminUI must have /// a registered , resolvable from the same DI container that hosts the /// admin-operations cluster singleton. The singleton is role-pinned to admin, 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". /// public sealed class DriverProbeRegistrationTests { // The canonical "all drivers" set — one entry per AdminUI typed driver page's DriverTypeKey. // Keep in sync with the DriverTypeKey constants in // src/Server/.../Components/Pages/Clusters/Drivers/*DriverPage.razor. private static readonly string[] AdminUiDriverTypeKeys = [ "ModbusTcp", "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", "Historian.Wonderware", ]; [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().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().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); } }