using System.Text; using Microsoft.Extensions.Configuration; using ROLAC.API.Services.Storage; using Xunit; namespace ROLAC.API.Tests.Services; public class LocalDiskFileStorageTests : IDisposable { private readonly string _root = Path.Combine(Path.GetTempPath(), "rolac-test-" + Guid.NewGuid()); private LocalDiskFileStorage Build() { var config = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { ["Storage:LocalRoot"] = _root }) .Build(); return new LocalDiskFileStorage(config); } [Fact] public async Task SaveThenOpen_RoundTrips() { var fs = Build(); using var input = new MemoryStream(Encoding.UTF8.GetBytes("hello")); var path = await fs.SaveAsync(input, "finance/receipts/2026/5/1-r.txt"); await using var read = await fs.OpenReadAsync(path); Assert.NotNull(read); using var sr = new StreamReader(read!); Assert.Equal("hello", await sr.ReadToEndAsync()); } [Fact] public async Task OpenRead_ReturnsNull_WhenMissing() { var fs = Build(); Assert.Null(await fs.OpenReadAsync("finance/receipts/none.txt")); } [Fact] public async Task Save_RejectsPathTraversal() { var fs = Build(); using var input = new MemoryStream(Encoding.UTF8.GetBytes("x")); await Assert.ThrowsAsync(() => fs.SaveAsync(input, "../escape.txt")); } public void Dispose() { if (Directory.Exists(_root)) Directory.Delete(_root, recursive: true); } }