Files
Ho Ngoc Hai 76d75c753b Migrate
2026-05-23 18:37:02 +07:00

236 lines
6.4 KiB
C#

using System.Net;
using System.Net.Http.Json;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using StorageService.Domain.AggregatesModel.FileAggregate;
using StorageService.Infrastructure.Persistence;
namespace StorageService.FunctionalTests.ApiTests;
/// <summary>
/// EN: Functional tests for Files API endpoints.
/// VI: Functional tests cho Files API endpoints.
/// </summary>
public class FilesApiTests : IClassFixture<CustomWebApplicationFactory>
{
private readonly HttpClient _client;
private readonly CustomWebApplicationFactory _factory;
public FilesApiTests(CustomWebApplicationFactory factory)
{
_factory = factory;
_client = factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
// EN: Add mock authentication header / VI: Thêm mock authentication header
_client.DefaultRequestHeaders.Add("X-User-Id", "test-user-123");
}
#region GetFile Tests
[Fact]
public async Task GetFile_ExistingFile_Returns200WithFileMetadata()
{
// Arrange
var fileId = await SeedTestFile("test-file.pdf");
// Act
var response = await _client.GetAsync($"/api/v1/storage/files/{fileId}");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var content = await response.Content.ReadFromJsonAsync<FileMetadataResponse>();
content.Should().NotBeNull();
content!.FileName.Should().Be("test-file.pdf");
}
[Fact]
public async Task GetFile_NotFound_Returns404()
{
// Arrange
var nonExistentId = Guid.NewGuid();
// Act
var response = await _client.GetAsync($"/api/v1/storage/files/{nonExistentId}");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
[Fact]
public async Task GetFile_InvalidId_Returns400()
{
// Act
var response = await _client.GetAsync("/api/v1/storage/files/invalid-guid");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}
#endregion
#region GetUserFiles Tests
[Fact]
public async Task GetUserFiles_WithFiles_ReturnsFileList()
{
// Arrange
await SeedTestFile("file1.pdf");
await SeedTestFile("file2.pdf");
// Act
var response = await _client.GetAsync("/api/v1/storage/files");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var content = await response.Content.ReadFromJsonAsync<UserFilesResponse>();
content.Should().NotBeNull();
content!.Files.Should().NotBeEmpty();
}
[Fact]
public async Task GetUserFiles_WithPagination_RespectsPageSize()
{
// Arrange
for (int i = 0; i < 5; i++)
{
await SeedTestFile($"file{i}.pdf");
}
// Act
var response = await _client.GetAsync("/api/v1/storage/files?page=1&pageSize=2");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
}
#endregion
#region DeleteFile Tests
[Fact]
public async Task DeleteFile_ExistingFile_Returns204()
{
// Arrange
var fileId = await SeedTestFile("to-delete.pdf");
// Act
var response = await _client.DeleteAsync($"/api/v1/storage/files/{fileId}");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.NoContent);
}
[Fact]
public async Task DeleteFile_NotFound_ReturnsError()
{
// Arrange
var nonExistentId = Guid.NewGuid();
// Act
var response = await _client.DeleteAsync($"/api/v1/storage/files/{nonExistentId}");
// Assert
// EN: Could be 404 or 400 depending on implementation
// VI: Có thể là 404 hoặc 400 tùy vào implementation
response.IsSuccessStatusCode.Should().BeFalse();
}
[Fact]
public async Task DeleteFile_AlreadyDeleted_ReturnsError()
{
// Arrange
var fileId = await SeedTestFile("already-deleted.pdf", isDeleted: true);
// Act
var response = await _client.DeleteAsync($"/api/v1/storage/files/{fileId}");
// Assert
response.IsSuccessStatusCode.Should().BeFalse();
}
#endregion
#region GetDownloadUrl Tests
[Fact]
public async Task GetDownloadUrl_ExistingFile_ReturnsUrl()
{
// Arrange
var fileId = await SeedTestFile("downloadable.pdf");
// Act
var response = await _client.GetAsync($"/api/v1/storage/files/{fileId}/download-url");
// Assert
// EN: May succeed or fail depending on storage provider mock
// VI: Có thể thành công hoặc thất bại tùy thuộc vào mock storage provider
// In real functional tests, we'd have proper MinIO mocking
}
#endregion
#region Helper Methods
private async Task<Guid> SeedTestFile(string fileName, bool isDeleted = false)
{
using var scope = _factory.Services.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<StorageServiceContext>();
var file = new StorageFile(
fileName: fileName,
bucketName: "test-bucket",
objectKey: $"private/test-user-123/{DateTime.UtcNow:yyyyMMdd}/{Guid.NewGuid():N}_{fileName}",
contentType: "application/pdf",
fileSizeBytes: 1024,
userId: "test-user-123",
provider: StorageProvider.MinIO,
accessLevel: FileAccessLevel.Private);
if (isDeleted)
{
file.Delete();
}
context.Files.Add(file);
await context.SaveChangesAsync();
return file.Id;
}
#endregion
}
#region Response DTOs
/// <summary>
/// EN: File metadata response DTO for tests.
/// VI: DTO response metadata file cho tests.
/// </summary>
public record FileMetadataResponse(
Guid Id,
string FileName,
string ContentType,
long FileSizeBytes,
string UserId,
string AccessLevel,
DateTime UploadedAt);
/// <summary>
/// EN: User files list response DTO for tests.
/// VI: DTO response danh sách files của user cho tests.
/// </summary>
public record UserFilesResponse(
IEnumerable<FileMetadataResponse> Files,
int TotalCount,
int Page,
int PageSize);
#endregion