mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 09:33:13 +00:00
AddMedia sized a file before validating its type, and ValidateSize's Unknown arm returned the video message, so a 150 MB .exe sent as application/octet-stream was rejected as FileTooLarge with "Videos must be 100 MB or smaller." EnsureAllowed now runs first, so an unsupported file always reports UnsupportedMediaType, and the Unknown arm uses a type-neutral message. The oversize controller tests now use real file headers so they pass the type check before reaching the size cap.
479 lines
20 KiB
C#
479 lines
20 KiB
C#
using Api.SeaHavenIndustries.Controllers;
|
|
using Data.SeaHavenIndustries;
|
|
using Data.SeaHavenIndustries.Enums;
|
|
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_HasContractRequestLimit()
|
|
{
|
|
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(110_000_000L, bytes.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void AddMedia_HasContractMultipartBodyLimit()
|
|
{
|
|
var method = typeof(WorkOrderMediaController).GetMethod(nameof(WorkOrderMediaController.AddMedia));
|
|
var attribute = Assert.IsType<RequestFormLimitsAttribute>(Assert.Single(
|
|
method!.GetCustomAttributes(typeof(RequestFormLimitsAttribute), inherit: true)));
|
|
|
|
Assert.Equal(110_000_000L, attribute.MultipartBodyLengthLimit);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AddMedia_VideoOverHundredMegabytes_ReturnsStableUnprocessableEntity()
|
|
{
|
|
var file = OversizeFile(100_000_001, "clip.mp4", "video/mp4", FtypHeader);
|
|
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("FileTooLarge", error.Code);
|
|
Assert.Equal("Videos must be 100 MB or smaller.", error.Message);
|
|
storage.VerifyNoOtherCalls();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AddMedia_PhotoOverTenMegabytes_ReturnsStableUnprocessableEntity()
|
|
{
|
|
var file = OversizeFile(10_000_001, "photo.jpg", "image/jpeg", JpegHeader);
|
|
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("FileTooLarge", error.Code);
|
|
Assert.Equal("Photos must be 10 MB or smaller.", error.Message);
|
|
storage.VerifyNoOtherCalls();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AddMedia_PhotoDeclaredAsForeignVideoType_IsSizedAsAPhoto()
|
|
{
|
|
// A .jpg with a foreign video type resolves to image/jpeg for validation, so it must
|
|
// also be sized as a photo, not given the 100 MB video allowance.
|
|
var file = OversizeFile(60_000_000, "photo.jpg", "video/3gpp", JpegHeader);
|
|
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("FileTooLarge", error.Code);
|
|
Assert.Equal("Photos must be 10 MB or smaller.", error.Message);
|
|
storage.VerifyNoOtherCalls();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AddMedia_DocumentOverFiftyMegabytes_ReturnsStableUnprocessableEntity()
|
|
{
|
|
var file = OversizeFile(50_000_001, "report.pdf", "application/pdf", PdfHeader);
|
|
var storage = new Mock<IFileStoragePort>(MockBehavior.Strict);
|
|
var controller = CreateController(storage: storage);
|
|
|
|
var result = await controller.AddMedia(1, WorkOrderMediaCategory.Extra, file, CancellationToken.None);
|
|
|
|
var response = Assert.IsType<UnprocessableEntityObjectResult>(result);
|
|
var error = Assert.IsType<WorkOrderBoardValidationErrorDto>(response.Value);
|
|
Assert.Equal("FileTooLarge", error.Code);
|
|
Assert.Equal("Documents must be 50 MB or smaller.", error.Message);
|
|
storage.VerifyNoOtherCalls();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AddMedia_OversizeUnsupportedType_ReportsUnsupportedTypeNotOversizeVideo()
|
|
{
|
|
var file = OversizeFile(150_000_000, "payload.exe", "application/octet-stream", [0x4D, 0x5A]);
|
|
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 void ValidateSize_OversizeUnknownKind_UsesTypeNeutralMessage()
|
|
{
|
|
Assert.Equal(
|
|
"Files must be 100 MB or smaller.",
|
|
WorkOrderMediaContract.ValidateSize(150_000_000, WorkOrderMediaContract.UploadKind.Unknown));
|
|
}
|
|
|
|
private static readonly byte[] FtypHeader = [0, 0, 0, 0x18, (byte)'f', (byte)'t', (byte)'y', (byte)'p', (byte)'i', (byte)'s', (byte)'o', (byte)'m'];
|
|
private static readonly byte[] JpegHeader = [0xFF, 0xD8, 0xFF, 0xE0];
|
|
private static readonly byte[] PdfHeader = [0x25, 0x50, 0x44, 0x46, 0x2D];
|
|
|
|
/// <summary>
|
|
/// A file that reports <paramref name="length"/> bytes but only backs the 512-byte header the
|
|
/// type rules read, so oversize cases run through real signature validation cheaply.
|
|
/// </summary>
|
|
private static FormFile OversizeFile(long length, string fileName, string contentType, byte[] header)
|
|
{
|
|
var bytes = new byte[512];
|
|
header.CopyTo(bytes, 0);
|
|
return new FormFile(new MemoryStream(bytes), 0, length, "file", fileName)
|
|
{
|
|
Headers = new HeaderDictionary(),
|
|
ContentType = contentType
|
|
};
|
|
}
|
|
|
|
private static FormFile VideoFormFile(int size, string fileName, string contentType)
|
|
{
|
|
var bytes = new byte[size];
|
|
// ISO BMFF ftyp box so the media type rules accept the payload as MP4/MOV.
|
|
bytes[4] = (byte)'f';
|
|
bytes[5] = (byte)'t';
|
|
bytes[6] = (byte)'y';
|
|
bytes[7] = (byte)'p';
|
|
bytes[8] = (byte)'i';
|
|
bytes[9] = (byte)'s';
|
|
bytes[10] = (byte)'o';
|
|
bytes[11] = (byte)'m';
|
|
return new FormFile(new MemoryStream(bytes), 0, bytes.Length, "file", fileName)
|
|
{
|
|
Headers = new HeaderDictionary(),
|
|
ContentType = contentType
|
|
};
|
|
}
|
|
|
|
private static (Mock<IWorkOrderMediaService> Service, Mock<IFileStoragePort> Storage) SetupSuccessfulAddMedia()
|
|
{
|
|
var service = new Mock<IWorkOrderMediaService>(MockBehavior.Strict);
|
|
service
|
|
.Setup(candidate => candidate.EnsureCanMutateMediaAsync(
|
|
1, It.IsAny<ClaimsPrincipal>(), It.IsAny<string?>(), It.IsAny<CancellationToken>(), null))
|
|
.Returns(Task.CompletedTask);
|
|
service
|
|
.Setup(candidate => candidate.AddMediaAsync(
|
|
1, null, "https://storage.test/stored", It.IsAny<ClaimsPrincipal>(), It.IsAny<string?>(),
|
|
It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(new WorkOrderMediaFileDto { Id = 5, Url = "https://storage.test/stored" });
|
|
var storage = new Mock<IFileStoragePort>(MockBehavior.Strict);
|
|
storage
|
|
.Setup(candidate => candidate.SaveFileAsync(It.IsAny<IFormFile>()))
|
|
.ReturnsAsync("https://storage.test/stored");
|
|
return (service, storage);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AddMedia_SixtyMegabyteMp4_IsAccepted()
|
|
{
|
|
var (service, storage) = SetupSuccessfulAddMedia();
|
|
var controller = CreateController(service, storage);
|
|
var file = VideoFormFile(60_000_000, "site-clip.mp4", "video/mp4");
|
|
|
|
var result = await controller.AddMedia(1, null, file, CancellationToken.None);
|
|
|
|
var ok = Assert.IsType<OkObjectResult>(result);
|
|
Assert.Equal(5, Assert.IsType<WorkOrderMediaFileDto>(ok.Value).Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AddMedia_VideoLongerThanNinetySeconds_ReturnsStableUnprocessableEntity()
|
|
{
|
|
var storage = new Mock<IFileStoragePort>(MockBehavior.Strict);
|
|
var controller = CreateController(storage: storage);
|
|
var file = PhoneVideo(durationSeconds: 95, "IMG_0042.MOV", "video/quicktime");
|
|
|
|
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("VideoTooLong", error.Code);
|
|
Assert.Equal("Videos must be 90 seconds or shorter.", error.Message);
|
|
storage.VerifyNoOtherCalls();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AddMedia_VideoWithinNinetySeconds_IsAccepted()
|
|
{
|
|
var (service, storage) = SetupSuccessfulAddMedia();
|
|
var controller = CreateController(service, storage);
|
|
var file = PhoneVideo(durationSeconds: 60, "IMG_0043.MOV", "");
|
|
|
|
var result = await controller.AddMedia(1, null, file, CancellationToken.None);
|
|
|
|
Assert.IsType<OkObjectResult>(result);
|
|
}
|
|
|
|
/// <summary>ftyp + mdat + moov/mvhd (moov last, as phones write it).</summary>
|
|
private static FormFile PhoneVideo(uint durationSeconds, string fileName, string contentType)
|
|
{
|
|
static byte[] Box(string type, byte[] body)
|
|
{
|
|
var box = new byte[8 + body.Length];
|
|
System.Buffers.Binary.BinaryPrimitives.WriteUInt32BigEndian(box, (uint)box.Length);
|
|
System.Text.Encoding.ASCII.GetBytes(type).CopyTo(box, 4);
|
|
body.CopyTo(box, 8);
|
|
return box;
|
|
}
|
|
|
|
var mvhd = new byte[20];
|
|
System.Buffers.Binary.BinaryPrimitives.WriteUInt32BigEndian(mvhd.AsSpan(12), 1000);
|
|
System.Buffers.Binary.BinaryPrimitives.WriteUInt32BigEndian(mvhd.AsSpan(16), durationSeconds * 1000);
|
|
var bytes = Box("ftyp", System.Text.Encoding.ASCII.GetBytes("qt \0\0\0\0"))
|
|
.Concat(Box("mdat", new byte[4096]))
|
|
.Concat(Box("moov", Box("mvhd", mvhd)))
|
|
.ToArray();
|
|
return new FormFile(new MemoryStream(bytes), 0, bytes.Length, "file", fileName)
|
|
{
|
|
Headers = new HeaderDictionary(),
|
|
ContentType = contentType
|
|
};
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AddMedia_SixtyMegabyteQuicktimeMov_IsAccepted()
|
|
{
|
|
var (service, storage) = SetupSuccessfulAddMedia();
|
|
var controller = CreateController(service, storage);
|
|
var file = VideoFormFile(60_000_000, "site-clip.mov", "video/quicktime");
|
|
|
|
var result = await controller.AddMedia(1, null, file, CancellationToken.None);
|
|
|
|
Assert.IsType<OkObjectResult>(result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AddMedia_SixtyMegabyteMovWithEmptyContentType_IsAccepted()
|
|
{
|
|
var (service, storage) = SetupSuccessfulAddMedia();
|
|
var controller = CreateController(service, storage);
|
|
var file = VideoFormFile(60_000_000, "site-clip.MOV", "");
|
|
|
|
var result = await controller.AddMedia(1, null, file, CancellationToken.None);
|
|
|
|
Assert.IsType<OkObjectResult>(result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AddMedia_SixtyMegabyteMp4WithOctetStreamContentType_IsAccepted()
|
|
{
|
|
var (service, storage) = SetupSuccessfulAddMedia();
|
|
var controller = CreateController(service, storage);
|
|
var file = VideoFormFile(60_000_000, "site-clip.mp4", "application/octet-stream");
|
|
|
|
var result = await controller.AddMedia(1, null, file, CancellationToken.None);
|
|
|
|
Assert.IsType<OkObjectResult>(result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AddMedia_HeicPhoto_IsAccepted()
|
|
{
|
|
var (service, storage) = SetupSuccessfulAddMedia();
|
|
var controller = CreateController(service, storage);
|
|
var bytes = new byte[512];
|
|
bytes[4] = (byte)'f';
|
|
bytes[5] = (byte)'t';
|
|
bytes[6] = (byte)'y';
|
|
bytes[7] = (byte)'p';
|
|
bytes[8] = (byte)'h';
|
|
bytes[9] = (byte)'e';
|
|
bytes[10] = (byte)'i';
|
|
bytes[11] = (byte)'c';
|
|
var file = new FormFile(new MemoryStream(bytes), 0, bytes.Length, "file", "capture.heic")
|
|
{
|
|
Headers = new HeaderDictionary(),
|
|
ContentType = "image/heic"
|
|
};
|
|
|
|
var result = await controller.AddMedia(1, null, file, CancellationToken.None);
|
|
|
|
Assert.IsType<OkObjectResult>(result);
|
|
}
|
|
|
|
[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,
|
|
new Mock<IUpliftDataService>(MockBehavior.Strict).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();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetMediaContent_ServesHeicAsImageHeic()
|
|
{
|
|
const string url = "https://example.test/Assets/Images/photo.heic";
|
|
var mediaData = new Mock<IWorkOrderMediaDataService>(MockBehavior.Strict);
|
|
mediaData.Setup(candidate => candidate.GetWorkOrderForMediaAuthAsync(1, null, It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(new WorkOrder { Id = 1 });
|
|
mediaData.Setup(candidate => candidate.GetAttachmentForReadAsync(10, 1, It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(new WorkOrderAttachments { Id = 10, WorkorderId = 1, Attachments = url });
|
|
var storage = new Mock<IFileStoragePort>(MockBehavior.Strict);
|
|
storage.Setup(candidate => candidate.OpenRead(url)).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,
|
|
new Mock<IUpliftDataService>(MockBehavior.Strict).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");
|
|
result.Content.Dispose();
|
|
|
|
Assert.Equal("image/heic", result.ContentType);
|
|
Assert.Equal("photo.heic", result.FileName);
|
|
}
|
|
}
|