using ColaFlow.Modules.Identity.Domain.Aggregates.Users; using ColaFlow.Modules.Identity.Domain.Entities; using FluentAssertions; using Xunit; namespace ColaFlow.Modules.Identity.Domain.Tests.Entities; public sealed class PasswordResetTokenTests { private readonly UserId _userId = UserId.CreateUnique(); private const string TestTokenHash = "hashed_token_value"; private const string TestIpAddress = "192.168.1.1"; private const string TestUserAgent = "Mozilla/5.0"; [Fact] public void Create_WithValidData_ShouldSucceed() { // Arrange var expiresAt = DateTime.UtcNow.AddHours(1); // Act var token = PasswordResetToken.Create( _userId, TestTokenHash, expiresAt, TestIpAddress, TestUserAgent); // Assert token.Should().NotBeNull(); token.Id.Should().NotBe(Guid.Empty); token.UserId.Should().Be(_userId); token.TokenHash.Should().Be(TestTokenHash); token.ExpiresAt.Should().Be(expiresAt); token.IpAddress.Should().Be(TestIpAddress); token.UserAgent.Should().Be(TestUserAgent); token.UsedAt.Should().BeNull(); token.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); token.IsExpired.Should().BeFalse(); token.IsUsed.Should().BeFalse(); token.IsValid.Should().BeTrue(); } [Fact] public void Create_WithoutOptionalParameters_ShouldSucceed() { // Arrange var expiresAt = DateTime.UtcNow.AddHours(1); // Act var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt); // Assert token.Should().NotBeNull(); token.IpAddress.Should().BeNull(); token.UserAgent.Should().BeNull(); } [Fact] public void IsExpired_WithFutureExpiration_ShouldReturnFalse() { // Arrange var expiresAt = DateTime.UtcNow.AddHours(1); var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt); // Act & Assert token.IsExpired.Should().BeFalse(); } [Fact] public void IsExpired_WithPastExpiration_ShouldReturnTrue() { // Arrange var expiresAt = DateTime.UtcNow.AddHours(-1); var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt); // Act & Assert token.IsExpired.Should().BeTrue(); } [Fact] public void IsUsed_WhenNotUsed_ShouldReturnFalse() { // Arrange var expiresAt = DateTime.UtcNow.AddHours(1); var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt); // Act & Assert token.IsUsed.Should().BeFalse(); } [Fact] public void IsUsed_WhenUsed_ShouldReturnTrue() { // Arrange var expiresAt = DateTime.UtcNow.AddHours(1); var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt); token.MarkAsUsed(); // Act & Assert token.IsUsed.Should().BeTrue(); } [Fact] public void IsValid_WithValidToken_ShouldReturnTrue() { // Arrange var expiresAt = DateTime.UtcNow.AddHours(1); var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt); // Act & Assert token.IsValid.Should().BeTrue(); } [Fact] public void IsValid_WithExpiredToken_ShouldReturnFalse() { // Arrange var expiresAt = DateTime.UtcNow.AddHours(-1); var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt); // Act & Assert token.IsValid.Should().BeFalse(); } [Fact] public void IsValid_WithUsedToken_ShouldReturnFalse() { // Arrange var expiresAt = DateTime.UtcNow.AddHours(1); var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt); token.MarkAsUsed(); // Act & Assert token.IsValid.Should().BeFalse(); } [Fact] public void MarkAsUsed_WithValidToken_ShouldSetUsedAt() { // Arrange var expiresAt = DateTime.UtcNow.AddHours(1); var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt); // Act token.MarkAsUsed(); // Assert token.UsedAt.Should().NotBeNull(); token.UsedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); token.IsUsed.Should().BeTrue(); token.IsValid.Should().BeFalse(); } [Fact] public void MarkAsUsed_WithExpiredToken_ShouldThrowException() { // Arrange var expiresAt = DateTime.UtcNow.AddHours(-1); var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt); // Act var act = () => token.MarkAsUsed(); // Assert act.Should().Throw() .WithMessage("*Token is not valid for password reset*"); } [Fact] public void MarkAsUsed_WithAlreadyUsedToken_ShouldThrowException() { // Arrange var expiresAt = DateTime.UtcNow.AddHours(1); var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt); token.MarkAsUsed(); // Act var act = () => token.MarkAsUsed(); // Assert act.Should().Throw() .WithMessage("*Token is not valid for password reset*"); } [Fact] public void Create_WithShortExpiration_ShouldBeSecure() { // Arrange - Tokens should expire quickly for security (e.g., 1 hour) var expiresAt = DateTime.UtcNow.AddHours(1); // Act var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt); // Assert token.ExpiresAt.Should().BeBefore(DateTime.UtcNow.AddHours(2)); } [Fact] public void MarkAsUsed_PreventTokenReuse_ShouldEnforceSingleUse() { // Arrange var expiresAt = DateTime.UtcNow.AddHours(1); var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt); // Act - Use token once token.MarkAsUsed(); // Assert - Subsequent use should fail var act = () => token.MarkAsUsed(); act.Should().Throw() .WithMessage("*Token is not valid for password reset*"); } }