test(mqtt): pin status-code half of republish suppression; note Sparkplug bypass
This commit is contained in:
@@ -755,6 +755,10 @@ public sealed class MqttSubscriptionManager
|
||||
// sink write) per message. The cache is still freshened — a read must serve the current
|
||||
// timestamp — and only the notification is skipped. "Cached but not raised" is already a valid
|
||||
// state on this path (see Raise: an authored-but-unsubscribed tag does exactly that).
|
||||
//
|
||||
// Sparkplug bypasses this entirely. SparkplugIngestor.cs updates Values and raises directly
|
||||
// (its own Values.Update()/Raise() calls, not through this Deliver method), so Sparkplug B
|
||||
// ingestion gets no unchanged-value suppression — deliberately out of scope here.
|
||||
var prior = Values.Read(def.Name);
|
||||
var unchanged = prior.StatusCode == snapshot.StatusCode && !ValuesAreDifferent(prior.Value, snapshot.Value);
|
||||
|
||||
|
||||
@@ -1032,6 +1032,13 @@ public sealed class MqttSubscriptionManagerTests
|
||||
/// <c>DegradeTopic</c>: a Good value followed by a refused subscription must reach the
|
||||
/// subscriber as Bad, or the client keeps a stale Good forever.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This pins the <c>DegradeTopic</c> path specifically — <c>DegradeTopic</c>'s own
|
||||
/// <c>Raise</c> call was already unconditional before the suppression change and is untouched
|
||||
/// by it. It does NOT exercise the <c>prior.StatusCode == snapshot.StatusCode</c> comparison
|
||||
/// added inside <see cref="MqttSubscriptionManager.Deliver"/>; see
|
||||
/// <see cref="Different_malformed_payloads_with_the_same_null_value_both_raise"/> for that.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task A_status_transition_still_raises()
|
||||
{
|
||||
@@ -1057,6 +1064,39 @@ public sealed class MqttSubscriptionManagerTests
|
||||
mgr.Values.Read(OvenTempPath).StatusCode.ShouldBe(BadCommunicationError);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pins the status-code half of <c>Deliver</c>'s suppression comparison
|
||||
/// (<c>prior.StatusCode == snapshot.StatusCode</c>) through the LIVE
|
||||
/// <see cref="MqttSubscriptionManager.HandleMessage"/> path, which
|
||||
/// <see cref="A_status_transition_still_raises"/> does not reach (that test goes through the
|
||||
/// unconditional <c>DegradeTopic</c> path instead). Two different malformed payloads on the
|
||||
/// SAME topic each decode to a <c>null</c> value but a DIFFERENT Bad status code — a decode
|
||||
/// failure (<c>BadDecodingError</c>) followed by a value that decodes but fails type coercion
|
||||
/// (<c>BadTypeMismatch</c>). A value-only comparison (ignoring status) would see
|
||||
/// <c>null == null</c> on both transitions and wrongly suppress the second.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Different_malformed_payloads_with_the_same_null_value_both_raise()
|
||||
{
|
||||
var mgr = NewManager();
|
||||
mgr.Register([Tag(OvenTempPath, $$"""{"topic":"{{OvenTopic}}","payloadFormat":"Json","jsonPath":"$.value","dataType":"Float64"}""")]);
|
||||
await mgr.SubscribeAsync([OvenTempPath], TimeSpan.FromSeconds(1), TestContext.Current.CancellationToken);
|
||||
|
||||
var raised = new List<DataValueSnapshot>();
|
||||
mgr.OnDataChange += (_, e) => raised.Add(e.Snapshot);
|
||||
|
||||
// Decode failure: not valid JSON at all.
|
||||
mgr.HandleMessage(OvenTopic, "{not json"u8.ToArray(), retained: false);
|
||||
// Decodes fine, but the selected value does not fit the declared Float64 type.
|
||||
mgr.HandleMessage(OvenTopic, """{"value":"not-a-number"}"""u8.ToArray(), retained: false);
|
||||
|
||||
raised.Count.ShouldBe(2);
|
||||
raised[0].StatusCode.ShouldBe(BadDecodingError);
|
||||
raised[0].Value.ShouldBeNull();
|
||||
raised[1].StatusCode.ShouldBe(BadTypeMismatch);
|
||||
raised[1].Value.ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>Records every subscribe call so dedupe / re-subscribe can be asserted without a broker.</summary>
|
||||
private sealed class FakeTransport : IMqttSubscribeTransport
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user