namespace NATS.Server.Core.Tests; // Go reference: server/client.go (stc channel, stall gate backpressure) public class StallGateTests { [Fact] public void Stall_gate_activates_at_threshold() { // Go reference: server/client.go stalledRoute — stalls at 75% capacity var gate = new NatsClient.StallGate(maxPending: 1000); gate.IsStalled.ShouldBeFalse(); gate.UpdatePending(750); // 75% = threshold gate.IsStalled.ShouldBeTrue(); gate.UpdatePending(500); // below threshold — releases gate.IsStalled.ShouldBeFalse(); } [Fact] public async Task Stall_gate_blocks_producer() { // Go reference: server/client.go stc channel blocks sends var gate = new NatsClient.StallGate(maxPending: 100); gate.UpdatePending(80); // stalled — 80% > 75% // Use a TCS to signal that the producer has entered WaitAsync var entered = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var released = false; var task = Task.Run(async () => { entered.SetResult(); await gate.WaitAsync(TimeSpan.FromSeconds(5)); released = true; }); // Wait until the producer has reached WaitAsync before asserting await entered.Task; released.ShouldBeFalse(); // still blocked gate.UpdatePending(50); // below threshold — releases await task; released.ShouldBeTrue(); } [Fact] public async Task Stall_gate_timeout_returns_false() { // Go reference: server/client.go stc timeout → close as slow consumer var gate = new NatsClient.StallGate(maxPending: 100); gate.UpdatePending(80); // stalled var result = await gate.WaitAsync(TimeSpan.FromMilliseconds(50)); result.ShouldBeFalse(); // timed out, not released } [Fact] public void Stall_gate_not_stalled_below_threshold() { // Go reference: server/client.go — no stall when below threshold var gate = new NatsClient.StallGate(maxPending: 1000); gate.UpdatePending(100); // well below 75% gate.IsStalled.ShouldBeFalse(); gate.UpdatePending(749); // just below 75% gate.IsStalled.ShouldBeFalse(); } [Fact] public async Task Stall_gate_wait_when_not_stalled_returns_immediately() { // Go reference: server/client.go — no stall, immediate return var gate = new NatsClient.StallGate(maxPending: 1000); var result = await gate.WaitAsync(TimeSpan.FromSeconds(1)); result.ShouldBeTrue(); // immediately released — not stalled } [Fact] public void Stall_gate_release_is_idempotent() { // Release when not stalled should not throw var gate = new NatsClient.StallGate(maxPending: 100); Should.NotThrow(() => gate.Release()); Should.NotThrow(() => gate.Release()); } }