Integration tests: - 5 happy path tests (TextMarkup, FreeText, StickyNote, Circle, Square) with multipart + Base64 mix - 5 validation error tests (invalid Base64, page number, missing content/style, invalid color) - All existing 7 merge tests retained (now 17 total in PdfOperationsControllerTests) Documentation updates (AGENTS.md): - Update test count: 82 -> 101 passed, 7 skipped - Update PdfOperationsController status: 1/N -> 2/3 endpoints (merge + annotate DONE, stamp TODO) - Add test breakdown by feature (6 features listed) - Update 'Run tests' section with Feature 6 mention Test results: 101 PASSED, 7 SKIPPED, 0 FAILED
501 lines
17 KiB
C#
501 lines
17 KiB
C#
using System.Net;
|
|
using System.Net.Http.Headers;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using DocumentOperator.API.Controllers;
|
|
using DocumentOperator.Domain.Models.ValueObjects;
|
|
using FluentAssertions;
|
|
using Microsoft.AspNetCore.Mvc.Testing;
|
|
|
|
namespace DocumentOperator.Tests.Integration.API;
|
|
|
|
public class PdfOperationsControllerTests : IClassFixture<WebApplicationFactory<Program>>
|
|
{
|
|
private readonly HttpClient _client;
|
|
|
|
public PdfOperationsControllerTests(WebApplicationFactory<Program> factory)
|
|
{
|
|
_client = factory.CreateClient();
|
|
}
|
|
|
|
private static Stream LoadTestPdfAsStream(string fileName)
|
|
{
|
|
var assembly = Assembly.GetExecutingAssembly();
|
|
var resourceName = $"DocumentOperator.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<string> { 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<string> { base64Pdf1, base64Pdf2 },
|
|
PageRanges = new List<string?> { "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<string> { "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<string> { base64Pdf, base64Pdf },
|
|
PageRanges = new List<string?> { "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
|
|
}
|