281 lines
7.7 KiB
C#
281 lines
7.7 KiB
C#
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");
|
|
r.PlainSubs.ShouldHaveSingleItem();
|
|
r.PlainSubs[0].ShouldBeSameAs(sub);
|
|
r.QueueSubs.ShouldBeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public void Match_returns_empty_for_no_match()
|
|
{
|
|
var sl = new SubList();
|
|
sl.Insert(MakeSub("foo.bar"));
|
|
|
|
var r = sl.Match("foo.baz");
|
|
r.PlainSubs.ShouldBeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public void Match_partial_wildcard()
|
|
{
|
|
var sl = new SubList();
|
|
var sub = MakeSub("foo.*");
|
|
sl.Insert(sub);
|
|
|
|
sl.Match("foo.bar").PlainSubs.ShouldHaveSingleItem();
|
|
sl.Match("foo.baz").PlainSubs.ShouldHaveSingleItem();
|
|
sl.Match("foo.bar.baz").PlainSubs.ShouldBeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public void Match_full_wildcard()
|
|
{
|
|
var sl = new SubList();
|
|
var sub = MakeSub("foo.>");
|
|
sl.Insert(sub);
|
|
|
|
sl.Match("foo.bar").PlainSubs.ShouldHaveSingleItem();
|
|
sl.Match("foo.bar.baz").PlainSubs.ShouldHaveSingleItem();
|
|
sl.Match("foo").PlainSubs.ShouldBeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public void Match_root_full_wildcard()
|
|
{
|
|
var sl = new SubList();
|
|
sl.Insert(MakeSub(">"));
|
|
|
|
sl.Match("foo").PlainSubs.ShouldHaveSingleItem();
|
|
sl.Match("foo.bar").PlainSubs.ShouldHaveSingleItem();
|
|
sl.Match("foo.bar.baz").PlainSubs.ShouldHaveSingleItem();
|
|
}
|
|
|
|
[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");
|
|
r.PlainSubs.Length.ShouldBe(4);
|
|
}
|
|
|
|
[Fact]
|
|
public void Remove_subscription()
|
|
{
|
|
var sl = new SubList();
|
|
var sub = MakeSub("foo.bar");
|
|
sl.Insert(sub);
|
|
sl.Match("foo.bar").PlainSubs.ShouldHaveSingleItem();
|
|
|
|
sl.Remove(sub);
|
|
sl.Match("foo.bar").PlainSubs.ShouldBeEmpty();
|
|
}
|
|
|
|
[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");
|
|
r.PlainSubs.ShouldBeEmpty();
|
|
r.QueueSubs.Length.ShouldBe(2); // 2 queue groups
|
|
}
|
|
|
|
[Fact]
|
|
public void Count_tracks_subscriptions()
|
|
{
|
|
var sl = new SubList();
|
|
sl.Count.ShouldBe(0u);
|
|
|
|
sl.Insert(MakeSub("foo", sid: "1"));
|
|
sl.Insert(MakeSub("bar", sid: "2"));
|
|
sl.Count.ShouldBe(2u);
|
|
|
|
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);
|
|
sl.Count.ShouldBe(1u);
|
|
sl.Remove(sub);
|
|
sl.Count.ShouldBe(0u);
|
|
}
|
|
|
|
[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");
|
|
r1.PlainSubs.ShouldHaveSingleItem();
|
|
|
|
// Insert a wildcard that matches — cache should be invalidated
|
|
sl.Insert(MakeSub("foo.*", sid: "2"));
|
|
|
|
var r2 = sl.Match("foo.bar");
|
|
r2.PlainSubs.Length.ShouldBe(2);
|
|
}
|
|
|
|
[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");
|
|
r.PlainSubs.Length.ShouldBe(3);
|
|
}
|
|
|
|
[Fact]
|
|
public void Stats_returns_correct_values()
|
|
{
|
|
var sl = new SubList();
|
|
sl.Insert(MakeSub("foo.bar", sid: "1"));
|
|
sl.Insert(MakeSub("foo.baz", sid: "2"));
|
|
sl.Match("foo.bar");
|
|
sl.Match("foo.bar"); // cache hit
|
|
|
|
var stats = sl.Stats();
|
|
stats.NumSubs.ShouldBe(2u);
|
|
stats.NumInserts.ShouldBe(2ul);
|
|
stats.NumMatches.ShouldBe(2ul);
|
|
stats.CacheHitRate.ShouldBeGreaterThan(0.0);
|
|
}
|
|
|
|
[Fact]
|
|
public void HasInterest_returns_true_when_subscribers_exist()
|
|
{
|
|
var sl = new SubList();
|
|
sl.Insert(MakeSub("foo.bar"));
|
|
sl.HasInterest("foo.bar").ShouldBeTrue();
|
|
sl.HasInterest("foo.baz").ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void HasInterest_with_wildcards()
|
|
{
|
|
var sl = new SubList();
|
|
sl.Insert(MakeSub("foo.*"));
|
|
sl.HasInterest("foo.bar").ShouldBeTrue();
|
|
sl.HasInterest("bar.baz").ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void NumInterest_counts_subscribers()
|
|
{
|
|
var sl = new SubList();
|
|
sl.Insert(MakeSub("foo.bar", sid: "1"));
|
|
sl.Insert(MakeSub("foo.*", sid: "2"));
|
|
sl.Insert(MakeSub("foo.bar", queue: "q1", sid: "3"));
|
|
|
|
var (np, nq) = sl.NumInterest("foo.bar");
|
|
np.ShouldBe(2); // foo.bar + foo.*
|
|
nq.ShouldBe(1); // queue sub
|
|
}
|
|
|
|
[Fact]
|
|
public void RemoveBatch_removes_all()
|
|
{
|
|
var sl = new SubList();
|
|
var sub1 = MakeSub("foo.bar", sid: "1");
|
|
var sub2 = MakeSub("foo.baz", sid: "2");
|
|
var sub3 = MakeSub("bar.qux", sid: "3");
|
|
sl.Insert(sub1);
|
|
sl.Insert(sub2);
|
|
sl.Insert(sub3);
|
|
sl.Count.ShouldBe(3u);
|
|
|
|
sl.RemoveBatch([sub1, sub2]);
|
|
sl.Count.ShouldBe(1u);
|
|
sl.Match("foo.bar").PlainSubs.ShouldBeEmpty();
|
|
sl.Match("bar.qux").PlainSubs.ShouldHaveSingleItem();
|
|
}
|
|
|
|
[Fact]
|
|
public void All_returns_every_subscription()
|
|
{
|
|
var sl = new SubList();
|
|
var sub1 = MakeSub("foo.bar", sid: "1");
|
|
var sub2 = MakeSub("foo.*", sid: "2");
|
|
var sub3 = MakeSub("bar.>", queue: "q", sid: "3");
|
|
sl.Insert(sub1);
|
|
sl.Insert(sub2);
|
|
sl.Insert(sub3);
|
|
|
|
var all = sl.All();
|
|
all.Count.ShouldBe(3);
|
|
all.ShouldContain(sub1);
|
|
all.ShouldContain(sub2);
|
|
all.ShouldContain(sub3);
|
|
}
|
|
|
|
[Fact]
|
|
public void ReverseMatch_finds_patterns_matching_literal()
|
|
{
|
|
var sl = new SubList();
|
|
var sub1 = MakeSub("foo.bar", sid: "1");
|
|
var sub2 = MakeSub("foo.*", sid: "2");
|
|
var sub3 = MakeSub("foo.>", sid: "3");
|
|
var sub4 = MakeSub("bar.baz", sid: "4");
|
|
sl.Insert(sub1);
|
|
sl.Insert(sub2);
|
|
sl.Insert(sub3);
|
|
sl.Insert(sub4);
|
|
|
|
var result = sl.ReverseMatch("foo.bar");
|
|
result.PlainSubs.Length.ShouldBe(3); // foo.bar, foo.*, foo.>
|
|
result.PlainSubs.ShouldContain(sub1);
|
|
result.PlainSubs.ShouldContain(sub2);
|
|
result.PlainSubs.ShouldContain(sub3);
|
|
}
|
|
|
|
[Fact]
|
|
public void Generation_ID_invalidates_cache()
|
|
{
|
|
var sl = new SubList();
|
|
sl.Insert(MakeSub("foo.bar", sid: "1"));
|
|
|
|
// Prime cache
|
|
var r1 = sl.Match("foo.bar");
|
|
r1.PlainSubs.Length.ShouldBe(1);
|
|
|
|
// Insert another sub (bumps generation)
|
|
sl.Insert(MakeSub("foo.bar", sid: "2"));
|
|
|
|
// Cache should be invalidated by generation mismatch
|
|
var r2 = sl.Match("foo.bar");
|
|
r2.PlainSubs.Length.ShouldBe(2);
|
|
}
|
|
}
|