using NATS.Server; using NATS.Server.Auth; using Shouldly; namespace NATS.Server.Core.Tests; /// /// Tests for and slow-consumer counters. /// Go reference: server/client.go — handleSlowConsumer, markConnAsSlow. /// public class SlowConsumerStallGateTests { // ── SlowConsumerTracker ──────────────────────────────────────────────────── [Fact] public void RecordSlowConsumer_increments_total_count() { var tracker = new SlowConsumerTracker(); tracker.RecordSlowConsumer(ClientKind.Client); tracker.RecordSlowConsumer(ClientKind.Client); tracker.RecordSlowConsumer(ClientKind.Router); tracker.TotalCount.ShouldBe(3L); } [Fact] public void RecordSlowConsumer_increments_per_kind_count() { var tracker = new SlowConsumerTracker(); tracker.RecordSlowConsumer(ClientKind.Client); tracker.RecordSlowConsumer(ClientKind.Client); tracker.RecordSlowConsumer(ClientKind.Gateway); tracker.GetCount(ClientKind.Client).ShouldBe(2L); tracker.GetCount(ClientKind.Gateway).ShouldBe(1L); } [Fact] public void GetCount_returns_zero_for_unrecorded_kind() { var tracker = new SlowConsumerTracker(); tracker.GetCount(ClientKind.Leaf).ShouldBe(0L); } [Fact] public void Multiple_kinds_tracked_independently() { var tracker = new SlowConsumerTracker(); tracker.RecordSlowConsumer(ClientKind.Client); tracker.RecordSlowConsumer(ClientKind.Router); tracker.RecordSlowConsumer(ClientKind.Gateway); tracker.RecordSlowConsumer(ClientKind.Leaf); tracker.GetCount(ClientKind.Client).ShouldBe(1L); tracker.GetCount(ClientKind.Router).ShouldBe(1L); tracker.GetCount(ClientKind.Gateway).ShouldBe(1L); tracker.GetCount(ClientKind.Leaf).ShouldBe(1L); tracker.GetCount(ClientKind.System).ShouldBe(0L); tracker.TotalCount.ShouldBe(4L); } [Fact] public void OnThresholdExceeded_fires_when_threshold_reached() { var tracker = new SlowConsumerTracker(threshold: 3); ClientKind? firedKind = null; tracker.OnThresholdExceeded(k => firedKind = k); tracker.RecordSlowConsumer(ClientKind.Client); firedKind.ShouldBeNull(); tracker.RecordSlowConsumer(ClientKind.Client); firedKind.ShouldBeNull(); tracker.RecordSlowConsumer(ClientKind.Router); firedKind.ShouldBe(ClientKind.Router); } [Fact] public void OnThresholdExceeded_not_fired_below_threshold() { var tracker = new SlowConsumerTracker(threshold: 5); var fired = false; tracker.OnThresholdExceeded(_ => fired = true); tracker.RecordSlowConsumer(ClientKind.Client); tracker.RecordSlowConsumer(ClientKind.Client); tracker.RecordSlowConsumer(ClientKind.Client); fired.ShouldBeFalse(); } [Fact] public void Reset_clears_all_counters() { var tracker = new SlowConsumerTracker(); tracker.RecordSlowConsumer(ClientKind.Client); tracker.RecordSlowConsumer(ClientKind.Router); tracker.TotalCount.ShouldBe(2L); tracker.Reset(); tracker.TotalCount.ShouldBe(0L); tracker.GetCount(ClientKind.Client).ShouldBe(0L); tracker.GetCount(ClientKind.Router).ShouldBe(0L); } // ── Account slow-consumer counters ──────────────────────────────────────── [Fact] public void Account_IncrementSlowConsumers_tracks_count() { var account = new Account("test-account"); account.SlowConsumerCount.ShouldBe(0L); account.IncrementSlowConsumers(); account.IncrementSlowConsumers(); account.IncrementSlowConsumers(); account.SlowConsumerCount.ShouldBe(3L); } [Fact] public void Account_ResetSlowConsumerCount_clears() { var account = new Account("test-account"); account.IncrementSlowConsumers(); account.IncrementSlowConsumers(); account.SlowConsumerCount.ShouldBe(2L); account.ResetSlowConsumerCount(); account.SlowConsumerCount.ShouldBe(0L); } [Fact] public void Thread_safety_concurrent_increments() { var tracker = new SlowConsumerTracker(); Parallel.For(0, 1000, _ => tracker.RecordSlowConsumer(ClientKind.Client)); tracker.TotalCount.ShouldBe(1000L); tracker.GetCount(ClientKind.Client).ShouldBe(1000L); } }