using NATS.Server.Subscriptions; namespace NATS.Server.Tests; public class SubListTests { private static Subscription MakeSub(string subject, string? queue = null, string sid = "1") => new() { Subject = subject, Queue = queue, Sid = sid }; [Fact] public void Insert_and_match_literal_subject() { var sl = new SubList(); var sub = MakeSub("foo.bar"); sl.Insert(sub); var r = sl.Match("foo.bar"); Assert.Single(r.PlainSubs); Assert.Same(sub, r.PlainSubs[0]); Assert.Empty(r.QueueSubs); } [Fact] public void Match_returns_empty_for_no_match() { var sl = new SubList(); sl.Insert(MakeSub("foo.bar")); var r = sl.Match("foo.baz"); Assert.Empty(r.PlainSubs); } [Fact] public void Match_partial_wildcard() { var sl = new SubList(); var sub = MakeSub("foo.*"); sl.Insert(sub); Assert.Single(sl.Match("foo.bar").PlainSubs); Assert.Single(sl.Match("foo.baz").PlainSubs); Assert.Empty(sl.Match("foo.bar.baz").PlainSubs); } [Fact] public void Match_full_wildcard() { var sl = new SubList(); var sub = MakeSub("foo.>"); sl.Insert(sub); Assert.Single(sl.Match("foo.bar").PlainSubs); Assert.Single(sl.Match("foo.bar.baz").PlainSubs); Assert.Empty(sl.Match("foo").PlainSubs); } [Fact] public void Match_root_full_wildcard() { var sl = new SubList(); sl.Insert(MakeSub(">")); Assert.Single(sl.Match("foo").PlainSubs); Assert.Single(sl.Match("foo.bar").PlainSubs); Assert.Single(sl.Match("foo.bar.baz").PlainSubs); } [Fact] public void Match_collects_multiple_subs() { var sl = new SubList(); sl.Insert(MakeSub("foo.bar", sid: "1")); sl.Insert(MakeSub("foo.*", sid: "2")); sl.Insert(MakeSub("foo.>", sid: "3")); sl.Insert(MakeSub(">", sid: "4")); var r = sl.Match("foo.bar"); Assert.Equal(4, r.PlainSubs.Length); } [Fact] public void Remove_subscription() { var sl = new SubList(); var sub = MakeSub("foo.bar"); sl.Insert(sub); Assert.Single(sl.Match("foo.bar").PlainSubs); sl.Remove(sub); Assert.Empty(sl.Match("foo.bar").PlainSubs); } [Fact] public void Queue_group_subscriptions() { var sl = new SubList(); sl.Insert(MakeSub("foo.bar", queue: "workers", sid: "1")); sl.Insert(MakeSub("foo.bar", queue: "workers", sid: "2")); sl.Insert(MakeSub("foo.bar", queue: "loggers", sid: "3")); var r = sl.Match("foo.bar"); Assert.Empty(r.PlainSubs); Assert.Equal(2, r.QueueSubs.Length); // 2 queue groups } [Fact] public void Count_tracks_subscriptions() { var sl = new SubList(); Assert.Equal(0u, sl.Count); sl.Insert(MakeSub("foo", sid: "1")); sl.Insert(MakeSub("bar", sid: "2")); Assert.Equal(2u, sl.Count); sl.Remove(MakeSub("foo", sid: "1")); // Remove by reference won't work — we need the same instance } [Fact] public void Count_tracks_with_same_instance() { var sl = new SubList(); var sub = MakeSub("foo"); sl.Insert(sub); Assert.Equal(1u, sl.Count); sl.Remove(sub); Assert.Equal(0u, sl.Count); } [Fact] public void Cache_invalidation_on_insert() { var sl = new SubList(); sl.Insert(MakeSub("foo.bar", sid: "1")); // Prime the cache var r1 = sl.Match("foo.bar"); Assert.Single(r1.PlainSubs); // Insert a wildcard that matches — cache should be invalidated sl.Insert(MakeSub("foo.*", sid: "2")); var r2 = sl.Match("foo.bar"); Assert.Equal(2, r2.PlainSubs.Length); } [Fact] public void Match_partial_wildcard_at_different_levels() { var sl = new SubList(); sl.Insert(MakeSub("*.bar.baz", sid: "1")); sl.Insert(MakeSub("foo.*.baz", sid: "2")); sl.Insert(MakeSub("foo.bar.*", sid: "3")); var r = sl.Match("foo.bar.baz"); Assert.Equal(3, r.PlainSubs.Length); } }