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 _mockProcessor; private readonly AddAnnotationHandler _handler; private static readonly byte[] FakePdfBytes = "%PDF"u8.ToArray(); public AddAnnotationHandlerTests() { _mockProcessor = new Mock(); _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(), 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(), 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(), 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(), It.IsAny(), It.IsAny(), It.IsAny<(double, double, double, double)>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ThrowsAsync(new BadRequestException("Page number 99 exceeds document page count")); // Act & Assert var ex = await Assert.ThrowsAsync( () => _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(), It.IsAny(), It.IsAny(), It.IsAny<(double, double, double, double)>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ThrowsAsync(new Exception("Unexpected error")); // Act & Assert await Assert.ThrowsAsync(() => _handler.Handle(command, CancellationToken.None)); } }