Files
ROLAC/API/ROLAC.API.Tests/Services/LocalDiskFileStorageTests.cs
T
Chris Chen e7bf07c2ad feat(storage): add IFileStorage + local-disk implementation
Adds IFileStorage abstraction and LocalDiskFileStorage for receipt file storage with path-traversal protection, and registers it in DI. Includes 3 TDD-verified xUnit tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 18:18:28 -07:00

53 lines
1.6 KiB
C#

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<string, string?> { ["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<ArgumentException>(() => fs.SaveAsync(input, "../escape.txt"));
}
public void Dispose()
{
if (Directory.Exists(_root)) Directory.Delete(_root, recursive: true);
}
}