Files
natsdotnet/tests/NATS.Server.Mqtt.Tests/Mqtt/MqttFlowControllerTests.cs
Joseph Doherty a6be5e11ed refactor: extract NATS.Server.Mqtt.Tests project
Move 29 MQTT test files from NATS.Server.Tests into a dedicated
NATS.Server.Mqtt.Tests project. Update namespaces, add
InternalsVisibleTo, and replace Task.Delay calls with
PollHelper.WaitUntilAsync for proper synchronization.
2026-03-12 15:03:12 -04:00

147 lines
3.9 KiB
C#

// Go reference: server/mqtt.go — mqttMaxAckPending, flow control logic.
using NATS.Server.Mqtt;
using Shouldly;
namespace NATS.Server.Mqtt.Tests.Mqtt;
public sealed class MqttFlowControllerTests
{
// 1. TryAcquire succeeds when under limit
[Fact]
public async Task TryAcquire_succeeds_when_under_limit()
{
using var fc = new MqttFlowController(defaultMaxAckPending: 1024);
var result = await fc.TryAcquireAsync("sub-1");
result.ShouldBeTrue();
}
// 2. TryAcquire fails when at limit
[Fact]
public async Task TryAcquire_fails_when_at_limit()
{
using var fc = new MqttFlowController(defaultMaxAckPending: 1);
var first = await fc.TryAcquireAsync("sub-1");
var second = await fc.TryAcquireAsync("sub-1");
first.ShouldBeTrue();
second.ShouldBeFalse();
}
// 3. Release allows next acquire
[Fact]
public async Task Release_allows_next_acquire()
{
using var fc = new MqttFlowController(defaultMaxAckPending: 1);
var first = await fc.TryAcquireAsync("sub-1");
first.ShouldBeTrue();
// At limit — second should fail
var atLimit = await fc.TryAcquireAsync("sub-1");
atLimit.ShouldBeFalse();
fc.Release("sub-1");
// After release a slot is available again
var afterRelease = await fc.TryAcquireAsync("sub-1");
afterRelease.ShouldBeTrue();
}
// 4. GetPendingCount tracks pending
[Fact]
public async Task GetPendingCount_tracks_pending()
{
using var fc = new MqttFlowController(defaultMaxAckPending: 10);
await fc.AcquireAsync("sub-1");
await fc.AcquireAsync("sub-1");
await fc.AcquireAsync("sub-1");
fc.GetPendingCount("sub-1").ShouldBe(3);
}
// 5. GetPendingCount decrements on release
[Fact]
public async Task GetPendingCount_decrements_on_release()
{
using var fc = new MqttFlowController(defaultMaxAckPending: 10);
await fc.AcquireAsync("sub-1");
await fc.AcquireAsync("sub-1");
await fc.AcquireAsync("sub-1");
fc.Release("sub-1");
fc.GetPendingCount("sub-1").ShouldBe(2);
}
// 6. GetPendingCount returns zero for unknown subscription
[Fact]
public void GetPendingCount_zero_for_unknown()
{
using var fc = new MqttFlowController();
fc.GetPendingCount("does-not-exist").ShouldBe(0);
}
// 7. RemoveSubscription cleans up
[Fact]
public async Task RemoveSubscription_cleans_up()
{
using var fc = new MqttFlowController(defaultMaxAckPending: 10);
await fc.AcquireAsync("sub-1");
fc.SubscriptionCount.ShouldBe(1);
fc.RemoveSubscription("sub-1");
fc.SubscriptionCount.ShouldBe(0);
}
// 8. SubscriptionCount tracks independent subscriptions
[Fact]
public async Task SubscriptionCount_tracks_subscriptions()
{
using var fc = new MqttFlowController(defaultMaxAckPending: 10);
await fc.AcquireAsync("sub-a");
await fc.AcquireAsync("sub-b");
await fc.AcquireAsync("sub-c");
fc.SubscriptionCount.ShouldBe(3);
}
// 9. DefaultMaxAckPending can be updated via UpdateLimit
[Fact]
public void DefaultMaxAckPending_can_be_updated()
{
using var fc = new MqttFlowController(defaultMaxAckPending: 1024);
fc.DefaultMaxAckPending.ShouldBe(1024);
fc.UpdateLimit(512);
fc.DefaultMaxAckPending.ShouldBe(512);
}
// 10. Dispose cleans up all subscriptions
[Fact]
public async Task Dispose_cleans_up_all()
{
var fc = new MqttFlowController(defaultMaxAckPending: 10);
await fc.AcquireAsync("sub-x");
await fc.AcquireAsync("sub-y");
await fc.AcquireAsync("sub-z");
fc.SubscriptionCount.ShouldBe(3);
fc.Dispose();
fc.SubscriptionCount.ShouldBe(0);
}
}