diff --git a/DocumentOperator.Tests/Unit/Application/CheckPdfAttachments/CheckPdfAttachmentsQueryHandlerTests.cs b/DocumentOperator.Tests/Unit/Application/CheckPdfAttachments/CheckPdfAttachmentsQueryHandlerTests.cs index 5850a74..b8a86bd 100644 --- a/DocumentOperator.Tests/Unit/Application/CheckPdfAttachments/CheckPdfAttachmentsQueryHandlerTests.cs +++ b/DocumentOperator.Tests/Unit/Application/CheckPdfAttachments/CheckPdfAttachmentsQueryHandlerTests.cs @@ -30,7 +30,7 @@ public class CheckPdfAttachmentsQueryHandlerTests { // Arrange 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( hasAttachments: true, @@ -72,8 +72,7 @@ public class CheckPdfAttachmentsQueryHandlerTests { // Arrange byte[] pdfBytes = "fake-pdf-content"u8.ToArray(); - string base64Pdf = Convert.ToBase64String(pdfBytes); - var query = new CheckPdfAttachmentsQuery { Base64Pdf = base64Pdf }; + var query = new CheckPdfAttachmentsQuery { PdfStream = new MemoryStream(pdfBytes) }; var domainResult = new AttachmentInfo(false, 0, []); var expectedDto = new AttachmentCheckResult { HasAttachments = false, AttachmentCount = 0, Attachments = [] }; @@ -97,7 +96,7 @@ public class CheckPdfAttachmentsQueryHandlerTests { // Arrange 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 expectedDto = new AttachmentCheckResult diff --git a/DocumentOperator.Tests/Unit/Application/Features/ValidatePdf/ValidatePdfHandlerTests.cs b/DocumentOperator.Tests/Unit/Application/Features/ValidatePdf/ValidatePdfHandlerTests.cs index ed6db4a..0a3c649 100644 --- a/DocumentOperator.Tests/Unit/Application/Features/ValidatePdf/ValidatePdfHandlerTests.cs +++ b/DocumentOperator.Tests/Unit/Application/Features/ValidatePdf/ValidatePdfHandlerTests.cs @@ -27,7 +27,7 @@ public class ValidatePdfHandlerTests { // Arrange var pdfBytes = "%PDF"u8.ToArray(); // "%PDF" - var query = new ValidatePdfQuery { PdfBytes = pdfBytes }; + var query = new ValidatePdfQuery { PdfStream = new MemoryStream(pdfBytes) }; var domainMetadata = new PdfMetadata( pageCount: 5, @@ -74,7 +74,7 @@ public class ValidatePdfHandlerTests { // Arrange var pdfBytes = "%PDF"u8.ToArray(); // "%PDF" - var query = new ValidatePdfQuery { PdfBytes = pdfBytes }; + var query = new ValidatePdfQuery { PdfStream = new MemoryStream(pdfBytes) }; _mockPdfProcessor .Setup(x => x.ValidateAsync(It.IsAny())) diff --git a/DocumentOperator.Tests/Unit/Application/Features/ValidatePdfA/ValidatePdfAQueryHandlerTests.cs b/DocumentOperator.Tests/Unit/Application/Features/ValidatePdfA/ValidatePdfAQueryHandlerTests.cs index 819bd2e..a880fb8 100644 --- a/DocumentOperator.Tests/Unit/Application/Features/ValidatePdfA/ValidatePdfAQueryHandlerTests.cs +++ b/DocumentOperator.Tests/Unit/Application/Features/ValidatePdfA/ValidatePdfAQueryHandlerTests.cs @@ -28,7 +28,7 @@ public class ValidatePdfAQueryHandlerTests { // Arrange var pdfBytes = "%PDF"u8.ToArray(); // "%PDF" - var query = new ValidatePdfAQuery { PdfBytes = pdfBytes }; + var query = new ValidatePdfAQuery { PdfStream = new MemoryStream(pdfBytes) }; var domainMetadata = new PdfAMetadata( isValid: true, @@ -86,7 +86,7 @@ public class ValidatePdfAQueryHandlerTests { // Arrange 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 { "Missing XMP metadata", "Invalid color space" }; var warnings = new List { "Embedded font not subset" }; @@ -144,7 +144,7 @@ public class ValidatePdfAQueryHandlerTests { // Arrange 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( isValid: true, @@ -196,7 +196,7 @@ public class ValidatePdfAQueryHandlerTests { // Arrange var pdfBytes = new byte[] { 0x25, 0x50, 0x44, 0x46 }; // "%PDF" - var query = new ValidatePdfAQuery { PdfBytes = pdfBytes }; + var query = new ValidatePdfAQuery { PdfStream = new MemoryStream(pdfBytes) }; _mockPdfProcessor .Setup(x => x.ValidatePdfAAsync(It.IsAny())) diff --git a/DocumentOperator.Tests/Unit/Infrastructure/Services/QrCodeProcessing/DevExpressSwissQrCodeProcessorTests.cs b/DocumentOperator.Tests/Unit/Infrastructure/Services/QrCodeProcessing/DevExpressSwissQrCodeProcessorTests.cs index ecad787..54938c9 100644 --- a/DocumentOperator.Tests/Unit/Infrastructure/Services/QrCodeProcessing/DevExpressSwissQrCodeProcessorTests.cs +++ b/DocumentOperator.Tests/Unit/Infrastructure/Services/QrCodeProcessing/DevExpressSwissQrCodeProcessorTests.cs @@ -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 /// - /// Loads a test PDF from embedded resources. + /// Loads a test PDF from embedded resources as a Stream. /// /// Name of the PDF file (e.g., "pdfWithSwissQRCode.pdf") - /// PDF content as byte array - private static byte[] LoadTestPdf(string filename) + /// PDF content as MemoryStream + 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() .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() .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(); + await act.Should().ThrowAsync(); } [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(); } + [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() + .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)