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); } }