test(adminui): guard NativeAlarmModel driver set against IAlarmSource implementers (#642)

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-08 16:56:04 -04:00
parent 6951faa08f
commit 62f19dd013
2 changed files with 57 additions and 0 deletions
@@ -0,0 +1,53 @@
using System.Reflection;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;
/// <summary>
/// Gitea #642 guard: <see cref="NativeAlarmModel.SupportsNativeAlarms"/> is a hand-maintained set; it must equal
/// the drivers whose concrete types implement <see cref="IAlarmSource"/>. Discovery is by reflection over the
/// driver assemblies deployed beside this test; an assembly's driver type is the <see cref="DriverTypeNames"/>
/// constant whose FIELD NAME equals the assembly suffix (Driver.Galaxy -> Galaxy = "GalaxyMxGateway").
/// </summary>
public sealed class NativeAlarmDriverSetGuardTests
{
private static readonly string[] NonDriverSuffixes = [".Contracts", ".Addressing", ".Cli", ".Historian.Gateway"];
[Fact]
public void SupportsNativeAlarms_matches_the_drivers_implementing_IAlarmSource()
{
var binDir = Path.GetDirectoryName(typeof(NativeAlarmDriverSetGuardTests).Assembly.Location)!;
var constants = typeof(DriverTypeNames).GetFields(BindingFlags.Public | BindingFlags.Static)
.Where(f => f.IsLiteral && f.FieldType == typeof(string))
.ToDictionary(f => f.Name, f => (string)f.GetRawConstantValue()!, StringComparer.OrdinalIgnoreCase);
const string prefix = "ZB.MOM.WW.OtOpcUa.Driver.";
var implementers = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var scanned = 0;
foreach (var dll in Directory.GetFiles(binDir, prefix + "*.dll"))
{
var name = Path.GetFileNameWithoutExtension(dll);
if (NonDriverSuffixes.Any(s => name.EndsWith(s, StringComparison.Ordinal))) { continue; }
Type[] types;
try { types = Assembly.LoadFrom(dll).GetTypes(); }
catch (ReflectionTypeLoadException ex) { types = ex.Types.Where(t => t is not null).ToArray()!; }
scanned++;
if (!types.Any(t => t.IsClass && !t.IsAbstract && typeof(IAlarmSource).IsAssignableFrom(t))) { continue; }
var suffix = name[prefix.Length..];
constants.TryGetValue(suffix, out var typeName).ShouldBeTrue($"no DriverTypeNames constant named '{suffix}' for {name}");
implementers.Add(typeName!);
}
scanned.ShouldBeGreaterThan(5, "driver assemblies were not deployed to the test output; the guard would be vacuous");
implementers.Count.ShouldBeGreaterThan(0);
foreach (var typeName in constants.Values)
{
NativeAlarmModel.SupportsNativeAlarms(typeName).ShouldBe(implementers.Contains(typeName),
$"NativeAlarmModel.AlarmDriverTypes disagrees with IAlarmSource for driver type '{typeName}'");
}
}
}
@@ -40,6 +40,10 @@
<!-- #576: the AB form tests deserialise the saved JSON through the drivers' own factory parse. --> <!-- #576: the AB form tests deserialise the saved JSON through the drivers' own factory parse. -->
<ProjectReference Include="..\..\..\src\Drivers\ZB.MOM.WW.OtOpcUa.Driver.AbCip\ZB.MOM.WW.OtOpcUa.Driver.AbCip.csproj"/> <ProjectReference Include="..\..\..\src\Drivers\ZB.MOM.WW.OtOpcUa.Driver.AbCip\ZB.MOM.WW.OtOpcUa.Driver.AbCip.csproj"/>
<ProjectReference Include="..\..\..\src\Drivers\ZB.MOM.WW.OtOpcUa.Driver.AbLegacy\ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.csproj"/> <ProjectReference Include="..\..\..\src\Drivers\ZB.MOM.WW.OtOpcUa.Driver.AbLegacy\ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.csproj"/>
<ProjectReference Include="..\..\..\src\Drivers\ZB.MOM.WW.OtOpcUa.Driver.MTConnect\ZB.MOM.WW.OtOpcUa.Driver.MTConnect.csproj"/>
<ProjectReference Include="..\..\..\src\Drivers\ZB.MOM.WW.OtOpcUa.Driver.FOCAS\ZB.MOM.WW.OtOpcUa.Driver.FOCAS.csproj"/>
<ProjectReference Include="..\..\..\src\Drivers\ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient\ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.csproj"/>
<ProjectReference Include="..\..\..\src\Drivers\ZB.MOM.WW.OtOpcUa.Driver.Galaxy\ZB.MOM.WW.OtOpcUa.Driver.Galaxy.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>