using System.Net; using System.Net.Http.Headers; using System.Reflection; using System.Text; using System.Text.Json; using DocumentService.API.Controllers; using DocumentService.Domain.Models.ValueObjects; using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; namespace DocumentService.Tests.Integration.API; public class PdfOperationsControllerTests : IClassFixture> { private readonly HttpClient _client; public PdfOperationsControllerTests(WebApplicationFactory factory) { _client = factory.CreateClient(); } private static Stream LoadTestPdfAsStream(string fileName) { var assembly = Assembly.GetExecutingAssembly(); var resourceName = $"DocumentService.Tests.TestData.Pdfs.{fileName}"; return assembly.GetManifestResourceStream(resourceName) ?? throw new FileNotFoundException($"Embedded resource not found: {resourceName}"); } private static string LoadTestPdfAsBase64(string fileName) { using var stream = LoadTestPdfAsStream(fileName); using var ms = new MemoryStream(); stream.CopyTo(ms); return Convert.ToBase64String(ms.ToArray()); } #region Merge Endpoint Tests (existing - keeping for reference) [Fact] public async Task MergeFromFiles_ValidPdfs_ReturnsMergedPdf() { // Arrange using var content = new MultipartFormDataContent(); using var pdf1Stream = LoadTestPdfAsStream("valid.pdf"); using var pdf2Stream = LoadTestPdfAsStream("valid.pdf"); var pdf1Content = new StreamContent(pdf1Stream); var pdf2Content = new StreamContent(pdf2Stream); pdf1Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); pdf2Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); content.Add(pdf1Content, "files", "file1.pdf"); content.Add(pdf2Content, "files", "file2.pdf"); // Act var response = await _client.PostAsync("/api/pdf/operations/merge", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); response.Content.Headers.ContentType?.MediaType.Should().Be("application/pdf"); byte[] mergedPdf = await response.Content.ReadAsByteArrayAsync(); mergedPdf.Should().NotBeEmpty(); mergedPdf.Length.Should().BeGreaterThan(100); // Sanity check } [Fact] public async Task MergeFromBase64_ValidPdfs_ReturnsMergedPdf() { // Arrange string base64Pdf1 = LoadTestPdfAsBase64("valid.pdf"); string base64Pdf2 = LoadTestPdfAsBase64("valid.pdf"); var request = new MergePdfsBase64Request { Base64Pdfs = new List { base64Pdf1, base64Pdf2 }, PageRanges = null }; var json = JsonSerializer.Serialize(request); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/api/pdf/operations/merge", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); byte[] mergedPdf = await response.Content.ReadAsByteArrayAsync(); mergedPdf.Should().NotBeEmpty(); } [Fact] public async Task MergeFromBase64_WithPageRanges_ReturnsMergedPdf() { // Arrange string base64Pdf1 = LoadTestPdfAsBase64("valid.pdf"); string base64Pdf2 = LoadTestPdfAsBase64("valid.pdf"); var request = new MergePdfsBase64Request { Base64Pdfs = new List { base64Pdf1, base64Pdf2 }, PageRanges = new List { "1", "1" } // Only first page from each }; var json = JsonSerializer.Serialize(request); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/api/pdf/operations/merge", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); byte[] mergedPdf = await response.Content.ReadAsByteArrayAsync(); mergedPdf.Should().NotBeEmpty(); } [Fact] public async Task MergeFromFiles_OnePdf_Returns400() { // Arrange using var content = new MultipartFormDataContent(); using var pdfStream = LoadTestPdfAsStream("valid.pdf"); var pdfContent = new StreamContent(pdfStream); pdfContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); content.Add(pdfContent, "files", "file1.pdf"); // Act var response = await _client.PostAsync("/api/pdf/operations/merge", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.BadRequest); } [Fact] public async Task MergeFromBase64_InvalidBase64_Returns400() { // Arrange var request = new MergePdfsBase64Request { Base64Pdfs = new List { "INVALID_BASE64!!!", "ANOTHER_INVALID" } }; var json = JsonSerializer.Serialize(request); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/api/pdf/operations/merge", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.BadRequest); } [Fact] public async Task MergeFromBase64_InvalidPageRange_Returns400() { // Arrange string base64Pdf = LoadTestPdfAsBase64("valid.pdf"); var request = new MergePdfsBase64Request { Base64Pdfs = new List { base64Pdf, base64Pdf }, PageRanges = new List { "999-1000", null } // Exceeds page count }; var json = JsonSerializer.Serialize(request); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/api/pdf/operations/merge", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.BadRequest); } [Fact] public async Task MergeFromFiles_CorruptedPdf_Returns500() { // Arrange using var content = new MultipartFormDataContent(); byte[] corruptedData = "NOT A PDF FILE"u8.ToArray(); var corruptedContent = new ByteArrayContent(corruptedData); corruptedContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); using var validPdfStream = LoadTestPdfAsStream("valid.pdf"); var validContent = new StreamContent(validPdfStream); validContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); content.Add(corruptedContent, "files", "corrupted.pdf"); content.Add(validContent, "files", "valid.pdf"); // Act var response = await _client.PostAsync("/api/pdf/operations/merge", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.InternalServerError); } #endregion #region Annotate Endpoint Tests [Fact] public async Task AnnotateFromFile_TextMarkupHighlight_ReturnsAnnotatedPdf() { // Arrange using var content = new MultipartFormDataContent(); using var pdfStream = LoadTestPdfAsStream("valid.pdf"); var pdfContent = new StreamContent(pdfStream); pdfContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); content.Add(pdfContent, "file", "test.pdf"); content.Add(new StringContent(AnnotationType.TextMarkup.ToString()), "annotationType"); content.Add(new StringContent("1"), "pageNumber"); content.Add(new StringContent("100"), "x1"); content.Add(new StringContent("100"), "y1"); content.Add(new StringContent("200"), "x2"); content.Add(new StringContent("120"), "y2"); content.Add(new StringContent("Important text"), "content"); content.Add(new StringContent("Test Author"), "author"); content.Add(new StringContent("FFFF00"), "color"); content.Add(new StringContent(TextMarkupStyle.Highlight.ToString()), "textMarkupStyle"); // Act var response = await _client.PostAsync("/api/pdf/operations/annotate", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); response.Content.Headers.ContentType?.MediaType.Should().Be("application/pdf"); byte[] annotatedPdf = await response.Content.ReadAsByteArrayAsync(); annotatedPdf.Should().NotBeEmpty(); annotatedPdf.Length.Should().BeGreaterThan(100); } [Fact] public async Task AnnotateFromBase64_FreeText_ReturnsAnnotatedPdf() { // Arrange string base64Pdf = LoadTestPdfAsBase64("valid.pdf"); var request = new AddAnnotationBase64Command { Base64Pdf = base64Pdf, AnnotationType = AnnotationType.FreeText, PageNumber = 1, X1 = 50, Y1 = 50, X2 = 150, Y2 = 100, Content = "Free text annotation", Author = "John Doe", Color = "FF0000" }; var json = JsonSerializer.Serialize(request); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/api/pdf/operations/annotate", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); byte[] annotatedPdf = await response.Content.ReadAsByteArrayAsync(); annotatedPdf.Should().NotBeEmpty(); } [Fact] public async Task AnnotateFromBase64_StickyNote_ReturnsAnnotatedPdf() { // Arrange string base64Pdf = LoadTestPdfAsBase64("valid.pdf"); var request = new AddAnnotationBase64Command { Base64Pdf = base64Pdf, AnnotationType = AnnotationType.StickyNote, PageNumber = 1, X1 = 300, Y1 = 300, X2 = 320, Y2 = 320, Content = "Please review this section", Author = "Reviewer" }; var json = JsonSerializer.Serialize(request); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/api/pdf/operations/annotate", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); byte[] annotatedPdf = await response.Content.ReadAsByteArrayAsync(); annotatedPdf.Should().NotBeEmpty(); } [Fact] public async Task AnnotateFromFile_Circle_ReturnsAnnotatedPdf() { // Arrange using var content = new MultipartFormDataContent(); using var pdfStream = LoadTestPdfAsStream("valid.pdf"); var pdfContent = new StreamContent(pdfStream); pdfContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); content.Add(pdfContent, "file", "test.pdf"); content.Add(new StringContent(AnnotationType.Circle.ToString()), "annotationType"); content.Add(new StringContent("1"), "pageNumber"); content.Add(new StringContent("100"), "x1"); content.Add(new StringContent("200"), "y1"); content.Add(new StringContent("200"), "x2"); content.Add(new StringContent("300"), "y2"); content.Add(new StringContent("Circle annotation"), "content"); content.Add(new StringContent("00FF00"), "color"); // Act var response = await _client.PostAsync("/api/pdf/operations/annotate", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); byte[] annotatedPdf = await response.Content.ReadAsByteArrayAsync(); annotatedPdf.Should().NotBeEmpty(); } [Fact] public async Task AnnotateFromBase64_Square_ReturnsAnnotatedPdf() { // Arrange string base64Pdf = LoadTestPdfAsBase64("valid.pdf"); var request = new AddAnnotationBase64Command { Base64Pdf = base64Pdf, AnnotationType = AnnotationType.Square, PageNumber = 1, X1 = 250, Y1 = 250, X2 = 350, Y2 = 350, Color = "0000FF" }; var json = JsonSerializer.Serialize(request); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/api/pdf/operations/annotate", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); byte[] annotatedPdf = await response.Content.ReadAsByteArrayAsync(); annotatedPdf.Should().NotBeEmpty(); } [Fact] public async Task AnnotateFromBase64_InvalidBase64_Returns400() { // Arrange var request = new AddAnnotationBase64Command { Base64Pdf = "INVALID_BASE64!!!", AnnotationType = AnnotationType.Circle, PageNumber = 1, X1 = 0, Y1 = 0, X2 = 100, Y2 = 100 }; var json = JsonSerializer.Serialize(request); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/api/pdf/operations/annotate", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.BadRequest); } [Fact] public async Task AnnotateFromBase64_InvalidPageNumber_Returns400() { // Arrange string base64Pdf = LoadTestPdfAsBase64("valid.pdf"); var request = new AddAnnotationBase64Command { Base64Pdf = base64Pdf, AnnotationType = AnnotationType.FreeText, PageNumber = 999, // Exceeds page count X1 = 0, Y1 = 0, X2 = 100, Y2 = 100, Content = "Test" }; var json = JsonSerializer.Serialize(request); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/api/pdf/operations/annotate", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.BadRequest); } [Fact] public async Task AnnotateFromBase64_FreeTextWithoutContent_Returns400() { // Arrange string base64Pdf = LoadTestPdfAsBase64("valid.pdf"); var request = new AddAnnotationBase64Command { Base64Pdf = base64Pdf, AnnotationType = AnnotationType.FreeText, PageNumber = 1, X1 = 0, Y1 = 0, X2 = 100, Y2 = 100 // Missing required Content }; var json = JsonSerializer.Serialize(request); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/api/pdf/operations/annotate", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.BadRequest); } [Fact] public async Task AnnotateFromBase64_TextMarkupWithoutStyle_Returns400() { // Arrange string base64Pdf = LoadTestPdfAsBase64("valid.pdf"); var request = new AddAnnotationBase64Command { Base64Pdf = base64Pdf, AnnotationType = AnnotationType.TextMarkup, PageNumber = 1, X1 = 0, Y1 = 0, X2 = 100, Y2 = 100 // Missing required TextMarkupStyle }; var json = JsonSerializer.Serialize(request); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/api/pdf/operations/annotate", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.BadRequest); } [Fact] public async Task AnnotateFromBase64_InvalidColorFormat_Returns400() { // Arrange string base64Pdf = LoadTestPdfAsBase64("valid.pdf"); var request = new AddAnnotationBase64Command { Base64Pdf = base64Pdf, AnnotationType = AnnotationType.Circle, PageNumber = 1, X1 = 0, Y1 = 0, X2 = 100, Y2 = 100, Color = "INVALID" // Invalid hex format }; var json = JsonSerializer.Serialize(request); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/api/pdf/operations/annotate", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.BadRequest); } #endregion }