using System.Text.Json; using System.Text.Json.Serialization; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Driver.Sql; using ZB.MOM.WW.OtOpcUa.Driver.Sql.Contracts; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns; /// /// Guards the AdminUI SqlDriverForm's driver-config output against the runtime factory that consumes /// it. The form is razor (no bUnit here), so these tests pin two things the live verify then confirms: /// (1) the exact JSON bytes the form emits for a minimal + fuller config are accepted by the authoritative /// reader, ; and (2) the /// serialization contract — provider as a NAME string (never an ordinal), camelCase keys, and no /// composer-owned rawTags / inert allowWrites leaking into the authored blob. A numeric enum /// or a stray connection string is the "authors fine, never reads / leaks a secret" defect class this guards. /// public sealed class SqlDriverFormContractTests { // The JsonSerializerOptions SqlDriverForm serializes with (camelCase + string enums + omit-nulls). private static readonly JsonSerializerOptions FormOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, WriteIndented = false, Converters = { new JsonStringEnumConverter() }, }; // ---- LOAD-BEARING: the exact bytes SqlDriverForm emits must construct a driver through the factory ---- [Theory] // Minimal valid config: provider + required connectionStringRef only (all timeouts left to the factory). [InlineData("""{"provider":"SqlServer","connectionStringRef":"AdminUiFormTest"}""")] // Fuller config with every optional the form authors (timeouts satisfy operationTimeout > commandTimeout). [InlineData("""{"provider":"SqlServer","connectionStringRef":"AdminUiFormTest","defaultPollInterval":"00:00:05","operationTimeout":"00:00:20","commandTimeout":"00:00:10","maxConcurrentGroups":8,"nullIsBad":true}""")] public void FormEmittedJson_constructs_a_driver_through_the_factory(string formEmittedJson) { var envVar = SqlConnectionStringResolver.EnvironmentVariableFor("AdminUiFormTest"); var previous = Environment.GetEnvironmentVariable(envVar); Environment.SetEnvironmentVariable(envVar, "Server=localhost;Database=Test;Trusted_Connection=True;"); try { var driver = SqlDriverFactoryExtensions.CreateInstance("adminui-form-test", formEmittedJson); driver.ShouldNotBeNull(); } finally { Environment.SetEnvironmentVariable(envVar, previous); } } [Fact] public void Factory_rejects_a_blob_missing_connectionStringRef() { // The form marks connectionStringRef required; the factory is the authoritative enforcement. var ex = Should.Throw( () => SqlDriverFactoryExtensions.CreateInstance("adminui-form-test", """{"provider":"SqlServer"}""")); ex.Message.ShouldContain("connectionStringRef"); } // ---- serialization contract: enum-by-name, camelCase, no rawTags / allowWrites / null optionals -------- [Fact] public void Minimal_config_serializes_provider_as_a_name_and_omits_owned_and_null_fields() { // Mirrors SqlDriverForm.FormModel.ToDto() for a minimal config (only the required ref set). var dto = new SqlDriverConfigDto { Provider = SqlProvider.SqlServer, ConnectionStringRef = "MesStaging" }; var json = JsonSerializer.Serialize(dto, FormOptions); json.ShouldBe("""{"provider":"SqlServer","connectionStringRef":"MesStaging"}"""); json.ShouldNotContain("\"provider\":0"); // never an ordinal — that faults the string-typed factory read json.ShouldNotContain("rawTags"); // composer-owned; never authored by the form json.ShouldNotContain("allowWrites"); // inert in v1 (read-only); never authored json.ShouldNotContain("defaultPollInterval"); // null optional omitted ⇒ factory default applies } [Fact] public void Provider_round_trips_as_the_SqlServer_name() { var json = JsonSerializer.Serialize( new SqlDriverConfigDto { Provider = SqlProvider.SqlServer, ConnectionStringRef = "R" }, FormOptions); json.ShouldContain("\"SqlServer\""); JsonSerializer.Deserialize(json, FormOptions)!.Provider.ShouldBe(SqlProvider.SqlServer); } }