test: Add PDF/A validation tests and manual testing guide
- Add unit tests for ValidatePdfAQueryHandler (4 tests) - Add integration tests for PDF/A validation endpoint (6 tests) - Fix FluentValidation: Add Base64 format validation to ValidatePdfAQueryValidator - Update AGENTS.md: Document 3-folder test structure rationale and test count (30 tests) - Add DocumentOperator.API/README.md: 16 manual test scenarios for all endpoints Test coverage: - Unit: ValidatePdfAQueryHandler (compliant, non-compliant, encrypted, exceptions) - Integration: PDF/A endpoint (multipart + Base64, validation, error handling) - Manual: Step-by-step Swagger UI testing guide for all features All 30 automated tests passing.
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
using AutoMapper;
|
||||
using DocumentOperator.Application.Common.DTOs;
|
||||
using DocumentOperator.Application.Common.Interfaces;
|
||||
using DocumentOperator.Application.ValidatePdfA.Queries;
|
||||
using DocumentOperator.Domain.Common.Exceptions;
|
||||
using DocumentOperator.Domain.Models.ValueObjects;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace DocumentOperator.Tests.Unit.Application.Features.ValidatePdfA;
|
||||
|
||||
public class ValidatePdfAQueryHandlerTests
|
||||
{
|
||||
private readonly Mock<IPdfProcessor> _mockPdfProcessor;
|
||||
private readonly Mock<IMapper> _mockMapper;
|
||||
private readonly ValidatePdfAQueryHandler _handler;
|
||||
|
||||
public ValidatePdfAQueryHandlerTests()
|
||||
{
|
||||
_mockPdfProcessor = new Mock<IPdfProcessor>();
|
||||
_mockMapper = new Mock<IMapper>();
|
||||
_handler = new ValidatePdfAQueryHandler(_mockPdfProcessor.Object, _mockMapper.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ValidPdfA_ReturnsPdfAMetadata()
|
||||
{
|
||||
// Arrange
|
||||
var pdfBytes = new byte[] { 0x25, 0x50, 0x44, 0x46 }; // "%PDF"
|
||||
var query = new ValidatePdfAQuery { PdfBytes = pdfBytes };
|
||||
|
||||
var domainMetadata = new PdfAMetadata(
|
||||
isValid: true,
|
||||
pdfVersion: "1.7",
|
||||
pageCount: 3,
|
||||
fileSizeBytes: 2048,
|
||||
encrypted: false,
|
||||
pdfaVersion: "PDF/A-3b",
|
||||
pdfaCompliant: true,
|
||||
errors: new List<string>(),
|
||||
warnings: new List<string>()
|
||||
);
|
||||
|
||||
var expectedDto = new PdfAValidationResult
|
||||
{
|
||||
IsValid = true,
|
||||
PdfVersion = "1.7",
|
||||
PageCount = 3,
|
||||
FileSize = 2048,
|
||||
Encrypted = false,
|
||||
PdfAVersion = "PDF/A-3b",
|
||||
PdfACompliant = true,
|
||||
Errors = new List<string>(),
|
||||
Warnings = new List<string>()
|
||||
};
|
||||
|
||||
_mockPdfProcessor
|
||||
.Setup(x => x.ValidatePdfAAsync(It.IsAny<byte[]>()))
|
||||
.ReturnsAsync(domainMetadata);
|
||||
|
||||
_mockMapper
|
||||
.Setup(x => x.Map<PdfAValidationResult>(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<byte[]>()), Times.Once);
|
||||
_mockMapper.Verify(x => x.Map<PdfAValidationResult>(domainMetadata), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_NonCompliantPdfA_ReturnsErrorsAndWarnings()
|
||||
{
|
||||
// Arrange
|
||||
var pdfBytes = new byte[] { 0x25, 0x50, 0x44, 0x46 }; // "%PDF"
|
||||
var query = new ValidatePdfAQuery { Base64Pdf = Convert.ToBase64String(pdfBytes) };
|
||||
|
||||
var errors = new List<string> { "Missing XMP metadata", "Invalid color space" };
|
||||
var warnings = new List<string> { "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<byte[]>()))
|
||||
.ReturnsAsync(domainMetadata);
|
||||
|
||||
_mockMapper
|
||||
.Setup(x => x.Map<PdfAValidationResult>(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<byte[]>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_EncryptedPdf_ReturnsEncryptedFlag()
|
||||
{
|
||||
// Arrange
|
||||
var pdfBytes = new byte[] { 0x25, 0x50, 0x44, 0x46 }; // "%PDF"
|
||||
var query = new ValidatePdfAQuery { PdfBytes = pdfBytes };
|
||||
|
||||
var domainMetadata = new PdfAMetadata(
|
||||
isValid: true,
|
||||
pdfVersion: "1.7",
|
||||
pageCount: 1,
|
||||
fileSizeBytes: 512,
|
||||
encrypted: true,
|
||||
pdfaVersion: null,
|
||||
pdfaCompliant: false,
|
||||
errors: new List<string> { "Encrypted PDFs cannot be PDF/A compliant" },
|
||||
warnings: new List<string>()
|
||||
);
|
||||
|
||||
var expectedDto = new PdfAValidationResult
|
||||
{
|
||||
IsValid = true,
|
||||
PdfVersion = "1.7",
|
||||
PageCount = 1,
|
||||
FileSize = 512,
|
||||
Encrypted = true,
|
||||
PdfAVersion = null,
|
||||
PdfACompliant = false,
|
||||
Errors = new List<string> { "Encrypted PDFs cannot be PDF/A compliant" },
|
||||
Warnings = new List<string>()
|
||||
};
|
||||
|
||||
_mockPdfProcessor
|
||||
.Setup(x => x.ValidatePdfAAsync(It.IsAny<byte[]>()))
|
||||
.ReturnsAsync(domainMetadata);
|
||||
|
||||
_mockMapper
|
||||
.Setup(x => x.Map<PdfAValidationResult>(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<byte[]>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_PdfProcessorThrowsException_PropagatesException()
|
||||
{
|
||||
// Arrange
|
||||
var pdfBytes = new byte[] { 0x25, 0x50, 0x44, 0x46 }; // "%PDF"
|
||||
var query = new ValidatePdfAQuery { PdfBytes = pdfBytes };
|
||||
|
||||
_mockPdfProcessor
|
||||
.Setup(x => x.ValidatePdfAAsync(It.IsAny<byte[]>()))
|
||||
.ThrowsAsync(new PdfProcessingException("Invalid PDF format"));
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<PdfProcessingException>(
|
||||
() => _handler.Handle(query, CancellationToken.None)
|
||||
);
|
||||
|
||||
exception.Message.Should().Be("Invalid PDF format");
|
||||
_mockPdfProcessor.Verify(x => x.ValidatePdfAAsync(It.IsAny<byte[]>()), Times.Once);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user