// Port of Go server/reload.go — auth change propagation tests. // Reference: golang/nats-server/server/reload.go — authOption.Apply, usersOption.Apply. using NATS.Server.Auth; using NATS.Server.Configuration; using Shouldly; namespace NATS.Server.Core.Tests.Configuration; public sealed class AuthChangePropagationTests { // ─── helpers ──────────────────────────────────────────────────── private static User MakeUser(string username, string password = "pw") => new() { Username = username, Password = password }; private static NatsOptions BaseOpts() => new(); // ─── tests ────────────────────────────────────────────────────── [Fact] public void No_changes_returns_no_changes() { // Same empty options → nothing changed. var oldOpts = BaseOpts(); var newOpts = BaseOpts(); var result = ConfigReloader.PropagateAuthChanges(oldOpts, newOpts); result.HasChanges.ShouldBeFalse(); result.UsersChanged.ShouldBeFalse(); result.AccountsChanged.ShouldBeFalse(); result.TokenChanged.ShouldBeFalse(); } [Fact] public void User_added_detected() { // Adding a user must set UsersChanged. var oldOpts = BaseOpts(); var newOpts = BaseOpts(); newOpts.Users = [MakeUser("alice")]; var result = ConfigReloader.PropagateAuthChanges(oldOpts, newOpts); result.UsersChanged.ShouldBeTrue(); result.HasChanges.ShouldBeTrue(); } [Fact] public void User_removed_detected() { // Removing a user must set UsersChanged. var oldOpts = BaseOpts(); oldOpts.Users = [MakeUser("alice")]; var newOpts = BaseOpts(); var result = ConfigReloader.PropagateAuthChanges(oldOpts, newOpts); result.UsersChanged.ShouldBeTrue(); result.HasChanges.ShouldBeTrue(); } [Fact] public void Account_added_detected() { // Adding an account must set AccountsChanged. var oldOpts = BaseOpts(); var newOpts = BaseOpts(); newOpts.Accounts = new Dictionary { ["engineering"] = new AccountConfig() }; var result = ConfigReloader.PropagateAuthChanges(oldOpts, newOpts); result.AccountsChanged.ShouldBeTrue(); result.HasChanges.ShouldBeTrue(); } [Fact] public void Token_changed_detected() { // Changing the Authorization token must set TokenChanged. var oldOpts = BaseOpts(); oldOpts.Authorization = "old-secret-token"; var newOpts = BaseOpts(); newOpts.Authorization = "new-secret-token"; var result = ConfigReloader.PropagateAuthChanges(oldOpts, newOpts); result.TokenChanged.ShouldBeTrue(); result.HasChanges.ShouldBeTrue(); } [Fact] public void Multiple_changes_all_flagged() { // Changing both users and accounts must set both flags. var oldOpts = BaseOpts(); oldOpts.Users = [MakeUser("alice")]; oldOpts.Accounts = new Dictionary { ["acct-a"] = new AccountConfig() }; var newOpts = BaseOpts(); newOpts.Users = [MakeUser("alice"), MakeUser("bob")]; newOpts.Accounts = new Dictionary { ["acct-a"] = new AccountConfig(), ["acct-b"] = new AccountConfig() }; var result = ConfigReloader.PropagateAuthChanges(oldOpts, newOpts); result.UsersChanged.ShouldBeTrue(); result.AccountsChanged.ShouldBeTrue(); result.HasChanges.ShouldBeTrue(); } [Fact] public void Same_users_different_order_no_change() { // Users in a different order with the same names must NOT trigger UsersChanged // because the comparison is set-based. var oldOpts = BaseOpts(); oldOpts.Users = [MakeUser("alice"), MakeUser("bob")]; var newOpts = BaseOpts(); newOpts.Users = [MakeUser("bob"), MakeUser("alice")]; var result = ConfigReloader.PropagateAuthChanges(oldOpts, newOpts); result.UsersChanged.ShouldBeFalse(); result.HasChanges.ShouldBeFalse(); } [Fact] public void HasChanges_true_when_any_change() { // A single changed field (token only) is enough to set HasChanges. var oldOpts = BaseOpts(); var newOpts = BaseOpts(); newOpts.Authorization = "token-xyz"; var result = ConfigReloader.PropagateAuthChanges(oldOpts, newOpts); result.HasChanges.ShouldBeTrue(); } [Fact] public void Empty_to_non_empty_users_detected() { // Going from zero users to one user must be detected. var oldOpts = BaseOpts(); // No Users assigned — null list. var newOpts = BaseOpts(); newOpts.Users = [MakeUser("charlie")]; var result = ConfigReloader.PropagateAuthChanges(oldOpts, newOpts); result.UsersChanged.ShouldBeTrue(); result.HasChanges.ShouldBeTrue(); } [Fact] public void No_auth_to_auth_detected() { // Going from null Authorization to a token string must be detected. var oldOpts = BaseOpts(); // Authorization is null by default. var newOpts = BaseOpts(); newOpts.Authorization = "brand-new-token"; var result = ConfigReloader.PropagateAuthChanges(oldOpts, newOpts); result.TokenChanged.ShouldBeTrue(); result.HasChanges.ShouldBeTrue(); } [Fact] public void Same_token_no_change() { // The same token value on both sides must NOT flag TokenChanged. var oldOpts = BaseOpts(); oldOpts.Authorization = "stable-token"; var newOpts = BaseOpts(); newOpts.Authorization = "stable-token"; var result = ConfigReloader.PropagateAuthChanges(oldOpts, newOpts); result.TokenChanged.ShouldBeFalse(); result.HasChanges.ShouldBeFalse(); } }