diff --git a/DocumentOperator.Tests/Integration/API/PdfAttachmentControllerTests.cs b/DocumentOperator.Tests/Integration/API/PdfAttachmentControllerTests.cs
new file mode 100644
index 0000000..9178479
--- /dev/null
+++ b/DocumentOperator.Tests/Integration/API/PdfAttachmentControllerTests.cs
@@ -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;
+
+///
+/// Integration tests for PdfAttachmentController.
+/// Tests /api/pdf/attachments/check endpoint with both multipart and Base64 input.
+///
+public class PdfAttachmentControllerTests : IClassFixture>
+{
+ private readonly WebApplicationFactory _factory;
+ private readonly HttpClient _client;
+
+ public PdfAttachmentControllerTests(WebApplicationFactory factory)
+ {
+ _factory = factory;
+ _client = _factory.CreateClient();
+ }
+
+ #region Helper Methods
+
+ ///
+ /// Loads a test PDF from embedded resources.
+ ///
+ private static async Task 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ result.Should().NotBeNull();
+ result!.HasAttachments.Should().BeFalse("Swiss QR PDF has no attachments");
+ result.AttachmentCount.Should().Be(0);
+ }
+
+ #endregion
+}
diff --git a/DocumentOperator.Tests/Unit/Application/CheckPdfAttachments/CheckPdfAttachmentsQueryHandlerTests.cs b/DocumentOperator.Tests/Unit/Application/CheckPdfAttachments/CheckPdfAttachmentsQueryHandlerTests.cs
new file mode 100644
index 0000000..5850a74
--- /dev/null
+++ b/DocumentOperator.Tests/Unit/Application/CheckPdfAttachments/CheckPdfAttachmentsQueryHandlerTests.cs
@@ -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;
+
+///
+/// Unit tests for CheckPdfAttachmentsQueryHandler.
+/// Tests handler logic with mocked dependencies (IPdfProcessor, IMapper).
+///
+public class CheckPdfAttachmentsQueryHandlerTests
+{
+ private readonly Mock _mockPdfProcessor;
+ private readonly Mock _mockMapper;
+ private readonly CheckPdfAttachmentsQueryHandler _sut;
+
+ public CheckPdfAttachmentsQueryHandlerTests()
+ {
+ _mockPdfProcessor = new Mock();
+ _mockMapper = new Mock();
+ _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())).ReturnsAsync(domainResult);
+ _mockMapper.Setup(m => m.Map(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()), Times.Once);
+ _mockMapper.Verify(m => m.Map(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())).ReturnsAsync(domainResult);
+ _mockMapper.Setup(m => m.Map(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()), 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())).ReturnsAsync(domainResult);
+ _mockMapper.Setup(m => m.Map(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();
+ }
+}
+
+