Refactor: Rename DocumentOperator to DocumentService
Updated project and solution files to rename and restructure `DocumentOperator` to `DocumentService`. Modified project references in `DocumentService.API.csproj`, `DocumentService.Application.csproj`, and `DocumentService.Infrastructure.csproj` to reflect the new naming convention. Updated `DocumentService.sln` to remove old project references, add new ones, and adjust configuration and nested project mappings accordingly. This change aligns the project structure with the new naming convention.
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
using AutoMapper;
|
||||
using DocumentService.Application.CheckPdfAttachments.Queries;
|
||||
using DocumentService.Application.Common.DTOs;
|
||||
using DocumentService.Application.Common.Interfaces;
|
||||
using DocumentService.Domain.Common.Exceptions;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace DocumentService.Tests.Unit.Application.CheckPdfAttachments;
|
||||
|
||||
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_PdfWithAttachments_ReturnsAttachmentDetails()
|
||||
{
|
||||
// Arrange
|
||||
var query = new CheckPdfAttachmentsQuery { PdfStream = new MemoryStream("fake-pdf"u8.ToArray()) };
|
||||
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.HasAttachments.Should().BeTrue();
|
||||
result.AttachmentCount.Should().Be(2);
|
||||
result.Attachments.Should().HaveCount(2);
|
||||
result.Attachments[0].FileName.Should().Be("invoice.xml");
|
||||
result.Attachments[0].MimeType.Should().Be("text/xml");
|
||||
result.Attachments[0].Size.Should().Be(1024);
|
||||
_mockPdfProcessor.Verify(p => p.CheckAttachmentsAsync(It.IsAny<Stream>()), Times.Once);
|
||||
_mockMapper.Verify(m => m.Map<AttachmentCheckResult>(domainResult), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_PdfWithNoAttachments_ReturnsEmptyResult()
|
||||
{
|
||||
// Arrange
|
||||
var query = new CheckPdfAttachmentsQuery { PdfStream = new MemoryStream("fake-pdf"u8.ToArray()) };
|
||||
var domainResult = AttachmentInfo.Empty;
|
||||
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();
|
||||
_mockPdfProcessor.Verify(p => p.CheckAttachmentsAsync(It.IsAny<Stream>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ProcessorThrowsBadRequest_PropagatesException()
|
||||
{
|
||||
// Arrange
|
||||
var query = new CheckPdfAttachmentsQuery { PdfStream = new MemoryStream("fake-pdf"u8.ToArray()) };
|
||||
_mockPdfProcessor.Setup(p => p.CheckAttachmentsAsync(It.IsAny<Stream>()))
|
||||
.ThrowsAsync(new BadRequestException("PDF stream cannot be empty"));
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => _sut.Handle(query, CancellationToken.None));
|
||||
ex.Message.Should().Be("PDF stream cannot be empty");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ProcessorThrowsGenericException_PropagatesException()
|
||||
{
|
||||
// Arrange
|
||||
var query = new CheckPdfAttachmentsQuery { PdfStream = new MemoryStream("fake-pdf"u8.ToArray()) };
|
||||
_mockPdfProcessor.Setup(p => p.CheckAttachmentsAsync(It.IsAny<Stream>()))
|
||||
.ThrowsAsync(new InvalidOperationException("Unexpected error"));
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => _sut.Handle(query, CancellationToken.None));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
using DocumentService.Application.AddAnnotation;
|
||||
using DocumentService.Application.Common.Interfaces;
|
||||
using DocumentService.Domain.Common.Exceptions;
|
||||
using DocumentService.Domain.Models.ValueObjects;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace DocumentService.Tests.Unit.Application.Features.AddAnnotation;
|
||||
|
||||
public class AddAnnotationHandlerTests
|
||||
{
|
||||
private readonly Mock<IPdfProcessor> _mockProcessor;
|
||||
private readonly AddAnnotationHandler _handler;
|
||||
private static readonly byte[] FakePdfBytes = "%PDF"u8.ToArray();
|
||||
|
||||
public AddAnnotationHandlerTests()
|
||||
{
|
||||
_mockProcessor = new Mock<IPdfProcessor>();
|
||||
_handler = new AddAnnotationHandler(_mockProcessor.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_TextMarkupAnnotation_ReturnsAnnotatedBytes()
|
||||
{
|
||||
// Arrange
|
||||
var command = new AddAnnotationCommand
|
||||
{
|
||||
PdfStream = new MemoryStream(FakePdfBytes),
|
||||
AnnotationType = AnnotationType.TextMarkup,
|
||||
PageNumber = 1,
|
||||
Rectangle = (10, 20, 200, 50),
|
||||
TextMarkupStyle = TextMarkupStyle.Highlight,
|
||||
Author = "TestUser",
|
||||
Color = "FFFF00"
|
||||
};
|
||||
var expected = new byte[] { 0x25, 0x50, 0x44, 0x46, 0x01 };
|
||||
_mockProcessor.Setup(x => x.AddAnnotationAsync(
|
||||
It.IsAny<Stream>(), AnnotationType.TextMarkup, 1,
|
||||
It.IsAny<(double, double, double, double)>(),
|
||||
null, "TestUser", "FFFF00", TextMarkupStyle.Highlight, AnnotationOrigin.BottomLeft))
|
||||
.ReturnsAsync(expected);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Should().BeEquivalentTo(expected);
|
||||
_mockProcessor.Verify(x => x.AddAnnotationAsync(
|
||||
It.IsAny<Stream>(), AnnotationType.TextMarkup, 1,
|
||||
It.IsAny<(double, double, double, double)>(),
|
||||
null, "TestUser", "FFFF00", TextMarkupStyle.Highlight, AnnotationOrigin.BottomLeft), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_FreeTextAnnotation_PassesContentToProcessor()
|
||||
{
|
||||
// Arrange
|
||||
var command = new AddAnnotationCommand
|
||||
{
|
||||
PdfStream = new MemoryStream(FakePdfBytes),
|
||||
AnnotationType = AnnotationType.FreeText,
|
||||
PageNumber = 2,
|
||||
Rectangle = (50, 100, 300, 150),
|
||||
Content = "Review this section"
|
||||
};
|
||||
var expected = new byte[] { 0x25, 0x50, 0x44, 0x46, 0x02 };
|
||||
_mockProcessor.Setup(x => x.AddAnnotationAsync(
|
||||
It.IsAny<Stream>(), AnnotationType.FreeText, 2,
|
||||
It.IsAny<(double, double, double, double)>(),
|
||||
"Review this section", null, null, null, AnnotationOrigin.BottomLeft))
|
||||
.ReturnsAsync(expected);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Should().BeEquivalentTo(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ProcessorThrowsBadRequest_PropagatesException()
|
||||
{
|
||||
// Arrange
|
||||
var command = new AddAnnotationCommand
|
||||
{
|
||||
PdfStream = new MemoryStream(FakePdfBytes),
|
||||
AnnotationType = AnnotationType.Circle,
|
||||
PageNumber = 99,
|
||||
Rectangle = (0, 0, 100, 100)
|
||||
};
|
||||
_mockProcessor.Setup(x => x.AddAnnotationAsync(
|
||||
It.IsAny<Stream>(), It.IsAny<AnnotationType>(), It.IsAny<int>(),
|
||||
It.IsAny<(double, double, double, double)>(),
|
||||
It.IsAny<string?>(), It.IsAny<string?>(), It.IsAny<string?>(),
|
||||
It.IsAny<TextMarkupStyle?>(), It.IsAny<AnnotationOrigin>()))
|
||||
.ThrowsAsync(new BadRequestException("Page number 99 exceeds document page count"));
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => _handler.Handle(command, CancellationToken.None));
|
||||
ex.Message.Should().Contain("Page number 99");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ProcessorThrowsGenericException_PropagatesException()
|
||||
{
|
||||
// Arrange
|
||||
var command = new AddAnnotationCommand
|
||||
{
|
||||
PdfStream = new MemoryStream(FakePdfBytes),
|
||||
AnnotationType = AnnotationType.Square,
|
||||
PageNumber = 1,
|
||||
Rectangle = (0, 0, 100, 100)
|
||||
};
|
||||
_mockProcessor.Setup(x => x.AddAnnotationAsync(
|
||||
It.IsAny<Stream>(), It.IsAny<AnnotationType>(), It.IsAny<int>(),
|
||||
It.IsAny<(double, double, double, double)>(),
|
||||
It.IsAny<string?>(), It.IsAny<string?>(), It.IsAny<string?>(),
|
||||
It.IsAny<TextMarkupStyle?>(), It.IsAny<AnnotationOrigin>()))
|
||||
.ThrowsAsync(new Exception("Unexpected error"));
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<Exception>(() => _handler.Handle(command, CancellationToken.None));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
using DocumentService.Application.AddStamp;
|
||||
using DocumentService.Application.Common.Interfaces;
|
||||
using DocumentService.Domain.Common.Exceptions;
|
||||
using DocumentService.Domain.Models.ValueObjects;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace DocumentService.Tests.Unit.Application.Features.AddStamp;
|
||||
|
||||
public class AddStampHandlerTests
|
||||
{
|
||||
private readonly Mock<IPdfProcessor> _mockProcessor;
|
||||
private readonly AddStampHandler _handler;
|
||||
private static readonly byte[] FakePdfBytes = "%PDF"u8.ToArray();
|
||||
|
||||
public AddStampHandlerTests()
|
||||
{
|
||||
_mockProcessor = new Mock<IPdfProcessor>();
|
||||
_handler = new AddStampHandler(_mockProcessor.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_TextStamp_ReturnsStampedBytes()
|
||||
{
|
||||
// Arrange
|
||||
var command = new AddStampCommand
|
||||
{
|
||||
PdfStream = new MemoryStream(FakePdfBytes),
|
||||
StampType = StampType.Text,
|
||||
Position = (100, 200),
|
||||
Text = "CONFIDENTIAL"
|
||||
};
|
||||
var expected = new byte[] { 0x25, 0x50, 0x44, 0x46, 0x03 };
|
||||
_mockProcessor.Setup(x => x.AddStampAsync(
|
||||
It.IsAny<Stream>(), StampType.Text, null,
|
||||
It.IsAny<(double, double)>(), null,
|
||||
AnnotationOrigin.BottomLeft, "CONFIDENTIAL",
|
||||
null, null, null, null, null,
|
||||
StampPlacement.Foreground, null, null))
|
||||
.ReturnsAsync(expected);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Should().BeEquivalentTo(expected);
|
||||
_mockProcessor.Verify(x => x.AddStampAsync(
|
||||
It.IsAny<Stream>(), StampType.Text, null,
|
||||
It.IsAny<(double, double)>(), null,
|
||||
AnnotationOrigin.BottomLeft, "CONFIDENTIAL",
|
||||
null, null, null, null, null,
|
||||
StampPlacement.Foreground, null, null), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_PredefinedStamp_PassesPredefinedTypeToProcessor()
|
||||
{
|
||||
// Arrange
|
||||
var command = new AddStampCommand
|
||||
{
|
||||
PdfStream = new MemoryStream(FakePdfBytes),
|
||||
StampType = StampType.Predefined,
|
||||
Position = (50, 50),
|
||||
PredefinedType = PredefinedStampType.Approved,
|
||||
Opacity = 0.8
|
||||
};
|
||||
var expected = new byte[] { 0x25, 0x50, 0x44, 0x46, 0x04 };
|
||||
_mockProcessor.Setup(x => x.AddStampAsync(
|
||||
It.IsAny<Stream>(), StampType.Predefined, null,
|
||||
It.IsAny<(double, double)>(), null,
|
||||
AnnotationOrigin.BottomLeft, null, null, null, null,
|
||||
0.8, null, StampPlacement.Foreground, null, PredefinedStampType.Approved))
|
||||
.ReturnsAsync(expected);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Should().BeEquivalentTo(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ProcessorThrowsBadRequest_PropagatesException()
|
||||
{
|
||||
// Arrange
|
||||
var command = new AddStampCommand
|
||||
{
|
||||
PdfStream = new MemoryStream(FakePdfBytes),
|
||||
StampType = StampType.Text,
|
||||
Position = (0, 0),
|
||||
Text = "DRAFT",
|
||||
Opacity = 2.0 // invalid
|
||||
};
|
||||
_mockProcessor.Setup(x => x.AddStampAsync(
|
||||
It.IsAny<Stream>(), It.IsAny<StampType>(), It.IsAny<int[]?>(),
|
||||
It.IsAny<(double, double)>(), It.IsAny<(double, double)?>(),
|
||||
It.IsAny<AnnotationOrigin>(), It.IsAny<string?>(), It.IsAny<string?>(),
|
||||
It.IsAny<double?>(), It.IsAny<string?>(), It.IsAny<double?>(),
|
||||
It.IsAny<double?>(), It.IsAny<StampPlacement>(),
|
||||
It.IsAny<byte[]?>(), It.IsAny<PredefinedStampType?>()))
|
||||
.ThrowsAsync(new BadRequestException("Opacity must be between 0.0 and 1.0"));
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => _handler.Handle(command, CancellationToken.None));
|
||||
ex.Message.Should().Contain("Opacity");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ProcessorThrowsGenericException_PropagatesException()
|
||||
{
|
||||
// Arrange
|
||||
var command = new AddStampCommand
|
||||
{
|
||||
PdfStream = new MemoryStream(FakePdfBytes),
|
||||
StampType = StampType.Text,
|
||||
Position = (0, 0),
|
||||
Text = "TEST"
|
||||
};
|
||||
_mockProcessor.Setup(x => x.AddStampAsync(
|
||||
It.IsAny<Stream>(), It.IsAny<StampType>(), It.IsAny<int[]?>(),
|
||||
It.IsAny<(double, double)>(), It.IsAny<(double, double)?>(),
|
||||
It.IsAny<AnnotationOrigin>(), It.IsAny<string?>(), It.IsAny<string?>(),
|
||||
It.IsAny<double?>(), It.IsAny<string?>(), It.IsAny<double?>(),
|
||||
It.IsAny<double?>(), It.IsAny<StampPlacement>(),
|
||||
It.IsAny<byte[]?>(), It.IsAny<PredefinedStampType?>()))
|
||||
.ThrowsAsync(new Exception("Unexpected"));
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<Exception>(() => _handler.Handle(command, CancellationToken.None));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using DocumentService.Application.Common.Interfaces;
|
||||
using DocumentService.Application.ConvertToPdfA;
|
||||
using DocumentService.Application.ConvertFromPdfA;
|
||||
using DocumentService.Domain.Common.Exceptions;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace DocumentService.Tests.Unit.Application.Features.ConvertPdfA;
|
||||
|
||||
public class ConvertToPdfAHandlerTests
|
||||
{
|
||||
private readonly Mock<IPdfProcessor> _mockProcessor;
|
||||
private readonly ConvertToPdfACommandHandler _handler;
|
||||
private static readonly byte[] FakePdfBytes = "%PDF"u8.ToArray();
|
||||
|
||||
public ConvertToPdfAHandlerTests()
|
||||
{
|
||||
_mockProcessor = new Mock<IPdfProcessor>();
|
||||
_handler = new ConvertToPdfACommandHandler(_mockProcessor.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ValidPdfAndLevel_ReturnsConvertedBytes()
|
||||
{
|
||||
// Arrange
|
||||
var command = new ConvertToPdfACommand { PdfStream = new MemoryStream(FakePdfBytes), PdfALevel = "PDF/A-3b" };
|
||||
var expected = new byte[] { 0x25, 0x50, 0x44, 0x46, 0x0A };
|
||||
_mockProcessor.Setup(x => x.ConvertToPdfAAsync(It.IsAny<Stream>(), "PDF/A-3b")).ReturnsAsync(expected);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Should().BeEquivalentTo(expected);
|
||||
_mockProcessor.Verify(x => x.ConvertToPdfAAsync(It.IsAny<Stream>(), "PDF/A-3b"), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ProcessorThrowsNotImplemented_PropagatesException()
|
||||
{
|
||||
// Arrange
|
||||
var command = new ConvertToPdfACommand { PdfStream = new MemoryStream(FakePdfBytes), PdfALevel = "PDF/A-1b" };
|
||||
_mockProcessor.Setup(x => x.ConvertToPdfAAsync(It.IsAny<Stream>(), "PDF/A-1b"))
|
||||
.ThrowsAsync(new NotImplementedException("PDF/A conversion not yet implemented"));
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<NotImplementedException>(
|
||||
() => _handler.Handle(command, CancellationToken.None));
|
||||
ex.Message.Should().Contain("not yet implemented");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ProcessorThrowsBadRequest_PropagatesException()
|
||||
{
|
||||
// Arrange
|
||||
var command = new ConvertToPdfACommand { PdfStream = new MemoryStream(FakePdfBytes), PdfALevel = "PDF/A-2b" };
|
||||
_mockProcessor.Setup(x => x.ConvertToPdfAAsync(It.IsAny<Stream>(), "PDF/A-2b"))
|
||||
.ThrowsAsync(new BadRequestException("PDF stream cannot be empty"));
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => _handler.Handle(command, CancellationToken.None));
|
||||
ex.Message.Should().Contain("empty");
|
||||
}
|
||||
}
|
||||
|
||||
public class ConvertFromPdfAHandlerTests
|
||||
{
|
||||
private readonly Mock<IPdfProcessor> _mockProcessor;
|
||||
private readonly ConvertFromPdfACommandHandler _handler;
|
||||
private static readonly byte[] FakePdfBytes = "%PDF"u8.ToArray();
|
||||
|
||||
public ConvertFromPdfAHandlerTests()
|
||||
{
|
||||
_mockProcessor = new Mock<IPdfProcessor>();
|
||||
_handler = new ConvertFromPdfACommandHandler(_mockProcessor.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ValidPdfA_ReturnsStandardPdfBytes()
|
||||
{
|
||||
// Arrange
|
||||
var command = new ConvertFromPdfACommand { PdfStream = new MemoryStream(FakePdfBytes) };
|
||||
var expected = new byte[] { 0x25, 0x50, 0x44, 0x46, 0x0B };
|
||||
_mockProcessor.Setup(x => x.ConvertFromPdfAAsync(It.IsAny<Stream>())).ReturnsAsync(expected);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Should().BeEquivalentTo(expected);
|
||||
_mockProcessor.Verify(x => x.ConvertFromPdfAAsync(It.IsAny<Stream>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ProcessorThrowsBadRequest_PropagatesException()
|
||||
{
|
||||
// Arrange
|
||||
var command = new ConvertFromPdfACommand { PdfStream = new MemoryStream(FakePdfBytes) };
|
||||
_mockProcessor.Setup(x => x.ConvertFromPdfAAsync(It.IsAny<Stream>()))
|
||||
.ThrowsAsync(new BadRequestException("PDF stream must be at position 0"));
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => _handler.Handle(command, CancellationToken.None));
|
||||
ex.Message.Should().Contain("position 0");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ProcessorThrowsGenericException_PropagatesException()
|
||||
{
|
||||
// Arrange
|
||||
var command = new ConvertFromPdfACommand { PdfStream = new MemoryStream(FakePdfBytes) };
|
||||
_mockProcessor.Setup(x => x.ConvertFromPdfAAsync(It.IsAny<Stream>()))
|
||||
.ThrowsAsync(new Exception("Unexpected error"));
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<Exception>(() => _handler.Handle(command, CancellationToken.None));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using DocumentService.Application.Common.Interfaces;
|
||||
using DocumentService.Application.ExtractPdfAttachments;
|
||||
using DocumentService.Domain.Common.Exceptions;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace DocumentService.Tests.Unit.Application.Features.ExtractAttachments;
|
||||
|
||||
public class ExtractPdfAttachmentsHandlerTests
|
||||
{
|
||||
private readonly Mock<IPdfProcessor> _mockProcessor;
|
||||
private readonly ExtractPdfAttachmentsCommandHandler _handler;
|
||||
private static readonly byte[] FakePdfBytes = "%PDF"u8.ToArray();
|
||||
|
||||
public ExtractPdfAttachmentsHandlerTests()
|
||||
{
|
||||
_mockProcessor = new Mock<IPdfProcessor>();
|
||||
_handler = new ExtractPdfAttachmentsCommandHandler(_mockProcessor.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_PdfWithAttachments_ReturnsZipBytes()
|
||||
{
|
||||
// Arrange
|
||||
var command = new ExtractPdfAttachmentsCommand { PdfStream = new MemoryStream(FakePdfBytes) };
|
||||
// ZIP magic bytes PK
|
||||
var expected = new byte[] { 0x50, 0x4B, 0x03, 0x04, 0x00, 0x00 };
|
||||
_mockProcessor.Setup(x => x.ExtractAttachmentsAsync(It.IsAny<Stream>())).ReturnsAsync(expected);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeEmpty();
|
||||
result[0].Should().Be(0x50, "ZIP starts with 'P'");
|
||||
result[1].Should().Be(0x4B, "ZIP continues with 'K'");
|
||||
_mockProcessor.Verify(x => x.ExtractAttachmentsAsync(It.IsAny<Stream>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_PdfWithNoAttachments_ThrowsNotFoundException()
|
||||
{
|
||||
// Arrange
|
||||
var command = new ExtractPdfAttachmentsCommand { PdfStream = new MemoryStream(FakePdfBytes) };
|
||||
_mockProcessor.Setup(x => x.ExtractAttachmentsAsync(It.IsAny<Stream>()))
|
||||
.ThrowsAsync(new NotFoundException("PDF does not contain any attachments"));
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => _handler.Handle(command, CancellationToken.None));
|
||||
ex.Message.Should().Contain("attachments");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_EmptyStream_ThrowsBadRequestException()
|
||||
{
|
||||
// Arrange
|
||||
var command = new ExtractPdfAttachmentsCommand { PdfStream = new MemoryStream(FakePdfBytes) };
|
||||
_mockProcessor.Setup(x => x.ExtractAttachmentsAsync(It.IsAny<Stream>()))
|
||||
.ThrowsAsync(new BadRequestException("PDF stream cannot be empty"));
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => _handler.Handle(command, CancellationToken.None));
|
||||
ex.Message.Should().Contain("empty");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ProcessorThrowsGenericException_PropagatesException()
|
||||
{
|
||||
// Arrange
|
||||
var command = new ExtractPdfAttachmentsCommand { PdfStream = new MemoryStream(FakePdfBytes) };
|
||||
_mockProcessor.Setup(x => x.ExtractAttachmentsAsync(It.IsAny<Stream>()))
|
||||
.ThrowsAsync(new Exception("Unexpected error"));
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<Exception>(() => _handler.Handle(command, CancellationToken.None));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using DocumentService.Application.Common.Interfaces;
|
||||
using DocumentService.Application.MergePdfs;
|
||||
using DocumentService.Domain.Common.Exceptions;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace DocumentService.Tests.Unit.Application.Features.MergePdfs;
|
||||
|
||||
public class MergePdfsCommandHandlerTests
|
||||
{
|
||||
private readonly Mock<IPdfProcessor> _mockProcessor;
|
||||
private readonly MergePdfsCommandHandler _handler;
|
||||
|
||||
public MergePdfsCommandHandlerTests()
|
||||
{
|
||||
_mockProcessor = new Mock<IPdfProcessor>();
|
||||
_handler = new MergePdfsCommandHandler(_mockProcessor.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_TwoStreams_ReturnsMergedBytes()
|
||||
{
|
||||
// Arrange
|
||||
var stream1 = new MemoryStream("%PDF-1"u8.ToArray());
|
||||
var stream2 = new MemoryStream("%PDF-2"u8.ToArray());
|
||||
var command = new MergePdfsCommand { PdfStreams = [stream1, stream2] };
|
||||
var expected = "%PDF-merged"u8.ToArray();
|
||||
|
||||
_mockProcessor.Setup(x => x.MergePdfsAsync(It.IsAny<IReadOnlyList<Stream>>(), null))
|
||||
.ReturnsAsync(expected);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull().And.NotBeEmpty();
|
||||
result.Should().BeEquivalentTo(expected);
|
||||
_mockProcessor.Verify(x => x.MergePdfsAsync(It.IsAny<IReadOnlyList<Stream>>(), null), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_WithPageRanges_PassesRangesToProcessor()
|
||||
{
|
||||
// Arrange
|
||||
var stream1 = new MemoryStream("%PDF-1"u8.ToArray());
|
||||
var stream2 = new MemoryStream("%PDF-2"u8.ToArray());
|
||||
var pageRanges = new List<string?> { "1-2", null };
|
||||
var command = new MergePdfsCommand { PdfStreams = [stream1, stream2], PageRanges = pageRanges };
|
||||
var expected = new byte[] { 0x25, 0x50, 0x44, 0x46 };
|
||||
|
||||
_mockProcessor.Setup(x => x.MergePdfsAsync(It.IsAny<IReadOnlyList<Stream>>(), pageRanges))
|
||||
.ReturnsAsync(expected);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Should().BeEquivalentTo(expected);
|
||||
_mockProcessor.Verify(x => x.MergePdfsAsync(It.IsAny<IReadOnlyList<Stream>>(), pageRanges), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ProcessorThrowsBadRequest_PropagatesException()
|
||||
{
|
||||
// Arrange
|
||||
var command = new MergePdfsCommand
|
||||
{
|
||||
PdfStreams = [new MemoryStream("%PDF-1"u8.ToArray()), new MemoryStream("%PDF-2"u8.ToArray())]
|
||||
};
|
||||
_mockProcessor.Setup(x => x.MergePdfsAsync(It.IsAny<IReadOnlyList<Stream>>(), null))
|
||||
.ThrowsAsync(new BadRequestException("At least 2 PDF files are required"));
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => _handler.Handle(command, CancellationToken.None));
|
||||
ex.Message.Should().Be("At least 2 PDF files are required");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ProcessorThrowsGenericException_PropagatesException()
|
||||
{
|
||||
// Arrange
|
||||
var command = new MergePdfsCommand
|
||||
{
|
||||
PdfStreams = [new MemoryStream("%PDF"u8.ToArray()), new MemoryStream("%PDF"u8.ToArray())]
|
||||
};
|
||||
_mockProcessor.Setup(x => x.MergePdfsAsync(It.IsAny<IReadOnlyList<Stream>>(), null))
|
||||
.ThrowsAsync(new InvalidOperationException("Unexpected error"));
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => _handler.Handle(command, CancellationToken.None));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using DocumentService.Application.Common.DTOs;
|
||||
using DocumentService.Application.Common.Interfaces;
|
||||
using DocumentService.Application.ValidatePdf.Queries;
|
||||
using DocumentService.Domain.Common.Exceptions;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace DocumentService.Tests.Unit.Application.Features.ValidatePdf;
|
||||
|
||||
public class ValidatePdfHandlerTests
|
||||
{
|
||||
private readonly Mock<IPdfProcessor> _mockPdfProcessor;
|
||||
private readonly ValidatePdfQueryHandler _handler;
|
||||
|
||||
public ValidatePdfHandlerTests()
|
||||
{
|
||||
_mockPdfProcessor = new Mock<IPdfProcessor>();
|
||||
_handler = new ValidatePdfQueryHandler(_mockPdfProcessor.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ValidPdf_ReturnsResult()
|
||||
{
|
||||
// Arrange
|
||||
var query = new ValidatePdfQuery { PdfStream = new MemoryStream("%PDF"u8.ToArray()) };
|
||||
var expected = new PdfValidationResult(5, 1024, 1024 / 1024.0 / 1024.0, "1.4", false, 0, false);
|
||||
|
||||
_mockPdfProcessor.Setup(x => x.ValidateAsync(It.IsAny<Stream>())).ReturnsAsync(expected);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(query, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.PageCount.Should().Be(5);
|
||||
result.FileSizeBytes.Should().Be(1024);
|
||||
result.PdfVersion.Should().Be("1.4");
|
||||
result.HasAttachments.Should().BeFalse();
|
||||
result.AttachmentCount.Should().Be(0);
|
||||
result.IsEncrypted.Should().BeFalse();
|
||||
_mockPdfProcessor.Verify(x => x.ValidateAsync(It.IsAny<Stream>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_EncryptedPdf_ReturnsIsEncryptedTrue()
|
||||
{
|
||||
// Arrange
|
||||
var query = new ValidatePdfQuery { PdfStream = new MemoryStream("%PDF"u8.ToArray()) };
|
||||
var expected = new PdfValidationResult(1, 512, 512 / 1024.0 / 1024.0, "1.7", false, 0, true);
|
||||
|
||||
_mockPdfProcessor.Setup(x => x.ValidateAsync(It.IsAny<Stream>())).ReturnsAsync(expected);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(query, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.IsEncrypted.Should().BeTrue();
|
||||
result.PageCount.Should().Be(1);
|
||||
_mockPdfProcessor.Verify(x => x.ValidateAsync(It.IsAny<Stream>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_PdfWithAttachments_ReturnsAttachmentData()
|
||||
{
|
||||
// Arrange
|
||||
var query = new ValidatePdfQuery { PdfStream = new MemoryStream("%PDF"u8.ToArray()) };
|
||||
var expected = new PdfValidationResult(2, 4096, 4096 / 1024.0 / 1024.0, "1.6", true, 3, false);
|
||||
|
||||
_mockPdfProcessor.Setup(x => x.ValidateAsync(It.IsAny<Stream>())).ReturnsAsync(expected);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(query, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.HasAttachments.Should().BeTrue();
|
||||
result.AttachmentCount.Should().Be(3);
|
||||
_mockPdfProcessor.Verify(x => x.ValidateAsync(It.IsAny<Stream>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ProcessorThrowsBadRequest_PropagatesException()
|
||||
{
|
||||
// Arrange
|
||||
var query = new ValidatePdfQuery { PdfStream = new MemoryStream("%PDF"u8.ToArray()) };
|
||||
_mockPdfProcessor.Setup(x => x.ValidateAsync(It.IsAny<Stream>()))
|
||||
.ThrowsAsync(new BadRequestException("Invalid PDF format"));
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => _handler.Handle(query, CancellationToken.None));
|
||||
ex.Message.Should().Be("Invalid PDF format");
|
||||
_mockPdfProcessor.Verify(x => x.ValidateAsync(It.IsAny<Stream>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ProcessorThrowsGenericException_PropagatesException()
|
||||
{
|
||||
// Arrange
|
||||
var query = new ValidatePdfQuery { PdfStream = new MemoryStream("%PDF"u8.ToArray()) };
|
||||
_mockPdfProcessor.Setup(x => x.ValidateAsync(It.IsAny<Stream>()))
|
||||
.ThrowsAsync(new InvalidOperationException("Unexpected error"));
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => _handler.Handle(query, CancellationToken.None));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
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<IPdfProcessor> _mockPdfProcessor;
|
||||
private readonly ValidatePdfAQueryHandler _handler;
|
||||
|
||||
public ValidatePdfAQueryHandlerTests()
|
||||
{
|
||||
_mockPdfProcessor = new Mock<IPdfProcessor>();
|
||||
_handler = new ValidatePdfAQueryHandler(_mockPdfProcessor.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ValidCompliantPdfA_ReturnsCompliantResult()
|
||||
{
|
||||
// Arrange
|
||||
var query = new ValidatePdfAQuery { PdfStream = new MemoryStream("%PDF"u8.ToArray()) };
|
||||
var expected = new PdfAValidationResult
|
||||
{
|
||||
IsValid = true, PdfVersion = "1.7", PageCount = 3, FileSize = 2048,
|
||||
Encrypted = false, PdfAVersion = "PDF/A-3b", PdfACompliant = true,
|
||||
Errors = [], Warnings = []
|
||||
};
|
||||
|
||||
_mockPdfProcessor.Setup(x => x.ValidatePdfAAsync(It.IsAny<Stream>())).ReturnsAsync(expected);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(query, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
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<Stream>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_NonCompliantPdfA_ReturnsErrorsAndWarnings()
|
||||
{
|
||||
// Arrange
|
||||
var query = new ValidatePdfAQuery { PdfStream = new MemoryStream("%PDF"u8.ToArray()) };
|
||||
var expected = new PdfAValidationResult
|
||||
{
|
||||
IsValid = false, PdfVersion = "1.4", PageCount = 2, FileSize = 1024,
|
||||
Encrypted = false, PdfAVersion = null, PdfACompliant = false,
|
||||
Errors = ["Missing XMP metadata", "Invalid color space"],
|
||||
Warnings = ["Embedded font not subset"]
|
||||
};
|
||||
|
||||
_mockPdfProcessor.Setup(x => x.ValidatePdfAAsync(It.IsAny<Stream>())).ReturnsAsync(expected);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(query, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.PdfACompliant.Should().BeFalse();
|
||||
result.PdfAVersion.Should().BeNull();
|
||||
result.Errors.Should().HaveCount(2).And.Contain("Missing XMP metadata").And.Contain("Invalid color space");
|
||||
result.Warnings.Should().HaveCount(1).And.Contain("Embedded font not subset");
|
||||
_mockPdfProcessor.Verify(x => x.ValidatePdfAAsync(It.IsAny<Stream>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_EncryptedPdf_ReturnsEncryptedFlagAndErrors()
|
||||
{
|
||||
// Arrange
|
||||
var query = new ValidatePdfAQuery { PdfStream = new MemoryStream("%PDF"u8.ToArray()) };
|
||||
var expected = new PdfAValidationResult
|
||||
{
|
||||
IsValid = false, PdfVersion = "1.7", PageCount = 1, FileSize = 512,
|
||||
Encrypted = true, PdfAVersion = null, PdfACompliant = false,
|
||||
Errors = ["PDF/A documents cannot be encrypted"],
|
||||
Warnings = []
|
||||
};
|
||||
|
||||
_mockPdfProcessor.Setup(x => x.ValidatePdfAAsync(It.IsAny<Stream>())).ReturnsAsync(expected);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(query, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Encrypted.Should().BeTrue();
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.PdfACompliant.Should().BeFalse();
|
||||
result.Errors.Should().Contain("PDF/A documents cannot be encrypted");
|
||||
_mockPdfProcessor.Verify(x => x.ValidatePdfAAsync(It.IsAny<Stream>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ProcessorThrowsBadRequest_PropagatesException()
|
||||
{
|
||||
// Arrange
|
||||
var query = new ValidatePdfAQuery { PdfStream = new MemoryStream("%PDF"u8.ToArray()) };
|
||||
_mockPdfProcessor.Setup(x => x.ValidatePdfAAsync(It.IsAny<Stream>()))
|
||||
.ThrowsAsync(new BadRequestException("Invalid PDF format"));
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => _handler.Handle(query, CancellationToken.None));
|
||||
ex.Message.Should().Be("Invalid PDF format");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ProcessorThrowsGenericException_PropagatesException()
|
||||
{
|
||||
// Arrange
|
||||
var query = new ValidatePdfAQuery { PdfStream = new MemoryStream("%PDF"u8.ToArray()) };
|
||||
_mockPdfProcessor.Setup(x => x.ValidatePdfAAsync(It.IsAny<Stream>()))
|
||||
.ThrowsAsync(new Exception("Unexpected processing error"));
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<Exception>(() => _handler.Handle(query, CancellationToken.None));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
using DocumentService.Application.AddAnnotation;
|
||||
using DocumentService.Application.AddStamp;
|
||||
using DocumentService.Application.ConvertToPdfA;
|
||||
using DocumentService.Application.MergePdfs;
|
||||
using DocumentService.Domain.Models.ValueObjects;
|
||||
using FluentAssertions;
|
||||
using FluentValidation;
|
||||
using FluentValidation.TestHelper;
|
||||
using Xunit;
|
||||
|
||||
namespace DocumentService.Tests.Unit.Application.Validators;
|
||||
|
||||
// ─── MergePdfsCommandValidator ────────────────────────────────────────────────
|
||||
|
||||
public class MergePdfsCommandValidatorTests
|
||||
{
|
||||
private readonly MergePdfsCommandValidator _validator = new();
|
||||
|
||||
[Fact]
|
||||
public void Validate_TwoOrMoreStreams_PassesValidation()
|
||||
{
|
||||
var cmd = new MergePdfsCommand
|
||||
{
|
||||
PdfStreams = [new MemoryStream(), new MemoryStream()]
|
||||
};
|
||||
_validator.TestValidate(cmd).ShouldNotHaveAnyValidationErrors();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_SingleStream_FailsValidation()
|
||||
{
|
||||
var cmd = new MergePdfsCommand { PdfStreams = [new MemoryStream()] };
|
||||
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.PdfStreams);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_NullStreams_FailsValidation()
|
||||
{
|
||||
var cmd = new MergePdfsCommand { PdfStreams = null! };
|
||||
var result = _validator.TestValidate(cmd);
|
||||
result.Errors.Should().NotBeEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── AddAnnotationValidator ───────────────────────────────────────────────────
|
||||
|
||||
public class AddAnnotationValidatorTests
|
||||
{
|
||||
private readonly AddAnnotationValidator _validator = new();
|
||||
|
||||
[Fact]
|
||||
public void Validate_ValidCircleAnnotation_PassesValidation()
|
||||
{
|
||||
var cmd = new AddAnnotationCommand
|
||||
{
|
||||
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
|
||||
AnnotationType = AnnotationType.Circle,
|
||||
PageNumber = 1,
|
||||
Rectangle = (10, 20, 200, 100)
|
||||
};
|
||||
_validator.TestValidate(cmd).ShouldNotHaveAnyValidationErrors();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_FreeTextWithoutContent_FailsValidation()
|
||||
{
|
||||
var cmd = new AddAnnotationCommand
|
||||
{
|
||||
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
|
||||
AnnotationType = AnnotationType.FreeText,
|
||||
PageNumber = 1,
|
||||
Rectangle = (0, 0, 100, 50),
|
||||
Content = null
|
||||
};
|
||||
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_TextMarkupWithoutStyle_FailsValidation()
|
||||
{
|
||||
var cmd = new AddAnnotationCommand
|
||||
{
|
||||
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
|
||||
AnnotationType = AnnotationType.TextMarkup,
|
||||
PageNumber = 1,
|
||||
Rectangle = (0, 0, 100, 50),
|
||||
TextMarkupStyle = null
|
||||
};
|
||||
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.TextMarkupStyle);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_InvalidColorFormat_FailsValidation()
|
||||
{
|
||||
var cmd = new AddAnnotationCommand
|
||||
{
|
||||
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
|
||||
AnnotationType = AnnotationType.Circle,
|
||||
PageNumber = 1,
|
||||
Rectangle = (0, 0, 100, 50),
|
||||
Color = "GGGGGG" // invalid hex
|
||||
};
|
||||
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.Color);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_ValidColorFormat_PassesValidation()
|
||||
{
|
||||
var cmd = new AddAnnotationCommand
|
||||
{
|
||||
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
|
||||
AnnotationType = AnnotationType.Circle,
|
||||
PageNumber = 1,
|
||||
Rectangle = (0, 0, 100, 50),
|
||||
Color = "FF0000"
|
||||
};
|
||||
_validator.TestValidate(cmd).ShouldNotHaveAnyValidationErrors();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_ZeroPageNumber_FailsValidation()
|
||||
{
|
||||
var cmd = new AddAnnotationCommand
|
||||
{
|
||||
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
|
||||
AnnotationType = AnnotationType.Circle,
|
||||
PageNumber = 0,
|
||||
Rectangle = (0, 0, 100, 50)
|
||||
};
|
||||
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.PageNumber);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_InvalidRectangle_FailsValidation()
|
||||
{
|
||||
var cmd = new AddAnnotationCommand
|
||||
{
|
||||
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
|
||||
AnnotationType = AnnotationType.Circle,
|
||||
PageNumber = 1,
|
||||
Rectangle = (100, 100, 50, 50) // X2 < X1
|
||||
};
|
||||
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.Rectangle);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── AddStampValidator ────────────────────────────────────────────────────────
|
||||
|
||||
public class AddStampValidatorTests
|
||||
{
|
||||
private readonly AddStampValidator _validator = new();
|
||||
|
||||
[Fact]
|
||||
public void Validate_ValidTextStamp_PassesValidation()
|
||||
{
|
||||
var cmd = new AddStampCommand
|
||||
{
|
||||
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
|
||||
StampType = StampType.Text,
|
||||
Position = (100, 200),
|
||||
Text = "DRAFT"
|
||||
};
|
||||
_validator.TestValidate(cmd).ShouldNotHaveAnyValidationErrors();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_TextStampWithoutText_FailsValidation()
|
||||
{
|
||||
var cmd = new AddStampCommand
|
||||
{
|
||||
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
|
||||
StampType = StampType.Text,
|
||||
Position = (100, 200),
|
||||
Text = null
|
||||
};
|
||||
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_ImageStampWithoutBytes_FailsValidation()
|
||||
{
|
||||
var cmd = new AddStampCommand
|
||||
{
|
||||
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
|
||||
StampType = StampType.Image,
|
||||
Position = (0, 0),
|
||||
ImageBytes = null
|
||||
};
|
||||
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.ImageBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_PredefinedStampWithoutType_FailsValidation()
|
||||
{
|
||||
var cmd = new AddStampCommand
|
||||
{
|
||||
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
|
||||
StampType = StampType.Predefined,
|
||||
Position = (0, 0),
|
||||
PredefinedType = null
|
||||
};
|
||||
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.PredefinedType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_OpacityAboveOne_FailsValidation()
|
||||
{
|
||||
var cmd = new AddStampCommand
|
||||
{
|
||||
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
|
||||
StampType = StampType.Text,
|
||||
Position = (0, 0),
|
||||
Text = "TEST",
|
||||
Opacity = 1.5
|
||||
};
|
||||
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.Opacity!.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_InvalidColorHex_FailsValidation()
|
||||
{
|
||||
var cmd = new AddStampCommand
|
||||
{
|
||||
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
|
||||
StampType = StampType.Text,
|
||||
Position = (0, 0),
|
||||
Text = "TEST",
|
||||
Color = "XYZ"
|
||||
};
|
||||
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.Color!);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── ConvertToPdfACommandValidator ───────────────────────────────────────────
|
||||
|
||||
public class ConvertToPdfAValidatorTests
|
||||
{
|
||||
private readonly ConvertToPdfACommandValidator _validator = new();
|
||||
|
||||
[Fact]
|
||||
public void Validate_ValidPdfALevel_PassesValidation()
|
||||
{
|
||||
foreach (var level in new[] { "PDF/A-1b", "PDF/A-2b", "PDF/A-3b", "PDF/A-3u" })
|
||||
{
|
||||
var cmd = new ConvertToPdfACommand { PdfStream = new MemoryStream(), PdfALevel = level };
|
||||
_validator.TestValidate(cmd).ShouldNotHaveAnyValidationErrors();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_InvalidPdfALevel_FailsValidation()
|
||||
{
|
||||
var cmd = new ConvertToPdfACommand { PdfStream = new MemoryStream(), PdfALevel = "PDF/A-99z" };
|
||||
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.PdfALevel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_EmptyPdfALevel_FailsValidation()
|
||||
{
|
||||
var cmd = new ConvertToPdfACommand { PdfStream = new MemoryStream(), PdfALevel = "" };
|
||||
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.PdfALevel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_NullStream_FailsValidation()
|
||||
{
|
||||
var cmd = new ConvertToPdfACommand { PdfStream = null!, PdfALevel = "PDF/A-1b" };
|
||||
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.PdfStream);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user