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(); // ?? HasZugferdAsync (Stream) ????????????????????????????????????????????? [Fact] public async Task HasZugferdAsync_Stream_WhenZugferdPresent_ReturnsTrue() { // Arrange var expected = new ZugferdCheckResult { HasZugferd = true, Version = "2.1", Profile = "EN 16931" }; var (client, handler) = BuildJson(expected); // Act var result = await client.HasZugferdAsync(new MemoryStream(FakePdfBytes())); // Assert result.HasZugferd.Should().BeTrue(); result.Version.Should().Be("2.1"); result.Profile.Should().Be("EN 16931"); handler.LastRequest!.RequestUri!.PathAndQuery.Should().Be("/api/pdf/zugferd/has-zugferd"); handler.LastRequest.Content.Should().BeOfType(); } [Fact] public async Task HasZugferdAsync_Stream_WhenNoZugferd_ReturnsFalse() { // Arrange var expected = new ZugferdCheckResult { HasZugferd = false }; var (client, _) = BuildJson(expected); // Act var result = await client.HasZugferdAsync(new MemoryStream(FakePdfBytes())); // Assert result.HasZugferd.Should().BeFalse(); result.Version.Should().BeNull(); } // ?? HasZugferdAsync (byte[]) ????????????????????????????????????????????? [Fact] public async Task HasZugferdAsync_Bytes_SendsJsonWithBase64() { // Arrange var expected = new ZugferdCheckResult { HasZugferd = true }; var (client, handler) = BuildJson(expected); // Act await client.HasZugferdAsync(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(); } // ?? ExtractZugferdAsync (Stream) — raw XML stream ????????????????????????? [Fact] public async Task ExtractZugferdAsync_Stream_SendsMultipartWithAsFileTrue() { // Arrange var (client, handler) = BuildBytes(FakeXmlBytes()); // Act using var result = await client.ExtractZugferdAsync(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 ExtractZugferdAsync_Stream_ReturnsXmlContent() { // Arrange var xmlBytes = ""u8.ToArray(); var (client, _) = BuildBytes(xmlBytes); // Act using var result = await client.ExtractZugferdAsync(new MemoryStream(FakePdfBytes())); var content = await new StreamReader(result).ReadToEndAsync(); // Assert content.Should().Be(""); } // ?? ExtractZugferdAsync (byte[]) ????????????????????????????????????????? [Fact] public async Task ExtractZugferdAsync_Bytes_SendsJsonWithFormatFile() { // Arrange var (client, handler) = BuildBytes(FakeXmlBytes()); // Act using var result = await client.ExtractZugferdAsync(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"); } // ?? ExtractZugferdAsResultAsync (Stream) ????????????????????????????????? [Fact] public async Task ExtractZugferdAsResultAsync_Stream_ReturnsStructuredResult() { // Arrange var expected = new ZugferdExtractionResult { FileName = "factur-x.xml", XmlContent = "", Version = "2.1", Profile = "EN 16931" }; var (client, handler) = BuildJson(expected); // Act var result = await client.ExtractZugferdAsResultAsync(new MemoryStream(FakePdfBytes())); // Assert result.FileName.Should().Be("factur-x.xml"); result.XmlContent.Should().Be(""); result.Version.Should().Be("2.1"); handler.LastRequest!.RequestUri!.PathAndQuery.Should().Be("/api/pdf/zugferd/extract?asFile=false"); } // ?? ExtractZugferdAsResultAsync (byte[]) ????????????????????????????????? [Fact] public async Task ExtractZugferdAsResultAsync_Bytes_SendsJsonWithFormatJson() { // Arrange var expected = new ZugferdExtractionResult { FileName = "zugferd.xml", XmlContent = "" }; var (client, handler) = BuildJson(expected); // Act await client.ExtractZugferdAsResultAsync(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 HasZugferdAsync_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.HasZugferdAsync(new MemoryStream(FakePdfBytes()))) .Should().ThrowAsync(); } }