feat(adminui): author + configure the Sql driver in /raw
The read-only Sql driver's runtime factory/probe and tag-side editors were wired, but the AdminUI could not author the DRIVER: the /raw "New driver" type list, the DriverConfigModal switch, and the legacy identity dropdown all omitted Sql, and no SqlDriverForm existed. Live-driving the AdminUI surfaced this. - Add SqlDriverForm.razor over the SqlDriverConfigDto shape: Provider (SqlServer-only in v1), required connectionStringRef (an env-var NAME, not a connection string — label is explicit), optional poll/operation/command timeouts, maxConcurrentGroups, nullIsBad. Enums serialize by NAME via JsonStringEnumConverter; a connection-string literal can never be authored (no such field is collected); rawTags (composer-owned) + allowWrites (inert, read-only v1) are never emitted. - Wire Sql into DriverConfigModal switch, RawDriverTypeDialog type list, and DriverIdentitySection legacy dropdown. - Add SqlDriverFormContractTests: the exact JSON the form emits constructs a driver through the real factory, missing connectionStringRef is rejected, and provider round-trips as the "SqlServer" name (never an ordinal). Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Guards the AdminUI <c>SqlDriverForm</c>'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, <see cref="SqlDriverFactoryExtensions.CreateInstance(string,string)"/>; and (2) the
|
||||
/// serialization contract — <c>provider</c> as a NAME string (never an ordinal), camelCase keys, and no
|
||||
/// composer-owned <c>rawTags</c> / inert <c>allowWrites</c> 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.
|
||||
/// </summary>
|
||||
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<InvalidOperationException>(
|
||||
() => 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<SqlDriverConfigDto>(json, FormOptions)!.Provider.ShouldBe(SqlProvider.SqlServer);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user