fix(common): coerce null/empty/whitespace observation Result to UNAVAILABLE#193
fix(common): coerce null/empty/whitespace observation Result to UNAVAILABLE#193ottobolyos wants to merge 4 commits into
Conversation
66db93a to
0a58cfb
Compare
a2c41aa to
a90e23b
Compare
a90e23b to
9d1e172
Compare
|
@PatrickRitchie, this PR fixes a Part 1 spec violation that touches every wire output: |
|
@PatrickRitchie — heads-up: while validating PR #189's dime-connector build I caught an unrelated multi-TFM Release-pack regression introduced by an earlier warnings sweep. The minimal fix lands in PR #194 (just opened as draft); the broader warnings cleanup plus a Release-pack CI gate ships in PR #195. |
Summary
MTConnectAgent.AddObservationaccepted observations whose Result value wasnull, the empty string"", or whitespace-only and forwarded that value verbatim onto every wire transport (HTTP/current,/sample, MQTT JSON-cppagent, SHDR). MTConnect Part 1 §Observation Information Model — Representation — Observation Values mandates"UNAVAILABLE"as the sole valid representation of a missing or undetermined observation value:The empty string is, by definition, not a Valid Data Value (it parses as no value at all under every typed DataItem schema). This PR adds an unconditional coerce step in the canonical
AddObservation(string deviceKey, IObservationInput, …)overload — null / empty / whitespace Result is rewritten toObservation.Unavailablebefore validation, so:Ignore/Warning/Remove: publishesUNAVAILABLErather than the empty value, satisfying the spec mandate on the wire.Strict: coerces toUNAVAILABLE, the observation lands rather than being silently dropped.The coerce skips CONDITION observations (their Level enum cannot carry the empty pathology). DATA_SET / TABLE / TIME_SERIES Representation pre-fill of Count / SampleCount runs after the coerce — semantics for those representations are unchanged.
No opt-out flag is exposed: the spec mandate is
MUST, so a way to opt out of compliance would itself be non-conformant.Behaviour change
null""(empty)"UNAVAILABLE""""""UNAVAILABLE"" "(whitespace)" ""UNAVAILABLE""AVAILABLE"(concrete)"AVAILABLE""AVAILABLE"(unchanged)""underStrict"UNAVAILABLE"Tests
Unit (NUnit,
tests/MTConnect.NET-Common-Tests/Agents/AddObservationEmptyResultCoerceTests.cs, 10 cases):InputValidationLevelnull/""/ whitespace family underWarningStrict(lands, not silently dropped)Wire-level E2E (xUnit,
[Trait("Category", "E2E")]):tests/MTConnect.NET-Integration-Tests/Workflows/AddObservationEmptyResultUnavailableWorkflowTests.cs— HTTP/current:UNAVAILABLE(6-case[Theory])UNAVAILABLE)Strictvalidation → AVAILABILITY element body isUNAVAILABLE(lands, not silently dropped; spins its own agent + server harness)tests/MTConnect.NET-Integration-Tests/Workflows/AddObservationEmptyResultUnavailableSampleStreamWorkflowTests.cs— HTTP/samplestream:UNAVAILABLE(6-case[Theory])UNAVAILABLE); proves the coerce does not silently drop the second observationAll wire surfaces fed by the agent's buffer read the same coerced payload — every transport (HTTP, MQTT relay, SHDR adapter output) inherits the fix because the coerce sits above the buffer.
Files touched
libraries/MTConnect.NET-Common/Agents/MTConnectAgent.cs— invoke the coerce inAddObservation; addIsEmptyResult+CoerceEmptyResultToUnavailablehelpers.tests/MTConnect.NET-Common-Tests/Agents/AddObservationEmptyResultCoerceTests.cs— new unit fixture.tests/MTConnect.NET-Integration-Tests/Workflows/AddObservationEmptyResultUnavailableWorkflowTests.cs— new wire-level E2E fixture against HTTP/current.tests/MTConnect.NET-Integration-Tests/Workflows/AddObservationEmptyResultUnavailableSampleStreamWorkflowTests.cs— new wire-level E2E fixture against HTTP/samplestream..github/workflows/dotnet.yml— drop matrix expressions from the two matrix jobs'name:fields so skipped-state status checks render cleanly (build-and-test/route-check-e2einstead ofbuild-and-test-${{ matrix.os }}).