using DocumentService.Application.Common.DTOs;
using DocumentService.Application.ExtractZugferd;
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;
namespace DocumentService.Tests.Unit.Client;
///
/// Unit tests for .
/// All tests use a fake � no real HTTP calls are made.
///
public class ZugferdClientTests
{
// ?? helpers ?????????????????????????????????????????????????????????????
private static (ZugferdClient client, MockHttpMessageHandler handler) BuildJson(T body)
{
var handler = MockHttpMessageHandler.ReturningJson(body);
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") };
var client = new ZugferdClient(httpClient, NullLogger.Instance);
return (client, handler);
}
private static (ZugferdClient client, MockHttpMessageHandler handler) BuildBytes(byte[] bytes, string mediaType = "application/xml")
{
var handler = MockHttpMessageHandler.ReturningBytes(bytes, mediaType);
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") };
var client = new ZugferdClient(httpClient, NullLogger.Instance);
return (client, handler);
}
private static byte[] FakePdfBytes() => "fake-pdf-content"u8.ToArray();
private static byte[] FakeXmlBytes() => "test"u8.ToArray();
// ?? CheckAsync (Stream) ?????????????????????????????????????????????
[Fact]
public async Task CheckAsync_Stream_WhenZugferdPresent_ReturnsTrue()
{
// Arrange
var expected = new ZugferdCheckResult { HasZugferd = true, ZugferdFileName = "factur-x.xml" };
var (client, handler) = BuildJson(expected);
// Act
var result = await client.CheckAsync(new MemoryStream(FakePdfBytes()));
// Assert
result.HasZugferd.Should().BeTrue();
handler.LastRequest!.RequestUri!.PathAndQuery.Should().Be("/api/pdf/zugferd/has-zugferd");
handler.LastRequest.Content.Should().BeOfType();
}
[Fact]
public async Task CheckAsync_Stream_WhenNoZugferd_ReturnsFalse()
{
// Arrange
var expected = new ZugferdCheckResult { HasZugferd = false };
var (client, _) = BuildJson(expected);
// Act
var result = await client.CheckAsync(new MemoryStream(FakePdfBytes()));
// Assert
result.HasZugferd.Should().BeFalse();
result.ZugferdFileName.Should().BeNull();
}
// ?? CheckAsync (byte[]) ?????????????????????????????????????????????
[Fact]
public async Task CheckAsync_Bytes_SendsJsonWithBase64()
{
// Arrange
var expected = new ZugferdCheckResult { HasZugferd = true };
var (client, handler) = BuildJson(expected);
// Act
await client.CheckAsync(FakePdfBytes());
// Assert
handler.LastRequest!.Content.Should().NotBeNull();
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();
}
// ?? ExtractAsync (Stream) � raw XML stream ?????????????????????????
[Fact]
public async Task ExtractAsync_Stream_SendsMultipartWithAsFileTrue()
{
// Arrange
var (client, handler) = BuildBytes(FakeXmlBytes());
// Act
using var result = await client.ExtractAsync(new MemoryStream(FakePdfBytes()));
// Assert
result.Should().NotBeNull();
handler.LastRequest!.RequestUri!.PathAndQuery.Should().Be("/api/pdf/zugferd/extract?asFile=true");
handler.LastRequest.Content.Should().BeOfType();
}
[Fact]
public async Task ExtractAsync_Stream_ReturnsXmlContent()
{
// Arrange
var xmlBytes = ""u8.ToArray();
var (client, _) = BuildBytes(xmlBytes);
// Act
using var result = await client.ExtractAsync(new MemoryStream(FakePdfBytes()));
var content = await new StreamReader(result).ReadToEndAsync();
// Assert
content.Should().Be("");
}
// ?? ExtractAsync (byte[]) ?????????????????????????????????????????
[Fact]
public async Task ExtractAsync_Bytes_SendsJsonWithFormatFile()
{
// Arrange
var (client, handler) = BuildBytes(FakeXmlBytes());
// Act
using var result = await client.ExtractAsync(FakePdfBytes());
// Assert
handler.LastRequest!.RequestUri!.PathAndQuery.Should().Be("/api/pdf/zugferd/extract?format=file");
handler.LastRequest.Content.Should().NotBeNull();
handler.LastRequest.Content!.Headers.ContentType!.MediaType.Should().Be("application/json");
}
// ?? ExtractAsResultAsync (Stream) ?????????????????????????????????
[Fact]
public async Task ExtractAsResultAsync_Stream_ReturnsStructuredResult()
{
// Arrange
var expected = new ZugferdExtractionResult
{
FileName = "factur-x.xml",
XmlContent = "",
};
var (client, handler) = BuildJson(expected);
// Act
var result = await client.ExtractAsResultAsync(new MemoryStream(FakePdfBytes()));
// Assert
result.FileName.Should().Be("factur-x.xml");
result.XmlContent.Should().Be("");
handler.LastRequest!.RequestUri!.PathAndQuery.Should().Be("/api/pdf/zugferd/extract?asFile=false");
}
// ?? ExtractAsResultAsync (byte[]) ?????????????????????????????????
[Fact]
public async Task ExtractAsResultAsync_Bytes_SendsJsonWithFormatJson()
{
// Arrange
var expected = new ZugferdExtractionResult { FileName = "zugferd.xml", XmlContent = "" };
var (client, handler) = BuildJson(expected);
// Act
await client.ExtractAsResultAsync(FakePdfBytes());
// Assert
handler.LastRequest!.RequestUri!.PathAndQuery.Should().Be("/api/pdf/zugferd/extract?format=json");
handler.LastRequest.Content.Should().NotBeNull();
handler.LastRequest.Content!.Headers.ContentType!.MediaType.Should().Be("application/json");
}
// ?? HTTP error propagation ???????????????????????????????????????????????
[Fact]
public async Task CheckAsync_WhenApiReturns400_ThrowsHttpRequestException()
{
// Arrange
var handler = MockHttpMessageHandler.ReturningStatus(HttpStatusCode.BadRequest);
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") };
var client = new ZugferdClient(httpClient, NullLogger.Instance);
// Act & Assert
await client.Invoking(c => c.CheckAsync(new MemoryStream(FakePdfBytes())))
.Should().ThrowAsync();
}
}