Port 405 new test methods across 5 subsystems for Go parity: - Monitoring: 102 tests (varz, connz, routez, subsz, stacksz) - Leaf Nodes: 85 tests (connection, forwarding, loop detection, subject filter, JetStream) - MQTT Bridge: 86 tests (advanced, auth, retained messages, topic mapping, will messages) - Client Protocol: 73 tests (connection handling, protocol violations, limits) - Config Reload: 59 tests (hot reload, option changes, permission updates) Total: 1,678 tests passing, 0 failures, 3 skipped
180 lines
6.1 KiB
C#
180 lines
6.1 KiB
C#
using NATS.Server.LeafNodes;
|
|
|
|
namespace NATS.Server.Tests.LeafNodes;
|
|
|
|
/// <summary>
|
|
/// Tests for leaf node loop detection via $LDS. prefix.
|
|
/// Reference: golang/nats-server/server/leafnode_test.go
|
|
/// </summary>
|
|
public class LeafNodeLoopDetectionTests
|
|
{
|
|
// Go: TestLeafNodeLoop server/leafnode_test.go:837
|
|
[Fact]
|
|
public void HasLoopMarker_returns_true_for_marked_subject()
|
|
{
|
|
var marked = LeafLoopDetector.Mark("orders.created", "SERVER1");
|
|
LeafLoopDetector.HasLoopMarker(marked).ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void HasLoopMarker_returns_false_for_plain_subject()
|
|
{
|
|
LeafLoopDetector.HasLoopMarker("orders.created").ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void Mark_prepends_LDS_prefix_with_server_id()
|
|
{
|
|
LeafLoopDetector.Mark("foo.bar", "ABC123").ShouldBe("$LDS.ABC123.foo.bar");
|
|
}
|
|
|
|
[Fact]
|
|
public void IsLooped_returns_true_when_subject_contains_own_server_id()
|
|
{
|
|
var marked = LeafLoopDetector.Mark("foo.bar", "MYSERVER");
|
|
LeafLoopDetector.IsLooped(marked, "MYSERVER").ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void IsLooped_returns_false_when_subject_contains_different_server_id()
|
|
{
|
|
var marked = LeafLoopDetector.Mark("foo.bar", "OTHER");
|
|
LeafLoopDetector.IsLooped(marked, "MYSERVER").ShouldBeFalse();
|
|
}
|
|
|
|
// Go: TestLeafNodeLoopDetectionOnActualLoop server/leafnode_test.go:9410
|
|
[Fact]
|
|
public void TryUnmark_extracts_original_subject_from_single_mark()
|
|
{
|
|
var marked = LeafLoopDetector.Mark("orders.created", "S1");
|
|
LeafLoopDetector.TryUnmark(marked, out var unmarked).ShouldBeTrue();
|
|
unmarked.ShouldBe("orders.created");
|
|
}
|
|
|
|
[Fact]
|
|
public void TryUnmark_extracts_original_subject_from_nested_marks()
|
|
{
|
|
var nested = LeafLoopDetector.Mark(LeafLoopDetector.Mark("data.stream", "S1"), "S2");
|
|
LeafLoopDetector.TryUnmark(nested, out var unmarked).ShouldBeTrue();
|
|
unmarked.ShouldBe("data.stream");
|
|
}
|
|
|
|
[Fact]
|
|
public void TryUnmark_extracts_original_from_triple_nested_marks()
|
|
{
|
|
var tripleNested = LeafLoopDetector.Mark(
|
|
LeafLoopDetector.Mark(LeafLoopDetector.Mark("test.subject", "S1"), "S2"), "S3");
|
|
LeafLoopDetector.TryUnmark(tripleNested, out var unmarked).ShouldBeTrue();
|
|
unmarked.ShouldBe("test.subject");
|
|
}
|
|
|
|
[Fact]
|
|
public void TryUnmark_returns_false_for_unmarked_subject()
|
|
{
|
|
LeafLoopDetector.TryUnmark("orders.created", out var unmarked).ShouldBeFalse();
|
|
unmarked.ShouldBe("orders.created");
|
|
}
|
|
|
|
[Fact]
|
|
public void Mark_preserves_dot_separated_structure()
|
|
{
|
|
var marked = LeafLoopDetector.Mark("a.b.c.d", "SRV");
|
|
marked.ShouldStartWith("$LDS.SRV.");
|
|
marked.ShouldEndWith("a.b.c.d");
|
|
}
|
|
|
|
// Go: TestLeafNodeLoopDetectionWithMultipleClusters server/leafnode_test.go:3546
|
|
[Fact]
|
|
public void IsLooped_detects_loop_in_nested_marks()
|
|
{
|
|
var marked = LeafLoopDetector.Mark(LeafLoopDetector.Mark("test", "REMOTE"), "LOCAL");
|
|
LeafLoopDetector.IsLooped(marked, "LOCAL").ShouldBeTrue();
|
|
LeafLoopDetector.IsLooped(marked, "REMOTE").ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void HasLoopMarker_works_with_prefix_only()
|
|
{
|
|
LeafLoopDetector.HasLoopMarker("$LDS.").ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void IsLooped_returns_false_for_plain_subject()
|
|
{
|
|
LeafLoopDetector.IsLooped("plain.subject", "MYSERVER").ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void Mark_with_single_token_subject()
|
|
{
|
|
var marked = LeafLoopDetector.Mark("simple", "S1");
|
|
marked.ShouldBe("$LDS.S1.simple");
|
|
LeafLoopDetector.TryUnmark(marked, out var unmarked).ShouldBeTrue();
|
|
unmarked.ShouldBe("simple");
|
|
}
|
|
|
|
// Go: TestLeafNodeLoopFromDAG server/leafnode_test.go:899
|
|
[Fact]
|
|
public void Multiple_servers_in_chain_each_add_their_mark()
|
|
{
|
|
var original = "data.stream";
|
|
var fromS1 = LeafLoopDetector.Mark(original, "S1");
|
|
fromS1.ShouldBe("$LDS.S1.data.stream");
|
|
|
|
var fromS2 = LeafLoopDetector.Mark(fromS1, "S2");
|
|
fromS2.ShouldBe("$LDS.S2.$LDS.S1.data.stream");
|
|
|
|
LeafLoopDetector.IsLooped(fromS2, "S2").ShouldBeTrue();
|
|
LeafLoopDetector.IsLooped(fromS2, "S1").ShouldBeFalse();
|
|
|
|
LeafLoopDetector.TryUnmark(fromS2, out var unmarked).ShouldBeTrue();
|
|
unmarked.ShouldBe("data.stream");
|
|
}
|
|
|
|
[Fact]
|
|
public void Roundtrip_mark_unmark_preserves_original()
|
|
{
|
|
var subjects = new[] { "foo", "foo.bar", "foo.bar.baz", "a.b.c.d.e", "single", "with.*.wildcard", "with.>" };
|
|
|
|
foreach (var subject in subjects)
|
|
{
|
|
var marked = LeafLoopDetector.Mark(subject, "TESTSRV");
|
|
LeafLoopDetector.TryUnmark(marked, out var unmarked).ShouldBeTrue();
|
|
unmarked.ShouldBe(subject, $"Failed roundtrip for: {subject}");
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Four_server_chain_marks_and_unmarks_correctly()
|
|
{
|
|
var step1 = LeafLoopDetector.Mark("test", "A");
|
|
var step2 = LeafLoopDetector.Mark(step1, "B");
|
|
var step3 = LeafLoopDetector.Mark(step2, "C");
|
|
var step4 = LeafLoopDetector.Mark(step3, "D");
|
|
|
|
LeafLoopDetector.IsLooped(step4, "D").ShouldBeTrue();
|
|
LeafLoopDetector.IsLooped(step4, "C").ShouldBeFalse();
|
|
LeafLoopDetector.IsLooped(step4, "B").ShouldBeFalse();
|
|
LeafLoopDetector.IsLooped(step4, "A").ShouldBeFalse();
|
|
|
|
LeafLoopDetector.TryUnmark(step4, out var unmarked).ShouldBeTrue();
|
|
unmarked.ShouldBe("test");
|
|
}
|
|
|
|
[Fact]
|
|
public void HasLoopMarker_is_case_sensitive()
|
|
{
|
|
LeafLoopDetector.HasLoopMarker("$LDS.SRV.foo").ShouldBeTrue();
|
|
LeafLoopDetector.HasLoopMarker("$lds.SRV.foo").ShouldBeFalse();
|
|
}
|
|
|
|
// Go: TestLeafNodeLoopDetectedOnAcceptSide server/leafnode_test.go:1522
|
|
[Fact]
|
|
public void IsLooped_is_case_sensitive_for_server_id()
|
|
{
|
|
var marked = LeafLoopDetector.Mark("foo", "MYSERVER");
|
|
LeafLoopDetector.IsLooped(marked, "MYSERVER").ShouldBeTrue();
|
|
LeafLoopDetector.IsLooped(marked, "myserver").ShouldBeFalse();
|
|
}
|
|
}
|