UDT-002: Logout + Refresh Token con rotación y chain revocation #3
136
tests/SIGCM2.Application.Tests/Domain/RefreshTokenTests.cs
Normal file
136
tests/SIGCM2.Application.Tests/Domain/RefreshTokenTests.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
using SIGCM2.Domain.Entities;
|
||||
|
||||
namespace SIGCM2.Application.Tests.Domain;
|
||||
|
||||
public class RefreshTokenTests
|
||||
{
|
||||
// --- IssueForNewFamily ---
|
||||
|
||||
[Fact]
|
||||
public void IssueForNewFamily_SetsNewFamilyIdAndExpiresAt()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var ttl = TimeSpan.FromDays(7);
|
||||
|
||||
var token = RefreshToken.IssueForNewFamily(
|
||||
usuarioId: 1,
|
||||
tokenHash: "hash_abc",
|
||||
now: now,
|
||||
ttl: ttl,
|
||||
createdByIp: "127.0.0.1",
|
||||
userAgent: "Mozilla/5.0");
|
||||
|
||||
Assert.Equal(1, token.UsuarioId);
|
||||
Assert.Equal("hash_abc", token.TokenHash);
|
||||
Assert.NotEqual(Guid.Empty, token.FamilyId);
|
||||
Assert.Equal(now, token.IssuedAt);
|
||||
Assert.Equal(now + ttl, token.ExpiresAt);
|
||||
Assert.Equal("127.0.0.1", token.CreatedByIp);
|
||||
Assert.Equal("Mozilla/5.0", token.UserAgent);
|
||||
Assert.Null(token.RevokedAt);
|
||||
Assert.Null(token.ReplacedById);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IssueForNewFamily_TwoCallsProduceDifferentFamilyIds()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var ttl = TimeSpan.FromDays(7);
|
||||
|
||||
var t1 = RefreshToken.IssueForNewFamily(1, "hash1", now, ttl, "127.0.0.1", null);
|
||||
var t2 = RefreshToken.IssueForNewFamily(1, "hash2", now, ttl, "127.0.0.1", null);
|
||||
|
||||
Assert.NotEqual(t1.FamilyId, t2.FamilyId);
|
||||
}
|
||||
|
||||
// --- IssueRotation ---
|
||||
|
||||
[Fact]
|
||||
public void IssueRotation_InheritsFamilyIdAndExpiresAt()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var original = RefreshToken.IssueForNewFamily(
|
||||
1, "hash_original", now.AddHours(-2), TimeSpan.FromDays(7), "10.0.0.1", "UA1");
|
||||
|
||||
var rotationTime = now;
|
||||
var rotated = RefreshToken.IssueRotation(
|
||||
previous: original,
|
||||
newTokenHash: "hash_new",
|
||||
now: rotationTime,
|
||||
createdByIp: "10.0.0.2",
|
||||
userAgent: "UA2");
|
||||
|
||||
Assert.Equal(original.FamilyId, rotated.FamilyId); // same family
|
||||
Assert.Equal(original.ExpiresAt, rotated.ExpiresAt); // ABSOLUTE — inherited
|
||||
Assert.Equal(original.UsuarioId, rotated.UsuarioId);
|
||||
Assert.Equal("hash_new", rotated.TokenHash);
|
||||
Assert.Equal(rotationTime, rotated.IssuedAt);
|
||||
Assert.Equal("10.0.0.2", rotated.CreatedByIp);
|
||||
Assert.Equal("UA2", rotated.UserAgent);
|
||||
Assert.Null(rotated.RevokedAt);
|
||||
Assert.Null(rotated.ReplacedById);
|
||||
}
|
||||
|
||||
// --- IsActive ---
|
||||
|
||||
[Fact]
|
||||
public void IsActive_False_WhenRevoked()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var token = RefreshToken.IssueForNewFamily(1, "h", now, TimeSpan.FromDays(7), "1.1.1.1", null);
|
||||
token.MarkAsPersistedRevocation(now.AddSeconds(-1), replacedById: null);
|
||||
|
||||
Assert.False(token.IsActive(now));
|
||||
Assert.True(token.IsRevoked);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsActive_False_WhenExpired()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
// issued 8 days ago, ttl 7 days → expired yesterday
|
||||
var token = RefreshToken.IssueForNewFamily(1, "h", now.AddDays(-8), TimeSpan.FromDays(7), "1.1.1.1", null);
|
||||
|
||||
Assert.False(token.IsActive(now));
|
||||
Assert.True(token.IsExpired(now));
|
||||
Assert.False(token.IsRevoked);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsActive_True_WhenFreshAndNotRevoked()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var token = RefreshToken.IssueForNewFamily(1, "h", now, TimeSpan.FromDays(7), "1.1.1.1", null);
|
||||
|
||||
Assert.True(token.IsActive(now.AddMinutes(1)));
|
||||
Assert.False(token.IsRevoked);
|
||||
Assert.False(token.IsExpired(now.AddMinutes(1)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsExpired_True_AtExpiresAt()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var token = RefreshToken.IssueForNewFamily(1, "h", now, TimeSpan.FromDays(7), "1.1.1.1", null);
|
||||
|
||||
// exactly at ExpiresAt it is expired (>= boundary)
|
||||
Assert.True(token.IsExpired(token.ExpiresAt));
|
||||
// one second before: not expired
|
||||
Assert.False(token.IsExpired(token.ExpiresAt.AddSeconds(-1)));
|
||||
}
|
||||
|
||||
// --- MarkAsPersistedRevocation ---
|
||||
|
||||
[Fact]
|
||||
public void MarkAsPersistedRevocation_SetsRevokedAtAndReplacedById()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var token = RefreshToken.IssueForNewFamily(1, "h", now, TimeSpan.FromDays(7), "1.1.1.1", null);
|
||||
|
||||
token.MarkAsPersistedRevocation(now.AddSeconds(30), replacedById: 42);
|
||||
|
||||
Assert.Equal(now.AddSeconds(30), token.RevokedAt);
|
||||
Assert.Equal(42, token.ReplacedById);
|
||||
Assert.True(token.IsRevoked);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user