using NATS.Server; using NATS.Server.Auth; using NATS.Server.Protocol; using NATS.Server.Subscriptions; namespace NATS.Server.Core.Tests.Subscriptions; public class SubListParityBatch2Tests { [Fact] public void RegisterQueueNotification_tracks_first_and_last_exact_queue_interest() { var subList = new SubList(); var notifications = new List(); Action callback = hasInterest => notifications.Add(hasInterest); subList.RegisterQueueNotification("foo.bar", "q", callback).ShouldBeTrue(); notifications.ShouldBe([false]); var sub1 = new Subscription { Subject = "foo.bar", Queue = "q", Sid = "1" }; var sub2 = new Subscription { Subject = "foo.bar", Queue = "q", Sid = "2" }; subList.Insert(sub1); subList.Insert(sub2); notifications.ShouldBe([false, true]); subList.Remove(sub1); notifications.ShouldBe([false, true]); subList.Remove(sub2); notifications.ShouldBe([false, true, false]); subList.ClearQueueNotification("foo.bar", "q", callback).ShouldBeTrue(); } [Fact] public void UpdateRemoteQSub_updates_queue_weight_for_match_remote() { var subList = new SubList(); var original = new RemoteSubscription("foo.bar", "q", "R1", Account: "A", QueueWeight: 1); subList.ApplyRemoteSub(original); subList.MatchRemote("A", "foo.bar").Count.ShouldBe(1); subList.UpdateRemoteQSub(original with { QueueWeight = 3 }); subList.MatchRemote("A", "foo.bar").Count.ShouldBe(3); } [Fact] public void SubListStats_Add_aggregates_stats_like_go() { var stats = new SubListStats { NumSubs = 1, NumCache = 2, NumInserts = 3, NumRemoves = 4, NumMatches = 10, MaxFanout = 5, TotalFanout = 8, CacheEntries = 2, CacheHits = 6, }; stats.Add(new SubListStats { NumSubs = 2, NumCache = 3, NumInserts = 4, NumRemoves = 5, NumMatches = 30, MaxFanout = 9, TotalFanout = 12, CacheEntries = 3, CacheHits = 15, }); stats.NumSubs.ShouldBe((uint)3); stats.NumCache.ShouldBe((uint)5); stats.NumInserts.ShouldBe((ulong)7); stats.NumRemoves.ShouldBe((ulong)9); stats.NumMatches.ShouldBe((ulong)40); stats.MaxFanout.ShouldBe((uint)9); stats.AvgFanout.ShouldBe(4.0); stats.CacheHitRate.ShouldBe(0.525); } [Fact] public void NumLevels_returns_max_trie_depth() { var subList = new SubList(); subList.Insert(new Subscription { Subject = "foo.bar.baz", Sid = "1" }); subList.Insert(new Subscription { Subject = "foo.bar", Sid = "2" }); subList.NumLevels().ShouldBe(3); } [Fact] public void LocalSubs_filters_non_local_kinds_and_optionally_includes_leaf() { var subList = new SubList(); subList.Insert(new Subscription { Subject = "foo.a", Sid = "1", Client = new TestClient(ClientKind.Client) }); subList.Insert(new Subscription { Subject = "foo.b", Sid = "2", Client = new TestClient(ClientKind.Router) }); subList.Insert(new Subscription { Subject = "foo.c", Sid = "3", Client = new TestClient(ClientKind.System) }); subList.Insert(new Subscription { Subject = "foo.d", Sid = "4", Client = new TestClient(ClientKind.Leaf) }); var local = subList.LocalSubs(includeLeafHubs: false).Select(s => s.Sid).OrderBy(x => x).ToArray(); local.ShouldBe(["1", "3"]); var withLeaf = subList.LocalSubs(includeLeafHubs: true).Select(s => s.Sid).OrderBy(x => x).ToArray(); withLeaf.ShouldBe(["1", "3", "4"]); } private sealed class TestClient(ClientKind kind) : INatsClient { public ulong Id => 1; public ClientKind Kind => kind; public Account? Account => null; public ClientOptions? ClientOpts => null; public ClientPermissions? Permissions => null; public void SendMessage(string subject, string sid, string? replyTo, ReadOnlyMemory headers, ReadOnlyMemory payload) { } public void SendMessageNoFlush(string subject, string sid, string? replyTo, ReadOnlyMemory headers, ReadOnlyMemory payload) { } public void SignalFlush() { } public bool QueueOutbound(ReadOnlyMemory data) => true; public void RemoveSubscription(string sid) { } } }