Files
pos-system/services/storage-service-net/tests/StorageService.UnitTests/Domain/StorageFileTests.cs

386 lines
10 KiB
C#

using FluentAssertions;
using StorageService.Domain.AggregatesModel.FileAggregate;
using Xunit;
namespace StorageService.UnitTests.Domain;
/// <summary>
/// EN: Tests for StorageFile aggregate root.
/// VI: Kiểm thử cho aggregate root StorageFile.
/// </summary>
public class StorageFileTests
{
private const string ValidFileName = "test-file.pdf";
private const string ValidBucketName = "storage-bucket";
private const string ValidObjectKey = "private/user-123/20260115/abc123_test-file.pdf";
private const string ValidContentType = "application/pdf";
private const long ValidFileSize = 1024 * 1024; // 1MB
private const string ValidUserId = "user-123";
#region Constructor Tests
[Fact]
public void Constructor_ValidParams_CreatesFileWithCorrectProperties()
{
// Arrange & Act
var file = new StorageFile(
ValidFileName,
ValidBucketName,
ValidObjectKey,
ValidContentType,
ValidFileSize,
ValidUserId,
StorageProvider.MinIO,
FileAccessLevel.Private,
tenantId: "tenant-1",
checksum: "abc123");
// Assert
file.FileName.Should().Be(ValidFileName);
file.BucketName.Should().Be(ValidBucketName);
file.ObjectKey.Should().Be(ValidObjectKey);
file.ContentType.Should().Be(ValidContentType);
file.FileSizeBytes.Should().Be(ValidFileSize);
file.UserId.Should().Be(ValidUserId);
file.Provider.Should().Be(StorageProvider.MinIO);
file.AccessLevel.Should().Be(FileAccessLevel.Private);
file.TenantId.Should().Be("tenant-1");
file.Checksum.Should().Be("abc123");
file.IsDeleted.Should().BeFalse();
file.UploadedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
}
[Fact]
public void Constructor_ValidParams_RaisesFileUploadedDomainEvent()
{
// Arrange & Act
var file = CreateValidStorageFile();
// Assert
file.DomainEvents.Should().ContainSingle(e => e is FileUploadedDomainEvent);
var domainEvent = file.DomainEvents.OfType<FileUploadedDomainEvent>().First();
domainEvent.FileId.Should().Be(file.Id);
domainEvent.FileName.Should().Be(ValidFileName);
domainEvent.UserId.Should().Be(ValidUserId);
domainEvent.FileSizeBytes.Should().Be(ValidFileSize);
}
[Fact]
public void Constructor_NullFileName_ThrowsArgumentNullException()
{
// Arrange & Act
var act = () => new StorageFile(
null!,
ValidBucketName,
ValidObjectKey,
ValidContentType,
ValidFileSize,
ValidUserId,
StorageProvider.MinIO);
// Assert
act.Should().Throw<ArgumentNullException>()
.WithParameterName("fileName");
}
[Fact]
public void Constructor_NullBucketName_ThrowsArgumentNullException()
{
// Arrange & Act
var act = () => new StorageFile(
ValidFileName,
null!,
ValidObjectKey,
ValidContentType,
ValidFileSize,
ValidUserId,
StorageProvider.MinIO);
// Assert
act.Should().Throw<ArgumentNullException>()
.WithParameterName("bucketName");
}
[Fact]
public void Constructor_NullObjectKey_ThrowsArgumentNullException()
{
// Arrange & Act
var act = () => new StorageFile(
ValidFileName,
ValidBucketName,
null!,
ValidContentType,
ValidFileSize,
ValidUserId,
StorageProvider.MinIO);
// Assert
act.Should().Throw<ArgumentNullException>()
.WithParameterName("objectKey");
}
[Fact]
public void Constructor_NullUserId_ThrowsArgumentNullException()
{
// Arrange & Act
var act = () => new StorageFile(
ValidFileName,
ValidBucketName,
ValidObjectKey,
ValidContentType,
ValidFileSize,
null!,
StorageProvider.MinIO);
// Assert
act.Should().Throw<ArgumentNullException>()
.WithParameterName("userId");
}
[Fact]
public void Constructor_NullContentType_SetsDefaultContentType()
{
// Arrange & Act
var file = new StorageFile(
ValidFileName,
ValidBucketName,
ValidObjectKey,
null!,
ValidFileSize,
ValidUserId,
StorageProvider.MinIO);
// Assert
file.ContentType.Should().Be("application/octet-stream");
}
#endregion
#region MarkAccessed Tests
[Fact]
public void MarkAccessed_UpdatesLastAccessedAt()
{
// Arrange
var file = CreateValidStorageFile();
var beforeAccess = DateTime.UtcNow;
// Act
file.MarkAccessed();
// Assert
file.LastAccessedAt.Should().NotBeNull();
file.LastAccessedAt.Should().BeOnOrAfter(beforeAccess);
file.LastAccessedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
}
[Fact]
public void MarkAccessed_CalledMultipleTimes_UpdatesToLatestTime()
{
// Arrange
var file = CreateValidStorageFile();
file.MarkAccessed();
var firstAccess = file.LastAccessedAt;
// Act
Thread.Sleep(10); // Small delay
file.MarkAccessed();
// Assert
file.LastAccessedAt.Should().BeAfter(firstAccess!.Value);
}
#endregion
#region UpdateAccessLevel Tests
[Fact]
public void UpdateAccessLevel_NonDeletedFile_UpdatesAccessLevel()
{
// Arrange
var file = CreateValidStorageFile();
file.AccessLevel.Should().Be(FileAccessLevel.Private);
// Act
file.UpdateAccessLevel(FileAccessLevel.Public);
// Assert
file.AccessLevel.Should().Be(FileAccessLevel.Public);
}
[Fact]
public void UpdateAccessLevel_ToShared_UpdatesAccessLevel()
{
// Arrange
var file = CreateValidStorageFile();
// Act
file.UpdateAccessLevel(FileAccessLevel.Shared);
// Assert
file.AccessLevel.Should().Be(FileAccessLevel.Shared);
}
[Fact]
public void UpdateAccessLevel_DeletedFile_ThrowsInvalidOperationException()
{
// Arrange
var file = CreateValidStorageFile();
file.Delete();
// Act
var act = () => file.UpdateAccessLevel(FileAccessLevel.Public);
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("*deleted*");
}
#endregion
#region Delete Tests
[Fact]
public void Delete_SetsIsDeletedAndRaisesEvent()
{
// Arrange
var file = CreateValidStorageFile();
file.ClearDomainEvents(); // Clear constructor event
// Act
file.Delete();
// Assert
file.IsDeleted.Should().BeTrue();
file.DeletedAt.Should().NotBeNull();
file.DeletedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
file.DomainEvents.Should().ContainSingle(e => e is FileDeletedDomainEvent);
var deletedEvent = file.DomainEvents.OfType<FileDeletedDomainEvent>().First();
deletedEvent.FileId.Should().Be(file.Id);
deletedEvent.UserId.Should().Be(ValidUserId);
deletedEvent.FileSizeBytes.Should().Be(ValidFileSize);
}
[Fact]
public void Delete_AlreadyDeleted_DoesNothing()
{
// Arrange
var file = CreateValidStorageFile();
file.Delete();
var firstDeletedAt = file.DeletedAt;
file.ClearDomainEvents();
// Act
Thread.Sleep(10);
file.Delete();
// Assert
file.DeletedAt.Should().Be(firstDeletedAt);
file.DomainEvents.Should().BeEmpty(); // No new event raised
}
#endregion
#region SetExpiration Tests
[Fact]
public void SetExpiration_FutureDate_SetsExpiresAt()
{
// Arrange
var file = CreateValidStorageFile();
var futureDate = DateTime.UtcNow.AddDays(7);
// Act
file.SetExpiration(futureDate);
// Assert
file.ExpiresAt.Should().Be(futureDate);
}
[Fact]
public void SetExpiration_PastDate_ThrowsArgumentException()
{
// Arrange
var file = CreateValidStorageFile();
var pastDate = DateTime.UtcNow.AddHours(-1);
// Act
var act = () => file.SetExpiration(pastDate);
// Assert
act.Should().Throw<ArgumentException>()
.WithParameterName("expiresAt")
.WithMessage("*future*");
}
[Fact]
public void SetExpiration_CurrentTime_ThrowsArgumentException()
{
// Arrange
var file = CreateValidStorageFile();
// Act
var act = () => file.SetExpiration(DateTime.UtcNow);
// Assert
act.Should().Throw<ArgumentException>();
}
#endregion
#region UpdateFromVersion Tests
[Fact]
public void UpdateFromVersion_NonDeletedFile_UpdatesProperties()
{
// Arrange
var file = CreateValidStorageFile();
var newObjectKey = "private/user-123/20260115/new_version.pdf";
var newSize = 2 * 1024 * 1024L; // 2MB
var newContentType = "application/vnd.pdf";
// Act
file.UpdateFromVersion(newObjectKey, newSize, newContentType);
// Assert
file.ObjectKey.Should().Be(newObjectKey);
file.FileSizeBytes.Should().Be(newSize);
file.ContentType.Should().Be(newContentType);
}
[Fact]
public void UpdateFromVersion_DeletedFile_ThrowsInvalidOperationException()
{
// Arrange
var file = CreateValidStorageFile();
file.Delete();
// Act
var act = () => file.UpdateFromVersion("new-key", 1024, "text/plain");
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("*deleted*");
}
#endregion
#region Helper Methods
private static StorageFile CreateValidStorageFile()
{
return new StorageFile(
ValidFileName,
ValidBucketName,
ValidObjectKey,
ValidContentType,
ValidFileSize,
ValidUserId,
StorageProvider.MinIO,
FileAccessLevel.Private);
}
#endregion
}