Files
natsdotnet/tests/NATS.Server.Tests/Auth/ActivationExpirationTests.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

201 lines
7.4 KiB
C#

// Tests for Account JWT activation claim expiration: RegisterActivation,
// CheckActivationExpiry, IsActivationExpired, GetExpiredActivations,
// RemoveExpiredActivations, and ActiveActivationCount.
// Go reference: server/accounts.go — checkActivation (~line 2943),
// activationExpired (~line 2920).
using NATS.Server.Auth;
namespace NATS.Server.Tests.Auth;
public class ActivationExpirationTests
{
// Helpers for well-past / well-future dates to avoid timing flakiness.
private static DateTime WellFuture => DateTime.UtcNow.AddDays(30);
private static DateTime WellPast => DateTime.UtcNow.AddDays(-30);
private static ActivationClaim ValidClaim(string subject) => new()
{
Subject = subject,
IssuedAt = DateTime.UtcNow.AddDays(-1),
ExpiresAt = WellFuture,
Issuer = "AABC123",
};
private static ActivationClaim ExpiredClaim(string subject) => new()
{
Subject = subject,
IssuedAt = DateTime.UtcNow.AddDays(-60),
ExpiresAt = WellPast,
Issuer = "AABC123",
};
// ---------------------------------------------------------------------------
// RegisterActivation
// ---------------------------------------------------------------------------
[Fact]
public void RegisterActivation_StoresActivation()
{
// Go ref: accounts.go — checkActivation stores the decoded activation claim.
var account = new Account("test");
var claim = ValidClaim("svc.foo");
account.RegisterActivation("svc.foo", claim);
var result = account.CheckActivationExpiry("svc.foo");
result.Found.ShouldBeTrue();
}
// ---------------------------------------------------------------------------
// CheckActivationExpiry
// ---------------------------------------------------------------------------
[Fact]
public void CheckActivationExpiry_Valid_NotExpired()
{
// Go ref: accounts.go — act.Expires > tn ⇒ checkActivation returns true (not expired).
var account = new Account("test");
account.RegisterActivation("svc.valid", ValidClaim("svc.valid"));
var result = account.CheckActivationExpiry("svc.valid");
result.Found.ShouldBeTrue();
result.IsExpired.ShouldBeFalse();
result.ExpiresAt.ShouldNotBeNull();
result.TimeToExpiry.ShouldNotBeNull();
result.TimeToExpiry!.Value.ShouldBeGreaterThan(TimeSpan.Zero);
}
[Fact]
public void CheckActivationExpiry_Expired_ReturnsExpired()
{
// Go ref: accounts.go — act.Expires <= tn ⇒ checkActivation returns false (expired).
var account = new Account("test");
account.RegisterActivation("svc.expired", ExpiredClaim("svc.expired"));
var result = account.CheckActivationExpiry("svc.expired");
result.Found.ShouldBeTrue();
result.IsExpired.ShouldBeTrue();
result.ExpiresAt.ShouldNotBeNull();
result.TimeToExpiry.ShouldBe(TimeSpan.Zero);
}
[Fact]
public void CheckActivationExpiry_NotFound()
{
// Go ref: accounts.go — checkActivation returns false when claim is nil/empty token.
var account = new Account("test");
var result = account.CheckActivationExpiry("svc.unknown");
result.Found.ShouldBeFalse();
result.IsExpired.ShouldBeFalse();
result.ExpiresAt.ShouldBeNull();
result.TimeToExpiry.ShouldBeNull();
}
// ---------------------------------------------------------------------------
// IsActivationExpired
// ---------------------------------------------------------------------------
[Fact]
public void IsActivationExpired_Valid_ReturnsFalse()
{
// Go ref: accounts.go — act.Expires > tn ⇒ not expired.
var account = new Account("test");
account.RegisterActivation("svc.ok", ValidClaim("svc.ok"));
account.IsActivationExpired("svc.ok").ShouldBeFalse();
}
[Fact]
public void IsActivationExpired_Expired_ReturnsTrue()
{
// Go ref: accounts.go — act.Expires <= tn ⇒ expired, activationExpired fires.
var account = new Account("test");
account.RegisterActivation("svc.past", ExpiredClaim("svc.past"));
account.IsActivationExpired("svc.past").ShouldBeTrue();
}
// ---------------------------------------------------------------------------
// GetExpiredActivations
// ---------------------------------------------------------------------------
[Fact]
public void GetExpiredActivations_ReturnsOnlyExpired()
{
// Go ref: accounts.go — activationExpired is called only for expired claims.
var account = new Account("test");
account.RegisterActivation("svc.a", ValidClaim("svc.a"));
account.RegisterActivation("svc.b", ExpiredClaim("svc.b"));
account.RegisterActivation("svc.c", ValidClaim("svc.c"));
account.RegisterActivation("svc.d", ExpiredClaim("svc.d"));
var expired = account.GetExpiredActivations();
expired.Count.ShouldBe(2);
expired.ShouldContain("svc.b");
expired.ShouldContain("svc.d");
expired.ShouldNotContain("svc.a");
expired.ShouldNotContain("svc.c");
}
// ---------------------------------------------------------------------------
// RemoveExpiredActivations
// ---------------------------------------------------------------------------
[Fact]
public void RemoveExpiredActivations_RemovesAndReturnsCount()
{
// Go ref: accounts.go — activationExpired removes the import when activation expires.
var account = new Account("test");
account.RegisterActivation("svc.live", ValidClaim("svc.live"));
account.RegisterActivation("svc.gone1", ExpiredClaim("svc.gone1"));
account.RegisterActivation("svc.gone2", ExpiredClaim("svc.gone2"));
var removed = account.RemoveExpiredActivations();
removed.ShouldBe(2);
// The expired ones should no longer be found.
account.CheckActivationExpiry("svc.gone1").Found.ShouldBeFalse();
account.CheckActivationExpiry("svc.gone2").Found.ShouldBeFalse();
// The live one should still be registered.
account.CheckActivationExpiry("svc.live").Found.ShouldBeTrue();
}
// ---------------------------------------------------------------------------
// ActiveActivationCount
// ---------------------------------------------------------------------------
[Fact]
public void ActiveActivationCount_ExcludesExpired()
{
// Go ref: accounts.go — only non-expired activations are considered active.
var account = new Account("test");
account.RegisterActivation("svc.1", ValidClaim("svc.1"));
account.RegisterActivation("svc.2", ValidClaim("svc.2"));
account.RegisterActivation("svc.3", ExpiredClaim("svc.3"));
account.ActiveActivationCount.ShouldBe(2);
}
// ---------------------------------------------------------------------------
// ActivationClaim.TimeToExpiry
// ---------------------------------------------------------------------------
[Fact]
public void ActivationClaim_TimeToExpiry_Zero_WhenExpired()
{
// Go ref: accounts.go — expired activation has no remaining time.
var claim = ExpiredClaim("svc.expired");
claim.IsExpired.ShouldBeTrue();
claim.TimeToExpiry.ShouldBe(TimeSpan.Zero);
}
}