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; /// /// EN: Tests for FileShare aggregate root. /// VI: Kiểm thử cho aggregate root FileShare. /// 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 }