Add unit tests for WorkflowsClient validation/extraction
Added `WorkflowsClientTests` to validate the behavior of the `WorkflowsClient` class, covering PDF validation and Swiss QR code extraction. Introduced test doubles for `IPdfValidationClient` and `ISwissQrCodeClient` using `Moq`. Implemented tests for all overloads of `ExtractSwissQrCodeValidatedAsync`: - Byte array, stream, and file path inputs. - Valid, invalid, and empty PDFs. - Raw mode and cancellation token handling. - Exception propagation and orchestration contracts. Ensured comprehensive test coverage for all branches and edge cases.
This commit is contained in:
473
DocumentService.Tests/Unit/Client/WorkflowsClientTests.cs
Normal file
473
DocumentService.Tests/Unit/Client/WorkflowsClientTests.cs
Normal file
@@ -0,0 +1,473 @@
|
||||
using DocumentService.Application.Common.DTOs;
|
||||
using DocumentService.Client.Clients;
|
||||
using DocumentService.Client.Interfaces;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
|
||||
namespace DocumentService.Tests.Unit.Client;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for <see cref="WorkflowsClient"/>.
|
||||
/// Both <see cref="IPdfValidationClient"/> and <see cref="ISwissQrCodeClient"/> are mocked –
|
||||
/// no real HTTP calls are made. Tests cover every overload, every branch, and all edge cases.
|
||||
/// </summary>
|
||||
public class WorkflowsClientTests
|
||||
{
|
||||
// ─── test doubles ────────────────────────────────────────────────────────
|
||||
|
||||
private readonly Mock<IPdfValidationClient> _validation = new();
|
||||
private readonly Mock<ISwissQrCodeClient> _swissQrCode = new();
|
||||
private WorkflowsClient Sut => new(_validation.Object, _swissQrCode.Object);
|
||||
|
||||
// ─── fixture data ─────────────────────────────────────────────────────────
|
||||
|
||||
private static byte[] ValidPdfBytes() => LoadTestPdf("valid.pdf");
|
||||
private static byte[] EncryptedPdfBytes() => LoadTestPdf("encryptedPdf.pdf");
|
||||
private static byte[] QrPdfBytes() => LoadTestPdf("pdfWithSwissQRCode.pdf");
|
||||
|
||||
private static byte[] LoadTestPdf(string filename)
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
var resourceName = $"DocumentService.Tests.TestData.Pdfs.{filename}";
|
||||
using var stream = assembly.GetManifestResourceStream(resourceName)
|
||||
?? throw new FileNotFoundException(
|
||||
$"Embedded resource '{resourceName}' not found. " +
|
||||
$"Available: {string.Join(", ", assembly.GetManifestResourceNames())}");
|
||||
using var ms = new MemoryStream();
|
||||
stream.CopyTo(ms);
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
// ─── valid PdfValidationResult helpers ───────────────────────────────────
|
||||
|
||||
private static PdfValidationResult ValidResult() =>
|
||||
new(PageCount: 1, FileSizeBytes: 1024, FileSizeMB: 0.001,
|
||||
PdfVersion: "1.4", HasAttachments: false, AttachmentCount: 0,
|
||||
IsEncrypted: false);
|
||||
|
||||
private static PdfValidationResult InvalidResult_Encrypted() =>
|
||||
new(PageCount: 1, FileSizeBytes: 1024, FileSizeMB: 0.001,
|
||||
PdfVersion: "1.4", HasAttachments: false, AttachmentCount: 0,
|
||||
IsEncrypted: true);
|
||||
|
||||
private static PdfValidationResult InvalidResult_EmptyFile() =>
|
||||
new(PageCount: 0, FileSizeBytes: 0, FileSizeMB: 0,
|
||||
PdfVersion: "", HasAttachments: false, AttachmentCount: 0,
|
||||
IsEncrypted: false);
|
||||
|
||||
private static SwissQrCodeExtractionResult QrResult() =>
|
||||
new(
|
||||
Bill: new SwissQrBillDto
|
||||
{
|
||||
Version = "V2_0",
|
||||
Currency = "CHF",
|
||||
Amount = 630.20m,
|
||||
Account = "CH9300762011623852957",
|
||||
Creditor = new AddressDto { Name = "ALMAT AG", Town = "Tagelswangen", CountryCode = "CH", Type = "S", PostalCode = "8317" },
|
||||
ReferenceType = "QRR",
|
||||
Reference = "000000000000000000252080824"
|
||||
},
|
||||
RawLines: ["SPC", "0200", "1"]
|
||||
);
|
||||
|
||||
private static SwissQrCodeExtractionResult RawQrResult() =>
|
||||
new(Bill: (SwissQrBillDto?)null, RawLines: ["SPC", "0200", "1", "CH9300762011623852957"]);
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════
|
||||
// byte[] overload
|
||||
// ═════════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public async Task Bytes_ValidPdf_CallsValidationThenExtraction_ReturnsResult()
|
||||
{
|
||||
// Arrange
|
||||
var pdfBytes = ValidPdfBytes();
|
||||
_validation.Setup(v => v.ValidatePdfAsync(pdfBytes, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(ValidResult());
|
||||
_swissQrCode.Setup(q => q.ExtractSwissQrCodeAsync(pdfBytes, false, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(QrResult());
|
||||
|
||||
// Act
|
||||
var result = await Sut.ExtractSwissQrCodeValidatedAsync(pdfBytes);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result!.Bill.Should().NotBeNull();
|
||||
result.Bill!.Currency.Should().Be("CHF");
|
||||
_validation.Verify(v => v.ValidatePdfAsync(pdfBytes, It.IsAny<CancellationToken>()), Times.Once);
|
||||
_swissQrCode.Verify(q => q.ExtractSwissQrCodeAsync(pdfBytes, false, It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Bytes_ValidPdf_RawMode_PassesRawFlagToExtraction()
|
||||
{
|
||||
// Arrange
|
||||
var pdfBytes = QrPdfBytes();
|
||||
_validation.Setup(v => v.ValidatePdfAsync(pdfBytes, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(ValidResult());
|
||||
_swissQrCode.Setup(q => q.ExtractSwissQrCodeAsync(pdfBytes, true, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(RawQrResult());
|
||||
|
||||
// Act
|
||||
var result = await Sut.ExtractSwissQrCodeValidatedAsync(pdfBytes, raw: true);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result!.RawLines.Should().Contain("SPC");
|
||||
_swissQrCode.Verify(q => q.ExtractSwissQrCodeAsync(pdfBytes, true, It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Bytes_InvalidPdf_ThrowIfInvalidTrue_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
var pdfBytes = EncryptedPdfBytes();
|
||||
_validation.Setup(v => v.ValidatePdfAsync(pdfBytes, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(InvalidResult_Encrypted());
|
||||
|
||||
// Act
|
||||
var act = async () => await Sut.ExtractSwissQrCodeValidatedAsync(pdfBytes, throwIfInvalid: true);
|
||||
|
||||
// Assert
|
||||
await act.Should().ThrowAsync<InvalidOperationException>()
|
||||
.WithMessage("*PDF validation failed*");
|
||||
_swissQrCode.Verify(q => q.ExtractSwissQrCodeAsync(It.IsAny<byte[]>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Bytes_InvalidPdf_ThrowIfInvalidFalse_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var pdfBytes = EncryptedPdfBytes();
|
||||
_validation.Setup(v => v.ValidatePdfAsync(pdfBytes, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(InvalidResult_Encrypted());
|
||||
|
||||
// Act
|
||||
var result = await Sut.ExtractSwissQrCodeValidatedAsync(pdfBytes, throwIfInvalid: false);
|
||||
|
||||
// Assert
|
||||
result.Should().BeNull();
|
||||
_swissQrCode.Verify(q => q.ExtractSwissQrCodeAsync(It.IsAny<byte[]>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Bytes_EmptyFilePdf_ThrowIfInvalidTrue_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange – PageCount=0 and FileSize=0 → IsValid=false
|
||||
var pdfBytes = new byte[] { 0x00 };
|
||||
_validation.Setup(v => v.ValidatePdfAsync(pdfBytes, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(InvalidResult_EmptyFile());
|
||||
|
||||
// Act & Assert
|
||||
await Sut.Invoking(s => s.ExtractSwissQrCodeValidatedAsync(pdfBytes, throwIfInvalid: true))
|
||||
.Should().ThrowAsync<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Bytes_ExceptionMessage_ContainsValidationDetails()
|
||||
{
|
||||
// Arrange – ensure the message includes diagnostic fields
|
||||
var pdfBytes = ValidPdfBytes();
|
||||
_validation.Setup(v => v.ValidatePdfAsync(pdfBytes, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(InvalidResult_Encrypted());
|
||||
|
||||
// Act
|
||||
var act = async () => await Sut.ExtractSwissQrCodeValidatedAsync(pdfBytes, throwIfInvalid: true);
|
||||
|
||||
// Assert
|
||||
var ex = await act.Should().ThrowAsync<InvalidOperationException>();
|
||||
ex.WithMessage("*IsEncrypted=True*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Bytes_ValidationThrows_PropagatesHttpRequestException()
|
||||
{
|
||||
// Arrange – simulate network/API failure during validation
|
||||
var pdfBytes = ValidPdfBytes();
|
||||
_validation.Setup(v => v.ValidatePdfAsync(pdfBytes, It.IsAny<CancellationToken>()))
|
||||
.ThrowsAsync(new HttpRequestException("API unreachable"));
|
||||
|
||||
// Act & Assert
|
||||
await Sut.Invoking(s => s.ExtractSwissQrCodeValidatedAsync(pdfBytes))
|
||||
.Should().ThrowAsync<HttpRequestException>()
|
||||
.WithMessage("*API unreachable*");
|
||||
_swissQrCode.Verify(q => q.ExtractSwissQrCodeAsync(It.IsAny<byte[]>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Bytes_ExtractionThrows_PropagatesHttpRequestException()
|
||||
{
|
||||
// Arrange – validation succeeds but extraction fails
|
||||
var pdfBytes = QrPdfBytes();
|
||||
_validation.Setup(v => v.ValidatePdfAsync(pdfBytes, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(ValidResult());
|
||||
_swissQrCode.Setup(q => q.ExtractSwissQrCodeAsync(pdfBytes, false, It.IsAny<CancellationToken>()))
|
||||
.ThrowsAsync(new HttpRequestException("Extraction failed"));
|
||||
|
||||
// Act & Assert
|
||||
await Sut.Invoking(s => s.ExtractSwissQrCodeValidatedAsync(pdfBytes))
|
||||
.Should().ThrowAsync<HttpRequestException>()
|
||||
.WithMessage("*Extraction failed*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Bytes_ValidPdf_CancellationToken_IsForwardedToBothClients()
|
||||
{
|
||||
// Arrange
|
||||
var pdfBytes = ValidPdfBytes();
|
||||
var cts = new CancellationTokenSource();
|
||||
var token = cts.Token;
|
||||
_validation.Setup(v => v.ValidatePdfAsync(pdfBytes, token)).ReturnsAsync(ValidResult());
|
||||
_swissQrCode.Setup(q => q.ExtractSwissQrCodeAsync(pdfBytes, false, token)).ReturnsAsync(QrResult());
|
||||
|
||||
// Act
|
||||
await Sut.ExtractSwissQrCodeValidatedAsync(pdfBytes, cancellationToken: token);
|
||||
|
||||
// Assert
|
||||
_validation.Verify(v => v.ValidatePdfAsync(pdfBytes, token), Times.Once);
|
||||
_swissQrCode.Verify(q => q.ExtractSwissQrCodeAsync(pdfBytes, false, token), Times.Once);
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════
|
||||
// Stream overload
|
||||
// ═════════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public async Task Stream_ValidPdf_CallsValidationWithStreamThenExtractionWithStream_ReturnsResult()
|
||||
{
|
||||
// Arrange – validation client receives a Stream, extraction client receives the same stream reset to 0
|
||||
_validation.Setup(v => v.ValidatePdfAsync(It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(ValidResult());
|
||||
_swissQrCode.Setup(q => q.ExtractSwissQrCodeAsync(It.IsAny<Stream>(), false, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(QrResult());
|
||||
|
||||
using var pdfStream = new MemoryStream(ValidPdfBytes());
|
||||
|
||||
// Act
|
||||
var result = await Sut.ExtractSwissQrCodeValidatedAsync(pdfStream);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
_validation.Verify(v => v.ValidatePdfAsync(It.IsAny<Stream>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_swissQrCode.Verify(q => q.ExtractSwissQrCodeAsync(It.IsAny<Stream>(), false, It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Stream_SeekableStream_IsResetBetweenValidationAndExtraction()
|
||||
{
|
||||
// Arrange – we verify the stream position is 0 when extraction is called
|
||||
Stream? streamAtExtraction = null;
|
||||
_validation.Setup(v => v.ValidatePdfAsync(It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(ValidResult());
|
||||
_swissQrCode.Setup(q => q.ExtractSwissQrCodeAsync(It.IsAny<Stream>(), false, It.IsAny<CancellationToken>()))
|
||||
.Callback<Stream, bool, CancellationToken>((s, _, _) => streamAtExtraction = s)
|
||||
.ReturnsAsync(QrResult());
|
||||
|
||||
using var pdfStream = new MemoryStream(ValidPdfBytes());
|
||||
|
||||
// Act
|
||||
await Sut.ExtractSwissQrCodeValidatedAsync(pdfStream);
|
||||
|
||||
// Assert
|
||||
streamAtExtraction.Should().NotBeNull();
|
||||
streamAtExtraction!.Position.Should().Be(0, "stream must be rewound before extraction");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Stream_InvalidPdf_ThrowIfInvalidTrue_ThrowsWithoutCallingExtraction()
|
||||
{
|
||||
// Arrange
|
||||
_validation.Setup(v => v.ValidatePdfAsync(It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(InvalidResult_Encrypted());
|
||||
|
||||
using var pdfStream = new MemoryStream(EncryptedPdfBytes());
|
||||
|
||||
// Act & Assert
|
||||
await Sut.Invoking(s => s.ExtractSwissQrCodeValidatedAsync(pdfStream, throwIfInvalid: true))
|
||||
.Should().ThrowAsync<InvalidOperationException>()
|
||||
.WithMessage("*PDF validation failed*");
|
||||
_swissQrCode.Verify(q => q.ExtractSwissQrCodeAsync(It.IsAny<Stream>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Stream_InvalidPdf_ThrowIfInvalidFalse_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
_validation.Setup(v => v.ValidatePdfAsync(It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(InvalidResult_Encrypted());
|
||||
|
||||
using var pdfStream = new MemoryStream(EncryptedPdfBytes());
|
||||
|
||||
// Act
|
||||
var result = await Sut.ExtractSwissQrCodeValidatedAsync(pdfStream, throwIfInvalid: false);
|
||||
|
||||
// Assert
|
||||
result.Should().BeNull();
|
||||
_swissQrCode.Verify(q => q.ExtractSwissQrCodeAsync(It.IsAny<Stream>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Stream_ValidPdf_RawMode_PassesRawFlagToExtraction()
|
||||
{
|
||||
// Arrange
|
||||
_validation.Setup(v => v.ValidatePdfAsync(It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(ValidResult());
|
||||
_swissQrCode.Setup(q => q.ExtractSwissQrCodeAsync(It.IsAny<Stream>(), true, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(RawQrResult());
|
||||
|
||||
using var pdfStream = new MemoryStream(QrPdfBytes());
|
||||
|
||||
// Act
|
||||
var result = await Sut.ExtractSwissQrCodeValidatedAsync(pdfStream, raw: true);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
_swissQrCode.Verify(q => q.ExtractSwissQrCodeAsync(It.IsAny<Stream>(), true, It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════
|
||||
// filePath overload
|
||||
// ═════════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public async Task FilePath_ValidPdf_ReadsFileAndCallsBothClients_ReturnsResult()
|
||||
{
|
||||
// Arrange – write a real temp file so File.ReadAllBytesAsync / File.OpenRead can read it
|
||||
var tempPath = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
await File.WriteAllBytesAsync(tempPath, ValidPdfBytes());
|
||||
|
||||
_validation.Setup(v => v.ValidatePdfAsync(It.IsAny<byte[]>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(ValidResult());
|
||||
_swissQrCode.Setup(q => q.ExtractSwissQrCodeAsync(It.IsAny<byte[]>(), false, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(QrResult());
|
||||
|
||||
// Act
|
||||
var result = await Sut.ExtractSwissQrCodeValidatedAsync(tempPath);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
_validation.Verify(v => v.ValidatePdfAsync(It.IsAny<byte[]>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_swissQrCode.Verify(q => q.ExtractSwissQrCodeAsync(It.IsAny<byte[]>(), false, It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(tempPath);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FilePath_InvalidPdf_ThrowIfInvalidTrue_ThrowsWithoutCallingExtraction()
|
||||
{
|
||||
// Arrange
|
||||
var tempPath = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
await File.WriteAllBytesAsync(tempPath, EncryptedPdfBytes());
|
||||
|
||||
_validation.Setup(v => v.ValidatePdfAsync(It.IsAny<byte[]>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(InvalidResult_Encrypted());
|
||||
|
||||
// Act & Assert
|
||||
await Sut.Invoking(s => s.ExtractSwissQrCodeValidatedAsync(tempPath, throwIfInvalid: true))
|
||||
.Should().ThrowAsync<InvalidOperationException>();
|
||||
_swissQrCode.Verify(q => q.ExtractSwissQrCodeAsync(It.IsAny<byte[]>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(tempPath);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FilePath_InvalidPdf_ThrowIfInvalidFalse_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var tempPath = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
await File.WriteAllBytesAsync(tempPath, EncryptedPdfBytes());
|
||||
|
||||
_validation.Setup(v => v.ValidatePdfAsync(It.IsAny<byte[]>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(InvalidResult_Encrypted());
|
||||
|
||||
// Act
|
||||
var result = await Sut.ExtractSwissQrCodeValidatedAsync(tempPath, throwIfInvalid: false);
|
||||
|
||||
// Assert
|
||||
result.Should().BeNull();
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(tempPath);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FilePath_NonExistentFile_ThrowsFileNotFoundException()
|
||||
{
|
||||
// Arrange
|
||||
var nonExistent = Path.Combine(Path.GetTempPath(), "does_not_exist_12345.pdf");
|
||||
|
||||
// Act & Assert
|
||||
await Sut.Invoking(s => s.ExtractSwissQrCodeValidatedAsync(nonExistent))
|
||||
.Should().ThrowAsync<FileNotFoundException>();
|
||||
_validation.Verify(v => v.ValidatePdfAsync(It.IsAny<byte[]>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
_swissQrCode.Verify(q => q.ExtractSwissQrCodeAsync(It.IsAny<byte[]>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FilePath_ValidPdf_RawMode_PassesRawFlagToExtraction()
|
||||
{
|
||||
// Arrange
|
||||
var tempPath = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
await File.WriteAllBytesAsync(tempPath, QrPdfBytes());
|
||||
|
||||
_validation.Setup(v => v.ValidatePdfAsync(It.IsAny<byte[]>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(ValidResult());
|
||||
_swissQrCode.Setup(q => q.ExtractSwissQrCodeAsync(It.IsAny<byte[]>(), true, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(RawQrResult());
|
||||
|
||||
// Act
|
||||
var result = await Sut.ExtractSwissQrCodeValidatedAsync(tempPath, raw: true);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
_swissQrCode.Verify(q => q.ExtractSwissQrCodeAsync(It.IsAny<byte[]>(), true, It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(tempPath);
|
||||
}
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════
|
||||
// Orchestration contract: extraction must NEVER be called on invalid PDF
|
||||
// ═════════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task AnyOverload_InvalidPdf_ExtractionIsNeverCalled_RegardlessOfThrowIfInvalid(bool throwIfInvalid)
|
||||
{
|
||||
// Arrange
|
||||
var pdfBytes = EncryptedPdfBytes();
|
||||
_validation.Setup(v => v.ValidatePdfAsync(pdfBytes, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(InvalidResult_Encrypted());
|
||||
|
||||
// Act
|
||||
try { await Sut.ExtractSwissQrCodeValidatedAsync(pdfBytes, throwIfInvalid: throwIfInvalid); }
|
||||
catch (InvalidOperationException) { /* expected when throwIfInvalid=true */ }
|
||||
|
||||
// Assert — extraction must never be called in either branch
|
||||
_swissQrCode.Verify(
|
||||
q => q.ExtractSwissQrCodeAsync(It.IsAny<byte[]>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()),
|
||||
Times.Never,
|
||||
"extraction must not be attempted on an invalid document");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user