diff --git a/DocumentOperator.Tests/Unit/Infrastructure/Services/PdfProcessing/DevExpressPdfProcessorTests.cs b/DocumentOperator.Tests/Unit/Infrastructure/Services/PdfProcessing/DevExpressPdfProcessorTests.cs index 7aed60b..c6616b8 100644 --- a/DocumentOperator.Tests/Unit/Infrastructure/Services/PdfProcessing/DevExpressPdfProcessorTests.cs +++ b/DocumentOperator.Tests/Unit/Infrastructure/Services/PdfProcessing/DevExpressPdfProcessorTests.cs @@ -565,5 +565,252 @@ public class DevExpressPdfProcessorTests } #endregion + + #region AddAnnotationAsync Tests + + [Fact] + public async Task AddAnnotationAsync_TextMarkupHighlight_ReturnsAnnotatedPdf() + { + // Arrange + using var pdfStream = LoadTestPdfAsStream("valid.pdf"); + var annotationType = Domain.Models.ValueObjects.AnnotationType.TextMarkup; + var rectangle = (X1: 100.0, Y1: 100.0, X2: 200.0, Y2: 120.0); + var textMarkupStyle = Domain.Models.ValueObjects.TextMarkupStyle.Highlight; + + // Act + byte[] result = await _sut.AddAnnotationAsync( + pdfStream, + annotationType, + pageNumber: 1, + rectangle, + content: "Important text", + author: "Test Author", + color: "FFFF00", // Yellow + textMarkupStyle); + + // Assert + result.Should().NotBeEmpty(); + result.Length.Should().BeGreaterThan((int)pdfStream.Length); // Annotation adds bytes + } + + [Fact] + public async Task AddAnnotationAsync_FreeText_ReturnsAnnotatedPdf() + { + // Arrange + using var pdfStream = LoadTestPdfAsStream("valid.pdf"); + var annotationType = Domain.Models.ValueObjects.AnnotationType.FreeText; + var rectangle = (X1: 50.0, Y1: 50.0, X2: 150.0, Y2: 100.0); + + // Act + byte[] result = await _sut.AddAnnotationAsync( + pdfStream, + annotationType, + pageNumber: 1, + rectangle, + content: "Free text annotation", + author: "John Doe", + color: "FF0000"); // Red + + // Assert + result.Should().NotBeEmpty(); + result.Length.Should().BeGreaterThan((int)pdfStream.Length); + } + + [Fact] + public async Task AddAnnotationAsync_StickyNote_ReturnsAnnotatedPdf() + { + // Arrange + using var pdfStream = LoadTestPdfAsStream("valid.pdf"); + var annotationType = Domain.Models.ValueObjects.AnnotationType.StickyNote; + var rectangle = (X1: 300.0, Y1: 300.0, X2: 320.0, Y2: 320.0); + + // Act + byte[] result = await _sut.AddAnnotationAsync( + pdfStream, + annotationType, + pageNumber: 1, + rectangle, + content: "Please review this section", + author: "Reviewer"); + + // Assert + result.Should().NotBeEmpty(); + } + + [Fact] + public async Task AddAnnotationAsync_Circle_ReturnsAnnotatedPdf() + { + // Arrange + using var pdfStream = LoadTestPdfAsStream("valid.pdf"); + var annotationType = Domain.Models.ValueObjects.AnnotationType.Circle; + var rectangle = (X1: 100.0, Y1: 200.0, X2: 200.0, Y2: 300.0); + + // Act + byte[] result = await _sut.AddAnnotationAsync( + pdfStream, + annotationType, + pageNumber: 1, + rectangle, + content: "Circle annotation", + color: "00FF00"); // Green + + // Assert + result.Should().NotBeEmpty(); + } + + [Fact] + public async Task AddAnnotationAsync_Square_ReturnsAnnotatedPdf() + { + // Arrange + using var pdfStream = LoadTestPdfAsStream("valid.pdf"); + var annotationType = Domain.Models.ValueObjects.AnnotationType.Square; + var rectangle = (X1: 250.0, Y1: 250.0, X2: 350.0, Y2: 350.0); + + // Act + byte[] result = await _sut.AddAnnotationAsync( + pdfStream, + annotationType, + pageNumber: 1, + rectangle, + color: "0000FF"); // Blue + + // Assert + result.Should().NotBeEmpty(); + } + + [Fact] + public async Task AddAnnotationAsync_EmptyStream_ThrowsBadRequestException() + { + // Arrange + using var emptyStream = new MemoryStream(); + + // Act + var act = async () => await _sut.AddAnnotationAsync( + emptyStream, + Domain.Models.ValueObjects.AnnotationType.FreeText, + pageNumber: 1, + rectangle: (0, 0, 100, 100), + content: "Test"); + + // Assert + await act.Should().ThrowAsync() + .WithMessage("*cannot be empty*"); + } + + [Fact] + public async Task AddAnnotationAsync_StreamNotAtPositionZero_ThrowsBadRequestException() + { + // Arrange + using var pdfStream = LoadTestPdfAsStream("valid.pdf"); + pdfStream.Position = 10; // Move stream position + + // Act + var act = async () => await _sut.AddAnnotationAsync( + pdfStream, + Domain.Models.ValueObjects.AnnotationType.Circle, + pageNumber: 1, + rectangle: (0, 0, 100, 100)); + + // Assert + await act.Should().ThrowAsync() + .WithMessage("*must be at position 0*"); + } + + [Fact] + public async Task AddAnnotationAsync_InvalidPageNumber_ThrowsBadRequestException() + { + // Arrange + using var pdfStream = LoadTestPdfAsStream("valid.pdf"); + + // Act + var act = async () => await _sut.AddAnnotationAsync( + pdfStream, + Domain.Models.ValueObjects.AnnotationType.FreeText, + pageNumber: 999, // Exceeds page count + rectangle: (0, 0, 100, 100), + content: "Test"); + + // Assert + await act.Should().ThrowAsync() + .WithMessage("*exceeds document page count*"); + } + + [Fact] + public async Task AddAnnotationAsync_FreeTextWithoutContent_ThrowsBadRequestException() + { + // Arrange + using var pdfStream = LoadTestPdfAsStream("valid.pdf"); + + // Act + var act = async () => await _sut.AddAnnotationAsync( + pdfStream, + Domain.Models.ValueObjects.AnnotationType.FreeText, + pageNumber: 1, + rectangle: (0, 0, 100, 100), + content: null); // Missing required content + + // Assert + await act.Should().ThrowAsync() + .WithMessage("*requires content*"); + } + + [Fact] + public async Task AddAnnotationAsync_StickyNoteWithoutContent_ThrowsBadRequestException() + { + // Arrange + using var pdfStream = LoadTestPdfAsStream("valid.pdf"); + + // Act + var act = async () => await _sut.AddAnnotationAsync( + pdfStream, + Domain.Models.ValueObjects.AnnotationType.StickyNote, + pageNumber: 1, + rectangle: (0, 0, 20, 20), + content: ""); // Empty content + + // Assert + await act.Should().ThrowAsync() + .WithMessage("*requires content*"); + } + + [Fact] + public async Task AddAnnotationAsync_TextMarkupWithoutStyle_ThrowsBadRequestException() + { + // Arrange + using var pdfStream = LoadTestPdfAsStream("valid.pdf"); + + // Act + var act = async () => await _sut.AddAnnotationAsync( + pdfStream, + Domain.Models.ValueObjects.AnnotationType.TextMarkup, + pageNumber: 1, + rectangle: (0, 0, 100, 100), + textMarkupStyle: null); // Missing required style + + // Assert + await act.Should().ThrowAsync() + .WithMessage("*requires textMarkupStyle*"); + } + + [Fact] + public async Task AddAnnotationAsync_InvalidColorFormat_ThrowsBadRequestException() + { + // Arrange + using var pdfStream = LoadTestPdfAsStream("valid.pdf"); + + // Act + var act = async () => await _sut.AddAnnotationAsync( + pdfStream, + Domain.Models.ValueObjects.AnnotationType.Circle, + pageNumber: 1, + rectangle: (0, 0, 100, 100), + color: "INVALID"); // Invalid hex format + + // Assert + await act.Should().ThrowAsync() + .WithMessage("*Invalid color format*"); + } + + #endregion }