using ColaFlow.Modules.Identity.Domain.Entities; using FluentAssertions; using Xunit; namespace ColaFlow.Modules.Identity.Domain.Tests.Entities; public sealed class EmailRateLimitTests { private readonly Guid _tenantId = Guid.NewGuid(); private const string TestEmail = "user@example.com"; private const string OperationType = "verification"; [Fact] public void Create_WithValidData_ShouldSucceed() { // Arrange & Act var rateLimit = EmailRateLimit.Create(TestEmail, _tenantId, OperationType); // Assert rateLimit.Should().NotBeNull(); rateLimit.Id.Should().NotBe(Guid.Empty); rateLimit.Email.Should().Be(TestEmail.ToLower()); rateLimit.TenantId.Should().Be(_tenantId); rateLimit.OperationType.Should().Be(OperationType); rateLimit.AttemptsCount.Should().Be(1); rateLimit.LastSentAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); } [Fact] public void Create_ShouldNormalizeEmail() { // Arrange const string mixedCaseEmail = "User@EXAMPLE.COM"; // Act var rateLimit = EmailRateLimit.Create(mixedCaseEmail, _tenantId, OperationType); // Assert rateLimit.Email.Should().Be("user@example.com"); } [Fact] public void Create_WithEmptyEmail_ShouldThrowException() { // Arrange & Act var act = () => EmailRateLimit.Create(string.Empty, _tenantId, OperationType); // Assert act.Should().Throw() .WithMessage("*Email cannot be empty*"); } [Fact] public void Create_WithEmptyOperationType_ShouldThrowException() { // Arrange & Act var act = () => EmailRateLimit.Create(TestEmail, _tenantId, string.Empty); // Assert act.Should().Throw() .WithMessage("*Operation type cannot be empty*"); } [Fact] public void RecordAttempt_ShouldIncrementCount() { // Arrange var rateLimit = EmailRateLimit.Create(TestEmail, _tenantId, OperationType); var initialCount = rateLimit.AttemptsCount; var initialLastSentAt = rateLimit.LastSentAt; // Wait a bit to ensure time difference System.Threading.Thread.Sleep(10); // Act rateLimit.RecordAttempt(); // Assert rateLimit.AttemptsCount.Should().Be(initialCount + 1); rateLimit.LastSentAt.Should().BeAfter(initialLastSentAt); } [Fact] public void RecordAttempt_MultipleCallsMultiple_ShouldIncrementCorrectly() { // Arrange var rateLimit = EmailRateLimit.Create(TestEmail, _tenantId, OperationType); // Act rateLimit.RecordAttempt(); rateLimit.RecordAttempt(); rateLimit.RecordAttempt(); // Assert rateLimit.AttemptsCount.Should().Be(4); // 1 initial + 3 increments } [Fact] public void ResetAttempts_ShouldResetCountToOne() { // Arrange var rateLimit = EmailRateLimit.Create(TestEmail, _tenantId, OperationType); rateLimit.RecordAttempt(); rateLimit.RecordAttempt(); rateLimit.RecordAttempt(); // Act rateLimit.ResetAttempts(); // Assert rateLimit.AttemptsCount.Should().Be(1); } [Fact] public void ResetAttempts_ShouldUpdateLastSentAt() { // Arrange var rateLimit = EmailRateLimit.Create(TestEmail, _tenantId, OperationType); var initialLastSentAt = rateLimit.LastSentAt; // Wait a bit to ensure time difference System.Threading.Thread.Sleep(10); // Act rateLimit.ResetAttempts(); // Assert rateLimit.LastSentAt.Should().BeAfter(initialLastSentAt); rateLimit.LastSentAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); } [Fact] public void IsWindowExpired_WithinWindow_ShouldReturnFalse() { // Arrange var rateLimit = EmailRateLimit.Create(TestEmail, _tenantId, OperationType); var window = TimeSpan.FromMinutes(5); // Act var isExpired = rateLimit.IsWindowExpired(window); // Assert isExpired.Should().BeFalse(); } [Fact] public void IsWindowExpired_OutsideWindow_ShouldReturnTrue() { // Arrange var rateLimit = EmailRateLimit.Create(TestEmail, _tenantId, OperationType); var window = TimeSpan.FromMilliseconds(1); // Wait for window to expire System.Threading.Thread.Sleep(10); // Act var isExpired = rateLimit.IsWindowExpired(window); // Assert isExpired.Should().BeTrue(); } [Fact] public void IsWindowExpired_WithZeroWindow_ShouldReturnTrue() { // Arrange var rateLimit = EmailRateLimit.Create(TestEmail, _tenantId, OperationType); var window = TimeSpan.Zero; // Act var isExpired = rateLimit.IsWindowExpired(window); // Assert isExpired.Should().BeTrue(); } }