215 lines
6.2 KiB
C#
215 lines
6.2 KiB
C#
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<InvalidOperationException>()
|
|
.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<InvalidOperationException>()
|
|
.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<InvalidOperationException>()
|
|
.WithMessage("*Token is not valid for password reset*");
|
|
}
|
|
}
|