92ba120964
The P1 live gate had to insert MQTT driver config directly via SQL: DriverConfigModal and DeviceModal both fell through to "No typed config form for driver type Mqtt" with no raw-JSON fallback, so broker host/port/TLS/credentials were unauthorable from the AdminUI. Task 12 built the *tag* editor; these are the driver + device surfaces. MqttDriverForm authors the whole MqttDriverOptions connection surface (host/port/clientId, TLS + CA pin, credentials, protocol version/clean-session/keep-alive, connect timeout, reconnect backoffs, mode, maxPayloadBytes, and the Plain sub-object). The Sparkplug sub-object stays P2 — a marked placeholder mirroring MqttTagConfigEditor's stub, with any existing sparkplug keys preserved untouched. The connection is authored on the DRIVER, not the device — unlike Modbus/S7/OpcUaClient and contrary to what docs/drivers/Mqtt.md said. DriverDeviceConfigMerger merges a device's keys up only when the driver has exactly ONE device, so a device-authored broker connection vanishes silently the moment a second device is added. MqttDeviceForm is therefore informational (the GalaxyDeviceForm shape) and round-trips DeviceConfig verbatim; it flags legacy connection keys left on a device by a pre-form deployment, because those still win via merge-up. Serialization goes through the ONE shared MqttJson.Options instance (Task 9's decision), never a fifth per-form copy — pinned by an assertion that an ordinal-only reader CANNOT bind the emitted blob, and by extending the fleet-wide DriverPageJsonConverterTests guard to resolve a form's serializer from an explicit external-instance registry when it has no _jsonOpts field. Dropping the converter from MqttJson.Options reddens 3 tests and leaves the symmetric round-trip green — the Task 9 lesson, reproduced. RawTags is stripped from the emitted blob (the deploy artifact owns it) and unknown top-level keys survive a load->save. Validation mirrors the driver's own [Range] bounds inline, and ToOptions() additionally clamps, so an ignored error still cannot persist a connectTimeoutSeconds:0 driver-brick. A non-blank clientId raises the P1 live-gate warning inline (fixed ids make redundant pair nodes evict each other while both report Healthy). AdminUI 761/761 green (was 730), TWAE-clean; Driver.Mqtt 266/266 green. Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
55 lines
2.2 KiB
C#
55 lines
2.2 KiB
C#
using System.Text.Json.Nodes;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers.DeviceForms;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;
|
|
|
|
/// <summary>
|
|
/// Guards for the MQTT <b>device</b> model. MQTT holds ONE broker connection per driver, so — like
|
|
/// Galaxy — the device carries no endpoint of its own and the model round-trips its DeviceConfig
|
|
/// verbatim. The one behaviour it does add is detecting <i>legacy</i> connection keys left on a
|
|
/// DeviceConfig by a pre-form (hand-authored / SQL-seeded) deployment, because
|
|
/// <c>DriverDeviceConfigMerger</c> merges a sole device's keys UP over the driver's and they would
|
|
/// otherwise silently win over what the driver form shows.
|
|
/// </summary>
|
|
public sealed class MqttDeviceModelTests
|
|
{
|
|
[Fact]
|
|
public void Round_trips_every_key_verbatim()
|
|
{
|
|
const string inbound = """{"Host":"10.100.0.35","Port":8883,"anything":{"nested":true}}""";
|
|
|
|
var json = MqttDeviceModel.FromJson(inbound).ToJson();
|
|
var o = (JsonObject)JsonNode.Parse(json)!;
|
|
|
|
o["Host"]!.GetValue<string>().ShouldBe("10.100.0.35");
|
|
o["Port"]!.GetValue<int>().ShouldBe(8883);
|
|
o["anything"]!["nested"]!.GetValue<bool>().ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void A_new_device_serializes_to_an_empty_object()
|
|
=> MqttDeviceModel.FromJson(null).ToJson().ShouldBe("{}");
|
|
|
|
[Fact]
|
|
public void A_malformed_blob_degrades_to_an_empty_object_rather_than_throwing()
|
|
=> MqttDeviceModel.FromJson("}{ not json").ToJson().ShouldBe("{}");
|
|
|
|
[Fact]
|
|
public void Validate_always_passes_because_there_is_no_per_device_endpoint()
|
|
=> MqttDeviceModel.FromJson("""{"Host":"x"}""").Validate().ShouldBeNull();
|
|
|
|
[Fact]
|
|
public void Legacy_connection_keys_are_reported_case_insensitively()
|
|
{
|
|
var model = MqttDeviceModel.FromJson("""{"host":"h","PORT":1883,"useTls":false,"unrelated":1}""");
|
|
|
|
model.LegacyConnectionKeys.ShouldBe(["host", "PORT", "useTls"], ignoreOrder: true);
|
|
}
|
|
|
|
[Fact]
|
|
public void An_empty_device_reports_no_legacy_connection_keys()
|
|
=> MqttDeviceModel.FromJson("{}").LegacyConnectionKeys.ShouldBeEmpty();
|
|
}
|