using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Pages.Clusters.Drivers; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests; /// /// Regression guard for the systemic driver-config enum-serialization bug found 2026-06-19. /// Every *DriverPage serialised enum config fields (e.g. S7 CpuType, Modbus /// DataType/Region, AbCip PlcFamily) as JSON numbers because its /// private static _jsonOpts had no . The driver /// factories, however, deserialise into string-typed DTOs (+ lenient ParseEnum) and /// throw when binding a JSON number to a string? — so an AdminUI-authored /// config that contained any enum field produced a blob the driver could not parse, faulting /// the driver on deploy. The fix makes every page serialise enums as strings (matching the /// factory + the long-correct OpcUaClient template). This test fails if any driver page loses /// its string-enum converter again. /// public sealed class DriverPageJsonConverterTests { /// Every concrete *DriverPage in the AdminUI assembly that declares a /// _jsonOpts config serializer. private static IReadOnlyList DriverPageTypes { get; } = typeof(S7DriverPage).Assembly.GetTypes() .Where(t => t.Name.EndsWith("DriverPage", StringComparison.Ordinal) && !t.IsAbstract) .Where(t => t.GetField("_jsonOpts", BindingFlags.NonPublic | BindingFlags.Static) is not null) .OrderBy(t => t.Name) .ToList(); /// xUnit theory source over the driver-page types discovered by reflection. public static TheoryData DriverPagesWithJsonOpts() { var data = new TheoryData(); foreach (var t in DriverPageTypes) data.Add(t); return data; } /// Verifies every driver page's config serializer registers a string-enum converter so /// enum config fields round-trip as strings (and the driver factory can parse the result). /// A driver page component type discovered by reflection. [Theory] [MemberData(nameof(DriverPagesWithJsonOpts))] public void Driver_page_json_options_register_string_enum_converter(Type pageType) { var field = pageType.GetField("_jsonOpts", BindingFlags.NonPublic | BindingFlags.Static); var opts = (JsonSerializerOptions)field!.GetValue(null)!; opts.Converters.OfType().ShouldNotBeEmpty( $"{pageType.Name}._jsonOpts must register a JsonStringEnumConverter; otherwise AdminUI-authored " + "enum config fields serialise as numbers and the string-typed driver factory throws on parse."); } /// Enforces that EVERY concrete *DriverPage routes config serialization through a /// _jsonOpts field — otherwise a new page that serialised config a different way would slip /// past the converter guard above. Also a floor check so a rename can't silently shrink the set. [Fact] public void Every_driver_page_uses_a_guarded_jsonOpts_serializer() { var allDriverPages = typeof(S7DriverPage).Assembly.GetTypes() .Where(t => t.Name.EndsWith("DriverPage", StringComparison.Ordinal) && !t.IsAbstract) .ToList(); allDriverPages.Count.ShouldBeGreaterThanOrEqualTo(9, "reflection should discover the full driver-page fleet"); DriverPageTypes.Count.ShouldBe(allDriverPages.Count, "every *DriverPage must declare a _jsonOpts config serializer so the string-enum converter guard covers it"); } }