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).
201 lines
7.4 KiB
C#
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);
|
|
}
|
|
}
|