395 lines
9.7 KiB
C#
395 lines
9.7 KiB
C#
using FluentAssertions;
|
|
using StorageService.Domain.AggregatesModel.FileShareAggregate;
|
|
using Xunit;
|
|
|
|
// EN: Alias to avoid collision with System.IO.FileShare
|
|
// VI: Alias để tránh xung đột với System.IO.FileShare
|
|
using DomainFileShare = StorageService.Domain.AggregatesModel.FileShareAggregate.FileShare;
|
|
|
|
namespace StorageService.UnitTests.Domain;
|
|
/// <summary>
|
|
/// EN: Tests for FileShare aggregate root.
|
|
/// VI: Kiểm thử cho aggregate root FileShare.
|
|
/// </summary>
|
|
public class FileShareTests
|
|
{
|
|
private static readonly Guid ValidFileId = Guid.NewGuid();
|
|
private const string ValidSharedBy = "user-123";
|
|
private const SharePermission ValidPermission = SharePermission.Download;
|
|
|
|
#region Constructor Tests
|
|
|
|
[Fact]
|
|
public void Constructor_ValidParams_GeneratesUniqueShareToken()
|
|
{
|
|
// Arrange & Act
|
|
var share1 = CreateValidFileShare();
|
|
var share2 = CreateValidFileShare();
|
|
|
|
// Assert
|
|
share1.ShareToken.Should().NotBeNullOrEmpty();
|
|
share2.ShareToken.Should().NotBeNullOrEmpty();
|
|
share1.ShareToken.Should().NotBe(share2.ShareToken);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_ValidParams_SetsCorrectProperties()
|
|
{
|
|
// Arrange & Act
|
|
var expiresAt = DateTime.UtcNow.AddDays(7);
|
|
var share = new DomainFileShare(
|
|
ValidFileId,
|
|
ValidSharedBy,
|
|
SharePermission.Edit,
|
|
sharedWith: "user-456",
|
|
password: null,
|
|
expiresAt: expiresAt,
|
|
maxDownloads: 10);
|
|
|
|
// Assert
|
|
share.FileId.Should().Be(ValidFileId);
|
|
share.SharedBy.Should().Be(ValidSharedBy);
|
|
share.SharedWith.Should().Be("user-456");
|
|
share.Permission.Should().Be(SharePermission.Edit);
|
|
share.ExpiresAt.Should().Be(expiresAt);
|
|
share.MaxDownloads.Should().Be(10);
|
|
share.DownloadCount.Should().Be(0);
|
|
share.Status.Should().Be(FileShareStatus.Active);
|
|
share.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_WithPassword_HashesPassword()
|
|
{
|
|
// Arrange
|
|
var password = "SecureP@ssw0rd!";
|
|
|
|
// Act
|
|
var share = new DomainFileShare(
|
|
ValidFileId,
|
|
ValidSharedBy,
|
|
ValidPermission,
|
|
password: password);
|
|
|
|
// Assert
|
|
share.PasswordHash.Should().NotBeNullOrEmpty();
|
|
share.PasswordHash.Should().NotBe(password); // Ensure hashed
|
|
share.PasswordHash!.Length.Should().BeGreaterThan(password.Length); // Hash includes salt
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_WithoutPassword_PasswordHashIsNull()
|
|
{
|
|
// Arrange & Act
|
|
var share = CreateValidFileShare();
|
|
|
|
// Assert
|
|
share.PasswordHash.Should().BeNull();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IsValid Tests
|
|
|
|
[Fact]
|
|
public void IsValid_ActiveShare_ReturnsTrue()
|
|
{
|
|
// Arrange
|
|
var share = CreateValidFileShare();
|
|
|
|
// Act
|
|
var result = share.IsValid();
|
|
|
|
// Assert
|
|
result.Should().BeTrue();
|
|
share.Status.Should().Be(FileShareStatus.Active);
|
|
}
|
|
|
|
[Fact]
|
|
public void IsValid_RevokedShare_ReturnsFalse()
|
|
{
|
|
// Arrange
|
|
var share = CreateValidFileShare();
|
|
share.Revoke();
|
|
|
|
// Act
|
|
var result = share.IsValid();
|
|
|
|
// Assert
|
|
result.Should().BeFalse();
|
|
share.Status.Should().Be(FileShareStatus.Revoked);
|
|
}
|
|
|
|
[Fact]
|
|
public void IsValid_ExpiredShare_ReturnsFalseAndUpdatesStatus()
|
|
{
|
|
// Arrange
|
|
var share = new DomainFileShare(
|
|
ValidFileId,
|
|
ValidSharedBy,
|
|
ValidPermission,
|
|
expiresAt: DateTime.UtcNow.AddMilliseconds(-100)); // Already expired
|
|
|
|
// Act
|
|
var result = share.IsValid();
|
|
|
|
// Assert
|
|
result.Should().BeFalse();
|
|
share.Status.Should().Be(FileShareStatus.Expired);
|
|
}
|
|
|
|
[Fact]
|
|
public void IsValid_LimitReached_ReturnsFalseAndUpdatesStatus()
|
|
{
|
|
// Arrange
|
|
var share = new DomainFileShare(
|
|
ValidFileId,
|
|
ValidSharedBy,
|
|
ValidPermission,
|
|
maxDownloads: 2);
|
|
|
|
// Simulate reaching limit
|
|
share.IncrementDownloadCount();
|
|
share.IncrementDownloadCount();
|
|
|
|
// Act
|
|
var result = share.IsValid();
|
|
|
|
// Assert
|
|
result.Should().BeFalse();
|
|
share.Status.Should().Be(FileShareStatus.LimitReached);
|
|
}
|
|
|
|
[Fact]
|
|
public void IsValid_WithFutureExpiration_ReturnsTrue()
|
|
{
|
|
// Arrange
|
|
var share = new DomainFileShare(
|
|
ValidFileId,
|
|
ValidSharedBy,
|
|
ValidPermission,
|
|
expiresAt: DateTime.UtcNow.AddDays(7));
|
|
|
|
// Act
|
|
var result = share.IsValid();
|
|
|
|
// Assert
|
|
result.Should().BeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void IsValid_UnderDownloadLimit_ReturnsTrue()
|
|
{
|
|
// Arrange
|
|
var share = new DomainFileShare(
|
|
ValidFileId,
|
|
ValidSharedBy,
|
|
ValidPermission,
|
|
maxDownloads: 5);
|
|
share.IncrementDownloadCount(); // 1/5
|
|
|
|
// Act
|
|
var result = share.IsValid();
|
|
|
|
// Assert
|
|
result.Should().BeTrue();
|
|
share.DownloadCount.Should().Be(1);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ValidatePassword Tests
|
|
|
|
[Fact]
|
|
public void ValidatePassword_NoPasswordRequired_ReturnsTrue()
|
|
{
|
|
// Arrange
|
|
var share = CreateValidFileShare(); // No password
|
|
|
|
// Act
|
|
var result = share.ValidatePassword(null);
|
|
|
|
// Assert
|
|
result.Should().BeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void ValidatePassword_CorrectPassword_ReturnsTrue()
|
|
{
|
|
// Arrange
|
|
var password = "MySecretP@ss123";
|
|
var share = new DomainFileShare(
|
|
ValidFileId,
|
|
ValidSharedBy,
|
|
ValidPermission,
|
|
password: password);
|
|
|
|
// Act
|
|
var result = share.ValidatePassword(password);
|
|
|
|
// Assert
|
|
result.Should().BeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void ValidatePassword_WrongPassword_ReturnsFalse()
|
|
{
|
|
// Arrange
|
|
var share = new DomainFileShare(
|
|
ValidFileId,
|
|
ValidSharedBy,
|
|
ValidPermission,
|
|
password: "CorrectPassword");
|
|
|
|
// Act
|
|
var result = share.ValidatePassword("WrongPassword");
|
|
|
|
// Assert
|
|
result.Should().BeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void ValidatePassword_NullPasswordWhenRequired_ReturnsFalse()
|
|
{
|
|
// Arrange
|
|
var share = new DomainFileShare(
|
|
ValidFileId,
|
|
ValidSharedBy,
|
|
ValidPermission,
|
|
password: "SomePassword");
|
|
|
|
// Act
|
|
var result = share.ValidatePassword(null);
|
|
|
|
// Assert
|
|
result.Should().BeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void ValidatePassword_EmptyPasswordWhenRequired_ReturnsFalse()
|
|
{
|
|
// Arrange
|
|
var share = new DomainFileShare(
|
|
ValidFileId,
|
|
ValidSharedBy,
|
|
ValidPermission,
|
|
password: "SomePassword");
|
|
|
|
// Act
|
|
var result = share.ValidatePassword(string.Empty);
|
|
|
|
// Assert
|
|
result.Should().BeFalse();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IncrementDownloadCount Tests
|
|
|
|
[Fact]
|
|
public void IncrementDownloadCount_UnderLimit_IncreasesCount()
|
|
{
|
|
// Arrange
|
|
var share = new DomainFileShare(
|
|
ValidFileId,
|
|
ValidSharedBy,
|
|
ValidPermission,
|
|
maxDownloads: 10);
|
|
|
|
// Act
|
|
share.IncrementDownloadCount();
|
|
share.IncrementDownloadCount();
|
|
share.IncrementDownloadCount();
|
|
|
|
// Assert
|
|
share.DownloadCount.Should().Be(3);
|
|
share.Status.Should().Be(FileShareStatus.Active);
|
|
}
|
|
|
|
[Fact]
|
|
public void IncrementDownloadCount_ReachesLimit_UpdatesStatus()
|
|
{
|
|
// Arrange
|
|
var share = new DomainFileShare(
|
|
ValidFileId,
|
|
ValidSharedBy,
|
|
ValidPermission,
|
|
maxDownloads: 2);
|
|
|
|
// Act
|
|
share.IncrementDownloadCount(); // 1
|
|
share.IncrementDownloadCount(); // 2 - limit reached
|
|
|
|
// Assert
|
|
share.DownloadCount.Should().Be(2);
|
|
share.Status.Should().Be(FileShareStatus.LimitReached);
|
|
}
|
|
|
|
[Fact]
|
|
public void IncrementDownloadCount_NoLimit_ContinuesIncrementing()
|
|
{
|
|
// Arrange
|
|
var share = CreateValidFileShare(); // No max downloads
|
|
|
|
// Act
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
share.IncrementDownloadCount();
|
|
}
|
|
|
|
// Assert
|
|
share.DownloadCount.Should().Be(100);
|
|
share.Status.Should().Be(FileShareStatus.Active);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Revoke Tests
|
|
|
|
[Fact]
|
|
public void Revoke_SetsStatusAndRevokedAt()
|
|
{
|
|
// Arrange
|
|
var share = CreateValidFileShare();
|
|
|
|
// Act
|
|
share.Revoke();
|
|
|
|
// Assert
|
|
share.Status.Should().Be(FileShareStatus.Revoked);
|
|
share.RevokedAt.Should().NotBeNull();
|
|
share.RevokedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
|
|
}
|
|
|
|
[Fact]
|
|
public void Revoke_AlreadyRevoked_UpdatesRevokedAt()
|
|
{
|
|
// Arrange
|
|
var share = CreateValidFileShare();
|
|
share.Revoke();
|
|
var firstRevokedAt = share.RevokedAt;
|
|
|
|
// Act
|
|
Thread.Sleep(10);
|
|
share.Revoke();
|
|
|
|
// Assert
|
|
share.Status.Should().Be(FileShareStatus.Revoked);
|
|
// Note: Current implementation updates RevokedAt each time
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helper Methods
|
|
|
|
private static DomainFileShare CreateValidFileShare()
|
|
{
|
|
return new DomainFileShare(
|
|
ValidFileId,
|
|
ValidSharedBy,
|
|
ValidPermission);
|
|
}
|
|
|
|
#endregion
|
|
}
|