using DocumentService.Client.Clients; using DocumentService.Client.Interfaces; using FluentAssertions; using Microsoft.Extensions.Logging.Abstractions; using System.Net; using System.Net.Http; using System.Text.Json; using Xunit; namespace DocumentService.Tests.Unit.Client; public class PdfValidationClientTests { private static (PdfValidationClient client, MockHttpMessageHandler handler) Build(T responseBody) { var handler = MockHttpMessageHandler.ReturningJson(responseBody); var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") }; return (new PdfValidationClient(httpClient, NullLogger.Instance), handler); } private static (PdfValidationClient client, MockHttpMessageHandler handler) BuildError(HttpStatusCode code) { var handler = MockHttpMessageHandler.ReturningStatus(code); var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") }; return (new PdfValidationClient(httpClient, NullLogger.Instance), handler); } private static byte[] FakePdfBytes() => "fake-pdf"u8.ToArray(); // ── ValidatePdfAsync (Stream) ──────────────────────────────────────────── [Fact] public async Task ValidatePdfAsync_Stream_SendsMultipartPost() { var expected = new PdfValidationResult { PageCount = 3, PdfVersion = "1.7", FileSizeBytes = 2048, IsEncrypted = false }; var (client, handler) = Build(expected); var result = await client.ValidatePdfAsync(new MemoryStream(FakePdfBytes())); result.PageCount.Should().Be(3); result.PdfVersion.Should().Be("1.7"); result.FileSizeBytes.Should().Be(2048); result.IsEncrypted.Should().BeFalse(); handler.LastRequest!.Method.Should().Be(HttpMethod.Post); handler.LastRequest.RequestUri!.PathAndQuery.Should().Be("/api/pdf/validation/validate"); handler.LastRequest.Content.Should().BeOfType(); } [Fact] public async Task ValidatePdfAsync_Stream_EncryptedPdf_DeserializesIsEncryptedTrue() { var expected = new PdfValidationResult { PageCount = 1, IsEncrypted = true, PdfVersion = "1.6" }; var (client, _) = Build(expected); var result = await client.ValidatePdfAsync(new MemoryStream(FakePdfBytes())); result.IsEncrypted.Should().BeTrue("encrypted PDFs should surface IsEncrypted=true"); } [Fact] public async Task ValidatePdfAsync_Stream_ThrowsWhenApiReturnsNull() { var handler = MockHttpMessageHandler.ReturningJson(null); var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") }; var client = new PdfValidationClient(httpClient, NullLogger.Instance); await client.Invoking(c => c.ValidatePdfAsync(new MemoryStream(FakePdfBytes()))) .Should().ThrowAsync(); } [Fact] public async Task ValidatePdfAsync_Stream_WhenApiReturns400_ThrowsHttpRequestException() { var (client, _) = BuildError(HttpStatusCode.BadRequest); await client.Invoking(c => c.ValidatePdfAsync(new MemoryStream(FakePdfBytes()))) .Should().ThrowAsync(); } [Fact] public async Task ValidatePdfAsync_Stream_WhenApiReturns500_ThrowsHttpRequestException() { var (client, _) = BuildError(HttpStatusCode.InternalServerError); await client.Invoking(c => c.ValidatePdfAsync(new MemoryStream(FakePdfBytes()))) .Should().ThrowAsync(); } // ── ValidatePdfAsync (byte[]) ──────────────────────────────────────────── [Fact] public async Task ValidatePdfAsync_Bytes_SendsJsonWithBase64() { var expected = new PdfValidationResult { PageCount = 1, IsEncrypted = false, HasAttachments = true, AttachmentCount = 2 }; var (client, handler) = Build(expected); var result = await client.ValidatePdfAsync(FakePdfBytes()); result.PageCount.Should().Be(1); result.HasAttachments.Should().BeTrue(); result.AttachmentCount.Should().Be(2); handler.LastRequest!.Content!.Headers.ContentType!.MediaType.Should().Be("application/json"); var body = await handler.LastRequest.Content!.ReadAsStringAsync(); var doc = JsonDocument.Parse(body); doc.RootElement.GetProperty("base64Pdf").GetString().Should().NotBeNullOrEmpty(); } [Fact] public async Task ValidatePdfAsync_Bytes_WhenApiReturns400_ThrowsHttpRequestException() { var (client, _) = BuildError(HttpStatusCode.BadRequest); await client.Invoking(c => c.ValidatePdfAsync(FakePdfBytes())) .Should().ThrowAsync(); } // ── ValidatePdfAAsync (Stream) ─────────────────────────────────────────── [Fact] public async Task ValidatePdfAAsync_Stream_DeserializesAllFields() { var expected = new PdfAValidationResult { IsValid = true, PdfAVersion = "PDF/A-3b", PageCount = 2, Errors = [], Warnings = [] }; var (client, handler) = Build(expected); var result = await client.ValidatePdfAAsync(new MemoryStream(FakePdfBytes())); result.IsValid.Should().BeTrue(); result.PdfAVersion.Should().Be("PDF/A-3b"); result.PageCount.Should().Be(2); result.Errors.Should().BeEmpty(); handler.LastRequest!.RequestUri!.PathAndQuery.Should().Be("/api/pdf/validation/validate-pdfa"); handler.LastRequest.Content.Should().BeOfType(); } [Fact] public async Task ValidatePdfAAsync_Stream_NonCompliant_ReturnsErrors() { var expected = new PdfAValidationResult { IsValid = false, Errors = ["Missing embedded font", "Encryption not allowed"], Warnings = ["Manual verification recommended"] }; var (client, _) = Build(expected); var result = await client.ValidatePdfAAsync(new MemoryStream(FakePdfBytes())); result.IsValid.Should().BeFalse(); result.Errors.Should().HaveCount(2).And.Contain("Missing embedded font"); result.Warnings.Should().HaveCount(1); } [Fact] public async Task ValidatePdfAAsync_Stream_WhenApiReturns400_ThrowsHttpRequestException() { var (client, _) = BuildError(HttpStatusCode.BadRequest); await client.Invoking(c => c.ValidatePdfAAsync(new MemoryStream(FakePdfBytes()))) .Should().ThrowAsync(); } // ── ValidatePdfAAsync (byte[]) ─────────────────────────────────────────── [Fact] public async Task ValidatePdfAAsync_Bytes_SendsJsonToCorrectEndpoint() { var expected = new PdfAValidationResult { IsValid = true }; var (client, handler) = Build(expected); await client.ValidatePdfAAsync(FakePdfBytes()); handler.LastRequest!.RequestUri!.PathAndQuery.Should().Be("/api/pdf/validation/validate-pdfa"); handler.LastRequest.Content!.Headers.ContentType!.MediaType.Should().Be("application/json"); } [Fact] public async Task ValidatePdfAAsync_Bytes_WhenApiReturns500_ThrowsHttpRequestException() { var (client, _) = BuildError(HttpStatusCode.InternalServerError); await client.Invoking(c => c.ValidatePdfAAsync(FakePdfBytes())) .Should().ThrowAsync(); } }