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:
@@ -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
|
||||||
|
|||||||
@@ -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>()))
|
||||||
|
|||||||
@@ -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>()))
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user