Consolidated DTOs under `DocumentService.Application.Common.DTOs.Requests` to centralize and align them with the application layer. Introduced `PdfBase64RequestBase` to encapsulate shared properties, reducing redundancy across request DTOs. Updated controllers, clients, and tests to use the new DTO structure. Removed redundant DTOs and cleaned up unused namespaces and comments. Improved documentation and ensured consistent naming conventions across the codebase.
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 DocumentService.Application.Common.DTOs.Requests;
|
|
using DocumentService.Domain.Models.ValueObjects;
|
|
using FluentAssertions;
|
|
using Microsoft.AspNetCore.Mvc.Testing;
|
|
|
|
namespace DocumentService.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 = $"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<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 AddAnnotationBase64Request
|
|
{
|
|
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 AddAnnotationBase64Request
|
|
{
|
|
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 AddAnnotationBase64Request
|
|
{
|
|
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 AddAnnotationBase64Request
|
|
{
|
|
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 AddAnnotationBase64Request
|
|
{
|
|
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 AddAnnotationBase64Request
|
|
{
|
|
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 AddAnnotationBase64Request
|
|
{
|
|
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 AddAnnotationBase64Request
|
|
{
|
|
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
|
|
}
|