using NATS.Server.JetStream.Consumers; namespace NATS.Server.JetStream.Tests.JetStream.Consumers; /// /// Tests for the new PriorityQueue-based RedeliveryTracker features. /// Go reference: consumer.go (rdq redelivery queue). /// public class RedeliveryTrackerPriorityQueueTests { [Fact] public void Schedule_and_get_due_returns_expired() { var tracker = new RedeliveryTracker(maxDeliveries: 5, ackWaitMs: 1000); var past = DateTimeOffset.UtcNow.AddMilliseconds(-100); tracker.Schedule(1, past); tracker.Schedule(2, DateTimeOffset.UtcNow.AddSeconds(60)); // future var due = tracker.GetDue(DateTimeOffset.UtcNow).ToList(); due.Count.ShouldBe(1); due[0].ShouldBe(1UL); } [Fact] public void Acknowledge_removes_from_queue() { var tracker = new RedeliveryTracker(maxDeliveries: 5, ackWaitMs: 1000); tracker.Schedule(1, DateTimeOffset.UtcNow.AddMilliseconds(-100)); tracker.Acknowledge(1); var due = tracker.GetDue(DateTimeOffset.UtcNow).ToList(); due.ShouldBeEmpty(); } [Fact] public void IsMaxDeliveries_returns_true_at_threshold() { var tracker = new RedeliveryTracker(maxDeliveries: 3, ackWaitMs: 1000); tracker.IncrementDeliveryCount(1); tracker.IncrementDeliveryCount(1); tracker.IsMaxDeliveries(1).ShouldBeFalse(); tracker.IncrementDeliveryCount(1); tracker.IsMaxDeliveries(1).ShouldBeTrue(); } [Fact] public void Backoff_schedule_uses_delivery_count() { var backoff = new long[] { 100, 500, 2000 }; var tracker = new RedeliveryTracker(maxDeliveries: 10, ackWaitMs: 1000, backoffMs: backoff); // First redeliver: 100ms var delay1 = tracker.GetBackoffDelay(deliveryCount: 1); delay1.ShouldBe(100L); // Second: 500ms var delay2 = tracker.GetBackoffDelay(deliveryCount: 2); delay2.ShouldBe(500L); // Beyond schedule: use last value var delay4 = tracker.GetBackoffDelay(deliveryCount: 4); delay4.ShouldBe(2000L); } [Fact] public void GetDue_returns_in_deadline_order() { var tracker = new RedeliveryTracker(maxDeliveries: 5, ackWaitMs: 1000); var now = DateTimeOffset.UtcNow; tracker.Schedule(3, now.AddMilliseconds(-300)); tracker.Schedule(1, now.AddMilliseconds(-100)); tracker.Schedule(2, now.AddMilliseconds(-200)); var due = tracker.GetDue(now).ToList(); due.Count.ShouldBe(3); due[0].ShouldBe(3UL); // earliest deadline first due[1].ShouldBe(2UL); due[2].ShouldBe(1UL); } [Fact] public void GetBackoffDelay_with_no_backoff_returns_ackWait() { var tracker = new RedeliveryTracker(maxDeliveries: 5, ackWaitMs: 2000); tracker.GetBackoffDelay(1).ShouldBe(2000L); tracker.GetBackoffDelay(5).ShouldBe(2000L); } [Fact] public void IncrementDeliveryCount_for_untracked_seq_starts_at_one() { var tracker = new RedeliveryTracker(maxDeliveries: 5, ackWaitMs: 1000); tracker.IncrementDeliveryCount(42); // First increment should make count = 1, so maxDeliveries=5 means not max yet tracker.IsMaxDeliveries(42).ShouldBeFalse(); } [Fact] public void Acknowledge_also_clears_delivery_count() { var tracker = new RedeliveryTracker(maxDeliveries: 3, ackWaitMs: 1000); tracker.IncrementDeliveryCount(1); tracker.IncrementDeliveryCount(1); tracker.Acknowledge(1); // After ack, delivery count should be cleared tracker.IsMaxDeliveries(1).ShouldBeFalse(); } }