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
118 lines
4.5 KiB
C#
118 lines
4.5 KiB
C#
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Modbus;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests;
|
|
|
|
public sealed class ModbusDriverPageFormSerializationTests
|
|
{
|
|
private static readonly JsonSerializerOptions _opts = new()
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
WriteIndented = false,
|
|
};
|
|
|
|
[Fact]
|
|
public void RoundTrip_PreservesKnownFields()
|
|
{
|
|
var original = new ModbusDriverOptions
|
|
{
|
|
Host = "10.0.0.42",
|
|
Port = 5020,
|
|
UnitId = 3,
|
|
Timeout = TimeSpan.FromSeconds(5),
|
|
MaxRegistersPerRead = 64,
|
|
MaxRegistersPerWrite = 60,
|
|
MaxCoilsPerRead = 500,
|
|
MaxReadGap = 8,
|
|
UseFC15ForSingleCoilWrites = true,
|
|
UseFC16ForSingleRegisterWrites = true,
|
|
DisableFC23 = true,
|
|
UseFC23ForMultiRegisterWrites = true,
|
|
DisableReadCoalescing = true,
|
|
WriteOnChangeOnly = true,
|
|
AutoReconnect = false,
|
|
Family = ModbusFamily.DL205,
|
|
MelsecSubFamily = MelsecFamily.F_iQF,
|
|
Probe = new ModbusProbeOptions
|
|
{
|
|
Enabled = false,
|
|
Interval = TimeSpan.FromSeconds(10),
|
|
Timeout = TimeSpan.FromSeconds(3),
|
|
ProbeAddress = 7,
|
|
},
|
|
KeepAlive = new ModbusKeepAliveOptions
|
|
{
|
|
Enabled = false,
|
|
Time = TimeSpan.FromSeconds(60),
|
|
Interval = TimeSpan.FromSeconds(15),
|
|
RetryCount = 5,
|
|
},
|
|
Reconnect = new ModbusReconnectOptions
|
|
{
|
|
InitialDelay = TimeSpan.FromSeconds(1),
|
|
MaxDelay = TimeSpan.FromSeconds(60),
|
|
BackoffMultiplier = 1.5,
|
|
},
|
|
ProbeTimeoutSeconds = 10,
|
|
};
|
|
|
|
var json = JsonSerializer.Serialize(original, _opts);
|
|
var back = JsonSerializer.Deserialize<ModbusDriverOptions>(json, _opts);
|
|
|
|
back.ShouldNotBeNull();
|
|
back.Host.ShouldBe("10.0.0.42");
|
|
back.Port.ShouldBe(5020);
|
|
back.UnitId.ShouldBe((byte)3);
|
|
back.Timeout.ShouldBe(TimeSpan.FromSeconds(5));
|
|
back.MaxRegistersPerRead.ShouldBe((ushort)64);
|
|
back.MaxRegistersPerWrite.ShouldBe((ushort)60);
|
|
back.MaxCoilsPerRead.ShouldBe((ushort)500);
|
|
back.MaxReadGap.ShouldBe((ushort)8);
|
|
back.UseFC15ForSingleCoilWrites.ShouldBeTrue();
|
|
back.UseFC16ForSingleRegisterWrites.ShouldBeTrue();
|
|
back.DisableFC23.ShouldBeTrue();
|
|
back.UseFC23ForMultiRegisterWrites.ShouldBeTrue();
|
|
back.DisableReadCoalescing.ShouldBeTrue();
|
|
back.WriteOnChangeOnly.ShouldBeTrue();
|
|
back.AutoReconnect.ShouldBeFalse();
|
|
back.Family.ShouldBe(ModbusFamily.DL205);
|
|
back.MelsecSubFamily.ShouldBe(MelsecFamily.F_iQF);
|
|
back.Probe.Enabled.ShouldBeFalse();
|
|
back.Probe.Interval.ShouldBe(TimeSpan.FromSeconds(10));
|
|
back.Probe.Timeout.ShouldBe(TimeSpan.FromSeconds(3));
|
|
back.Probe.ProbeAddress.ShouldBe((ushort)7);
|
|
back.KeepAlive.Enabled.ShouldBeFalse();
|
|
back.KeepAlive.Time.ShouldBe(TimeSpan.FromSeconds(60));
|
|
back.KeepAlive.Interval.ShouldBe(TimeSpan.FromSeconds(15));
|
|
back.KeepAlive.RetryCount.ShouldBe(5);
|
|
back.Reconnect.InitialDelay.ShouldBe(TimeSpan.FromSeconds(1));
|
|
back.Reconnect.MaxDelay.ShouldBe(TimeSpan.FromSeconds(60));
|
|
back.Reconnect.BackoffMultiplier.ShouldBe(1.5);
|
|
back.ProbeTimeoutSeconds.ShouldBe(10);
|
|
}
|
|
|
|
[Fact]
|
|
public void Deserialize_DropsUnknownFields()
|
|
{
|
|
var jsonWithExtra = """{"unknownField":"old-value","probeTimeoutSeconds":10}""";
|
|
|
|
var optsWithSkip = new JsonSerializerOptions(_opts)
|
|
{
|
|
UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip,
|
|
};
|
|
|
|
var back = JsonSerializer.Deserialize<ModbusDriverOptions>(jsonWithExtra, optsWithSkip);
|
|
back.ShouldNotBeNull();
|
|
back.ProbeTimeoutSeconds.ShouldBe(10);
|
|
}
|
|
|
|
// v3 Batch-1 migration: the pre-declared Tags editor (ModbusDriverPage.ModbusTagRow +
|
|
// ModbusDriverOptions.Tags) was retired — tags moved to the Raw tree (/raw, Batch 2). The former
|
|
// TagRow_* / Tag_list_survives_* / ValidateRow_* / ToDefinition_preserves_* tests were removed.
|
|
// The connection/protocol-field + enum-serialization coverage (ModbusFamily, MelsecFamily) is
|
|
// retained by RoundTrip_PreservesKnownFields above unchanged.
|
|
}
|