// Tests for MqttQoS1Tracker (Gap 6.3 — JetStream-backed QoS 1/2 tracking). // Go reference: golang/nats-server/server/mqtt.go mqttProcessPub (~line 1200). using NATS.Server.Mqtt; using Shouldly; namespace NATS.Server.Tests.Mqtt; public sealed class MqttQoSTrackingTests { // ── QoS 1 Tracker ──────────────────────────────────────────────────────── [Fact] public void Register_assigns_packet_id() { // Go reference: server/mqtt.go mqttProcessPub — assigns non-zero packet ID for QoS 1 var tracker = new MqttQoS1Tracker(); var id = tracker.Register("sensors/temp", [0x01, 0x02]); id.ShouldNotBe((ushort)0); } [Fact] public void Register_increments_packet_id() { // Go reference: server/mqtt.go — each outgoing QoS 1 message gets a unique packet ID var tracker = new MqttQoS1Tracker(); var id1 = tracker.Register("sensors/temp", [0x01]); var id2 = tracker.Register("sensors/humidity", [0x02]); id1.ShouldNotBe(id2); } [Fact] public void Acknowledge_removes_pending() { // Go reference: server/mqtt.go mqttProcessPubAck — removes message from pending set var tracker = new MqttQoS1Tracker(); var id = tracker.Register("sensors/temp", [0xAB]); tracker.PendingCount.ShouldBe(1); var removed = tracker.Acknowledge(id); removed.ShouldBeTrue(); tracker.PendingCount.ShouldBe(0); } [Fact] public void Acknowledge_returns_false_for_unknown() { // Go reference: server/mqtt.go — PUBACK for unknown packet ID is silently ignored var tracker = new MqttQoS1Tracker(); var result = tracker.Acknowledge(9999); result.ShouldBeFalse(); } [Fact] public void PendingCount_reflects_current_state() { // Register 3 messages, acknowledge 1, expect count of 2 var tracker = new MqttQoS1Tracker(); var id1 = tracker.Register("a/b", [1]); tracker.Register("c/d", [2]); tracker.Register("e/f", [3]); tracker.Acknowledge(id1); tracker.PendingCount.ShouldBe(2); } [Fact] public void IsPending_true_for_registered() { // Go reference: server/mqtt.go — registered QoS 1 message is in the pending set var tracker = new MqttQoS1Tracker(); var id = tracker.Register("topic/x", [0xFF]); tracker.IsPending(id).ShouldBeTrue(); } [Fact] public void IsPending_false_after_acknowledge() { // Go reference: server/mqtt.go — message is removed from pending after PUBACK var tracker = new MqttQoS1Tracker(); var id = tracker.Register("topic/x", [0xFF]); tracker.Acknowledge(id); tracker.IsPending(id).ShouldBeFalse(); } [Fact] public void GetPendingForRedelivery_returns_all_pending() { // Go reference: server/mqtt.go reconnect path — all unacked messages are redelivered var tracker = new MqttQoS1Tracker(); tracker.Register("a", [1]); tracker.Register("b", [2]); tracker.Register("c", [3]); var pending = tracker.GetPendingForRedelivery(); pending.Count.ShouldBe(3); } [Fact] public void GetPendingForRedelivery_increments_delivery_count() { // Go reference: server/mqtt.go reconnect redelivery — DUP flag set, delivery count increments var tracker = new MqttQoS1Tracker(); tracker.Register("test/topic", [0xDE, 0xAD]); var pending = tracker.GetPendingForRedelivery(); pending[0].DeliveryCount.ShouldBe(2); } [Fact] public void Clear_removes_all_pending() { // Go reference: server/mqtt.go session cleanup — all pending messages discarded on clean session var tracker = new MqttQoS1Tracker(); tracker.Register("x", [1]); tracker.Register("y", [2]); tracker.Register("z", [3]); tracker.Clear(); tracker.PendingCount.ShouldBe(0); } }