using AutoMapper; using DocumentService.Application.Common.DTOs; using DocumentService.Application.Common.Interfaces; using DocumentService.Application.ValidatePdfA.Queries; using DocumentService.Domain.Common.Exceptions; using FluentAssertions; using Moq; using Xunit; namespace DocumentService.Tests.Unit.Application.Features.ValidatePdfA; public class ValidatePdfAQueryHandlerTests { private readonly Mock _mockPdfProcessor; private readonly Mock _mockMapper; private readonly ValidatePdfAQueryHandler _handler; public ValidatePdfAQueryHandlerTests() { _mockPdfProcessor = new Mock(); _mockMapper = new Mock(); _handler = new ValidatePdfAQueryHandler(_mockPdfProcessor.Object, _mockMapper.Object); } [Fact] public async Task Handle_ValidPdfA_ReturnsPdfAMetadata() { // Arrange var pdfBytes = "%PDF"u8.ToArray(); // "%PDF" var query = new ValidatePdfAQuery { PdfStream = new MemoryStream(pdfBytes) }; var domainMetadata = new PdfAMetadata( isValid: true, pdfVersion: "1.7", pageCount: 3, fileSizeBytes: 2048, encrypted: false, pdfaVersion: "PDF/A-3b", pdfaCompliant: true, errors: [], warnings: [] ); var expectedDto = new PdfAValidationResult { IsValid = true, PdfVersion = "1.7", PageCount = 3, FileSize = 2048, Encrypted = false, PdfAVersion = "PDF/A-3b", PdfACompliant = true, Errors = new List(), Warnings = new List() }; _mockPdfProcessor .Setup(x => x.ValidatePdfAAsync(It.IsAny())) .ReturnsAsync(domainMetadata); _mockMapper .Setup(x => x.Map(domainMetadata)) .Returns(expectedDto); // Act var result = await _handler.Handle(query, CancellationToken.None); // Assert result.Should().NotBeNull(); result.IsValid.Should().BeTrue(); result.PageCount.Should().Be(3); result.PdfVersion.Should().Be("1.7"); result.PdfAVersion.Should().Be("PDF/A-3b"); result.PdfACompliant.Should().BeTrue(); result.Encrypted.Should().BeFalse(); result.Errors.Should().BeEmpty(); result.Warnings.Should().BeEmpty(); _mockPdfProcessor.Verify(x => x.ValidatePdfAAsync(It.IsAny()), Times.Once); _mockMapper.Verify(x => x.Map(domainMetadata), Times.Once); } [Fact] public async Task Handle_NonCompliantPdfA_ReturnsErrorsAndWarnings() { // Arrange var pdfBytes = new byte[] { 0x25, 0x50, 0x44, 0x46 }; // "%PDF" var query = new ValidatePdfAQuery { PdfStream = new MemoryStream(pdfBytes) }; var errors = new List { "Missing XMP metadata", "Invalid color space" }; var warnings = new List { "Embedded font not subset" }; var domainMetadata = new PdfAMetadata( isValid: false, pdfVersion: "1.4", pageCount: 2, fileSizeBytes: 1024, encrypted: false, pdfaVersion: null, pdfaCompliant: false, errors: errors, warnings: warnings ); var expectedDto = new PdfAValidationResult { IsValid = false, PdfVersion = "1.4", PageCount = 2, FileSize = 1024, Encrypted = false, PdfAVersion = null, PdfACompliant = false, Errors = errors, Warnings = warnings }; _mockPdfProcessor .Setup(x => x.ValidatePdfAAsync(It.IsAny())) .ReturnsAsync(domainMetadata); _mockMapper .Setup(x => x.Map(domainMetadata)) .Returns(expectedDto); // Act var result = await _handler.Handle(query, CancellationToken.None); // Assert result.Should().NotBeNull(); result.IsValid.Should().BeFalse(); result.PdfACompliant.Should().BeFalse(); result.PdfAVersion.Should().BeNull(); result.Errors.Should().HaveCount(2); result.Errors.Should().Contain("Missing XMP metadata"); result.Warnings.Should().HaveCount(1); _mockPdfProcessor.Verify(x => x.ValidatePdfAAsync(It.IsAny()), Times.Once); } [Fact] public async Task Handle_EncryptedPdf_ReturnsEncryptedFlag() { // Arrange var pdfBytes = new byte[] { 0x25, 0x50, 0x44, 0x46 }; // "%PDF" var query = new ValidatePdfAQuery { PdfStream = new MemoryStream(pdfBytes) }; var domainMetadata = new PdfAMetadata( isValid: true, pdfVersion: "1.7", pageCount: 1, fileSizeBytes: 512, encrypted: true, pdfaVersion: null, pdfaCompliant: false, errors: new List { "Encrypted PDFs cannot be PDF/A compliant" }, warnings: new List() ); var expectedDto = new PdfAValidationResult { IsValid = true, PdfVersion = "1.7", PageCount = 1, FileSize = 512, Encrypted = true, PdfAVersion = null, PdfACompliant = false, Errors = new List { "Encrypted PDFs cannot be PDF/A compliant" }, Warnings = new List() }; _mockPdfProcessor .Setup(x => x.ValidatePdfAAsync(It.IsAny())) .ReturnsAsync(domainMetadata); _mockMapper .Setup(x => x.Map(domainMetadata)) .Returns(expectedDto); // Act var result = await _handler.Handle(query, CancellationToken.None); // Assert result.Should().NotBeNull(); result.Encrypted.Should().BeTrue(); result.PdfACompliant.Should().BeFalse(); result.Errors.Should().Contain("Encrypted PDFs cannot be PDF/A compliant"); _mockPdfProcessor.Verify(x => x.ValidatePdfAAsync(It.IsAny()), Times.Once); } [Fact] public async Task Handle_PdfProcessorThrowsException_PropagatesException() { // Arrange var pdfBytes = new byte[] { 0x25, 0x50, 0x44, 0x46 }; // "%PDF" var query = new ValidatePdfAQuery { PdfStream = new MemoryStream(pdfBytes) }; _mockPdfProcessor .Setup(x => x.ValidatePdfAAsync(It.IsAny())) .ThrowsAsync(new BadRequestException("Invalid PDF format")); // Act & Assert var exception = await Assert.ThrowsAsync( () => _handler.Handle(query, CancellationToken.None) ); exception.Message.Should().Be("Invalid PDF format"); _mockPdfProcessor.Verify(x => x.ValidatePdfAAsync(It.IsAny()), Times.Once); } }