This commit implements a complete rebranding of the project: - Updated all namespaces from `DocumentOperator` to `DocumentService`. - Renamed file paths, embedded resources, and test data references. - Updated configuration keys, logging paths, and Redis instance names. - Revised documentation to reflect the new project name. - Modified project and solution files to align with the new structure. - Updated class names, DTOs, commands, queries, and handlers. - Adjusted middleware, controllers, and API endpoints. - Updated Swagger metadata and API titles to `DocumentService API`. - Refactored test namespaces, resource paths, and embedded resources. - Updated build and deployment configurations for the new name. - Replaced all references to `DocumentOperator` in comments and literals. These changes ensure consistency across the codebase and documentation.
384 lines
14 KiB
C#
384 lines
14 KiB
C#
using DocumentService.API.Controllers; // For CheckPdfAttachmentsRequest DTO
|
|
using DocumentService.Application.Common.DTOs;
|
|
using FluentAssertions;
|
|
using Microsoft.AspNetCore.Mvc.Testing;
|
|
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using Xunit;
|
|
|
|
namespace DocumentService.Tests.Integration.API;
|
|
|
|
/// <summary>
|
|
/// Integration tests for PdfAttachmentController.
|
|
/// Tests /api/pdf/attachments/check endpoint with both multipart and Base64 input.
|
|
/// </summary>
|
|
public class PdfAttachmentControllerTests : IClassFixture<WebApplicationFactory<Program>>
|
|
{
|
|
private readonly WebApplicationFactory<Program> _factory;
|
|
private readonly HttpClient _client;
|
|
|
|
public PdfAttachmentControllerTests(WebApplicationFactory<Program> factory)
|
|
{
|
|
_factory = factory;
|
|
_client = _factory.CreateClient();
|
|
}
|
|
|
|
#region Helper Methods
|
|
|
|
/// <summary>
|
|
/// Loads a test PDF from embedded resources.
|
|
/// </summary>
|
|
private static async Task<byte[]> LoadTestPdfAsync(string filename)
|
|
{
|
|
var assembly = typeof(PdfAttachmentControllerTests).Assembly;
|
|
var resourceName = $"DocumentService.Tests.TestData.Pdfs.{filename}";
|
|
|
|
using var stream = assembly.GetManifestResourceStream(resourceName);
|
|
if (stream == null)
|
|
{
|
|
throw new InvalidOperationException($"Test resource '{resourceName}' not found");
|
|
}
|
|
|
|
using var ms = new MemoryStream();
|
|
await stream.CopyToAsync(ms);
|
|
return ms.ToArray();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Base64 JSON Tests
|
|
|
|
[Fact]
|
|
public async Task POST_CheckAttachments_Base64_PdfWithoutAttachments_Returns200()
|
|
{
|
|
// Arrange
|
|
byte[] pdfBytes = await LoadTestPdfAsync("pdfWithSwissQRCode.pdf");
|
|
string base64Pdf = Convert.ToBase64String(pdfBytes);
|
|
var request = new CheckPdfAttachmentsRequest { Base64Pdf = base64Pdf };
|
|
|
|
// Act
|
|
var response = await _client.PostAsJsonAsync("/api/pdf/attachments/check", request);
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
|
|
var result = await response.Content.ReadFromJsonAsync<AttachmentCheckResult>();
|
|
result.Should().NotBeNull();
|
|
result!.HasAttachments.Should().BeFalse();
|
|
result.AttachmentCount.Should().Be(0);
|
|
result.Attachments.Should().BeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task POST_CheckAttachments_Base64_PdfWithMultipleAttachments_Returns200()
|
|
{
|
|
// Arrange
|
|
byte[] pdfBytes = await LoadTestPdfAsync("pdfWithMoreThanOneAttachment.pdf");
|
|
string base64Pdf = Convert.ToBase64String(pdfBytes);
|
|
var request = new CheckPdfAttachmentsRequest { Base64Pdf = base64Pdf };
|
|
|
|
// Act
|
|
var response = await _client.PostAsJsonAsync("/api/pdf/attachments/check", request);
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
|
|
var result = await response.Content.ReadFromJsonAsync<AttachmentCheckResult>();
|
|
result.Should().NotBeNull();
|
|
result!.HasAttachments.Should().BeTrue();
|
|
result.AttachmentCount.Should().Be(6, "PDF has exactly 6 attachments");
|
|
result.Attachments.Should().HaveCount(6);
|
|
|
|
// Verify each attachment has required properties
|
|
foreach (var attachment in result.Attachments)
|
|
{
|
|
attachment.FileName.Should().NotBeNullOrEmpty();
|
|
attachment.MimeType.Should().NotBeNullOrEmpty();
|
|
attachment.Size.Should().BeGreaterThan(0);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task POST_CheckAttachments_Base64_InvalidBase64_Returns400()
|
|
{
|
|
// Arrange
|
|
var request = new CheckPdfAttachmentsRequest { Base64Pdf = "invalid-base64!!!" };
|
|
|
|
// Act
|
|
var response = await _client.PostAsJsonAsync("/api/pdf/attachments/check", request);
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
|
|
|
|
var problemDetails = await response.Content.ReadAsStringAsync();
|
|
// FormatException message contains "Base-64" (with hyphen)
|
|
problemDetails.Should().MatchRegex("(?i)base.?64", "should contain Base64 validation error");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task POST_CheckAttachments_Base64_EmptyPdf_Returns400()
|
|
{
|
|
// Arrange
|
|
var request = new CheckPdfAttachmentsRequest { Base64Pdf = string.Empty };
|
|
|
|
// Act
|
|
var response = await _client.PostAsJsonAsync("/api/pdf/attachments/check", request);
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
|
|
|
|
var problemDetails = await response.Content.ReadAsStringAsync();
|
|
// Empty Base64 causes FormatException or empty stream error
|
|
problemDetails.Should().MatchRegex("(Base64|empty|stream)", "should contain validation error message");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Multipart/Form-Data Tests
|
|
|
|
[Fact]
|
|
public async Task POST_CheckAttachments_Multipart_PdfWithoutAttachments_Returns200()
|
|
{
|
|
// Arrange
|
|
byte[] pdfBytes = await LoadTestPdfAsync("pdfWithSwissQRCode.pdf");
|
|
|
|
using var content = new MultipartFormDataContent();
|
|
var fileContent = new ByteArrayContent(pdfBytes);
|
|
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf");
|
|
content.Add(fileContent, "file", "pdfWithSwissQRCode.pdf");
|
|
|
|
// Act
|
|
var response = await _client.PostAsync("/api/pdf/attachments/check", content);
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
|
|
var result = await response.Content.ReadFromJsonAsync<AttachmentCheckResult>();
|
|
result.Should().NotBeNull();
|
|
result!.HasAttachments.Should().BeFalse();
|
|
result.AttachmentCount.Should().Be(0);
|
|
result.Attachments.Should().BeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task POST_CheckAttachments_Multipart_PdfWithMultipleAttachments_Returns200()
|
|
{
|
|
// Arrange
|
|
byte[] pdfBytes = await LoadTestPdfAsync("pdfWithMoreThanOneAttachment.pdf");
|
|
|
|
using var content = new MultipartFormDataContent();
|
|
var fileContent = new ByteArrayContent(pdfBytes);
|
|
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf");
|
|
content.Add(fileContent, "file", "pdfWithMoreThanOneAttachment.pdf");
|
|
|
|
// Act
|
|
var response = await _client.PostAsync("/api/pdf/attachments/check", content);
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
|
|
var result = await response.Content.ReadFromJsonAsync<AttachmentCheckResult>();
|
|
result.Should().NotBeNull();
|
|
result!.HasAttachments.Should().BeTrue();
|
|
result.AttachmentCount.Should().Be(6);
|
|
result.Attachments.Should().HaveCount(6);
|
|
|
|
// Verify first attachment details
|
|
var firstAttachment = result.Attachments.First();
|
|
firstAttachment.FileName.Should().NotBeNullOrEmpty();
|
|
firstAttachment.MimeType.Should().NotBeNullOrEmpty();
|
|
firstAttachment.Size.Should().BeGreaterThan(0);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task POST_CheckAttachments_Multipart_MissingFile_Returns400()
|
|
{
|
|
// Arrange
|
|
using var content = new MultipartFormDataContent(); // No file added
|
|
|
|
// Act
|
|
var response = await _client.PostAsync("/api/pdf/attachments/check", content);
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task POST_CheckAttachments_Multipart_CorruptedPdf_Returns500()
|
|
{
|
|
// Arrange
|
|
byte[] corruptedBytes = "This is not a valid PDF content"u8.ToArray();
|
|
|
|
using var content = new MultipartFormDataContent();
|
|
var fileContent = new ByteArrayContent(corruptedBytes);
|
|
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf");
|
|
content.Add(fileContent, "file", "corrupted.pdf");
|
|
|
|
// Act
|
|
var response = await _client.PostAsync("/api/pdf/attachments/check", content);
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.InternalServerError);
|
|
|
|
var problemDetails = await response.Content.ReadAsStringAsync();
|
|
// Note: Generic error message for security reasons (doesn't expose internal details)
|
|
problemDetails.Should().Contain("error");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Edge Cases
|
|
|
|
[Fact]
|
|
public async Task POST_CheckAttachments_Base64_PdfWithSwissQrCode_Returns200()
|
|
{
|
|
// Arrange
|
|
byte[] pdfBytes = await LoadTestPdfAsync("pdfWithSwissQRCode.pdf");
|
|
string base64Pdf = Convert.ToBase64String(pdfBytes);
|
|
var request = new CheckPdfAttachmentsRequest { Base64Pdf = base64Pdf };
|
|
|
|
// Act
|
|
var response = await _client.PostAsJsonAsync("/api/pdf/attachments/check", request);
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
|
|
var result = await response.Content.ReadFromJsonAsync<AttachmentCheckResult>();
|
|
result.Should().NotBeNull();
|
|
result!.HasAttachments.Should().BeFalse("Swiss QR PDF has no attachments");
|
|
result.AttachmentCount.Should().Be(0);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Extract Attachments Tests (Multipart)
|
|
|
|
[Fact]
|
|
public async Task POST_ExtractAttachments_Multipart_ValidPdfWithAttachments_Returns200WithZip()
|
|
{
|
|
// Arrange
|
|
byte[] pdfBytes = await LoadTestPdfAsync("pdfWithMoreThanOneAttachment.pdf");
|
|
|
|
using var content = new MultipartFormDataContent();
|
|
var fileContent = new ByteArrayContent(pdfBytes);
|
|
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf");
|
|
content.Add(fileContent, "file", "test.pdf");
|
|
|
|
// Act
|
|
var response = await _client.PostAsync("/api/pdf/attachments/extract", content);
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
response.Content.Headers.ContentType?.MediaType.Should().Be("application/zip");
|
|
response.Content.Headers.ContentDisposition?.FileName.Should().Be("attachments.zip");
|
|
|
|
byte[] zipBytes = await response.Content.ReadAsByteArrayAsync();
|
|
zipBytes.Should().NotBeEmpty("ZIP should contain data");
|
|
|
|
// Verify ZIP structure
|
|
using var zipStream = new MemoryStream(zipBytes);
|
|
using var zipArchive = new System.IO.Compression.ZipArchive(zipStream, System.IO.Compression.ZipArchiveMode.Read);
|
|
|
|
zipArchive.Entries.Should().HaveCount(6, "PDF contains 6 attachments");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task POST_ExtractAttachments_Multipart_EmptyPdf_Returns400()
|
|
{
|
|
// Arrange
|
|
using var content = new MultipartFormDataContent();
|
|
var fileContent = new ByteArrayContent(Array.Empty<byte>());
|
|
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf");
|
|
content.Add(fileContent, "file", "empty.pdf");
|
|
|
|
// Act
|
|
var response = await _client.PostAsync("/api/pdf/attachments/extract", content);
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task POST_ExtractAttachments_Multipart_CorruptedPdf_Returns500()
|
|
{
|
|
// Arrange
|
|
byte[] corruptedBytes = "This is not a valid PDF content"u8.ToArray();
|
|
|
|
using var content = new MultipartFormDataContent();
|
|
var fileContent = new ByteArrayContent(corruptedBytes);
|
|
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf");
|
|
content.Add(fileContent, "file", "corrupted.pdf");
|
|
|
|
// Act
|
|
var response = await _client.PostAsync("/api/pdf/attachments/extract", content);
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.InternalServerError);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Extract Attachments Tests (Base64)
|
|
|
|
[Fact]
|
|
public async Task POST_ExtractAttachments_Base64_ValidPdfWithAttachments_Returns200WithZip()
|
|
{
|
|
// Arrange
|
|
byte[] pdfBytes = await LoadTestPdfAsync("pdfWithMoreThanOneAttachment.pdf");
|
|
string base64Pdf = Convert.ToBase64String(pdfBytes);
|
|
var request = new ExtractPdfAttachmentsRequest { Base64Pdf = base64Pdf };
|
|
|
|
// Act
|
|
var response = await _client.PostAsJsonAsync("/api/pdf/attachments/extract", request);
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
response.Content.Headers.ContentType?.MediaType.Should().Be("application/zip");
|
|
response.Content.Headers.ContentDisposition?.FileName.Should().Be("attachments.zip");
|
|
|
|
byte[] zipBytes = await response.Content.ReadAsByteArrayAsync();
|
|
zipBytes.Should().NotBeEmpty("ZIP should contain data");
|
|
|
|
// Verify ZIP structure
|
|
using var zipStream = new MemoryStream(zipBytes);
|
|
using var zipArchive = new System.IO.Compression.ZipArchive(zipStream, System.IO.Compression.ZipArchiveMode.Read);
|
|
|
|
zipArchive.Entries.Should().HaveCount(6, "PDF contains 6 attachments");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task POST_ExtractAttachments_Base64_InvalidBase64_Returns400()
|
|
{
|
|
// Arrange
|
|
var request = new ExtractPdfAttachmentsRequest { Base64Pdf = "invalid-base64!!!" };
|
|
|
|
// Act
|
|
var response = await _client.PostAsJsonAsync("/api/pdf/attachments/extract", request);
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
|
|
|
|
var problemDetails = await response.Content.ReadAsStringAsync();
|
|
// FormatException message contains "Base-64" (with hyphen)
|
|
problemDetails.Should().MatchRegex("(?i)base.?64", "should contain Base64 validation error");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task POST_ExtractAttachments_Base64_EmptyPdf_Returns400()
|
|
{
|
|
// Arrange
|
|
var request = new ExtractPdfAttachmentsRequest { Base64Pdf = string.Empty };
|
|
|
|
// Act
|
|
var response = await _client.PostAsJsonAsync("/api/pdf/attachments/extract", request);
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
|
|
|
|
var problemDetails = await response.Content.ReadAsStringAsync();
|
|
problemDetails.Should().MatchRegex("(Base64|empty|stream)", "should contain validation error message");
|
|
}
|
|
|
|
#endregion
|
|
}
|