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".
///
///
/// ⚠️ The expected set is derived from , not hand-written.
/// It used to be a literal array kept "in sync" by comment, and it drifted the moment `Sql`
/// (9b5a9687) and `Mqtt` (f79d13e2) shipped: the count assertion below went red
/// reporting 12 where 10 expected — a true statement that named neither driver and read like
/// the probes were wrong rather than the list. Worse is the direction it could NOT catch: a new
/// driver type registered with no probe passed silently, because a hand-written list grows
/// only when someone remembers to grow it, and this test exists precisely for the case where nobody
/// did. Deriving from the constants makes the guard bidirectional and self-maintaining.
///
public sealed class DriverProbeRegistrationTests
{
[Fact]
public void AddOtOpcUaDriverProbes_registers_a_probe_for_every_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);
var missing = DriverTypeNames.All.Where(t => !byType.ContainsKey(t)).ToList();
missing.ShouldBeEmpty(
"Driver types with no IDriverProbe — Test Connect returns 'No probe registered for driver "
+ $"type X' for each: {string.Join(", ", missing)}");
}
[Fact]
public void No_probe_is_registered_for_a_driver_type_that_does_not_exist()
{
// The other direction. A probe whose DriverType matches no DriverTypeNames constant can never
// be reached — AdminOperationsActor looks probes up by the authored DriverType — so it is a
// typo or a leftover from a renamed driver, and it reads as coverage while providing none.
var services = new ServiceCollection();
services.AddOtOpcUaDriverProbes();
using var sp = services.BuildServiceProvider();
var orphans = sp.GetServices()
.Select(p => p.DriverType)
.Where(t => !DriverTypeNames.All.Contains(t, StringComparer.OrdinalIgnoreCase))
.ToList();
orphans.ShouldBeEmpty($"Probes for unknown driver types: {string.Join(", ", orphans)}");
}
///
/// Two driver types are spelled differently by the constant and by the AdminUI's own key
/// (TwinCAT/TwinCat, FOCAS/Focas), so the lookup must fold case. The
/// derived tests above cannot cover this — they compare the constants against themselves.
///
[Theory]
[InlineData("TwinCat", DriverTypeNames.TwinCAT)]
[InlineData("Focas", DriverTypeNames.FOCAS)]
[InlineData("modbus", DriverTypeNames.Modbus)]
public void A_probe_resolves_by_a_case_drifted_driver_type_key(string authoredKey, string canonical)
{
var services = new ServiceCollection();
services.AddOtOpcUaDriverProbes();
using var sp = services.BuildServiceProvider();
var byType = sp.GetServices().ToDictionary(p => p.DriverType, StringComparer.OrdinalIgnoreCase);
byType.ShouldContainKey(authoredKey);
byType[authoredKey].DriverType.ShouldBe(canonical);
}
[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(DriverTypeNames.All.Count);
}
}