137 lines
4.5 KiB
C#
137 lines
4.5 KiB
C#
|
|
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);
|
||
|
|
}
|
||
|
|
}
|