Refactor: Rename DocumentOperator to DocumentService
Updated project and solution files to rename and restructure `DocumentOperator` to `DocumentService`. Modified project references in `DocumentService.API.csproj`, `DocumentService.Application.csproj`, and `DocumentService.Infrastructure.csproj` to reflect the new naming convention. Updated `DocumentService.sln` to remove old project references, add new ones, and adjust configuration and nested project mappings accordingly. This change aligns the project structure with the new naming convention.
This commit is contained in:
56
DocumentService.Tests/Unit/Client/MockHttpMessageHandler.cs
Normal file
56
DocumentService.Tests/Unit/Client/MockHttpMessageHandler.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace DocumentService.Tests.Unit.Client;
|
||||
|
||||
/// <summary>
|
||||
/// Reusable fake <see cref="HttpMessageHandler"/> for unit-testing HTTP clients.
|
||||
/// Captures the outgoing request and returns the configured response.
|
||||
/// </summary>
|
||||
internal sealed class MockHttpMessageHandler : HttpMessageHandler
|
||||
{
|
||||
private readonly HttpResponseMessage _response;
|
||||
|
||||
/// <summary>The last request that was sent through this handler.</summary>
|
||||
public HttpRequestMessage? LastRequest { get; private set; }
|
||||
|
||||
public MockHttpMessageHandler(HttpResponseMessage response)
|
||||
{
|
||||
_response = response;
|
||||
}
|
||||
|
||||
// ?? convenience factories ????????????????????????????????????????????????
|
||||
|
||||
/// <summary>Creates a handler that returns 200 OK with a JSON-serialised body.</summary>
|
||||
public static MockHttpMessageHandler ReturningJson<T>(T body, HttpStatusCode status = HttpStatusCode.OK)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(body, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
|
||||
var response = new HttpResponseMessage(status)
|
||||
{
|
||||
Content = new StringContent(json, Encoding.UTF8, "application/json")
|
||||
};
|
||||
return new MockHttpMessageHandler(response);
|
||||
}
|
||||
|
||||
/// <summary>Creates a handler that returns 200 OK with raw bytes as the body.</summary>
|
||||
public static MockHttpMessageHandler ReturningBytes(byte[] bytes, string mediaType = "application/octet-stream", HttpStatusCode status = HttpStatusCode.OK)
|
||||
{
|
||||
var response = new HttpResponseMessage(status)
|
||||
{
|
||||
Content = new ByteArrayContent(bytes) { Headers = { ContentType = new(mediaType) } }
|
||||
};
|
||||
return new MockHttpMessageHandler(response);
|
||||
}
|
||||
|
||||
/// <summary>Creates a handler that returns the given status code with no body.</summary>
|
||||
public static MockHttpMessageHandler ReturningStatus(HttpStatusCode status)
|
||||
=> new(new HttpResponseMessage(status));
|
||||
|
||||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
LastRequest = request;
|
||||
return Task.FromResult(_response);
|
||||
}
|
||||
}
|
||||
221
DocumentService.Tests/Unit/Client/PdfAttachmentClientTests.cs
Normal file
221
DocumentService.Tests/Unit/Client/PdfAttachmentClientTests.cs
Normal file
@@ -0,0 +1,221 @@
|
||||
using DocumentService.Client.Clients;
|
||||
using DocumentService.Client.Interfaces;
|
||||
using DocumentService.Client.Models.Requests;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using System.IO.Compression;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace DocumentService.Tests.Unit.Client;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for <see cref="PdfAttachmentClient"/>.
|
||||
/// All tests use a fake <see cref="MockHttpMessageHandler"/> — no real HTTP calls are made.
|
||||
/// </summary>
|
||||
public class PdfAttachmentClientTests
|
||||
{
|
||||
// ?? helpers ?????????????????????????????????????????????????????????????
|
||||
|
||||
private static (PdfAttachmentClient client, MockHttpMessageHandler handler) BuildJson<T>(T body)
|
||||
{
|
||||
var handler = MockHttpMessageHandler.ReturningJson(body);
|
||||
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") };
|
||||
var client = new PdfAttachmentClient(httpClient, NullLogger<PdfAttachmentClient>.Instance);
|
||||
return (client, handler);
|
||||
}
|
||||
|
||||
private static (PdfAttachmentClient client, MockHttpMessageHandler handler) BuildBytes(byte[] bytes, string mediaType = "application/zip")
|
||||
{
|
||||
var handler = MockHttpMessageHandler.ReturningBytes(bytes, mediaType);
|
||||
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") };
|
||||
var client = new PdfAttachmentClient(httpClient, NullLogger<PdfAttachmentClient>.Instance);
|
||||
return (client, handler);
|
||||
}
|
||||
|
||||
private static byte[] FakePdfBytes() => "fake-pdf-content"u8.ToArray();
|
||||
|
||||
/// <summary>Builds a minimal valid ZIP containing the given entries.</summary>
|
||||
private static byte[] BuildZip(Dictionary<string, string> entries)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, leaveOpen: true))
|
||||
{
|
||||
foreach (var (name, content) in entries)
|
||||
{
|
||||
var entry = archive.CreateEntry(name);
|
||||
using var writer = new StreamWriter(entry.Open());
|
||||
writer.Write(content);
|
||||
}
|
||||
}
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
// ?? CheckAttachmentsAsync (Stream) ???????????????????????????????????????
|
||||
|
||||
[Fact]
|
||||
public async Task CheckAttachmentsAsync_Stream_SendsMultipartPost()
|
||||
{
|
||||
// Arrange
|
||||
var expected = new AttachmentCheckResult
|
||||
{
|
||||
HasAttachments = true,
|
||||
AttachmentCount = 1,
|
||||
Attachments = new List<AttachmentMetadata>
|
||||
{
|
||||
new() { FileName = "factur-x.xml", MimeType = "application/xml", Size = 512 }
|
||||
}
|
||||
};
|
||||
var (client, handler) = BuildJson(expected);
|
||||
|
||||
// Act
|
||||
var result = await client.CheckAttachmentsAsync(new MemoryStream(FakePdfBytes()));
|
||||
|
||||
// Assert
|
||||
result.HasAttachments.Should().BeTrue();
|
||||
result.AttachmentCount.Should().Be(1);
|
||||
result.Attachments[0].FileName.Should().Be("factur-x.xml");
|
||||
handler.LastRequest!.RequestUri!.PathAndQuery.Should().Be("/api/pdf/attachments/check");
|
||||
handler.LastRequest.Content.Should().BeOfType<MultipartFormDataContent>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CheckAttachmentsAsync_Stream_PdfWithNoAttachments_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var expected = new AttachmentCheckResult { HasAttachments = false, AttachmentCount = 0 };
|
||||
var (client, _) = BuildJson(expected);
|
||||
|
||||
// Act
|
||||
var result = await client.CheckAttachmentsAsync(new MemoryStream(FakePdfBytes()));
|
||||
|
||||
// Assert
|
||||
result.HasAttachments.Should().BeFalse();
|
||||
result.AttachmentCount.Should().Be(0);
|
||||
result.Attachments.Should().BeEmpty();
|
||||
}
|
||||
|
||||
// ?? CheckAttachmentsAsync (byte[]) ???????????????????????????????????????
|
||||
|
||||
[Fact]
|
||||
public async Task CheckAttachmentsAsync_Bytes_SendsJsonWithBase64()
|
||||
{
|
||||
// Arrange
|
||||
var expected = new AttachmentCheckResult { HasAttachments = false };
|
||||
var (client, handler) = BuildJson(expected);
|
||||
|
||||
// Act
|
||||
await client.CheckAttachmentsAsync(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();
|
||||
}
|
||||
|
||||
// ?? ExtractAttachmentsAsync (Stream) — ZIP unzip ?????????????????????????
|
||||
|
||||
[Fact]
|
||||
public async Task ExtractAttachmentsAsync_Stream_UnzipsAndReturnsDictionary()
|
||||
{
|
||||
// Arrange
|
||||
var zipBytes = BuildZip(new Dictionary<string, string>
|
||||
{
|
||||
["factur-x.xml"] = "<invoice>test</invoice>",
|
||||
["readme.txt"] = "Hello World"
|
||||
});
|
||||
var (client, handler) = BuildBytes(zipBytes);
|
||||
|
||||
// Act
|
||||
var result = await client.ExtractAttachmentsAsync(new MemoryStream(FakePdfBytes()));
|
||||
|
||||
// Assert
|
||||
result.Should().HaveCount(2);
|
||||
result.Should().ContainKey("factur-x.xml");
|
||||
result.Should().ContainKey("readme.txt");
|
||||
|
||||
using var xmlStream = result["factur-x.xml"];
|
||||
var xmlContent = await new StreamReader(xmlStream).ReadToEndAsync();
|
||||
xmlContent.Should().Be("<invoice>test</invoice>");
|
||||
|
||||
handler.LastRequest!.RequestUri!.PathAndQuery.Should().Be("/api/pdf/attachments/extract");
|
||||
|
||||
// Cleanup
|
||||
foreach (var s in result.Values) s.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExtractAttachmentsAsync_Stream_WithSingleEntry_ReturnsOneItem()
|
||||
{
|
||||
// Arrange
|
||||
var zipBytes = BuildZip(new Dictionary<string, string>
|
||||
{
|
||||
["data.xml"] = "<root/>"
|
||||
});
|
||||
var (client, _) = BuildBytes(zipBytes);
|
||||
|
||||
// Act
|
||||
var result = await client.ExtractAttachmentsAsync(new MemoryStream(FakePdfBytes()));
|
||||
|
||||
// Assert
|
||||
result.Should().HaveCount(1);
|
||||
result.Should().ContainKey("data.xml");
|
||||
|
||||
foreach (var s in result.Values) s.Dispose();
|
||||
}
|
||||
|
||||
// ?? ExtractAttachmentsAsync (byte[]) — ZIP unzip ?????????????????????????
|
||||
|
||||
[Fact]
|
||||
public async Task ExtractAttachmentsAsync_Bytes_SendsJsonAndUnzips()
|
||||
{
|
||||
// Arrange
|
||||
var zipBytes = BuildZip(new Dictionary<string, string>
|
||||
{
|
||||
["invoice.xml"] = "<invoice/>"
|
||||
});
|
||||
var (client, handler) = BuildBytes(zipBytes);
|
||||
|
||||
// Act
|
||||
var result = await client.ExtractAttachmentsAsync(FakePdfBytes());
|
||||
|
||||
// Assert
|
||||
result.Should().ContainKey("invoice.xml");
|
||||
handler.LastRequest!.Content.Should().NotBeNull();
|
||||
handler.LastRequest.Content!.Headers.ContentType!.MediaType.Should().Be("application/json");
|
||||
|
||||
foreach (var s in result.Values) s.Dispose();
|
||||
}
|
||||
|
||||
// ?? HTTP error propagation ???????????????????????????????????????????????
|
||||
|
||||
[Fact]
|
||||
public async Task CheckAttachmentsAsync_WhenApiReturns404_ThrowsHttpRequestException()
|
||||
{
|
||||
// Arrange
|
||||
var handler = MockHttpMessageHandler.ReturningStatus(HttpStatusCode.NotFound);
|
||||
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") };
|
||||
var client = new PdfAttachmentClient(httpClient, NullLogger<PdfAttachmentClient>.Instance);
|
||||
|
||||
// Act & Assert
|
||||
await client.Invoking(c => c.CheckAttachmentsAsync(new MemoryStream(FakePdfBytes())))
|
||||
.Should().ThrowAsync<HttpRequestException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExtractAttachmentsAsync_WhenApiReturns500_ThrowsHttpRequestException()
|
||||
{
|
||||
// Arrange
|
||||
var handler = MockHttpMessageHandler.ReturningStatus(HttpStatusCode.InternalServerError);
|
||||
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") };
|
||||
var client = new PdfAttachmentClient(httpClient, NullLogger<PdfAttachmentClient>.Instance);
|
||||
|
||||
// Act & Assert
|
||||
await client.Invoking(c => c.ExtractAttachmentsAsync(new MemoryStream(FakePdfBytes())))
|
||||
.Should().ThrowAsync<HttpRequestException>();
|
||||
}
|
||||
}
|
||||
236
DocumentService.Tests/Unit/Client/PdfOperationsClientTests.cs
Normal file
236
DocumentService.Tests/Unit/Client/PdfOperationsClientTests.cs
Normal file
@@ -0,0 +1,236 @@
|
||||
using DocumentService.Client.Clients;
|
||||
using DocumentService.Client.Interfaces;
|
||||
using DocumentService.Client.Models.Requests;
|
||||
using DocumentService.Client.Models.ValueObjects;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace DocumentService.Tests.Unit.Client;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for <see cref="PdfOperationsClient"/>.
|
||||
/// All tests use a fake <see cref="MockHttpMessageHandler"/> — no real HTTP calls are made.
|
||||
/// </summary>
|
||||
public class PdfOperationsClientTests
|
||||
{
|
||||
// ?? helpers ?????????????????????????????????????????????????????????????
|
||||
|
||||
private static (PdfOperationsClient client, MockHttpMessageHandler handler) BuildBytes(byte[] bytes = null!)
|
||||
{
|
||||
var handler = MockHttpMessageHandler.ReturningBytes(bytes ?? "merged-pdf"u8.ToArray(), "application/pdf");
|
||||
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") };
|
||||
var client = new PdfOperationsClient(httpClient, NullLogger<PdfOperationsClient>.Instance);
|
||||
return (client, handler);
|
||||
}
|
||||
|
||||
private static byte[] FakePdfBytes() => "fake-pdf-content"u8.ToArray();
|
||||
|
||||
private static AddAnnotationBase64Request FakeAnnotationRequest() => new()
|
||||
{
|
||||
Base64Pdf = string.Empty,
|
||||
AnnotationType = AnnotationType.TextMarkup,
|
||||
PageNumber = 1,
|
||||
X1 = 10, Y1 = 20, Width = 100, Height = 30,
|
||||
Color = "FFFF00",
|
||||
TextMarkupStyle = TextMarkupStyle.Highlight,
|
||||
Origin = AnnotationOrigin.TopLeft
|
||||
};
|
||||
|
||||
private static AddStampBase64Request FakeStampRequest() => new()
|
||||
{
|
||||
Base64Pdf = string.Empty,
|
||||
StampType = StampType.Text,
|
||||
X = 100, Y = 50,
|
||||
Text = "CONFIDENTIAL",
|
||||
FontSize = 24,
|
||||
Color = "FF0000",
|
||||
Opacity = 0.5,
|
||||
Placement = StampPlacement.Foreground,
|
||||
Origin = AnnotationOrigin.BottomLeft
|
||||
};
|
||||
|
||||
// ?? MergeAsync (Streams) ?????????????????????????????????????????????????
|
||||
|
||||
[Fact]
|
||||
public async Task MergeAsync_Streams_SendsMultipartWithAllFiles()
|
||||
{
|
||||
// Arrange
|
||||
var (client, handler) = BuildBytes();
|
||||
var streams = new List<Stream>
|
||||
{
|
||||
new MemoryStream(FakePdfBytes()),
|
||||
new MemoryStream(FakePdfBytes())
|
||||
};
|
||||
|
||||
// Act
|
||||
using var result = await client.MergeAsync(streams);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
handler.LastRequest!.RequestUri!.PathAndQuery.Should().Be("/api/pdf/operations/merge");
|
||||
handler.LastRequest.Content.Should().BeOfType<MultipartFormDataContent>();
|
||||
|
||||
foreach (var s in streams) s.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MergeAsync_Streams_ReturnsMergedPdfStream()
|
||||
{
|
||||
// Arrange
|
||||
var expectedBytes = "merged-content"u8.ToArray();
|
||||
var (client, _) = BuildBytes(expectedBytes);
|
||||
|
||||
// Act
|
||||
using var result = await client.MergeAsync(new[] { new MemoryStream(FakePdfBytes()), new MemoryStream(FakePdfBytes()) });
|
||||
var actualBytes = await result.ReadAllBytesAsync();
|
||||
|
||||
// Assert
|
||||
actualBytes.Should().BeEquivalentTo(expectedBytes);
|
||||
}
|
||||
|
||||
// ?? MergeAsync (byte[][]) ????????????????????????????????????????????????
|
||||
|
||||
[Fact]
|
||||
public async Task MergeAsync_ByteArrays_SendsJsonWithBase64List()
|
||||
{
|
||||
// Arrange
|
||||
var (client, handler) = BuildBytes();
|
||||
|
||||
// Act
|
||||
using var result = await client.MergeAsync(new[] { FakePdfBytes(), 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("base64Pdfs").GetArrayLength().Should().Be(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MergeAsync_ByteArrays_WithPageRanges_IncludesPageRangesInJson()
|
||||
{
|
||||
// Arrange
|
||||
var (client, handler) = BuildBytes();
|
||||
var pageRanges = new List<string?> { "1-2", null };
|
||||
|
||||
// Act
|
||||
await client.MergeAsync(new[] { FakePdfBytes(), FakePdfBytes() }, pageRanges);
|
||||
|
||||
// Assert
|
||||
handler.LastRequest!.Content.Should().NotBeNull();
|
||||
var body = await handler.LastRequest!.Content!.ReadAsStringAsync();
|
||||
var doc = JsonDocument.Parse(body);
|
||||
doc.RootElement.GetProperty("pageRanges").GetArrayLength().Should().Be(2);
|
||||
}
|
||||
|
||||
// ?? AnnotateAsync (Stream) ????????????????????????????????????????????????
|
||||
|
||||
[Fact]
|
||||
public async Task AnnotateAsync_Stream_SendsMultipartWithAnnotationFields()
|
||||
{
|
||||
// Arrange
|
||||
var (client, handler) = BuildBytes();
|
||||
var request = FakeAnnotationRequest();
|
||||
|
||||
// Act
|
||||
using var result = await client.AnnotateAsync(new MemoryStream(FakePdfBytes()), request);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
handler.LastRequest!.RequestUri!.PathAndQuery.Should().Be("/api/pdf/operations/annotate");
|
||||
handler.LastRequest.Content.Should().BeOfType<MultipartFormDataContent>();
|
||||
}
|
||||
|
||||
// ?? AnnotateAsync (byte[]) ????????????????????????????????????????????????
|
||||
|
||||
[Fact]
|
||||
public async Task AnnotateAsync_Bytes_SendsJsonWithBase64Pdf()
|
||||
{
|
||||
// Arrange
|
||||
var (client, handler) = BuildBytes();
|
||||
var request = FakeAnnotationRequest();
|
||||
|
||||
// Act
|
||||
await client.AnnotateAsync(FakePdfBytes(), request);
|
||||
|
||||
// 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();
|
||||
// annotationType serializes as integer by default (TextMarkup = 0)
|
||||
doc.RootElement.GetProperty("annotationType").GetInt32().Should().Be((int)AnnotationType.TextMarkup);
|
||||
}
|
||||
|
||||
// ?? StampAsync (Stream) ???????????????????????????????????????????????????
|
||||
|
||||
[Fact]
|
||||
public async Task StampAsync_Stream_SendsMultipartWithStampFields()
|
||||
{
|
||||
// Arrange
|
||||
var (client, handler) = BuildBytes();
|
||||
var request = FakeStampRequest();
|
||||
|
||||
// Act
|
||||
using var result = await client.StampAsync(new MemoryStream(FakePdfBytes()), request);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
handler.LastRequest!.RequestUri!.PathAndQuery.Should().Be("/api/pdf/operations/stamp");
|
||||
handler.LastRequest.Content.Should().BeOfType<MultipartFormDataContent>();
|
||||
}
|
||||
|
||||
// ?? StampAsync (byte[]) ???????????????????????????????????????????????????
|
||||
|
||||
[Fact]
|
||||
public async Task StampAsync_Bytes_SendsJsonWithBase64Pdf()
|
||||
{
|
||||
// Arrange
|
||||
var (client, handler) = BuildBytes();
|
||||
var request = FakeStampRequest();
|
||||
|
||||
// Act
|
||||
await client.StampAsync(FakePdfBytes(), request);
|
||||
|
||||
// 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();
|
||||
// stampType serializes as integer by default (Text = 0)
|
||||
doc.RootElement.GetProperty("stampType").GetInt32().Should().Be((int)StampType.Text);
|
||||
}
|
||||
|
||||
// ?? HTTP error propagation ???????????????????????????????????????????????
|
||||
|
||||
[Fact]
|
||||
public async Task MergeAsync_WhenApiReturns400_ThrowsHttpRequestException()
|
||||
{
|
||||
// Arrange
|
||||
var handler = MockHttpMessageHandler.ReturningStatus(HttpStatusCode.BadRequest);
|
||||
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") };
|
||||
var client = new PdfOperationsClient(httpClient, NullLogger<PdfOperationsClient>.Instance);
|
||||
|
||||
// Act & Assert
|
||||
await client.Invoking(c => c.MergeAsync(new[] { new MemoryStream(FakePdfBytes()), new MemoryStream(FakePdfBytes()) }))
|
||||
.Should().ThrowAsync<HttpRequestException>();
|
||||
}
|
||||
}
|
||||
|
||||
// ?? local helper extension ???????????????????????????????????????????????????
|
||||
|
||||
file static class StreamHelper
|
||||
{
|
||||
public static async Task<byte[]> ReadAllBytesAsync(this Stream stream)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
await stream.CopyToAsync(ms);
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
183
DocumentService.Tests/Unit/Client/PdfValidationClientTests.cs
Normal file
183
DocumentService.Tests/Unit/Client/PdfValidationClientTests.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
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>(T responseBody)
|
||||
{
|
||||
var handler = MockHttpMessageHandler.ReturningJson(responseBody);
|
||||
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") };
|
||||
return (new PdfValidationClient(httpClient, NullLogger<PdfValidationClient>.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<PdfValidationClient>.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<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]
|
||||
public async Task ValidatePdfAsync_Stream_ThrowsWhenApiReturnsNull()
|
||||
{
|
||||
var handler = MockHttpMessageHandler.ReturningJson<PdfValidationResult?>(null);
|
||||
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") };
|
||||
var client = new PdfValidationClient(httpClient, NullLogger<PdfValidationClient>.Instance);
|
||||
|
||||
await client.Invoking(c => c.ValidatePdfAsync(new MemoryStream(FakePdfBytes())))
|
||||
.Should().ThrowAsync<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[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]
|
||||
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<HttpRequestException>();
|
||||
}
|
||||
|
||||
// ── 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<MultipartFormDataContent>();
|
||||
}
|
||||
|
||||
[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<HttpRequestException>();
|
||||
}
|
||||
|
||||
// ── 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<HttpRequestException>();
|
||||
}
|
||||
}
|
||||
123
DocumentService.Tests/Unit/Client/SwissQrCodeClientTests.cs
Normal file
123
DocumentService.Tests/Unit/Client/SwissQrCodeClientTests.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for <see cref="SwissQrCodeClient"/>.
|
||||
/// All tests use a fake <see cref="MockHttpMessageHandler"/> — no real HTTP calls are made.
|
||||
/// </summary>
|
||||
public class SwissQrCodeClientTests
|
||||
{
|
||||
// ?? helpers ?????????????????????????????????????????????????????????????
|
||||
|
||||
private static (SwissQrCodeClient client, MockHttpMessageHandler handler) Build<T>(T body)
|
||||
{
|
||||
var handler = MockHttpMessageHandler.ReturningJson(body);
|
||||
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") };
|
||||
var client = new SwissQrCodeClient(httpClient, NullLogger<SwissQrCodeClient>.Instance);
|
||||
return (client, handler);
|
||||
}
|
||||
|
||||
private static byte[] FakePdfBytes() => "fake-pdf-content"u8.ToArray();
|
||||
|
||||
// ?? ExtractSwissQrCodeAsync (Stream) — parsed Bill ???????????????????????
|
||||
|
||||
[Fact]
|
||||
public async Task ExtractSwissQrCodeAsync_Stream_ParsedMode_SendsMultipartToCorrectEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var expected = new SwissQrCodeExtractionResult { Bill = new { Iban = "CH93-0076-2011-6238-5295-7" } };
|
||||
var (client, handler) = Build(expected);
|
||||
|
||||
// Act
|
||||
var result = await client.ExtractSwissQrCodeAsync(new MemoryStream(FakePdfBytes()), raw: false);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
handler.LastRequest!.RequestUri!.PathAndQuery.Should().Be("/api/pdf/qr-code/extract-swiss?raw=False");
|
||||
handler.LastRequest.Content.Should().BeOfType<MultipartFormDataContent>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExtractSwissQrCodeAsync_Stream_RawMode_SendsRawFlagInUrl()
|
||||
{
|
||||
// Arrange
|
||||
var expected = new SwissQrCodeExtractionResult { RawLines = new List<string> { "SPC", "0200", "1" } };
|
||||
var (client, handler) = Build(expected);
|
||||
|
||||
// Act
|
||||
var result = await client.ExtractSwissQrCodeAsync(new MemoryStream(FakePdfBytes()), raw: true);
|
||||
|
||||
// Assert
|
||||
result.RawLines.Should().HaveCount(3).And.StartWith("SPC");
|
||||
handler.LastRequest!.RequestUri!.PathAndQuery.Should().Be("/api/pdf/qr-code/extract-swiss?raw=True");
|
||||
}
|
||||
|
||||
// ?? ExtractSwissQrCodeAsync (byte[]) ?????????????????????????????????????
|
||||
|
||||
[Fact]
|
||||
public async Task ExtractSwissQrCodeAsync_Bytes_SendsJsonWithBase64()
|
||||
{
|
||||
// Arrange
|
||||
var expected = new SwissQrCodeExtractionResult();
|
||||
var (client, handler) = Build(expected);
|
||||
|
||||
// Act
|
||||
await client.ExtractSwissQrCodeAsync(FakePdfBytes(), raw: false);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExtractSwissQrCodeAsync_Bytes_RawMode_IncludesRawFlagInUrl()
|
||||
{
|
||||
// Arrange
|
||||
var expected = new SwissQrCodeExtractionResult { RawLines = new List<string> { "SPC" } };
|
||||
var (client, handler) = Build(expected);
|
||||
|
||||
// Act
|
||||
await client.ExtractSwissQrCodeAsync(FakePdfBytes(), raw: true);
|
||||
|
||||
// Assert
|
||||
handler.LastRequest!.RequestUri!.PathAndQuery.Should().Be("/api/pdf/qr-code/extract-swiss?raw=True");
|
||||
}
|
||||
|
||||
// ?? HTTP error propagation ???????????????????????????????????????????????
|
||||
|
||||
[Fact]
|
||||
public async Task ExtractSwissQrCodeAsync_WhenApiReturns404_ThrowsHttpRequestException()
|
||||
{
|
||||
// Arrange
|
||||
var handler = MockHttpMessageHandler.ReturningStatus(HttpStatusCode.NotFound);
|
||||
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") };
|
||||
var client = new SwissQrCodeClient(httpClient, NullLogger<SwissQrCodeClient>.Instance);
|
||||
|
||||
// Act & Assert
|
||||
await client.Invoking(c => c.ExtractSwissQrCodeAsync(new MemoryStream(FakePdfBytes())))
|
||||
.Should().ThrowAsync<HttpRequestException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExtractSwissQrCodeAsync_WhenApiReturns500_ThrowsHttpRequestException()
|
||||
{
|
||||
// Arrange
|
||||
var handler = MockHttpMessageHandler.ReturningStatus(HttpStatusCode.InternalServerError);
|
||||
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") };
|
||||
var client = new SwissQrCodeClient(httpClient, NullLogger<SwissQrCodeClient>.Instance);
|
||||
|
||||
// Act & Assert
|
||||
await client.Invoking(c => c.ExtractSwissQrCodeAsync(FakePdfBytes()))
|
||||
.Should().ThrowAsync<HttpRequestException>();
|
||||
}
|
||||
}
|
||||
199
DocumentService.Tests/Unit/Client/ZugferdClientTests.cs
Normal file
199
DocumentService.Tests/Unit/Client/ZugferdClientTests.cs
Normal file
@@ -0,0 +1,199 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for <see cref="ZugferdClient"/>.
|
||||
/// All tests use a fake <see cref="MockHttpMessageHandler"/> — no real HTTP calls are made.
|
||||
/// </summary>
|
||||
public class ZugferdClientTests
|
||||
{
|
||||
// ?? helpers ?????????????????????????????????????????????????????????????
|
||||
|
||||
private static (ZugferdClient client, MockHttpMessageHandler handler) BuildJson<T>(T body)
|
||||
{
|
||||
var handler = MockHttpMessageHandler.ReturningJson(body);
|
||||
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") };
|
||||
var client = new ZugferdClient(httpClient, NullLogger<ZugferdClient>.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<ZugferdClient>.Instance);
|
||||
return (client, handler);
|
||||
}
|
||||
|
||||
private static byte[] FakePdfBytes() => "fake-pdf-content"u8.ToArray();
|
||||
private static byte[] FakeXmlBytes() => "<invoice>test</invoice>"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<MultipartFormDataContent>();
|
||||
}
|
||||
|
||||
[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<MultipartFormDataContent>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExtractZugferdAsync_Stream_ReturnsXmlContent()
|
||||
{
|
||||
// Arrange
|
||||
var xmlBytes = "<root><invoice/></root>"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("<root><invoice/></root>");
|
||||
}
|
||||
|
||||
// ?? 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 = "<invoice/>",
|
||||
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("<invoice/>");
|
||||
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 = "<x/>" };
|
||||
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<ZugferdClient>.Instance);
|
||||
|
||||
// Act & Assert
|
||||
await client.Invoking(c => c.HasZugferdAsync(new MemoryStream(FakePdfBytes())))
|
||||
.Should().ThrowAsync<HttpRequestException>();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user