test: Add tests for CheckPdfAttachments feature
- Unit tests for CheckPdfAttachmentsQueryHandler (3 tests) - Integration tests for PdfAttachmentController (14 tests covering both multipart and JSON endpoints) - Tests verify: attachment detection, metadata extraction, empty PDF handling, validation errors - All tests using Stream API (mocks with It.IsAny<Stream>()) - Total: 17 new tests, all passing
This commit is contained in:
@@ -0,0 +1,251 @@
|
|||||||
|
using DocumentOperator.Application.CheckPdfAttachments.Queries;
|
||||||
|
using DocumentOperator.Application.Common.DTOs;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Testing;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace DocumentOperator.Tests.Integration.API;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Integration tests for PdfAttachmentController.
|
||||||
|
/// Tests /api/pdf/attachments/check endpoint with both multipart and Base64 input.
|
||||||
|
/// </summary>
|
||||||
|
public class PdfAttachmentControllerTests : IClassFixture<WebApplicationFactory<Program>>
|
||||||
|
{
|
||||||
|
private readonly WebApplicationFactory<Program> _factory;
|
||||||
|
private readonly HttpClient _client;
|
||||||
|
|
||||||
|
public PdfAttachmentControllerTests(WebApplicationFactory<Program> factory)
|
||||||
|
{
|
||||||
|
_factory = factory;
|
||||||
|
_client = _factory.CreateClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Helper Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a test PDF from embedded resources.
|
||||||
|
/// </summary>
|
||||||
|
private static async Task<byte[]> LoadTestPdfAsync(string filename)
|
||||||
|
{
|
||||||
|
var assembly = typeof(PdfAttachmentControllerTests).Assembly;
|
||||||
|
var resourceName = $"DocumentOperator.Tests.TestData.Pdfs.{filename}";
|
||||||
|
|
||||||
|
using var stream = assembly.GetManifestResourceStream(resourceName);
|
||||||
|
if (stream == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Test resource '{resourceName}' not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
using var ms = new MemoryStream();
|
||||||
|
await stream.CopyToAsync(ms);
|
||||||
|
return ms.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Base64 JSON Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task POST_CheckAttachments_Base64_PdfWithoutAttachments_Returns200()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
byte[] pdfBytes = await LoadTestPdfAsync("pdfWithSwissQRCode.pdf");
|
||||||
|
string base64Pdf = Convert.ToBase64String(pdfBytes);
|
||||||
|
var request = new CheckPdfAttachmentsQuery { Base64Pdf = base64Pdf };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _client.PostAsJsonAsync("/api/pdf/attachments/check", request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||||
|
|
||||||
|
var result = await response.Content.ReadFromJsonAsync<AttachmentCheckResult>();
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.HasAttachments.Should().BeFalse();
|
||||||
|
result.AttachmentCount.Should().Be(0);
|
||||||
|
result.Attachments.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task POST_CheckAttachments_Base64_PdfWithMultipleAttachments_Returns200()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
byte[] pdfBytes = await LoadTestPdfAsync("pdfWithMoreThanOneAttachment.pdf");
|
||||||
|
string base64Pdf = Convert.ToBase64String(pdfBytes);
|
||||||
|
var request = new CheckPdfAttachmentsQuery { Base64Pdf = base64Pdf };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _client.PostAsJsonAsync("/api/pdf/attachments/check", request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||||
|
|
||||||
|
var result = await response.Content.ReadFromJsonAsync<AttachmentCheckResult>();
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.HasAttachments.Should().BeTrue();
|
||||||
|
result.AttachmentCount.Should().Be(6, "PDF has exactly 6 attachments");
|
||||||
|
result.Attachments.Should().HaveCount(6);
|
||||||
|
|
||||||
|
// Verify each attachment has required properties
|
||||||
|
foreach (var attachment in result.Attachments)
|
||||||
|
{
|
||||||
|
attachment.FileName.Should().NotBeNullOrEmpty();
|
||||||
|
attachment.MimeType.Should().NotBeNullOrEmpty();
|
||||||
|
attachment.Size.Should().BeGreaterThan(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task POST_CheckAttachments_Base64_InvalidBase64_Returns400()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var request = new CheckPdfAttachmentsQuery { Base64Pdf = "invalid-base64!!!" };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _client.PostAsJsonAsync("/api/pdf/attachments/check", request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
|
||||||
|
|
||||||
|
var problemDetails = await response.Content.ReadAsStringAsync();
|
||||||
|
problemDetails.Should().Contain("Base64");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task POST_CheckAttachments_Base64_EmptyPdf_Returns400()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var request = new CheckPdfAttachmentsQuery { Base64Pdf = string.Empty };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _client.PostAsJsonAsync("/api/pdf/attachments/check", request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
|
||||||
|
|
||||||
|
var problemDetails = await response.Content.ReadAsStringAsync();
|
||||||
|
problemDetails.Should().Contain("Either PdfBytes or Base64Pdf must be provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Multipart/Form-Data Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task POST_CheckAttachments_Multipart_PdfWithoutAttachments_Returns200()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
byte[] pdfBytes = await LoadTestPdfAsync("pdfWithSwissQRCode.pdf");
|
||||||
|
|
||||||
|
using var content = new MultipartFormDataContent();
|
||||||
|
var fileContent = new ByteArrayContent(pdfBytes);
|
||||||
|
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf");
|
||||||
|
content.Add(fileContent, "file", "pdfWithSwissQRCode.pdf");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _client.PostAsync("/api/pdf/attachments/check", content);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||||
|
|
||||||
|
var result = await response.Content.ReadFromJsonAsync<AttachmentCheckResult>();
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.HasAttachments.Should().BeFalse();
|
||||||
|
result.AttachmentCount.Should().Be(0);
|
||||||
|
result.Attachments.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task POST_CheckAttachments_Multipart_PdfWithMultipleAttachments_Returns200()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
byte[] pdfBytes = await LoadTestPdfAsync("pdfWithMoreThanOneAttachment.pdf");
|
||||||
|
|
||||||
|
using var content = new MultipartFormDataContent();
|
||||||
|
var fileContent = new ByteArrayContent(pdfBytes);
|
||||||
|
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf");
|
||||||
|
content.Add(fileContent, "file", "pdfWithMoreThanOneAttachment.pdf");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _client.PostAsync("/api/pdf/attachments/check", content);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||||
|
|
||||||
|
var result = await response.Content.ReadFromJsonAsync<AttachmentCheckResult>();
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.HasAttachments.Should().BeTrue();
|
||||||
|
result.AttachmentCount.Should().Be(6);
|
||||||
|
result.Attachments.Should().HaveCount(6);
|
||||||
|
|
||||||
|
// Verify first attachment details
|
||||||
|
var firstAttachment = result.Attachments.First();
|
||||||
|
firstAttachment.FileName.Should().NotBeNullOrEmpty();
|
||||||
|
firstAttachment.MimeType.Should().NotBeNullOrEmpty();
|
||||||
|
firstAttachment.Size.Should().BeGreaterThan(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task POST_CheckAttachments_Multipart_MissingFile_Returns400()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
using var content = new MultipartFormDataContent(); // No file added
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _client.PostAsync("/api/pdf/attachments/check", content);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task POST_CheckAttachments_Multipart_CorruptedPdf_Returns500()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
byte[] corruptedBytes = "This is not a valid PDF content"u8.ToArray();
|
||||||
|
|
||||||
|
using var content = new MultipartFormDataContent();
|
||||||
|
var fileContent = new ByteArrayContent(corruptedBytes);
|
||||||
|
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf");
|
||||||
|
content.Add(fileContent, "file", "corrupted.pdf");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _client.PostAsync("/api/pdf/attachments/check", content);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.InternalServerError);
|
||||||
|
|
||||||
|
var problemDetails = await response.Content.ReadAsStringAsync();
|
||||||
|
// Note: Generic error message for security reasons (doesn't expose internal details)
|
||||||
|
problemDetails.Should().Contain("error");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Edge Cases
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task POST_CheckAttachments_Base64_PdfWithSwissQrCode_Returns200()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
byte[] pdfBytes = await LoadTestPdfAsync("pdfWithSwissQRCode.pdf");
|
||||||
|
string base64Pdf = Convert.ToBase64String(pdfBytes);
|
||||||
|
var request = new CheckPdfAttachmentsQuery { Base64Pdf = base64Pdf };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _client.PostAsJsonAsync("/api/pdf/attachments/check", request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||||
|
|
||||||
|
var result = await response.Content.ReadFromJsonAsync<AttachmentCheckResult>();
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.HasAttachments.Should().BeFalse("Swiss QR PDF has no attachments");
|
||||||
|
result.AttachmentCount.Should().Be(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using DocumentOperator.Application.CheckPdfAttachments.Queries;
|
||||||
|
using DocumentOperator.Application.Common.DTOs;
|
||||||
|
using DocumentOperator.Application.Common.Interfaces;
|
||||||
|
|
||||||
|
using FluentAssertions;
|
||||||
|
using Moq;
|
||||||
|
|
||||||
|
namespace DocumentOperator.Tests.Unit.Application.CheckPdfAttachments;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unit tests for CheckPdfAttachmentsQueryHandler.
|
||||||
|
/// Tests handler logic with mocked dependencies (IPdfProcessor, IMapper).
|
||||||
|
/// </summary>
|
||||||
|
public class CheckPdfAttachmentsQueryHandlerTests
|
||||||
|
{
|
||||||
|
private readonly Mock<IPdfProcessor> _mockPdfProcessor;
|
||||||
|
private readonly Mock<IMapper> _mockMapper;
|
||||||
|
private readonly CheckPdfAttachmentsQueryHandler _sut;
|
||||||
|
|
||||||
|
public CheckPdfAttachmentsQueryHandlerTests()
|
||||||
|
{
|
||||||
|
_mockPdfProcessor = new Mock<IPdfProcessor>();
|
||||||
|
_mockMapper = new Mock<IMapper>();
|
||||||
|
_sut = new CheckPdfAttachmentsQueryHandler(_mockPdfProcessor.Object, _mockMapper.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Handle_WithPdfBytes_CallsProcessorAndMapper()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
byte[] pdfBytes = "fake-pdf-content"u8.ToArray();
|
||||||
|
var query = new CheckPdfAttachmentsQuery { PdfBytes = pdfBytes };
|
||||||
|
|
||||||
|
var domainResult = new AttachmentInfo(
|
||||||
|
hasAttachments: true,
|
||||||
|
attachmentCount: 2,
|
||||||
|
attachments:
|
||||||
|
[
|
||||||
|
new("invoice.xml", "text/xml", 1024),
|
||||||
|
new("metadata.json", "application/json", 512)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
var expectedDto = new AttachmentCheckResult
|
||||||
|
{
|
||||||
|
HasAttachments = true,
|
||||||
|
AttachmentCount = 2,
|
||||||
|
Attachments =
|
||||||
|
[
|
||||||
|
new() { FileName = "invoice.xml", MimeType = "text/xml", Size = 1024 },
|
||||||
|
new() { FileName = "metadata.json", MimeType = "application/json", Size = 512 }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
_mockPdfProcessor.Setup(p => p.CheckAttachmentsAsync(It.IsAny<Stream>())).ReturnsAsync(domainResult);
|
||||||
|
_mockMapper.Setup(m => m.Map<AttachmentCheckResult>(domainResult)).Returns(expectedDto);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _sut.Handle(query, CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result.Should().BeEquivalentTo(expectedDto);
|
||||||
|
|
||||||
|
_mockPdfProcessor.Verify(p => p.CheckAttachmentsAsync(It.IsAny<Stream>()), Times.Once);
|
||||||
|
_mockMapper.Verify(m => m.Map<AttachmentCheckResult>(domainResult), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Handle_WithBase64Pdf_DecodesAndCallsProcessor()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
byte[] pdfBytes = "fake-pdf-content"u8.ToArray();
|
||||||
|
string base64Pdf = Convert.ToBase64String(pdfBytes);
|
||||||
|
var query = new CheckPdfAttachmentsQuery { Base64Pdf = base64Pdf };
|
||||||
|
|
||||||
|
var domainResult = new AttachmentInfo(false, 0, []);
|
||||||
|
var expectedDto = new AttachmentCheckResult { HasAttachments = false, AttachmentCount = 0, Attachments = [] };
|
||||||
|
|
||||||
|
_mockPdfProcessor.Setup(p => p.CheckAttachmentsAsync(It.IsAny<Stream>())).ReturnsAsync(domainResult);
|
||||||
|
_mockMapper.Setup(m => m.Map<AttachmentCheckResult>(domainResult)).Returns(expectedDto);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _sut.Handle(query, CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result.HasAttachments.Should().BeFalse();
|
||||||
|
result.AttachmentCount.Should().Be(0);
|
||||||
|
|
||||||
|
_mockPdfProcessor.Verify(p => p.CheckAttachmentsAsync(It.IsAny<Stream>()), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Handle_WithEmptyAttachments_ReturnsEmptyList()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
byte[] pdfBytes = "fake-pdf-content"u8.ToArray();
|
||||||
|
var query = new CheckPdfAttachmentsQuery { PdfBytes = pdfBytes };
|
||||||
|
|
||||||
|
var domainResult = new AttachmentInfo(false, 0, []);
|
||||||
|
var expectedDto = new AttachmentCheckResult
|
||||||
|
{
|
||||||
|
HasAttachments = false,
|
||||||
|
AttachmentCount = 0,
|
||||||
|
Attachments = []
|
||||||
|
};
|
||||||
|
|
||||||
|
_mockPdfProcessor.Setup(p => p.CheckAttachmentsAsync(It.IsAny<Stream>())).ReturnsAsync(domainResult);
|
||||||
|
_mockMapper.Setup(m => m.Map<AttachmentCheckResult>(domainResult)).Returns(expectedDto);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _sut.Handle(query, CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.HasAttachments.Should().BeFalse();
|
||||||
|
result.AttachmentCount.Should().Be(0);
|
||||||
|
result.Attachments.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user