shoc-backend/Api.SeaHavenIndustries.Tests/WorkOrderPrivateFileStorageTests.cs
Arthur Bassi 719a5b05fd fix(work-orders): validate uploads before persist and store privately
Board completion/media uploads now check domain state first, use server-controlled filenames with an allowlist, store under App_Data, and serve via authenticated download endpoints. Also harden WeekRolled log arguments against CodeQL log-injection findings.
2026-07-21 09:25:33 -03:00

88 lines
2.9 KiB
C#

using Api.SeaHavenIndustries.Helper;
using FluentAssertions;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Moq;
using SeaHaven.Services.Helpers;
using Xunit;
namespace Api.SeaHavenIndustries.Tests;
public class WorkOrderPrivateFileStorageTests
{
private static (WorkOrderPrivateFileStorage Storage, string Root) CreateStorage()
{
var root = Path.Combine(Path.GetTempPath(), "wo-files-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(root);
var env = new Mock<IWebHostEnvironment>();
env.Setup(e => e.ContentRootPath).Returns(root);
return (new WorkOrderPrivateFileStorage(env.Object), root);
}
private static IFormFile FakeFile(string fileName, byte[]? bytes = null)
{
bytes ??= new byte[] { 1, 2, 3, 4 };
var stream = new MemoryStream(bytes);
return new FormFile(stream, 0, bytes.Length, "file", fileName)
{
Headers = new HeaderDictionary(),
ContentType = "application/octet-stream"
};
}
[Fact]
public async Task SaveAsync_UsesServerControlledName_OutsideWwwroot()
{
var (storage, root) = CreateStorage();
try
{
var key = await storage.SaveAsync(42, FakeFile("../../evil.pdf"));
key.Should().StartWith("workorder-files/42/");
key.Should().EndWith(".pdf");
key.Should().NotContain("..");
key.Should().NotContain("evil");
storage.TryResolvePhysicalPath(key, out var physical).Should().BeTrue();
physical.Should().StartWith(Path.Combine(root, "App_Data", "workorder-files"));
File.Exists(physical).Should().BeTrue();
}
finally
{
if (Directory.Exists(root))
Directory.Delete(root, recursive: true);
}
}
[Fact]
public async Task SaveAsync_RejectsDisallowedExtension()
{
var (storage, root) = CreateStorage();
try
{
var act = async () => await storage.SaveAsync(1, FakeFile("payload.exe"));
await act.Should().ThrowAsync<InvalidOperationException>();
Directory.Exists(Path.Combine(root, "App_Data", "workorder-files", "1")).Should().BeFalse();
}
finally
{
if (Directory.Exists(root))
Directory.Delete(root, recursive: true);
}
}
[Fact]
public void TryResolvePhysicalPath_RejectsPathEscape()
{
var (storage, root) = CreateStorage();
try
{
storage.TryResolvePhysicalPath("workorder-files/../secret.txt", out _).Should().BeFalse();
storage.TryResolvePhysicalPath("https://host/Assets/Documents/x.pdf", out _).Should().BeFalse();
}
finally
{
if (Directory.Exists(root))
Directory.Delete(root, recursive: true);
}
}
}