using DocumentService.Application.Common.DTOs; using FluentAssertions; namespace DocumentService.Tests.Live.API; /// /// Live tests for PDF attachment endpoints (check + extract). /// Uses PdfAttachmentClient — routes managed by the client. /// Target: http://172.24.12.39:9393/ /// [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()); var act = async () => await AttachmentClient.CheckAsync(stream); await act.Should().ThrowAsync(); } [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( "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()); await act.Should().ThrowAsync(); } // ────────────────────────────────────────────────────────────── // 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()); var act = async () => await AttachmentClient.ExtractAsync(stream); await act.Should().ThrowAsync(); } [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(); } // ────────────────────────────────────────────────────────────── // 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()); await act.Should().ThrowAsync(); } }