Consolidated DTOs under `DocumentService.Application.Common.DTOs.Requests` to centralize and align them with the application layer. Introduced `PdfBase64RequestBase` to encapsulate shared properties, reducing redundancy across request DTOs. Updated controllers, clients, and tests to use the new DTO structure. Removed redundant DTOs and cleaned up unused namespaces and comments. Improved documentation and ensured consistent naming conventions across the codebase.
223 lines
8.1 KiB
C#
223 lines
8.1 KiB
C#
using DocumentService.Application.Common.DTOs;
|
||
using DocumentService.Client.Clients;
|
||
using DocumentService.Client.Interfaces;
|
||
using DocumentService.Application.Common.DTOs.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"/> <20> 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<AttachmentDto>
|
||
{
|
||
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, Attachments = [] };
|
||
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) <20> 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[]) <20> 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>();
|
||
}
|
||
}
|