18f8eb5d2f
Array nodes were forced read-only for every driver. A new single authority, Core.Abstractions DriverArrayWriteSupport, says which (driver, element type) pairs encode a whole-array write: Modbus (Coils / numeric HoldingRegisters), S7 (Byte/Int16/UInt16/Int32/UInt32/Float32), OpcUaClient (pass-through) and Galaxy (Boolean/Int32/Int64/Float/Double/String/DateTime). Everything else stays read-only, so the server never advertises a write the driver refuses. - Composer derives RawTagPlan.ArrayWritable + UnsReferenceVariable .BackingArrayWritable at compose time (both seams, byte-parity); the applier's one writability rule is Writable && (!IsArray || ArrayWritable) for materialise and surgical paths, raw and UNS alike. - ArrayWritable is on the classifier's surgical raw-tag whitelist and mirrored on the UNS record (UnsReferenceBackingParityTests updated). - Node manager refuses a wrong-length / non-array value on an array node (BadTypeMismatch) and any IndexRange write (BadWriteNotSupported) inline, before dispatch; WriteOperate/ACL gates and the revert are unchanged. - Per-driver parity tests pin the table against the real Modbus write path, S7 EncodeArrayBlock and Galaxy MxValueEncoder (UInt32/UInt64 excluded: array covariance reinterprets their bits). - Over-the-wire proof in OpcUaServer.IntegrationTests (ArrayWriteWireTests). - ValueRank > 1 documented as out of scope by decision (docs/Uns.md). Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
50 lines
1.8 KiB
C#
50 lines
1.8 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.S7.Tests;
|
|
|
|
/// <summary>
|
|
/// Gitea #610 — pins <see cref="DriverArrayWriteSupport"/>'s S7 row against the REAL array encoder
|
|
/// (<see cref="S7Driver.EncodeArrayBlock"/>). For every <see cref="S7DataType"/>, the server advertises an
|
|
/// array node writable exactly when the encoder accepts the element type, so a driver change that adds or
|
|
/// drops an array element type fails here until the table follows.
|
|
/// </summary>
|
|
[Trait("Category", "Unit")]
|
|
public sealed class S7ArrayWriteSupportParityTests
|
|
{
|
|
public static TheoryData<S7DataType> AllTypes() => new(Enum.GetValues<S7DataType>());
|
|
|
|
// An address whose access size matches the element type, so only the element type can make the encoder refuse.
|
|
private static string AddressFor(S7DataType dt) => dt switch
|
|
{
|
|
S7DataType.Bool => "DB1.DBX0.0",
|
|
S7DataType.Byte => "DB1.DBB0",
|
|
S7DataType.Int16 or S7DataType.UInt16 => "DB1.DBW0",
|
|
_ => "DB1.DBD0",
|
|
};
|
|
|
|
[Theory]
|
|
[MemberData(nameof(AllTypes))]
|
|
public void Table_agrees_with_EncodeArrayBlock(S7DataType dataType)
|
|
{
|
|
var address = AddressFor(dataType);
|
|
var tag = new S7TagDefinition("Arr", address, dataType, Writable: true, ArrayCount: 2);
|
|
|
|
bool encoderAccepts;
|
|
try
|
|
{
|
|
S7Driver.EncodeArrayBlock(tag, S7AddressParser.Parse(address), new object[] { 1, 1 });
|
|
encoderAccepts = true;
|
|
}
|
|
catch (NotSupportedException)
|
|
{
|
|
encoderAccepts = false;
|
|
}
|
|
|
|
DriverArrayWriteSupport.Supports(
|
|
DriverTypeNames.S7, "Int16", $$"""{"address":"{{address}}","dataType":"{{dataType}}","isArray":true,"arrayLength":2}""")
|
|
.ShouldBe(encoderAccepts, dataType.ToString());
|
|
}
|
|
}
|