From 2efe4115c4c4546633d8c066f0a3242fb91cec7c Mon Sep 17 00:00:00 2001 From: dmolinari Date: Tue, 14 Apr 2026 13:16:36 -0300 Subject: [PATCH] test(domain): add RefreshToken entity tests RED --- .../Domain/RefreshTokenTests.cs | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 tests/SIGCM2.Application.Tests/Domain/RefreshTokenTests.cs diff --git a/tests/SIGCM2.Application.Tests/Domain/RefreshTokenTests.cs b/tests/SIGCM2.Application.Tests/Domain/RefreshTokenTests.cs new file mode 100644 index 0000000..5127512 --- /dev/null +++ b/tests/SIGCM2.Application.Tests/Domain/RefreshTokenTests.cs @@ -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); + } +}