Add 5 validated Process* methods to JetStreamMetaGroup for stream and consumer assignment processing: ProcessStreamAssignment, ProcessUpdateStreamAssignment, ProcessStreamRemoval, ProcessConsumerAssignment, and ProcessConsumerRemoval. Each returns a bool indicating success, with validation guards matching Go reference jetstream_cluster.go:4541-5925. Includes 12 new unit tests.
170 lines
5.6 KiB
C#
170 lines
5.6 KiB
C#
using NATS.Server.JetStream.Cluster;
|
|
|
|
namespace NATS.Server.Tests.JetStream.Cluster;
|
|
|
|
/// <summary>
|
|
/// Tests for validated stream/consumer assignment processing.
|
|
/// Go reference: jetstream_cluster.go:4541-5925.
|
|
/// </summary>
|
|
public class JetStreamAssignmentProcessingTests
|
|
{
|
|
[Fact]
|
|
public void ProcessStreamAssignment_validates_config()
|
|
{
|
|
var meta = new JetStreamMetaGroup(3);
|
|
var sa = new StreamAssignment
|
|
{
|
|
StreamName = "valid-stream",
|
|
Group = new RaftGroup { Name = "rg-1", Peers = ["n1", "n2", "n3"] },
|
|
ConfigJson = """{"subjects":["test.>"]}""",
|
|
};
|
|
|
|
meta.ProcessStreamAssignment(sa).ShouldBeTrue();
|
|
meta.StreamCount.ShouldBe(1);
|
|
}
|
|
|
|
[Fact]
|
|
public void ProcessStreamAssignment_rejects_empty_name()
|
|
{
|
|
var meta = new JetStreamMetaGroup(3);
|
|
var sa = CreateStreamAssignment("", "rg-1");
|
|
meta.ProcessStreamAssignment(sa).ShouldBeFalse();
|
|
meta.StreamCount.ShouldBe(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void ProcessUpdateStreamAssignment_applies_config_change()
|
|
{
|
|
var meta = new JetStreamMetaGroup(3);
|
|
meta.ProcessStreamAssignment(CreateStreamAssignment("updatable", "rg-u", """{"subjects":["old.>"]}"""));
|
|
|
|
var updated = CreateStreamAssignment("updatable", "rg-u", """{"subjects":["new.>"]}""");
|
|
meta.ProcessUpdateStreamAssignment(updated).ShouldBeTrue();
|
|
|
|
var assignment = meta.GetStreamAssignment("updatable");
|
|
assignment!.ConfigJson.ShouldContain("new.>");
|
|
}
|
|
|
|
[Fact]
|
|
public void ProcessUpdateStreamAssignment_returns_false_for_nonexistent()
|
|
{
|
|
var meta = new JetStreamMetaGroup(3);
|
|
var sa = CreateStreamAssignment("ghost", "rg-g");
|
|
meta.ProcessUpdateStreamAssignment(sa).ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void ProcessConsumerAssignment_requires_existing_stream()
|
|
{
|
|
var meta = new JetStreamMetaGroup(3);
|
|
var ca = new ConsumerAssignment
|
|
{
|
|
ConsumerName = "orphan-consumer",
|
|
StreamName = "nonexistent-stream",
|
|
Group = new RaftGroup { Name = "rg-c", Peers = ["n1", "n2", "n3"] },
|
|
};
|
|
|
|
meta.ProcessConsumerAssignment(ca).ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void ProcessConsumerAssignment_succeeds_with_existing_stream()
|
|
{
|
|
var meta = new JetStreamMetaGroup(3);
|
|
meta.ProcessStreamAssignment(CreateStreamAssignment("s1", "rg-s1"));
|
|
|
|
var ca = new ConsumerAssignment
|
|
{
|
|
ConsumerName = "c1",
|
|
StreamName = "s1",
|
|
Group = new RaftGroup { Name = "rg-c1", Peers = ["n1", "n2", "n3"] },
|
|
};
|
|
|
|
meta.ProcessConsumerAssignment(ca).ShouldBeTrue();
|
|
meta.ConsumerCount.ShouldBe(1);
|
|
}
|
|
|
|
[Fact]
|
|
public void ProcessStreamRemoval_cascades_to_consumers()
|
|
{
|
|
var meta = new JetStreamMetaGroup(3);
|
|
meta.ProcessStreamAssignment(CreateStreamAssignment("cascade", "rg-cas"));
|
|
meta.ProcessConsumerAssignment(new ConsumerAssignment
|
|
{
|
|
ConsumerName = "c1",
|
|
StreamName = "cascade",
|
|
Group = new RaftGroup { Name = "rg-c1", Peers = ["n1", "n2", "n3"] },
|
|
});
|
|
|
|
meta.ProcessStreamRemoval("cascade").ShouldBeTrue();
|
|
meta.StreamCount.ShouldBe(0);
|
|
meta.ConsumerCount.ShouldBe(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void ProcessStreamRemoval_returns_false_for_nonexistent()
|
|
{
|
|
var meta = new JetStreamMetaGroup(3);
|
|
meta.ProcessStreamRemoval("nope").ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void ProcessConsumerRemoval_returns_false_for_nonexistent_stream()
|
|
{
|
|
var meta = new JetStreamMetaGroup(3);
|
|
meta.ProcessConsumerRemoval("ghost", "c1").ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void ProcessConsumerRemoval_returns_false_for_nonexistent_consumer()
|
|
{
|
|
var meta = new JetStreamMetaGroup(3);
|
|
meta.ProcessStreamAssignment(CreateStreamAssignment("s1", "rg-s1"));
|
|
meta.ProcessConsumerRemoval("s1", "nope").ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void ProcessConsumerRemoval_succeeds()
|
|
{
|
|
var meta = new JetStreamMetaGroup(3);
|
|
meta.ProcessStreamAssignment(CreateStreamAssignment("s1", "rg-s1"));
|
|
meta.ProcessConsumerAssignment(new ConsumerAssignment
|
|
{
|
|
ConsumerName = "c1",
|
|
StreamName = "s1",
|
|
Group = new RaftGroup { Name = "rg-c1", Peers = ["n1", "n2"] },
|
|
});
|
|
|
|
meta.ProcessConsumerRemoval("s1", "c1").ShouldBeTrue();
|
|
meta.ConsumerCount.ShouldBe(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void ProcessUpdateStreamAssignment_preserves_consumers()
|
|
{
|
|
var meta = new JetStreamMetaGroup(3);
|
|
meta.ProcessStreamAssignment(CreateStreamAssignment("s1", "rg-s1", """{"subjects":["old"]}"""));
|
|
meta.ProcessConsumerAssignment(new ConsumerAssignment
|
|
{
|
|
ConsumerName = "c1",
|
|
StreamName = "s1",
|
|
Group = new RaftGroup { Name = "rg-c1", Peers = ["n1", "n2"] },
|
|
});
|
|
|
|
var updated = CreateStreamAssignment("s1", "rg-s1", """{"subjects":["new"]}""");
|
|
meta.ProcessUpdateStreamAssignment(updated).ShouldBeTrue();
|
|
|
|
meta.ConsumerCount.ShouldBe(1);
|
|
meta.GetConsumerAssignment("s1", "c1").ShouldNotBeNull();
|
|
}
|
|
|
|
// Helper to create a StreamAssignment (StreamName is `required` so we must always provide it)
|
|
private static StreamAssignment CreateStreamAssignment(string name, string groupName, string config = "{}")
|
|
=> new()
|
|
{
|
|
StreamName = name,
|
|
Group = new RaftGroup { Name = groupName, Peers = ["n1", "n2", "n3"] },
|
|
ConfigJson = config,
|
|
};
|
|
}
|