fix(adminui): authoring models consult the driver's own parse for non-enum TagConfig rules (#554)
#549/#551 made every typed editor judge its driver's enum KEYS against the runtime's own reader and stopped there. Every other reason a reader refuses a tag met no gate until deploy, or until the driver silently skipped the tag: an absent Modbus `region`, an `"address":"notanumber"`, an S7 `stringLength` of 300, a TwinCAT `dataType: "Structure"` (a VALID enum name the reader refuses), a Sql blob the parser cannot recognise as Sql at all, an MQTT `"qos":null`. Rather than hand-mirroring N more rules into N models — the drift #551 closed on the deploy side — each `<Driver>TagConfigModel.Validate()` now asks its driver's own parse whether it would read the AS-LOADED blob, via the new `TagConfigParseGate`. One authority for the verdict AND the wording. Two cautions are load-bearing and are pinned by tests: - The gate is the PARSE VERDICT, never "the inspector said something". FOCAS, MQTT and Calculation inspectors emit advisories for blobs their reader ACCEPTS, so a non-empty-inspection gate would make the modal stricter than the runtime and leave rows nobody can save (#520/#535). Inspect is consulted only to word a refusal that already happened. - It judges the blob the model was LOADED FROM, not the typed fields — the lenient load ("address":"notanumber" ⇒ 0) is what hides the defect. Same capture-the-source shape as S7's `_dataTypeMissingInSource`. Deliberately-stricter editor rules were surveyed and KEPT, not aligned: AbLegacy's arrayLength > 256 (reader clamps, but the un-clamped value still sets ArrayDimensions), FOCAS's unparseable address (#540), MQTT wildcard topics + Sparkplug segment chars, and Calculation's trigger/50 ms floors. They are now recorded fleet-wide in a table the tests assert is still accurate. Behaviour change: Modbus `{}` is now refused. Modbus was the one mapped editor with no required field, and `{}` is exactly what the modal persists for a config nobody touched — the driver skips it and it reads BadNodeIdUnknown forever with the deploy green. Its pinned test asserted the old, weaker behaviour and is rewritten with the rationale. Falsification: neutering only the Modbus consult turns 14 tests red, all Modbus-scoped; every #549 enum test stays green.
This commit is contained in:
@@ -31,16 +31,23 @@ public sealed class RawTreeServiceWaveBTests
|
||||
return selector(db);
|
||||
}
|
||||
|
||||
private static RawTagInput Tag(string name, string dataType = "Float", string tagConfig = "{}") =>
|
||||
/// <summary>
|
||||
/// A <c>TagConfig</c> the <b>Modbus</b> driver would actually read — <c>FolderedDeviceId</c> hangs off
|
||||
/// the seed's Modbus driver, and the service runs the driver-typed <c>TagConfigValidator</c> on every
|
||||
/// tag it writes. Gitea #554 made the Modbus editor ask the driver's own reader, which requires a
|
||||
/// <c>region</c> and an in-range <c>address</c>, so the bare <c>{}</c> this helper used to default to
|
||||
/// is a config the driver skips (<c>BadNodeIdUnknown</c>) and the service now refuses. None of the
|
||||
/// tests below is about the config blob — they are tree-structure and concurrency tests — so they
|
||||
/// take the minimal readable one.
|
||||
/// </summary>
|
||||
private const string ModbusConfig = """{"region":"HoldingRegisters","address":1}""";
|
||||
|
||||
private static RawTagInput Tag(string name, string dataType = "Float", string tagConfig = ModbusConfig) =>
|
||||
new(name, dataType, TagAccessLevel.ReadWrite, WriteIdempotent: false, PollGroupId: null, tagConfig);
|
||||
|
||||
/// <summary>
|
||||
/// A tag input whose <c>TagConfig</c> the <b>S7</b> driver would actually read — <c>RootDeviceId</c>
|
||||
/// hangs off the seed's S7 driver, and since Gitea #551 the service runs the driver-typed
|
||||
/// <c>TagConfigValidator</c> on every tag it writes. S7 requires BOTH an <c>address</c> and a
|
||||
/// present <c>dataType</c> key, so the bare <c>{}</c> the sibling helper defaults to is a config the
|
||||
/// driver rejects and the service now refuses. The Modbus-device tests keep <c>{}</c>: Modbus reads
|
||||
/// only enums out of the blob and accepts an empty one.
|
||||
/// The same, for the <b>S7</b> device — <c>RootDeviceId</c> hangs off the seed's S7 driver, which
|
||||
/// requires BOTH an <c>address</c> and a present <c>dataType</c> key (Gitea #551).
|
||||
/// </summary>
|
||||
/// <param name="name">The tag name.</param>
|
||||
/// <returns>The import-ready tag input.</returns>
|
||||
@@ -74,7 +81,7 @@ public sealed class RawTreeServiceWaveBTests
|
||||
|
||||
var result = await service.CreateTagAsync(
|
||||
RawTreeTestDb.FolderedDeviceId, null,
|
||||
new RawTagInput("flow", "Float", TagAccessLevel.Read, false, " ", "{}"));
|
||||
new RawTagInput("flow", "Float", TagAccessLevel.Read, false, " ", ModbusConfig));
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
using var db = RawTreeTestDb.CreateNamed(dbName);
|
||||
@@ -126,7 +133,12 @@ public sealed class RawTreeServiceWaveBTests
|
||||
|
||||
var result = await service.UpdateTagAsync(
|
||||
RawTreeTestDb.PlainTagId,
|
||||
new RawTagInput("running", "Boolean", TagAccessLevel.ReadWrite, true, "pg1", "{\"scale\":2}"),
|
||||
// The unknown "scale" key rides a readable Modbus config: this test is about field edits and
|
||||
// unknown-key survival, and since Gitea #554 the service's driver-typed gate refuses a blob the
|
||||
// Modbus reader would skip.
|
||||
new RawTagInput(
|
||||
"running", "Boolean", TagAccessLevel.ReadWrite, true, "pg1",
|
||||
"""{"region":"HoldingRegisters","address":1,"scale":2}"""),
|
||||
rv);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
|
||||
Reference in New Issue
Block a user