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; /// /// EN: Functional tests for Files API endpoints. /// VI: Functional tests cho Files API endpoints. /// public class FilesApiTests : IClassFixture { 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(); 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(); 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 SeedTestFile(string fileName, bool isDeleted = false) { using var scope = _factory.Services.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); 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 /// /// EN: File metadata response DTO for tests. /// VI: DTO response metadata file cho tests. /// public record FileMetadataResponse( Guid Id, string FileName, string ContentType, long FileSizeBytes, string UserId, string AccessLevel, DateTime UploadedAt); /// /// EN: User files list response DTO for tests. /// VI: DTO response danh sách files của user cho tests. /// public record UserFilesResponse( IEnumerable Files, int TotalCount, int Page, int PageSize); #endregion