test(live): add live API integration test project (DocumentService.Tests.Live)
End-to-end tests that run against a real, deployed API instance to verify that
all controllers respond correctly over HTTP - bypassing WebApplicationFactory.
Project setup:
- Targets net8.0; xUnit 2.9.3 + FluentAssertions 7.0.0
- References DocumentService.Client, Application and Domain as project refs
- Embeds 4 test PDFs as EmbeddedResources:
valid.pdf, pdfWithSwissQRCode.pdf, pdfWithMoreThanOneAttachment.pdf,
encryptedPdf.pdf
LiveApiTestBase:
- Base URL defaults to http://172.24.12.39:9393, overridable via
LIVE_API_BASE_URL env var
- Constructs typed clients (PdfValidationClient, PdfAttachmentClient,
PdfOperationsClient, SwissQrCodeClient) backed by a single HttpClient
- LoadPdfBytes / LoadPdfStream helpers for EmbeddedResource access
Test coverage per file:
- PdfValidationLiveTests : Stream + byte[] overloads for validate and
validate-pdfa; empty/corrupted inputs assert 4xx/5xx
- PdfAttachmentLiveTests : check + extract endpoints; stream and byte[] variants
- PdfOperationsLiveTests : merge and stamp endpoints
- SwissQrCodeLiveTests : extract Swiss QR bill; invalid content asserts error
Tests are intentionally skipped in CI (no live server); run manually with
the LIVE_API_BASE_URL variable pointing to a running instance.
This commit is contained in:
235
DocumentService.Tests.Live/API/PdfAttachmentLiveTests.cs
Normal file
235
DocumentService.Tests.Live/API/PdfAttachmentLiveTests.cs
Normal file
@@ -0,0 +1,235 @@
|
||||
using DocumentService.Application.Common.DTOs;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace DocumentService.Tests.Live.API;
|
||||
|
||||
/// <summary>
|
||||
/// Live tests for PDF attachment endpoints (check + extract).
|
||||
/// Uses PdfAttachmentClient — routes managed by the client.
|
||||
/// Target: http://172.24.12.39:9393/
|
||||
/// </summary>
|
||||
[Collection("Live API")]
|
||||
public class PdfAttachmentLiveTests : LiveApiTestBase
|
||||
{
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
// CheckAsync(Stream) → multipart → /api/pdf/attachments/check
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task Check_Stream_PdfWithoutAttachments_ReturnsHasAttachmentsFalse()
|
||||
{
|
||||
using var stream = LoadPdfStream("valid.pdf");
|
||||
|
||||
var result = await AttachmentClient.CheckAsync(stream);
|
||||
|
||||
result.Should().NotBeNull();
|
||||
result.HasAttachments.Should().BeFalse();
|
||||
result.AttachmentCount.Should().Be(0);
|
||||
result.Attachments.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Check_Stream_PdfWith6Attachments_ReturnsAllAttachmentsWithMetadata()
|
||||
{
|
||||
using var stream = LoadPdfStream("pdfWithMoreThanOneAttachment.pdf");
|
||||
|
||||
var result = await AttachmentClient.CheckAsync(stream);
|
||||
|
||||
result.HasAttachments.Should().BeTrue();
|
||||
result.AttachmentCount.Should().Be(6, "PDF has exactly 6 attachments");
|
||||
result.Attachments.Should().HaveCount(6);
|
||||
|
||||
foreach (var attachment in result.Attachments)
|
||||
{
|
||||
attachment.FileName.Should().NotBeNullOrEmpty();
|
||||
attachment.MimeType.Should().NotBeNullOrEmpty();
|
||||
attachment.Size.Should().BeGreaterThan(0);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Check_Stream_PdfWithSwissQrCode_HasNoAttachments()
|
||||
{
|
||||
// QR code PDF has no embedded file attachments
|
||||
using var stream = LoadPdfStream("pdfWithSwissQRCode.pdf");
|
||||
|
||||
var result = await AttachmentClient.CheckAsync(stream);
|
||||
|
||||
result.HasAttachments.Should().BeFalse();
|
||||
result.AttachmentCount.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Check_Stream_EmptyStream_Throws()
|
||||
{
|
||||
using var stream = new MemoryStream(Array.Empty<byte>());
|
||||
|
||||
var act = async () => await AttachmentClient.CheckAsync(stream);
|
||||
|
||||
await act.Should().ThrowAsync<HttpRequestException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Check_Stream_CorruptedContent_Throws()
|
||||
{
|
||||
byte[] garbage = "This is not a valid PDF content"u8.ToArray();
|
||||
using var stream = new MemoryStream(garbage);
|
||||
|
||||
var act = async () => await AttachmentClient.CheckAsync(stream);
|
||||
|
||||
await act.Should().ThrowAsync<HttpRequestException>(
|
||||
"corrupted PDF should return 500 → client throws");
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
// CheckAsync(byte[]) → Base64 JSON → /api/pdf/attachments/check
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task Check_Bytes_PdfWithoutAttachments_ReturnsHasAttachmentsFalse()
|
||||
{
|
||||
byte[] bytes = LoadPdfBytes("valid.pdf");
|
||||
|
||||
var result = await AttachmentClient.CheckAsync(bytes);
|
||||
|
||||
result.HasAttachments.Should().BeFalse();
|
||||
result.AttachmentCount.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Check_Bytes_PdfWith6Attachments_ReturnsAllAttachmentsWithMetadata()
|
||||
{
|
||||
byte[] bytes = LoadPdfBytes("pdfWithMoreThanOneAttachment.pdf");
|
||||
|
||||
var result = await AttachmentClient.CheckAsync(bytes);
|
||||
|
||||
result.HasAttachments.Should().BeTrue();
|
||||
result.AttachmentCount.Should().Be(6);
|
||||
result.Attachments.Should().HaveCount(6);
|
||||
|
||||
foreach (var attachment in result.Attachments)
|
||||
{
|
||||
attachment.FileName.Should().NotBeNullOrEmpty();
|
||||
attachment.MimeType.Should().NotBeNullOrEmpty();
|
||||
attachment.Size.Should().BeGreaterThan(0);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Check_Bytes_EmptyArray_Throws()
|
||||
{
|
||||
var act = async () => await AttachmentClient.CheckAsync(Array.Empty<byte>());
|
||||
|
||||
await act.Should().ThrowAsync<HttpRequestException>();
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
// ExtractAsync(Stream) → multipart → /api/pdf/attachments/extract
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task Extract_Stream_PdfWith6Attachments_Returns6NamedStreams()
|
||||
{
|
||||
using var stream = LoadPdfStream("pdfWithMoreThanOneAttachment.pdf");
|
||||
|
||||
var attachments = await AttachmentClient.ExtractAsync(stream);
|
||||
|
||||
attachments.Should().HaveCount(6, "PDF contains 6 attachments");
|
||||
|
||||
foreach (var (name, content) in attachments)
|
||||
{
|
||||
name.Should().NotBeNullOrEmpty();
|
||||
content.Length.Should().BeGreaterThan(0);
|
||||
content.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Extract_Stream_AttachmentNamesAreUnique()
|
||||
{
|
||||
using var stream = LoadPdfStream("pdfWithMoreThanOneAttachment.pdf");
|
||||
|
||||
var attachments = await AttachmentClient.ExtractAsync(stream);
|
||||
|
||||
try
|
||||
{
|
||||
attachments.Keys.Should().OnlyHaveUniqueItems(
|
||||
"ZIP entry names must be unique within the archive");
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var s in attachments.Values) s.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Extract_Stream_EmptyStream_Throws()
|
||||
{
|
||||
using var stream = new MemoryStream(Array.Empty<byte>());
|
||||
|
||||
var act = async () => await AttachmentClient.ExtractAsync(stream);
|
||||
|
||||
await act.Should().ThrowAsync<HttpRequestException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Extract_Stream_CorruptedContent_Throws()
|
||||
{
|
||||
byte[] garbage = "This is not a valid PDF content"u8.ToArray();
|
||||
using var stream = new MemoryStream(garbage);
|
||||
|
||||
var act = async () => await AttachmentClient.ExtractAsync(stream);
|
||||
|
||||
await act.Should().ThrowAsync<HttpRequestException>();
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
// ExtractAsync(byte[]) → Base64 JSON → /api/pdf/attachments/extract
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task Extract_Bytes_PdfWith6Attachments_Returns6NamedStreams()
|
||||
{
|
||||
byte[] bytes = LoadPdfBytes("pdfWithMoreThanOneAttachment.pdf");
|
||||
|
||||
var attachments = await AttachmentClient.ExtractAsync(bytes);
|
||||
|
||||
attachments.Should().HaveCount(6);
|
||||
|
||||
foreach (var (name, content) in attachments)
|
||||
{
|
||||
name.Should().NotBeNullOrEmpty();
|
||||
content.Length.Should().BeGreaterThan(0);
|
||||
content.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Extract_Bytes_AttachmentContentsAreNonEmpty()
|
||||
{
|
||||
byte[] bytes = LoadPdfBytes("pdfWithMoreThanOneAttachment.pdf");
|
||||
|
||||
var attachments = await AttachmentClient.ExtractAsync(bytes);
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var (_, content) in attachments)
|
||||
{
|
||||
((MemoryStream)content).ToArray().Should().NotBeEmpty(
|
||||
"every extracted attachment must have content");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var s in attachments.Values) s.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Extract_Bytes_EmptyArray_Throws()
|
||||
{
|
||||
var act = async () => await AttachmentClient.ExtractAsync(Array.Empty<byte>());
|
||||
|
||||
await act.Should().ThrowAsync<HttpRequestException>();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user