shoc-backend/Api.SeaHavenIndustries.Tests/WorkOrderMediaControllerTests.cs

177 lines
7.3 KiB
C#

using Api.SeaHavenIndustries.Controllers;
using Data.SeaHavenIndustries;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Moq;
using SeaHaven.DataServices.Interfaces;
using SeaHaven.Services.DTOs;
using SeaHaven.Services.Exceptions;
using SeaHaven.Services.Helpers;
using SeaHaven.Services.Implementation;
using SeaHaven.Services.Interfaces;
using System.Security.Claims;
using Xunit;
namespace Api.SeaHavenIndustries.Tests;
public class WorkOrderMediaControllerTests
{
private static WorkOrderMediaController CreateController(
Mock<IWorkOrderMediaService>? service = null,
Mock<IFileStoragePort>? storage = null)
{
var controller = new WorkOrderMediaController(
(service ?? new Mock<IWorkOrderMediaService>()).Object,
(storage ?? new Mock<IFileStoragePort>()).Object);
controller.ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext()
};
return controller;
}
[Fact]
public void AddMedia_HasFiftyMegabyteRequestLimit()
{
var method = typeof(WorkOrderMediaController).GetMethod(nameof(WorkOrderMediaController.AddMedia));
var attribute = Assert.Single(
method!.CustomAttributes,
candidate => candidate.AttributeType == typeof(RequestSizeLimitAttribute));
var bytes = Assert.Single(attribute.ConstructorArguments);
Assert.Equal(50_000_000L, bytes.Value);
}
[Fact]
public async Task AddMedia_FileOverLimit_ReturnsStableUnprocessableEntity()
{
var file = new Mock<IFormFile>();
file.SetupGet(candidate => candidate.Length).Returns(50_000_001);
var storage = new Mock<IFileStoragePort>(MockBehavior.Strict);
var controller = CreateController(storage: storage);
var result = await controller.AddMedia(1, null, file.Object, CancellationToken.None);
var response = Assert.IsType<UnprocessableEntityObjectResult>(result);
var error = Assert.IsType<WorkOrderBoardValidationErrorDto>(response.Value);
Assert.Equal("FileTooLarge", error.Code);
Assert.Equal("The uploaded file must not exceed 50 MB.", error.Message);
storage.VerifyNoOtherCalls();
}
[Fact]
public async Task AddMedia_UnsupportedType_ReturnsUnprocessableEntity()
{
var bytes = new byte[] { 1, 2, 3 };
var file = new FormFile(new MemoryStream(bytes), 0, bytes.Length, "file", "payload.exe")
{
Headers = new HeaderDictionary(),
ContentType = "application/octet-stream"
};
var storage = new Mock<IFileStoragePort>(MockBehavior.Strict);
var controller = CreateController(storage: storage);
var result = await controller.AddMedia(1, null, file, CancellationToken.None);
var response = Assert.IsType<UnprocessableEntityObjectResult>(result);
var error = Assert.IsType<WorkOrderBoardValidationErrorDto>(response.Value);
Assert.Equal("UnsupportedMediaType", error.Code);
storage.VerifyNoOtherCalls();
}
[Fact]
public async Task GetMediaContent_DeletedMedia_ReturnsNotFound()
{
var service = new Mock<IWorkOrderMediaService>(MockBehavior.Strict);
service.Setup(candidate => candidate.GetMediaContentAsync(
1, 10, It.IsAny<ClaimsPrincipal>(), It.IsAny<string?>(), It.IsAny<CancellationToken>()))
.ThrowsAsync(new WorkOrderBoardValidationException("NotFound", "Media not found."));
var storage = new Mock<IFileStoragePort>(MockBehavior.Strict);
var controller = CreateController(service, storage);
var result = await controller.GetMediaContent(1, 10, CancellationToken.None);
var response = Assert.IsType<NotFoundObjectResult>(result);
var error = Assert.IsType<WorkOrderBoardValidationErrorDto>(response.Value);
Assert.Equal("NotFound", error.Code);
storage.Verify(candidate => candidate.TryDelete(It.IsAny<string>()), Times.Never);
storage.VerifyNoOtherCalls();
}
[Fact]
public async Task DeleteMedia_Success_DoesNotCallTryDelete()
{
var service = new Mock<IWorkOrderMediaService>(MockBehavior.Strict);
service.Setup(candidate => candidate.DeleteMediaAsync(
1, 10, null, It.IsAny<ClaimsPrincipal>(), It.IsAny<string?>(), It.IsAny<CancellationToken>()))
.Returns(Task.CompletedTask);
var storage = new Mock<IFileStoragePort>(MockBehavior.Strict);
var controller = CreateController(service, storage);
var result = await controller.DeleteMedia(1, 10);
Assert.IsType<NoContentResult>(result);
storage.Verify(candidate => candidate.TryDelete(It.IsAny<string>()), Times.Never);
storage.VerifyNoOtherCalls();
}
}
public class WorkOrderCompletionControllerUploadLimitTests
{
[Fact]
public void UploadCompletionDoc_HasFiftyMegabyteRequestLimit()
{
var method = typeof(WorkOrderCompletionController).GetMethod(
nameof(WorkOrderCompletionController.UploadCompletionDoc));
var attribute = Assert.Single(
method!.CustomAttributes,
candidate => candidate.AttributeType == typeof(RequestSizeLimitAttribute));
var bytes = Assert.Single(attribute.ConstructorArguments);
Assert.Equal(50_000_000L, bytes.Value);
}
}
public class WorkOrderMediaServiceCancellationTests
{
[Fact]
public async Task GetMediaContent_ForwardsCancellationTokenToAllDataReads()
{
using var source = new CancellationTokenSource();
var token = source.Token;
var mediaData = new Mock<IWorkOrderMediaDataService>(MockBehavior.Strict);
mediaData.Setup(candidate => candidate.GetWorkOrderForMediaAuthAsync(1, null, token))
.ReturnsAsync(new WorkOrder { Id = 1 });
mediaData.Setup(candidate => candidate.GetAttachmentForReadAsync(10, 1, token))
.ReturnsAsync(new WorkOrderAttachments
{
Id = 10,
WorkorderId = 1,
Attachments = "https://example.test/Assets/Documents/report.pdf"
});
var storage = new Mock<IFileStoragePort>(MockBehavior.Strict);
storage.Setup(candidate => candidate.OpenRead(
"https://example.test/Assets/Documents/report.pdf"))
.Returns(new MemoryStream([1, 2, 3]));
var service = new WorkOrderMediaService(
mediaData.Object,
new Mock<IWorkOrderDetailDataService>(MockBehavior.Strict).Object,
new Mock<IWorkOrderAuditService>(MockBehavior.Strict).Object,
storage.Object);
var user = new ClaimsPrincipal(new ClaimsIdentity(
new[]
{
new Claim(ClaimTypes.NameIdentifier, "actor-1"),
new Claim(ClaimTypes.Role, "Admin"),
new Claim(SeaHavenClaimTypes.OrgScope, SeaHavenClaimTypes.OrgScopeAll)
},
"Test"));
var result = await service.GetMediaContentAsync(1, 10, user, "actor-1", token);
result.Content.Dispose();
mediaData.Verify(candidate => candidate.GetWorkOrderForMediaAuthAsync(1, null, token), Times.Once);
mediaData.Verify(candidate => candidate.GetAttachmentForReadAsync(10, 1, token), Times.Once);
mediaData.VerifyNoOtherCalls();
}
}