mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-07 16:19:07 +00:00
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.
116 lines
4 KiB
C#
116 lines
4 KiB
C#
using Data.SeaHavenIndustries;
|
|
using Data.SeaHavenIndustries.Enums;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using SeaHaven.DataServices.Implementation;
|
|
using SeaHaven.Services.DTOs;
|
|
using SeaHaven.Services.Exceptions;
|
|
using SeaHaven.Services.Helpers;
|
|
using SeaHaven.Services.Implementation;
|
|
using Xunit;
|
|
|
|
namespace SeaHavenIndustries.Tests;
|
|
|
|
public class WorkOrderUploadFileRulesTests
|
|
{
|
|
[Theory]
|
|
[InlineData("report.pdf", ".pdf")]
|
|
[InlineData(@"C:\temp\photo.PNG", ".png")]
|
|
[InlineData("../evil.jpg", ".jpg")]
|
|
public void TryGetAllowedExtension_AcceptsSafeTypes(string name, string expectedExt)
|
|
{
|
|
var ok = WorkOrderUploadFileRules.TryGetAllowedExtension(name, out var ext, out var error);
|
|
Assert.True(ok, error);
|
|
Assert.Equal(expectedExt, ext);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("payload.exe")]
|
|
[InlineData("noext")]
|
|
[InlineData("")]
|
|
[InlineData("..")]
|
|
public void TryGetAllowedExtension_RejectsUnsafeTypes(string name)
|
|
{
|
|
var ok = WorkOrderUploadFileRules.TryGetAllowedExtension(name, out _, out var error);
|
|
Assert.False(ok);
|
|
Assert.False(string.IsNullOrWhiteSpace(error));
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildServerFileName_IsGuidPlusExtension_NoClientName()
|
|
{
|
|
var name = WorkOrderUploadFileRules.BuildServerFileName(".pdf");
|
|
Assert.StartsWith(name[..32], name);
|
|
Assert.EndsWith(".pdf", name);
|
|
Assert.DoesNotContain("..", name);
|
|
Assert.DoesNotContain("/", name);
|
|
Assert.DoesNotContain("\\", name);
|
|
}
|
|
}
|
|
|
|
public class WorkOrderUploadValidationOrderTests
|
|
{
|
|
private static (ApplicationDbContext Context, WorkOrderCompletionService Completion, WorkOrderMediaService Media) CreateSut()
|
|
{
|
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options;
|
|
var context = new ApplicationDbContext(options);
|
|
var templateData = new CompletionDocTemplateDataService(context);
|
|
var detailData = new WorkOrderDetailDataService(context);
|
|
var fieldLocks = new WorkOrderFieldLockService(context);
|
|
var audit = new WorkOrderAuditService(context, fieldLocks);
|
|
var completion = new WorkOrderCompletionService(context, templateData, detailData, audit);
|
|
var media = new WorkOrderMediaService(context, detailData);
|
|
return (context, completion, media);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task EnsureCanUploadCompletionDoc_NotFound_ThrowsBeforeAnyPersist()
|
|
{
|
|
var (_, completion, _) = CreateSut();
|
|
|
|
var ex = await Assert.ThrowsAsync<WorkOrderBoardValidationException>(() =>
|
|
completion.EnsureCanUploadCompletionDocAsync(
|
|
999,
|
|
new WorkOrderCompletionDocUploadDto
|
|
{
|
|
WorkOrderVersion = Convert.ToBase64String(new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 })
|
|
}));
|
|
|
|
Assert.Equal("NotFound", ex.Code);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task EnsureCanUploadCompletionDoc_ReadOnly_Throws()
|
|
{
|
|
var (context, completion, _) = CreateSut();
|
|
context.workOrders.Add(new WorkOrder
|
|
{
|
|
Id = 1,
|
|
LifecycleStatus = LifecycleStatus.Completed,
|
|
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
|
|
});
|
|
await context.SaveChangesAsync();
|
|
|
|
var ex = await Assert.ThrowsAsync<WorkOrderBoardValidationException>(() =>
|
|
completion.EnsureCanUploadCompletionDocAsync(
|
|
1,
|
|
new WorkOrderCompletionDocUploadDto
|
|
{
|
|
WorkOrderVersion = Convert.ToBase64String(new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 })
|
|
}));
|
|
|
|
Assert.Equal("ReadOnly", ex.Code);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task EnsureCanAddMedia_NotFound_Throws()
|
|
{
|
|
var (_, _, media) = CreateSut();
|
|
|
|
var ex = await Assert.ThrowsAsync<WorkOrderBoardValidationException>(() =>
|
|
media.EnsureCanAddMediaAsync(999, WorkOrderMediaCategory.Before));
|
|
|
|
Assert.Equal("NotFound", ex.Code);
|
|
}
|
|
}
|