using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Moq; using PowderCoating.Application.Services; namespace PowderCoating.UnitTests; public class FileServiceTests { [Fact] public async Task DeleteFileAsync_ReturnsError_WhenPathEscapesUploadsRoot() { using var harness = new FileServiceHarness(); var result = await harness.Service.DeleteFileAsync("uploads/../secrets.txt"); Assert.False(result.Success); Assert.Equal("Invalid file path.", result.ErrorMessage); } [Fact] public async Task GetFileAsync_ReturnsError_WhenPathEscapesUploadsRoot() { using var harness = new FileServiceHarness(); var result = await harness.Service.GetFileAsync("uploads/../../appsettings.json"); Assert.False(result.Success); Assert.Equal("Invalid file path.", result.ErrorMessage); Assert.Empty(result.FileContent); } [Fact] public async Task SaveFileAsync_ReturnsError_WhenSubfolderEscapesUploadsRoot() { using var harness = new FileServiceHarness(); var result = await harness.Service.SaveFileAsync( CreateFormFile("manual.pdf"), "../outside", new[] { ".pdf" }, 1024 * 1024); Assert.False(result.Success); Assert.Equal("Invalid upload subfolder.", result.ErrorMessage); } [Fact] public async Task GetFileAsync_ReturnsFile_WhenPathIsUnderUploadsRoot() { using var harness = new FileServiceHarness(); var uploadsPath = Path.Combine(harness.WebRootPath, "uploads", "equipment-manuals"); Directory.CreateDirectory(uploadsPath); var fullPath = Path.Combine(uploadsPath, "manual.pdf"); await File.WriteAllBytesAsync(fullPath, [1, 2, 3]); var result = await harness.Service.GetFileAsync("uploads/equipment-manuals/manual.pdf"); Assert.True(result.Success); Assert.Equal("application/pdf", result.ContentType); Assert.Equal(new byte[] { 1, 2, 3 }, result.FileContent); } private static IFormFile CreateFormFile(string fileName) { var bytes = new byte[] { 1, 2, 3, 4 }; var stream = new MemoryStream(bytes); return new FormFile(stream, 0, bytes.Length, "file", fileName); } private sealed class FileServiceHarness : IDisposable { public FileServiceHarness() { WebRootPath = Path.Combine(Path.GetTempPath(), "powdercoating-fileservice-tests", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(WebRootPath); var environment = Mock.Of(x => x.WebRootPath == WebRootPath); Service = new FileService(environment, Mock.Of>()); } public string WebRootPath { get; } public FileService Service { get; } public void Dispose() { if (Directory.Exists(WebRootPath)) { Directory.Delete(WebRootPath, recursive: true); } } } }