diff --git a/src/NATS.Server/Configuration/JetStreamOptions.cs b/src/NATS.Server/Configuration/JetStreamOptions.cs index 8c94d2c..6655a47 100644 --- a/src/NATS.Server/Configuration/JetStreamOptions.cs +++ b/src/NATS.Server/Configuration/JetStreamOptions.cs @@ -34,4 +34,11 @@ public sealed class JetStreamOptions /// Maps to Go's JetStreamAccountLimits.MaxConsumers (jetstream.go). /// public int MaxConsumers { get; set; } + + /// + /// JetStream domain name for this server. Used for domain-scoped routing and + /// subject prefix isolation in clustered deployments. + /// Maps to Go's Options.JetStreamDomain (opts.go). + /// + public string? Domain { get; set; } } diff --git a/tests/NATS.Server.Tests/Configuration/JetStreamConfigReloadTests.cs b/tests/NATS.Server.Tests/Configuration/JetStreamConfigReloadTests.cs new file mode 100644 index 0000000..8b3d348 --- /dev/null +++ b/tests/NATS.Server.Tests/Configuration/JetStreamConfigReloadTests.cs @@ -0,0 +1,171 @@ +// Port of Go server/reload.go — JetStream config change detection tests. +// Reference: golang/nats-server/server/reload.go — jetStreamOption.Apply. + +using NATS.Server.Configuration; +using Shouldly; + +namespace NATS.Server.Tests.Configuration; + +public sealed class JetStreamConfigReloadTests +{ + // ─── helpers ──────────────────────────────────────────────────── + + private static NatsOptions BaseOpts() => new(); + + private static NatsOptions OptsWithJs(long maxMemory = 0, long maxStore = 0, string? domain = null) => + new() + { + JetStream = new JetStreamOptions + { + MaxMemoryStore = maxMemory, + MaxFileStore = maxStore, + Domain = domain + } + }; + + // ─── tests ────────────────────────────────────────────────────── + + [Fact] + public void No_changes_returns_no_changes() + { + // Identical JetStream config on both sides → no changes detected. + var oldOpts = OptsWithJs(maxMemory: 512, maxStore: 1024, domain: "hub"); + var newOpts = OptsWithJs(maxMemory: 512, maxStore: 1024, domain: "hub"); + + var result = ConfigReloader.ApplyJetStreamConfigChanges(oldOpts, newOpts); + + result.HasChanges.ShouldBeFalse(); + result.MaxMemoryChanged.ShouldBeFalse(); + result.MaxStoreChanged.ShouldBeFalse(); + result.DomainChanged.ShouldBeFalse(); + } + + [Fact] + public void MaxMemory_changed_detected() + { + // Changing MaxMemoryStore must set MaxMemoryChanged and HasChanges. + var oldOpts = OptsWithJs(maxMemory: 1024 * 1024); + var newOpts = OptsWithJs(maxMemory: 2 * 1024 * 1024); + + var result = ConfigReloader.ApplyJetStreamConfigChanges(oldOpts, newOpts); + + result.MaxMemoryChanged.ShouldBeTrue(); + result.HasChanges.ShouldBeTrue(); + result.MaxStoreChanged.ShouldBeFalse(); + result.DomainChanged.ShouldBeFalse(); + } + + [Fact] + public void MaxStore_changed_detected() + { + // Changing MaxFileStore must set MaxStoreChanged and HasChanges. + var oldOpts = OptsWithJs(maxStore: 10 * 1024 * 1024); + var newOpts = OptsWithJs(maxStore: 20 * 1024 * 1024); + + var result = ConfigReloader.ApplyJetStreamConfigChanges(oldOpts, newOpts); + + result.MaxStoreChanged.ShouldBeTrue(); + result.HasChanges.ShouldBeTrue(); + result.MaxMemoryChanged.ShouldBeFalse(); + result.DomainChanged.ShouldBeFalse(); + } + + [Fact] + public void Domain_changed_detected() + { + // Changing the domain name must set DomainChanged and HasChanges. + var oldOpts = OptsWithJs(domain: "hub"); + var newOpts = OptsWithJs(domain: "edge"); + + var result = ConfigReloader.ApplyJetStreamConfigChanges(oldOpts, newOpts); + + result.DomainChanged.ShouldBeTrue(); + result.HasChanges.ShouldBeTrue(); + result.MaxMemoryChanged.ShouldBeFalse(); + result.MaxStoreChanged.ShouldBeFalse(); + } + + [Fact] + public void OldMaxMemory_and_NewMaxMemory_set() + { + // When MaxMemoryStore changes the old and new values must be captured. + var oldOpts = OptsWithJs(maxMemory: 100_000); + var newOpts = OptsWithJs(maxMemory: 200_000); + + var result = ConfigReloader.ApplyJetStreamConfigChanges(oldOpts, newOpts); + + result.OldMaxMemory.ShouldBe(100_000L); + result.NewMaxMemory.ShouldBe(200_000L); + } + + [Fact] + public void OldMaxStore_and_NewMaxStore_set() + { + // When MaxFileStore changes the old and new values must be captured. + var oldOpts = OptsWithJs(maxStore: 500_000); + var newOpts = OptsWithJs(maxStore: 1_000_000); + + var result = ConfigReloader.ApplyJetStreamConfigChanges(oldOpts, newOpts); + + result.OldMaxStore.ShouldBe(500_000L); + result.NewMaxStore.ShouldBe(1_000_000L); + } + + [Fact] + public void OldDomain_and_NewDomain_set() + { + // When Domain changes both old and new values must be captured. + var oldOpts = OptsWithJs(domain: "alpha"); + var newOpts = OptsWithJs(domain: "beta"); + + var result = ConfigReloader.ApplyJetStreamConfigChanges(oldOpts, newOpts); + + result.OldDomain.ShouldBe("alpha"); + result.NewDomain.ShouldBe("beta"); + } + + [Fact] + public void Multiple_changes_detected() + { + // Changing MaxMemory, MaxStore, and Domain together must flag all three. + var oldOpts = OptsWithJs(maxMemory: 1024, maxStore: 2048, domain: "primary"); + var newOpts = OptsWithJs(maxMemory: 4096, maxStore: 8192, domain: "secondary"); + + var result = ConfigReloader.ApplyJetStreamConfigChanges(oldOpts, newOpts); + + result.MaxMemoryChanged.ShouldBeTrue(); + result.MaxStoreChanged.ShouldBeTrue(); + result.DomainChanged.ShouldBeTrue(); + result.HasChanges.ShouldBeTrue(); + } + + [Fact] + public void Zero_to_nonzero_memory_detected() + { + // Going from the default (0 = unlimited) to a concrete limit must be detected. + var oldOpts = BaseOpts(); // JetStream is null → effective MaxMemoryStore = 0 + var newOpts = OptsWithJs(maxMemory: 512 * 1024 * 1024); + + var result = ConfigReloader.ApplyJetStreamConfigChanges(oldOpts, newOpts); + + result.MaxMemoryChanged.ShouldBeTrue(); + result.HasChanges.ShouldBeTrue(); + result.OldMaxMemory.ShouldBe(0L); + result.NewMaxMemory.ShouldBe(512 * 1024 * 1024L); + } + + [Fact] + public void Domain_null_to_value_detected() + { + // Going from no JetStream config (domain null/empty) to a named domain must be detected. + var oldOpts = BaseOpts(); // JetStream is null → effective domain = "" + var newOpts = OptsWithJs(domain: "cloud"); + + var result = ConfigReloader.ApplyJetStreamConfigChanges(oldOpts, newOpts); + + result.DomainChanged.ShouldBeTrue(); + result.HasChanges.ShouldBeTrue(); + result.OldDomain.ShouldBe(string.Empty); + result.NewDomain.ShouldBe("cloud"); + } +}