Files
natsdotnet/tests/NATS.Server.Tests/Auth/AccountClaimReloadTests.cs
Joseph Doherty e4b5ed9a83 feat: add JWT activation claim expiration checking (Gap 9.7)
Add ActivationClaim and ActivationCheckResult types plus five methods on
Account (RegisterActivation, CheckActivationExpiry, IsActivationExpired,
GetExpiredActivations, RemoveExpiredActivations) and an ActiveActivationCount
property, mirroring Go accounts.go checkActivation / activationExpired logic.
Adds 10 targeted tests in Auth/ActivationExpirationTests.cs (all pass).
2026-02-25 12:57:41 -05:00

174 lines
5.9 KiB
C#

// Tests for account claim hot-reload with diff-based update detection.
// Go reference: accounts_test.go TestUpdateAccountClaims, updateAccountClaimsWithRefresh (~line 3374).
using NATS.Server.Auth;
namespace NATS.Server.Tests.Auth;
public class AccountClaimReloadTests
{
// 1. First update: all provided fields are reported as changed.
[Fact]
public void UpdateAccountClaims_FirstUpdate_AllFieldsChanged()
{
var account = new Account("test");
var claims = new AccountClaimData
{
MaxConnections = 10,
MaxSubscriptions = 100,
Nkey = "NKEY123",
Issuer = "ISSUER_OP",
ExpiresAt = new DateTime(2030, 1, 1, 0, 0, 0, DateTimeKind.Utc),
};
var result = account.UpdateAccountClaims(claims);
result.Changed.ShouldBeTrue();
result.ChangedFields.ShouldContain(nameof(AccountClaimData.MaxConnections));
result.ChangedFields.ShouldContain(nameof(AccountClaimData.MaxSubscriptions));
result.ChangedFields.ShouldContain(nameof(AccountClaimData.Nkey));
result.ChangedFields.ShouldContain(nameof(AccountClaimData.Issuer));
result.ChangedFields.ShouldContain(nameof(AccountClaimData.ExpiresAt));
result.ChangedFields.Count.ShouldBe(5);
}
// 2. Applying the exact same claims a second time returns Changed=false.
[Fact]
public void UpdateAccountClaims_NoChange_ReturnsFalse()
{
var account = new Account("test");
var claims = new AccountClaimData
{
MaxConnections = 5,
MaxSubscriptions = 50,
Nkey = "NKEY_A",
Issuer = "OP",
};
account.UpdateAccountClaims(claims);
var result = account.UpdateAccountClaims(claims);
result.Changed.ShouldBeFalse();
result.ChangedFields.Count.ShouldBe(0);
}
// 3. Changing MaxConnections is detected.
[Fact]
public void UpdateAccountClaims_MaxConnectionsChanged_Detected()
{
var account = new Account("test");
var initial = new AccountClaimData { MaxConnections = 10 };
account.UpdateAccountClaims(initial);
var updated = new AccountClaimData { MaxConnections = 20 };
var result = account.UpdateAccountClaims(updated);
result.Changed.ShouldBeTrue();
result.ChangedFields.ShouldContain(nameof(AccountClaimData.MaxConnections));
account.MaxConnections.ShouldBe(20);
}
// 4. Changing MaxSubscriptions is detected.
[Fact]
public void UpdateAccountClaims_MaxSubscriptionsChanged_Detected()
{
var account = new Account("test");
var initial = new AccountClaimData { MaxSubscriptions = 100 };
account.UpdateAccountClaims(initial);
var updated = new AccountClaimData { MaxSubscriptions = 200 };
var result = account.UpdateAccountClaims(updated);
result.Changed.ShouldBeTrue();
result.ChangedFields.ShouldContain(nameof(AccountClaimData.MaxSubscriptions));
account.MaxSubscriptions.ShouldBe(200);
}
// 5. Changing Nkey is detected.
[Fact]
public void UpdateAccountClaims_NkeyChanged_Detected()
{
var account = new Account("test");
var initial = new AccountClaimData { Nkey = "OLD_NKEY" };
account.UpdateAccountClaims(initial);
var updated = new AccountClaimData { Nkey = "NEW_NKEY" };
var result = account.UpdateAccountClaims(updated);
result.Changed.ShouldBeTrue();
result.ChangedFields.ShouldContain(nameof(AccountClaimData.Nkey));
account.Nkey.ShouldBe("NEW_NKEY");
}
// 6. Changing Issuer is detected.
[Fact]
public void UpdateAccountClaims_IssuerChanged_Detected()
{
var account = new Account("test");
var initial = new AccountClaimData { Issuer = "ISSUER_A" };
account.UpdateAccountClaims(initial);
var updated = new AccountClaimData { Issuer = "ISSUER_B" };
var result = account.UpdateAccountClaims(updated);
result.Changed.ShouldBeTrue();
result.ChangedFields.ShouldContain(nameof(AccountClaimData.Issuer));
account.Issuer.ShouldBe("ISSUER_B");
}
// 7. A successful claim update increments the generation counter.
[Fact]
public void UpdateAccountClaims_IncrementsGeneration()
{
var account = new Account("test");
var before = account.GenerationId;
var claims = new AccountClaimData { MaxConnections = 5 };
account.UpdateAccountClaims(claims);
account.GenerationId.ShouldBe(before + 1);
}
// 8. HasClaims is false on a fresh account.
[Fact]
public void HasClaims_BeforeUpdate_ReturnsFalse()
{
var account = new Account("test");
account.HasClaims.ShouldBeFalse();
}
// 9. HasClaims is true after the first update.
[Fact]
public void HasClaims_AfterUpdate_ReturnsTrue()
{
var account = new Account("test");
var claims = new AccountClaimData { MaxConnections = 1 };
account.UpdateAccountClaims(claims);
account.HasClaims.ShouldBeTrue();
account.CurrentClaims.ShouldNotBeNull();
account.CurrentClaims!.MaxConnections.ShouldBe(1);
}
// 10. ClaimUpdateCount increments only when claims actually change.
[Fact]
public void ClaimUpdateCount_IncrementsOnChange()
{
var account = new Account("test");
account.ClaimUpdateCount.ShouldBe(0);
var claimsA = new AccountClaimData { MaxConnections = 5 };
account.UpdateAccountClaims(claimsA);
account.ClaimUpdateCount.ShouldBe(1);
// Reapplying same claims does NOT increment count.
account.UpdateAccountClaims(claimsA);
account.ClaimUpdateCount.ShouldBe(1);
// Applying different claims does increment.
var claimsB = new AccountClaimData { MaxConnections = 10 };
account.UpdateAccountClaims(claimsB);
account.ClaimUpdateCount.ShouldBe(2);
}
}