refactor(mqtt): accept a blank jsonPath; guide with a seeded "$" instead
Review follow-up. Dropped the hard Validate rejection of a blank jsonPath under a Json payload: the runtime defaults it to the document root, which is the real "publisher puts a bare JSON scalar on the topic" case, so rejecting it made the editor refuse what the driver accepts — inverting "authoring surface accepts <=> publish accepts" — and blocked whole CSV-import batches, since this validator also gates RawManualTagEntryModal's review grid. Replaced with guidance: an UNAUTHORED tag (no topic AND no jsonPath) seeds the field with "$", so a fresh tag emits an explicit "jsonPath":"$" while an existing path-less tag keeps the key ABSENT and the driver defaults it. The wildcard-topic rule stays strict — a wildcard has no legitimate single-Tag interpretation. Also: omit a blank "topic" rather than writing "topic":"", and record why the _lastConfigJson guard diverges from the Modbus template — the landmine is a DERIVED, NON-PERSISTED UI FIELD INFERRED FROM BLOB CONTENT (Mode), which Task 24's Sparkplug field group will be tempted to add more of. Named the host-side dependency (RawTagModal only mutates through this callback) that keeps the staleness trade benign today. Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
This commit is contained in:
@@ -30,7 +30,9 @@ public sealed class MqttTagConfigModelTests
|
||||
m.Mode.ShouldBe(MqttMode.Plain);
|
||||
m.Topic.ShouldBe("");
|
||||
m.PayloadFormat.ShouldBe(MqttPayloadFormat.Json);
|
||||
m.JsonPath.ShouldBe("");
|
||||
// An UNAUTHORED tag seeds the document-root path so the common "no extraction needed" case is
|
||||
// visible and one click away — it is a guide, not a requirement (blank is accepted, see below).
|
||||
m.JsonPath.ShouldBe("$");
|
||||
m.DataType.ShouldBe(DriverDataType.String);
|
||||
m.Qos.ShouldBeNull();
|
||||
m.RetainSeed.ShouldBeNull();
|
||||
@@ -107,6 +109,15 @@ public sealed class MqttTagConfigModelTests
|
||||
json.ShouldNotContain("jsonPath");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToJson_omits_a_blank_topic()
|
||||
{
|
||||
// Toggling the shape dropdown on an untouched tag must not leave a stray "topic":"" behind.
|
||||
var json = new MqttTagConfigModel { PayloadFormat = MqttPayloadFormat.Raw }.ToJson();
|
||||
|
||||
json.ShouldNotContain("topic");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FromJson_then_ToJson_preserves_unknown_keys()
|
||||
{
|
||||
@@ -171,13 +182,38 @@ public sealed class MqttTagConfigModelTests
|
||||
=> MqttTagConfigModel.FromJson($$"""{"topic":"{{topic}}","payloadFormat":"Raw","dataType":"String"}""")
|
||||
.Validate().ShouldNotBeNullOrWhiteSpace();
|
||||
|
||||
// A blank jsonPath under a Json payload is ACCEPTED — the runtime defaults it to the document root
|
||||
// ("$"), which is the real "publisher puts a bare JSON scalar on the topic" case. Rejecting it would
|
||||
// make the editor refuse what the driver accepts, and would block CSV-import batches (this validator
|
||||
// also gates RawManualTagEntryModal's review grid) over a sane default.
|
||||
[Fact]
|
||||
public void Validate_JsonWithoutPath_Fails()
|
||||
public void Validate_JsonWithoutPath_IsAccepted()
|
||||
=> MqttTagConfigModel.FromJson("""{"topic":"a/b","payloadFormat":"Json","dataType":"Float64"}""")
|
||||
.Validate().ShouldNotBeNullOrWhiteSpace();
|
||||
.Validate().ShouldBeNull();
|
||||
|
||||
// …and the key stays ABSENT through a load→save, so the driver applies its own default rather than
|
||||
// this editor freezing "$" into an already-deployed blob.
|
||||
[Fact]
|
||||
public void ToJson_leaves_an_existing_blank_jsonPath_absent()
|
||||
{
|
||||
var json = MqttTagConfigModel
|
||||
.FromJson("""{"topic":"a/b","payloadFormat":"Json","dataType":"Float64"}""")
|
||||
.ToJson();
|
||||
|
||||
json.ShouldNotContain("jsonPath");
|
||||
}
|
||||
|
||||
// The seed applies ONLY to an unauthored tag (no topic AND no jsonPath) — a brand-new tag emits an
|
||||
// explicit "$" once the operator touches anything, which is the guided half of the deal.
|
||||
[Fact]
|
||||
public void ToJson_seeds_the_root_jsonPath_on_a_fresh_tag()
|
||||
{
|
||||
var m = MqttTagConfigModel.FromJson(null);
|
||||
m.Topic = "a/b";
|
||||
|
||||
m.ToJson().ShouldContain("\"jsonPath\":\"$\"");
|
||||
}
|
||||
|
||||
// Falsifiability control for the test above: the SAME blob with a jsonPath must validate clean, so
|
||||
// the failure above is provably the missing jsonPath and not the topic or the dataType.
|
||||
[Fact]
|
||||
public void Validate_JsonWithPath_ReturnsNull()
|
||||
=> MqttTagConfigModel.FromJson("""{"topic":"a/b","payloadFormat":"Json","jsonPath":"$","dataType":"Float64"}""")
|
||||
|
||||
Reference in New Issue
Block a user