test: strengthen PdfValidationClient tests (IsEncrypted + full field assertions)

- Add IsEncrypted=true deserialization test (EncryptedPdf_DeserializesIsEncryptedTrue)
- Add HasAttachments/AttachmentCount assertions on Bytes overload
- Add 400/500 error tests for both byte[] overloads of ValidatePdfAAsync
- Strengthen ValidatePdfAAsync assertions (Errors count, Warnings count)
- Extract BuildError() helper to reduce boilerplate
- Total: 8 -> 11 tests
This commit is contained in:
2026-08-28 11:46:37 +02:00
parent 7559028164
commit 050cb55bf9

View File

@@ -1,168 +1,183 @@
using DocumentService.Client.Clients; using DocumentService.Client.Clients;
using DocumentService.Client.Interfaces; using DocumentService.Client.Interfaces;
using DocumentService.Client.Models.Requests;
using FluentAssertions; using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Text.Json; using System.Text.Json;
using Xunit;
namespace DocumentService.Tests.Unit.Client; namespace DocumentService.Tests.Unit.Client;
/// <summary>
/// Unit tests for <see cref="PdfValidationClient"/>.
/// All tests use a fake <see cref="MockHttpMessageHandler"/> — no real HTTP calls are made.
/// </summary>
public class PdfValidationClientTests public class PdfValidationClientTests
{ {
// ?? helpers ?????????????????????????????????????????????????????????????
private static (PdfValidationClient client, MockHttpMessageHandler handler) Build<T>(T responseBody) private static (PdfValidationClient client, MockHttpMessageHandler handler) Build<T>(T responseBody)
{ {
var handler = MockHttpMessageHandler.ReturningJson(responseBody); var handler = MockHttpMessageHandler.ReturningJson(responseBody);
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") }; var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") };
var client = new PdfValidationClient(httpClient, NullLogger<PdfValidationClient>.Instance); return (new PdfValidationClient(httpClient, NullLogger<PdfValidationClient>.Instance), handler);
return (client, handler);
} }
private static byte[] FakePdfBytes() => "fake-pdf-content"u8.ToArray(); 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<PdfValidationClient>.Instance), handler);
}
// ?? ValidatePdfAsync (Stream) ???????????????????????????????????????????? private static byte[] FakePdfBytes() => "fake-pdf"u8.ToArray();
// ── ValidatePdfAsync (Stream) ────────────────────────────────────────────
[Fact] [Fact]
public async Task ValidatePdfAsync_Stream_SendsMultipartPost() public async Task ValidatePdfAsync_Stream_SendsMultipartPost()
{ {
// Arrange var expected = new PdfValidationResult { PageCount = 3, PdfVersion = "1.7", FileSizeBytes = 2048, IsEncrypted = false };
var expected = new PdfValidationResult { PageCount = 3, PdfVersion = "1.7", FileSizeBytes = 2048 };
var (client, handler) = Build(expected); var (client, handler) = Build(expected);
// Act
var result = await client.ValidatePdfAsync(new MemoryStream(FakePdfBytes())); var result = await client.ValidatePdfAsync(new MemoryStream(FakePdfBytes()));
// Assert
result.PageCount.Should().Be(3); result.PageCount.Should().Be(3);
result.PdfVersion.Should().Be("1.7"); result.PdfVersion.Should().Be("1.7");
result.FileSizeBytes.Should().Be(2048);
result.IsEncrypted.Should().BeFalse();
handler.LastRequest!.Method.Should().Be(HttpMethod.Post); handler.LastRequest!.Method.Should().Be(HttpMethod.Post);
handler.LastRequest.RequestUri!.PathAndQuery.Should().Be("/api/pdf/validation/validate"); handler.LastRequest.RequestUri!.PathAndQuery.Should().Be("/api/pdf/validation/validate");
handler.LastRequest.Content.Should().BeOfType<MultipartFormDataContent>(); handler.LastRequest.Content.Should().BeOfType<MultipartFormDataContent>();
} }
[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] [Fact]
public async Task ValidatePdfAsync_Stream_ThrowsWhenApiReturnsNull() public async Task ValidatePdfAsync_Stream_ThrowsWhenApiReturnsNull()
{ {
// Arrange — API returns JSON null
var handler = MockHttpMessageHandler.ReturningJson<PdfValidationResult?>(null); var handler = MockHttpMessageHandler.ReturningJson<PdfValidationResult?>(null);
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") }; var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") };
var client = new PdfValidationClient(httpClient, NullLogger<PdfValidationClient>.Instance); var client = new PdfValidationClient(httpClient, NullLogger<PdfValidationClient>.Instance);
// Act & Assert
await client.Invoking(c => c.ValidatePdfAsync(new MemoryStream(FakePdfBytes()))) await client.Invoking(c => c.ValidatePdfAsync(new MemoryStream(FakePdfBytes())))
.Should().ThrowAsync<InvalidOperationException>(); .Should().ThrowAsync<InvalidOperationException>();
} }
// ?? ValidatePdfAsync (byte[]) ???????????????????????????????????????????? [Fact]
public async Task ValidatePdfAsync_Stream_WhenApiReturns400_ThrowsHttpRequestException()
{
var (client, _) = BuildError(HttpStatusCode.BadRequest);
await client.Invoking(c => c.ValidatePdfAsync(new MemoryStream(FakePdfBytes())))
.Should().ThrowAsync<HttpRequestException>();
}
[Fact]
public async Task ValidatePdfAsync_Stream_WhenApiReturns500_ThrowsHttpRequestException()
{
var (client, _) = BuildError(HttpStatusCode.InternalServerError);
await client.Invoking(c => c.ValidatePdfAsync(new MemoryStream(FakePdfBytes())))
.Should().ThrowAsync<HttpRequestException>();
}
// ── ValidatePdfAsync (byte[]) ────────────────────────────────────────────
[Fact] [Fact]
public async Task ValidatePdfAsync_Bytes_SendsJsonWithBase64() public async Task ValidatePdfAsync_Bytes_SendsJsonWithBase64()
{ {
// Arrange var expected = new PdfValidationResult { PageCount = 1, IsEncrypted = false, HasAttachments = true, AttachmentCount = 2 };
var expected = new PdfValidationResult { PageCount = 1, IsEncrypted = false };
var (client, handler) = Build(expected); var (client, handler) = Build(expected);
// Act
var result = await client.ValidatePdfAsync(FakePdfBytes()); var result = await client.ValidatePdfAsync(FakePdfBytes());
// Assert
result.PageCount.Should().Be(1); result.PageCount.Should().Be(1);
handler.LastRequest!.Content.Should().NotBeNull(); result.HasAttachments.Should().BeTrue();
handler.LastRequest.Content!.Headers.ContentType!.MediaType.Should().Be("application/json"); result.AttachmentCount.Should().Be(2);
handler.LastRequest!.Content!.Headers.ContentType!.MediaType.Should().Be("application/json");
var body = await handler.LastRequest.Content!.ReadAsStringAsync(); var body = await handler.LastRequest.Content!.ReadAsStringAsync();
var doc = JsonDocument.Parse(body); var doc = JsonDocument.Parse(body);
doc.RootElement.GetProperty("base64Pdf").GetString().Should().NotBeNullOrEmpty(); doc.RootElement.GetProperty("base64Pdf").GetString().Should().NotBeNullOrEmpty();
} }
// ?? ValidatePdfAAsync (Stream) ??????????????????????????????????????????? [Fact]
public async Task ValidatePdfAsync_Bytes_WhenApiReturns400_ThrowsHttpRequestException()
{
var (client, _) = BuildError(HttpStatusCode.BadRequest);
await client.Invoking(c => c.ValidatePdfAsync(FakePdfBytes()))
.Should().ThrowAsync<HttpRequestException>();
}
// ── ValidatePdfAAsync (Stream) ───────────────────────────────────────────
[Fact] [Fact]
public async Task ValidatePdfAAsync_Stream_SendsMultipartPost() public async Task ValidatePdfAAsync_Stream_DeserializesAllFields()
{ {
// Arrange var expected = new PdfAValidationResult
var expected = new PdfAValidationResult { IsValid = true, PdfAVersion = "PDF/A-3b", PageCount = 2 }; {
IsValid = true, PdfAVersion = "PDF/A-3b", PageCount = 2,
Errors = [], Warnings = []
};
var (client, handler) = Build(expected); var (client, handler) = Build(expected);
// Act
var result = await client.ValidatePdfAAsync(new MemoryStream(FakePdfBytes())); var result = await client.ValidatePdfAAsync(new MemoryStream(FakePdfBytes()));
// Assert
result.IsValid.Should().BeTrue(); result.IsValid.Should().BeTrue();
result.PdfAVersion.Should().Be("PDF/A-3b"); 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!.RequestUri!.PathAndQuery.Should().Be("/api/pdf/validation/validate-pdfa");
handler.LastRequest.Content.Should().BeOfType<MultipartFormDataContent>(); handler.LastRequest.Content.Should().BeOfType<MultipartFormDataContent>();
} }
[Fact] [Fact]
public async Task ValidatePdfAAsync_Stream_WithErrors_ReturnsErrors() public async Task ValidatePdfAAsync_Stream_NonCompliant_ReturnsErrors()
{ {
// Arrange
var expected = new PdfAValidationResult var expected = new PdfAValidationResult
{ {
IsValid = false, IsValid = false,
Errors = new List<string> { "Missing embedded font", "Encryption not allowed" } Errors = ["Missing embedded font", "Encryption not allowed"],
Warnings = ["Manual verification recommended"]
}; };
var (client, _) = Build(expected); var (client, _) = Build(expected);
// Act
var result = await client.ValidatePdfAAsync(new MemoryStream(FakePdfBytes())); var result = await client.ValidatePdfAAsync(new MemoryStream(FakePdfBytes()));
// Assert
result.IsValid.Should().BeFalse(); result.IsValid.Should().BeFalse();
result.Errors.Should().HaveCount(2).And.Contain("Missing embedded font"); result.Errors.Should().HaveCount(2).And.Contain("Missing embedded font");
result.Warnings.Should().HaveCount(1);
} }
// ?? ValidatePdfAAsync (byte[]) ??????????????????????????????????????????? [Fact]
public async Task ValidatePdfAAsync_Stream_WhenApiReturns400_ThrowsHttpRequestException()
{
var (client, _) = BuildError(HttpStatusCode.BadRequest);
await client.Invoking(c => c.ValidatePdfAAsync(new MemoryStream(FakePdfBytes())))
.Should().ThrowAsync<HttpRequestException>();
}
// ── ValidatePdfAAsync (byte[]) ───────────────────────────────────────────
[Fact] [Fact]
public async Task ValidatePdfAAsync_Bytes_SendsJson() public async Task ValidatePdfAAsync_Bytes_SendsJsonToCorrectEndpoint()
{ {
// Arrange
var expected = new PdfAValidationResult { IsValid = true }; var expected = new PdfAValidationResult { IsValid = true };
var (client, handler) = Build(expected); var (client, handler) = Build(expected);
// Act
await client.ValidatePdfAAsync(FakePdfBytes()); await client.ValidatePdfAAsync(FakePdfBytes());
// Assert
handler.LastRequest!.RequestUri!.PathAndQuery.Should().Be("/api/pdf/validation/validate-pdfa"); handler.LastRequest!.RequestUri!.PathAndQuery.Should().Be("/api/pdf/validation/validate-pdfa");
handler.LastRequest.Content.Should().NotBeNull();
handler.LastRequest.Content!.Headers.ContentType!.MediaType.Should().Be("application/json"); handler.LastRequest.Content!.Headers.ContentType!.MediaType.Should().Be("application/json");
} }
// ?? HTTP error propagation ???????????????????????????????????????????????
[Fact] [Fact]
public async Task ValidatePdfAsync_WhenApiReturns400_ThrowsHttpRequestException() public async Task ValidatePdfAAsync_Bytes_WhenApiReturns500_ThrowsHttpRequestException()
{ {
// Arrange var (client, _) = BuildError(HttpStatusCode.InternalServerError);
var handler = MockHttpMessageHandler.ReturningStatus(HttpStatusCode.BadRequest); await client.Invoking(c => c.ValidatePdfAAsync(FakePdfBytes()))
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") };
var client = new PdfValidationClient(httpClient, NullLogger<PdfValidationClient>.Instance);
// Act & Assert
await client.Invoking(c => c.ValidatePdfAsync(new MemoryStream(FakePdfBytes())))
.Should().ThrowAsync<HttpRequestException>();
}
[Fact]
public async Task ValidatePdfAsync_WhenApiReturns500_ThrowsHttpRequestException()
{
// Arrange
var handler = MockHttpMessageHandler.ReturningStatus(HttpStatusCode.InternalServerError);
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") };
var client = new PdfValidationClient(httpClient, NullLogger<PdfValidationClient>.Instance);
// Act & Assert
await client.Invoking(c => c.ValidatePdfAsync(new MemoryStream(FakePdfBytes())))
.Should().ThrowAsync<HttpRequestException>(); .Should().ThrowAsync<HttpRequestException>();
} }
} }