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 _mockProcessor; private readonly AddStampHandler _handler; private static readonly byte[] FakePdfBytes = "%PDF"u8.ToArray(); public AddStampHandlerTests() { _mockProcessor = new Mock(); _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(), 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(), 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(), 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(), It.IsAny(), It.IsAny(), It.IsAny<(double, double)>(), It.IsAny<(double, double)?>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ThrowsAsync(new BadRequestException("Opacity must be between 0.0 and 1.0")); // Act & Assert var ex = await Assert.ThrowsAsync( () => _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(), It.IsAny(), It.IsAny(), It.IsAny<(double, double)>(), It.IsAny<(double, double)?>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ThrowsAsync(new Exception("Unexpected")); // Act & Assert await Assert.ThrowsAsync(() => _handler.Handle(command, CancellationToken.None)); } }