Add PropagateAuthChanges to ConfigReloader that compares Users, Accounts, and Authorization token between old and new NatsOptions, returning an AuthChangeResult describing which auth fields changed for connection re-evaluation.
197 lines
6.1 KiB
C#
197 lines
6.1 KiB
C#
// 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.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<string, AccountConfig>
|
|
{
|
|
["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<string, AccountConfig>
|
|
{
|
|
["acct-a"] = new AccountConfig()
|
|
};
|
|
|
|
var newOpts = BaseOpts();
|
|
newOpts.Users = [MakeUser("alice"), MakeUser("bob")];
|
|
newOpts.Accounts = new Dictionary<string, AccountConfig>
|
|
{
|
|
["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();
|
|
}
|
|
}
|