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

@@ -30,7 +30,7 @@ public class CheckPdfAttachmentsQueryHandlerTests
{ {
// Arrange // Arrange
byte[] pdfBytes = "fake-pdf-content"u8.ToArray(); byte[] pdfBytes = "fake-pdf-content"u8.ToArray();
var query = new CheckPdfAttachmentsQuery { PdfBytes = pdfBytes }; var query = new CheckPdfAttachmentsQuery { PdfStream = new MemoryStream(pdfBytes) };
var domainResult = new AttachmentInfo( var domainResult = new AttachmentInfo(
hasAttachments: true, hasAttachments: true,
@@ -72,8 +72,7 @@ public class CheckPdfAttachmentsQueryHandlerTests
{ {
// Arrange // Arrange
byte[] pdfBytes = "fake-pdf-content"u8.ToArray(); byte[] pdfBytes = "fake-pdf-content"u8.ToArray();
string base64Pdf = Convert.ToBase64String(pdfBytes); var query = new CheckPdfAttachmentsQuery { PdfStream = new MemoryStream(pdfBytes) };
var query = new CheckPdfAttachmentsQuery { Base64Pdf = base64Pdf };
var domainResult = new AttachmentInfo(false, 0, []); var domainResult = new AttachmentInfo(false, 0, []);
var expectedDto = new AttachmentCheckResult { HasAttachments = false, AttachmentCount = 0, Attachments = [] }; var expectedDto = new AttachmentCheckResult { HasAttachments = false, AttachmentCount = 0, Attachments = [] };
@@ -97,7 +96,7 @@ public class CheckPdfAttachmentsQueryHandlerTests
{ {
// Arrange // Arrange
byte[] pdfBytes = "fake-pdf-content"u8.ToArray(); byte[] pdfBytes = "fake-pdf-content"u8.ToArray();
var query = new CheckPdfAttachmentsQuery { PdfBytes = pdfBytes }; var query = new CheckPdfAttachmentsQuery { PdfStream = new MemoryStream(pdfBytes) };
var domainResult = new AttachmentInfo(false, 0, []); var domainResult = new AttachmentInfo(false, 0, []);
var expectedDto = new AttachmentCheckResult var expectedDto = new AttachmentCheckResult

View File

@@ -27,7 +27,7 @@ public class ValidatePdfHandlerTests
{ {
// Arrange // Arrange
var pdfBytes = "%PDF"u8.ToArray(); // "%PDF" var pdfBytes = "%PDF"u8.ToArray(); // "%PDF"
var query = new ValidatePdfQuery { PdfBytes = pdfBytes }; var query = new ValidatePdfQuery { PdfStream = new MemoryStream(pdfBytes) };
var domainMetadata = new PdfMetadata( var domainMetadata = new PdfMetadata(
pageCount: 5, pageCount: 5,
@@ -74,7 +74,7 @@ public class ValidatePdfHandlerTests
{ {
// Arrange // Arrange
var pdfBytes = "%PDF"u8.ToArray(); // "%PDF" var pdfBytes = "%PDF"u8.ToArray(); // "%PDF"
var query = new ValidatePdfQuery { PdfBytes = pdfBytes }; var query = new ValidatePdfQuery { PdfStream = new MemoryStream(pdfBytes) };
_mockPdfProcessor _mockPdfProcessor
.Setup(x => x.ValidateAsync(It.IsAny<Stream>())) .Setup(x => x.ValidateAsync(It.IsAny<Stream>()))

View File

@@ -28,7 +28,7 @@ public class ValidatePdfAQueryHandlerTests
{ {
// Arrange // Arrange
var pdfBytes = "%PDF"u8.ToArray(); // "%PDF" var pdfBytes = "%PDF"u8.ToArray(); // "%PDF"
var query = new ValidatePdfAQuery { PdfBytes = pdfBytes }; var query = new ValidatePdfAQuery { PdfStream = new MemoryStream(pdfBytes) };
var domainMetadata = new PdfAMetadata( var domainMetadata = new PdfAMetadata(
isValid: true, isValid: true,
@@ -86,7 +86,7 @@ public class ValidatePdfAQueryHandlerTests
{ {
// Arrange // Arrange
var pdfBytes = new byte[] { 0x25, 0x50, 0x44, 0x46 }; // "%PDF" var pdfBytes = new byte[] { 0x25, 0x50, 0x44, 0x46 }; // "%PDF"
var query = new ValidatePdfAQuery { Base64Pdf = Convert.ToBase64String(pdfBytes) }; var query = new ValidatePdfAQuery { PdfStream = new MemoryStream(pdfBytes) };
var errors = new List<string> { "Missing XMP metadata", "Invalid color space" }; var errors = new List<string> { "Missing XMP metadata", "Invalid color space" };
var warnings = new List<string> { "Embedded font not subset" }; var warnings = new List<string> { "Embedded font not subset" };
@@ -144,7 +144,7 @@ public class ValidatePdfAQueryHandlerTests
{ {
// Arrange // Arrange
var pdfBytes = new byte[] { 0x25, 0x50, 0x44, 0x46 }; // "%PDF" var pdfBytes = new byte[] { 0x25, 0x50, 0x44, 0x46 }; // "%PDF"
var query = new ValidatePdfAQuery { PdfBytes = pdfBytes }; var query = new ValidatePdfAQuery { PdfStream = new MemoryStream(pdfBytes) };
var domainMetadata = new PdfAMetadata( var domainMetadata = new PdfAMetadata(
isValid: true, isValid: true,
@@ -196,7 +196,7 @@ public class ValidatePdfAQueryHandlerTests
{ {
// Arrange // Arrange
var pdfBytes = new byte[] { 0x25, 0x50, 0x44, 0x46 }; // "%PDF" var pdfBytes = new byte[] { 0x25, 0x50, 0x44, 0x46 }; // "%PDF"
var query = new ValidatePdfAQuery { PdfBytes = pdfBytes }; var query = new ValidatePdfAQuery { PdfStream = new MemoryStream(pdfBytes) };
_mockPdfProcessor _mockPdfProcessor
.Setup(x => x.ValidatePdfAAsync(It.IsAny<Stream>())) .Setup(x => x.ValidatePdfAAsync(It.IsAny<Stream>()))

View File

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