3531834cce
N1: the m2 precedence trusted region/address whenever the keys were present,
but the AdminUI model stamps defaulted HoldingRegisters/0 on every save, so a
string-only tag (CSV import, or any editor toggle) silently became HR[0] -
reads AND writes. The resolved keys are now authoritative only when the editor
marked them ("addressResolved": true, written by Resolve); an unmarked
addressString is always resolved through the parser with the driver's family.
The model no longer stamps address fields for a string-only tag, CSV export
leaves its address cells blank, and the editor hint distinguishes string-only
from pinned tags. Tests: CSV import and editor save of a string-only tag, a
stamped-default blob, and the pinned positive control across families.
n1: docs/v2/modbus-addressing.md updated (FC23 opt-in, maxReadGap semantics,
coalescing scope, addressString/family rules, coil coercion, refusals).
n2: form round-trip tests for UseFC23ForMultiRegisterWrites /
DisableReadCoalescing. n4: the editor keeps unresolved address text when the
parent echoes back its own emit. n3: noted, no change.
Refs #741
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
93 lines
3.8 KiB
C#
93 lines
3.8 KiB
C#
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers.Forms;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Modbus;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;
|
|
|
|
/// <summary>
|
|
/// Round-trip + enum-serialization guard for the Modbus <b>driver</b> (channel/protocol) form model.
|
|
/// Reproduces the historical driver-enum-serialization bug: enums MUST serialize as their name strings
|
|
/// (JsonStringEnumConverter) with camelCase keys, or the runtime factory faults on the numeric form.
|
|
/// </summary>
|
|
public sealed class ModbusDriverFormModelTests
|
|
{
|
|
private static readonly JsonSerializerOptions JsonOpts = new()
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip,
|
|
WriteIndented = false,
|
|
Converters = { new JsonStringEnumConverter() },
|
|
};
|
|
|
|
/// <summary>Review r1 n2: the lane's new form keys survive FromOptions → ToOptions, and their defaults keep
|
|
/// the historical wire (FC16, coalescing on at gap 8 only when the key is absent).</summary>
|
|
[Fact]
|
|
public void Form_round_trips_the_FC23_opt_in_and_coalescing_keys()
|
|
{
|
|
var form = new ModbusDriverForm.FormModel { UseFC23ForMultiRegisterWrites = true, DisableFC23 = true, DisableReadCoalescing = true, MaxReadGap = 0 };
|
|
var back = ModbusDriverForm.FormModel.FromOptions(form.ToOptions());
|
|
back.UseFC23ForMultiRegisterWrites.ShouldBeTrue();
|
|
back.DisableFC23.ShouldBeTrue();
|
|
back.DisableReadCoalescing.ShouldBeTrue();
|
|
back.MaxReadGap.ShouldBe(0);
|
|
|
|
var defaults = new ModbusDriverForm.FormModel().ToOptions();
|
|
defaults.UseFC23ForMultiRegisterWrites.ShouldBeFalse();
|
|
defaults.MaxReadGap.ShouldBe((ushort)8);
|
|
}
|
|
|
|
[Fact]
|
|
public void Round_trip_preserves_channel_fields_and_enum()
|
|
{
|
|
var form = new ModbusDriverForm.FormModel
|
|
{
|
|
Family = ModbusFamily.MELSEC,
|
|
MelsecSubFamily = MelsecFamily.Q_L_iQR,
|
|
TimeoutSeconds = 4,
|
|
MaxRegistersPerRead = 100,
|
|
WriteOnChangeOnly = true,
|
|
KeepAliveRetryCount = 5,
|
|
ReconnectBackoffMultiplier = 3.0,
|
|
ProbeAddress = 40,
|
|
};
|
|
|
|
var json = JsonSerializer.Serialize(form.ToOptions(), JsonOpts);
|
|
var back = ModbusDriverForm.FormModel.FromOptions(
|
|
JsonSerializer.Deserialize<ModbusDriverOptions>(json, JsonOpts)!);
|
|
|
|
back.Family.ShouldBe(ModbusFamily.MELSEC);
|
|
back.MelsecSubFamily.ShouldBe(MelsecFamily.Q_L_iQR);
|
|
back.TimeoutSeconds.ShouldBe(4);
|
|
back.MaxRegistersPerRead.ShouldBe(100);
|
|
back.WriteOnChangeOnly.ShouldBeTrue();
|
|
back.KeepAliveRetryCount.ShouldBe(5);
|
|
back.ReconnectBackoffMultiplier.ShouldBe(3.0);
|
|
back.ProbeAddress.ShouldBe(40);
|
|
}
|
|
|
|
[Fact]
|
|
public void Serialized_config_uses_camelCase_keys_and_enum_names()
|
|
{
|
|
var form = new ModbusDriverForm.FormModel { Family = ModbusFamily.MELSEC };
|
|
|
|
var json = JsonSerializer.Serialize(form.ToOptions(), JsonOpts);
|
|
|
|
json.ShouldContain("\"family\":\"MELSEC\""); // enum-as-name (not a number), camelCase key
|
|
json.ShouldNotContain("\"family\":0", Case.Sensitive); // never the numeric enum form
|
|
}
|
|
|
|
[Fact]
|
|
public void Round_trip_preserves_Transport_mode()
|
|
{
|
|
var form = new ModbusDriverForm.FormModel { Transport = ModbusTransportMode.RtuOverTcp };
|
|
var json = JsonSerializer.Serialize(form.ToOptions(), JsonOpts);
|
|
var back = ModbusDriverForm.FormModel.FromOptions(
|
|
JsonSerializer.Deserialize<ModbusDriverOptions>(json, JsonOpts)!);
|
|
back.Transport.ShouldBe(ModbusTransportMode.RtuOverTcp);
|
|
json.ShouldContain("\"transport\":\"RtuOverTcp\""); // name string, never a number
|
|
}
|
|
}
|