test(unit): migrate unit tests to Stream API

Application Handler Tests:
- ValidatePdfHandlerTests: PdfStream = new MemoryStream(pdfBytes)
- ValidatePdfAQueryHandlerTests: PdfStream = new MemoryStream(pdfBytes)
- CheckPdfAttachmentsQueryHandlerTests: PdfStream = new MemoryStream(pdfBytes)
- Remove Base64/PdfBytes property usage

Infrastructure Tests:
- DevExpressSwissQrCodeProcessorTests: LoadTestPdf() returns Stream
- All test methods use 'using var stream' pattern
- Add test: ExtractSwissQrCodeAsync_StreamNotAtBeginning_ThrowsBadRequestException

Result: All unit tests pass with Stream-based API
This commit is contained in:
2026-07-20 16:33:00 +02:00
parent 07be9b9f02
commit 645dfceafa
4 changed files with 53 additions and 39 deletions

View File

@@ -1,7 +1,6 @@
using System.Reflection;
using DocumentOperator.Application.Common.Interfaces;
using DocumentOperator.Domain.Common.Exceptions;
using DocumentOperator.Domain.Exceptions;
using DocumentOperator.Infrastructure.Services.QrCodeProcessing;
using FluentAssertions;
@@ -24,16 +23,16 @@ public class DevExpressSwissQrCodeProcessorTests
#region Helper Methods
/// <summary>
/// Loads a test PDF from embedded resources.
/// Loads a test PDF from embedded resources as a Stream.
/// </summary>
/// <param name="filename">Name of the PDF file (e.g., "pdfWithSwissQRCode.pdf")</param>
/// <returns>PDF content as byte array</returns>
private static byte[] LoadTestPdf(string filename)
/// <returns>PDF content as MemoryStream</returns>
private static Stream LoadTestPdf(string filename)
{
var assembly = Assembly.GetExecutingAssembly();
var resourceName = $"DocumentOperator.Tests.TestData.Pdfs.{filename}";
using var stream = assembly.GetManifestResourceStream(resourceName);
var stream = assembly.GetManifestResourceStream(resourceName);
if (stream == null)
{
@@ -42,9 +41,11 @@ public class DevExpressSwissQrCodeProcessorTests
$"Available resources: {string.Join(", ", assembly.GetManifestResourceNames())}");
}
using var memoryStream = new MemoryStream();
// Copy to MemoryStream so caller can reuse/seek
var memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
return memoryStream.ToArray();
memoryStream.Position = 0; // Reset position for reading
return memoryStream;
}
#endregion
@@ -55,10 +56,10 @@ public class DevExpressSwissQrCodeProcessorTests
public async Task ExtractSwissQrCodeAsync_PdfWithSwissQrCode_ReturnsQrCodeData()
{
// Arrange
byte[] pdfBytes = LoadTestPdf("pdfWithSwissQRCode.pdf");
using var pdfStream = LoadTestPdf("pdfWithSwissQRCode.pdf");
// Act
var (bill, rawLines) = await _sut.ExtractSwissQrCodeAsync(pdfBytes);
var (bill, rawLines) = await _sut.ExtractSwissQrCodeAsync(pdfStream);
// Assert
bill.Should().NotBeNull("PDF contains a Swiss QR Code");
@@ -73,10 +74,10 @@ public class DevExpressSwissQrCodeProcessorTests
public async Task ExtractSwissQrCodeAsync_PdfWithSwissQrCode_ReturnsValidIban()
{
// Arrange
byte[] pdfBytes = LoadTestPdf("pdfWithSwissQRCode.pdf");
using var pdfStream = LoadTestPdf("pdfWithSwissQRCode.pdf");
// Act
var (bill, _) = await _sut.ExtractSwissQrCodeAsync(pdfBytes);
var (bill, _) = await _sut.ExtractSwissQrCodeAsync(pdfStream);
// Assert
bill.Account.Should().MatchRegex(@"^CH\d{2}[A-Z0-9]{17}$",
@@ -87,10 +88,10 @@ public class DevExpressSwissQrCodeProcessorTests
public async Task ExtractSwissQrCodeAsync_PdfWithSwissQrCode_ReturnsCurrency()
{
// Arrange
byte[] pdfBytes = LoadTestPdf("pdfWithSwissQRCode.pdf");
using var pdfStream = LoadTestPdf("pdfWithSwissQRCode.pdf");
// Act
var (bill, _) = await _sut.ExtractSwissQrCodeAsync(pdfBytes);
var (bill, _) = await _sut.ExtractSwissQrCodeAsync(pdfStream);
// Assert
bill.Currency.Should().BeOneOf("CHF", "EUR",
@@ -101,10 +102,10 @@ public class DevExpressSwissQrCodeProcessorTests
public async Task ExtractSwissQrCodeAsync_PdfWithoutQrCode_ThrowsSwissQrCodeNotFoundException()
{
// Arrange: valid.pdf doesn't contain a Swiss QR Code
byte[] pdfBytes = LoadTestPdf("valid.pdf");
using var pdfStream = LoadTestPdf("valid.pdf");
// Act & Assert
var act = async () => await _sut.ExtractSwissQrCodeAsync(pdfBytes);
var act = async () => await _sut.ExtractSwissQrCodeAsync(pdfStream);
await act.Should().ThrowAsync<NotFoundException>()
.WithMessage("*No valid Swiss QR Code found*");
@@ -113,11 +114,11 @@ public class DevExpressSwissQrCodeProcessorTests
[Fact]
public async Task ExtractSwissQrCodeAsync_EmptyPdf_ThrowsException()
{
// Arrange: Empty byte array
byte[] emptyPdfBytes = [];
// Arrange: Empty stream
using var emptyStream = new MemoryStream();
// Act & Assert
var act = async () => await _sut.ExtractSwissQrCodeAsync(emptyPdfBytes);
var act = async () => await _sut.ExtractSwissQrCodeAsync(emptyStream);
await act.Should().ThrowAsync<Exception>()
.Where(ex => ex is ArgumentException);
@@ -127,26 +128,40 @@ public class DevExpressSwissQrCodeProcessorTests
public async Task ExtractSwissQrCodeAsync_NullInput_ThrowsArgumentNullException()
{
// Arrange
byte[] nullPdfBytes = null!;
Stream nullStream = null!;
// Act & Assert
var act = async () => await _sut.ExtractSwissQrCodeAsync(nullPdfBytes);
var act = async () => await _sut.ExtractSwissQrCodeAsync(nullStream);
await act.Should().ThrowAsync<NullReferenceException>();
await act.Should().ThrowAsync<ArgumentNullException>();
}
[Fact]
public async Task ExtractSwissQrCodeAsync_InvalidPdfBytes_ThrowsPdfProcessingException()
{
// Arrange: Random bytes that are not a valid PDF
byte[] invalidPdfBytes = "This is not a PDF file"u8.ToArray();
using var invalidStream = new MemoryStream("This is not a PDF file"u8.ToArray());
// Act & Assert
var act = async () => await _sut.ExtractSwissQrCodeAsync(invalidPdfBytes);
var act = async () => await _sut.ExtractSwissQrCodeAsync(invalidStream);
await act.Should().ThrowAsync<ArgumentException>();
}
[Fact]
public async Task ExtractSwissQrCodeAsync_StreamNotAtBeginning_ThrowsArgumentException()
{
// Arrange: Valid PDF but stream position is not at 0
using var pdfStream = LoadTestPdf("valid.pdf");
pdfStream.Position = 10; // Move position away from beginning
// Act & Assert
var act = async () => await _sut.ExtractSwissQrCodeAsync(pdfStream);
await act.Should().ThrowAsync<ArgumentException>()
.WithMessage("*Position = 0*");
}
#endregion
#region Swiss QR Code Content Validation Tests
@@ -155,10 +170,10 @@ public class DevExpressSwissQrCodeProcessorTests
public async Task ExtractSwissQrCodeAsync_ValidQrCode_ParsesCreditorInformation()
{
// Arrange
byte[] pdfBytes = LoadTestPdf("pdfWithSwissQRCode.pdf");
using var pdfStream = LoadTestPdf("pdfWithSwissQRCode.pdf");
// Act
var (bill, _) = await _sut.ExtractSwissQrCodeAsync(pdfBytes);
var (bill, _) = await _sut.ExtractSwissQrCodeAsync(pdfStream);
// Assert
bill.Creditor.Should().NotBeNull("Creditor information is required");
@@ -170,10 +185,10 @@ public class DevExpressSwissQrCodeProcessorTests
public async Task ExtractSwissQrCodeAsync_ValidQrCode_ParsesDebtorInformationIfPresent()
{
// Arrange
byte[] pdfBytes = LoadTestPdf("pdfWithSwissQRCode.pdf");
using var pdfStream = LoadTestPdf("pdfWithSwissQRCode.pdf");
// Act
var (bill, _) = await _sut.ExtractSwissQrCodeAsync(pdfBytes);
var (bill, _) = await _sut.ExtractSwissQrCodeAsync(pdfStream);
// Assert
// Debtor information is OPTIONAL in Swiss QR Bill Standard 2.0
@@ -190,10 +205,10 @@ public class DevExpressSwissQrCodeProcessorTests
public async Task ExtractSwissQrCodeAsync_ValidQrCode_ParsesAmountIfPresent()
{
// Arrange
byte[] pdfBytes = LoadTestPdf("pdfWithSwissQRCode.pdf");
using var pdfStream = LoadTestPdf("pdfWithSwissQRCode.pdf");
// Act
var (bill, _) = await _sut.ExtractSwissQrCodeAsync(pdfBytes);
var (bill, _) = await _sut.ExtractSwissQrCodeAsync(pdfStream);
// Assert
// Amount is OPTIONAL in Swiss QR Bill (can be 0.00 or null for payment slips)