using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers.Forms; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests; /// /// Regression guard for the systemic driver-config enum-serialization bug found 2026-06-19. /// Every driver-config editor serialised enum config fields (e.g. S7 CpuType, Modbus /// Family, AbCip PlcFamily) as JSON numbers when 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 /// editor serialise enums as strings (matching the factory + the long-correct OpcUaClient template). /// v3 Batch-2 (WP3): the config serializer moved from the routed *DriverPage into the /// embeddable *DriverForm body (shared by the page + the raw-tree DriverConfigModal), so this /// guard now reflects over the *DriverForm fleet. It fails if any driver form loses its /// string-enum converter again. /// public sealed class DriverPageJsonConverterTests { /// Every concrete *DriverForm in the AdminUI assembly that declares a /// _jsonOpts config serializer. private static IReadOnlyList DriverFormTypes { get; } = typeof(ModbusDriverForm).Assembly.GetTypes() .Where(t => t.Name.EndsWith("DriverForm", 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-form types discovered by reflection. public static TheoryData DriverFormsWithJsonOpts() { var data = new TheoryData(); foreach (var t in DriverFormTypes) data.Add(t); return data; } /// Verifies every driver form'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 form component type discovered by reflection. [Theory] [MemberData(nameof(DriverFormsWithJsonOpts))] public void Driver_form_json_options_register_string_enum_converter(Type formType) { var field = formType.GetField("_jsonOpts", BindingFlags.NonPublic | BindingFlags.Static); var opts = (JsonSerializerOptions)field!.GetValue(null)!; opts.Converters.OfType().ShouldNotBeEmpty( $"{formType.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 *DriverForm routes config serialization through a /// _jsonOpts field — otherwise a new form 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_form_uses_a_guarded_jsonOpts_serializer() { var allDriverForms = typeof(ModbusDriverForm).Assembly.GetTypes() .Where(t => t.Name.EndsWith("DriverForm", StringComparison.Ordinal) && !t.IsAbstract) .ToList(); allDriverForms.Count.ShouldBeGreaterThanOrEqualTo(8, "reflection should discover the full driver-form fleet"); DriverFormTypes.Count.ShouldBe(allDriverForms.Count, "every *DriverForm must declare a _jsonOpts config serializer so the string-enum converter guard covers it"); } }