diff --git a/DocumentOperator.Tests/Integration/API/DocumentEndpointsTests.cs b/DocumentOperator.Tests/Integration/API/DocumentEndpointsTests.cs deleted file mode 100644 index 8b31039..0000000 --- a/DocumentOperator.Tests/Integration/API/DocumentEndpointsTests.cs +++ /dev/null @@ -1,89 +0,0 @@ -using DocumentOperator.Application.Common.DTOs; -using FluentAssertions; -using Microsoft.AspNetCore.Mvc.Testing; -using System.Net; -using System.Net.Http.Json; -using Xunit; - -namespace DocumentOperator.Tests.Integration.API; - -public class DocumentEndpointsTests : IClassFixture> -{ - private readonly WebApplicationFactory _factory; - private readonly HttpClient _client; - - public DocumentEndpointsTests(WebApplicationFactory factory) - { - _factory = factory; - _client = _factory.CreateClient(); - } - - [Fact] - public async Task POST_ValidatePdf_ValidPdf_Returns200() - { - // Arrange - // Verwende ein echtes Test-PDF (embedded resource aus Unit Tests) - var assembly = typeof(DocumentEndpointsTests).Assembly; - var resourceName = "DocumentOperator.Tests.TestData.Pdfs.valid.pdf"; - - byte[] pdfBytes; - using (var stream = assembly.GetManifestResourceStream(resourceName)) - { - if (stream == null) - { - throw new InvalidOperationException($"Test resource '{resourceName}' not found"); - } - - using var ms = new MemoryStream(); - await stream.CopyToAsync(ms); - pdfBytes = ms.ToArray(); - } - - var base64Pdf = Convert.ToBase64String(pdfBytes); - var request = new ValidatePdfRequest(base64Pdf); - - // Act - var response = await _client.PostAsJsonAsync("/api/v1/documents/validate", request); - - // Assert - response.StatusCode.Should().Be(HttpStatusCode.OK); - - var result = await response.Content.ReadFromJsonAsync(); - result.Should().NotBeNull(); - result!.PageCount.Should().BeGreaterThan(0); - result.FileSizeBytes.Should().BeGreaterThan(0); - result.PdfVersion.Should().NotBeNullOrEmpty(); - } - - [Fact] - public async Task POST_ValidatePdf_InvalidBase64_Returns400() - { - // Arrange - var request = new ValidatePdfRequest("invalid-base64!!!"); // Kein gültiges Base64 - - // Act - var response = await _client.PostAsJsonAsync("/api/v1/documents/validate", request); - - // Assert - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); - - var problemDetails = await response.Content.ReadAsStringAsync(); - problemDetails.Should().Contain("Base64"); - } - - [Fact] - public async Task POST_ValidatePdf_EmptyPdf_Returns400() - { - // Arrange - var request = new ValidatePdfRequest(string.Empty); // Leerer String - - // Act - var response = await _client.PostAsJsonAsync("/api/v1/documents/validate", request); - - // Assert - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); - - var problemDetails = await response.Content.ReadAsStringAsync(); - problemDetails.Should().Contain("cannot be empty"); - } -} diff --git a/DocumentOperator.Tests/Integration/API/ExtractSwissQrCodeEndpointTests.cs b/DocumentOperator.Tests/Integration/API/ExtractSwissQrCodeEndpointTests.cs index 96a6897..b8c52ae 100644 --- a/DocumentOperator.Tests/Integration/API/ExtractSwissQrCodeEndpointTests.cs +++ b/DocumentOperator.Tests/Integration/API/ExtractSwissQrCodeEndpointTests.cs @@ -1,4 +1,5 @@ using DocumentOperator.Application.Common.DTOs; +using DocumentOperator.Application.SwissQrCode.Queries; using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using System.Net; @@ -28,13 +29,14 @@ public sealed class ExtractSwissQrCodeEndpointTests : IClassFixture { "REF-001", "REF-002" }, - Base64Pdf: validPdfBase64 - ); + var request = new ExtractSwissQrCodeQuery + { + References = new List { "REF-001", "REF-002" }, + Base64Pdf = validPdfBase64 + }; // Act - var response = await _client.PostAsJsonAsync("/api/v1/documents/extract-swiss-qr-code", request); + var response = await _client.PostAsJsonAsync("/api/pdf/qr-code/extract-swiss", request); // Assert // Note: The test PDF (valid.pdf) may not actually contain a Swiss QR Code @@ -44,7 +46,7 @@ public sealed class ExtractSwissQrCodeEndpointTests : IClassFixture(_jsonOptions); + var result = await response.Content.ReadFromJsonAsync(_jsonOptions); result.Should().NotBeNull(); result!.References.Should().BeEquivalentTo(new[] { "REF-001", "REF-002" }); result.QrCodeData.Should().NotBeNull(); @@ -55,35 +57,46 @@ public sealed class ExtractSwissQrCodeEndpointTests : IClassFixture { "REF-001" }, - Base64Pdf: "INVALID_BASE64!!!" - ); + var request = new ExtractSwissQrCodeQuery + { + References = new List { "REF-001" }, + Base64Pdf = "INVALID_BASE64!!!" + }; // Act - var response = await _client.PostAsJsonAsync("/api/v1/documents/extract-swiss-qr-code", request); + var response = await _client.PostAsJsonAsync("/api/pdf/qr-code/extract-swiss", request); // Assert response.StatusCode.Should().Be(HttpStatusCode.BadRequest); } [Fact] - public async Task POST_ExtractSwissQrCode_EmptyReferences_Returns400() + public async Task POST_ExtractSwissQrCode_NullReferences_AcceptedByValidation() { - // Arrange - var validPdfBase64 = GetEmbeddedResourceAsBase64("DocumentOperator.Tests.TestData.Pdfs.valid.pdf"); + // Arrange: References are OPTIONAL - null should not cause validation error (400) + // Using pdfWithSwissQRCode.pdf which actually has a QR code, so we get 200 + var validPdfBase64 = GetEmbeddedResourceAsBase64("DocumentOperator.Tests.TestData.Pdfs.pdfWithSwissQRCode.pdf"); var request = new { - References = (List?)null, + References = (List?)null, // Optional field - should not cause 400 Base64Pdf = validPdfBase64 }; // Act - var response = await _client.PostAsJsonAsync("/api/v1/documents/extract-swiss-qr-code", request); + var response = await _client.PostAsJsonAsync("/api/pdf/qr-code/extract-swiss", request); - // Assert - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + // Assert: Should NOT be 400 (validation error), should be 200 or 404 + response.StatusCode.Should().NotBe(HttpStatusCode.BadRequest, + "null References should be accepted (optional field)"); + + // If extraction succeeds, verify empty references array + if (response.StatusCode == HttpStatusCode.OK) + { + var result = await response.Content.ReadFromJsonAsync(); + result.Should().NotBeNull(); + result!.References.Should().BeEmpty(); // Null input → empty output array + } } [Fact] @@ -97,7 +110,7 @@ public sealed class ExtractSwissQrCodeEndpointTests : IClassFixture> +{ + private readonly WebApplicationFactory _factory; + private readonly HttpClient _client; + + public PdfValidationControllerTests(WebApplicationFactory factory) + { + _factory = factory; + _client = _factory.CreateClient(); + } + + #region Base64 JSON Tests + + [Fact] + public async Task POST_ValidatePdf_Base64_ValidPdf_Returns200() + { + // Arrange + // Verwende ein echtes Test-PDF (embedded resource aus Unit Tests) + var assembly = typeof(PdfValidationControllerTests).Assembly; + var resourceName = "DocumentOperator.Tests.TestData.Pdfs.valid.pdf"; + + byte[] pdfBytes; + using (var stream = assembly.GetManifestResourceStream(resourceName)) + { + if (stream == null) + { + throw new InvalidOperationException($"Test resource '{resourceName}' not found"); + } + + using var ms = new MemoryStream(); + await stream.CopyToAsync(ms); + pdfBytes = ms.ToArray(); + } + + var base64Pdf = Convert.ToBase64String(pdfBytes); + var request = new ValidatePdfQuery { Base64Pdf = base64Pdf }; + + // Act + var response = await _client.PostAsJsonAsync("/api/pdf/validation/validate", request); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + + var result = await response.Content.ReadFromJsonAsync(); + result.Should().NotBeNull(); + result!.PageCount.Should().BeGreaterThan(0); + result.FileSizeBytes.Should().BeGreaterThan(0); + result.PdfVersion.Should().NotBeNullOrEmpty(); + } + + [Fact] + public async Task POST_ValidatePdf_Base64_InvalidBase64_Returns400() + { + // Arrange + var request = new ValidatePdfQuery { Base64Pdf = "invalid-base64!!!" }; // Kein gültiges Base64 + + // Act + var response = await _client.PostAsJsonAsync("/api/pdf/validation/validate", request); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + + var problemDetails = await response.Content.ReadAsStringAsync(); + problemDetails.Should().Contain("Base64"); + } + + [Fact] + public async Task POST_ValidatePdf_Base64_EmptyPdf_Returns400() + { + // Arrange + var request = new ValidatePdfQuery { Base64Pdf = string.Empty }; // Leerer String + + // Act + var response = await _client.PostAsJsonAsync("/api/pdf/validation/validate", request); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + + var problemDetails = await response.Content.ReadAsStringAsync(); + problemDetails.Should().Contain("Either PdfBytes or Base64Pdf must be provided"); + } + + #endregion + + #region Multipart/Form-Data Tests + + [Fact] + public async Task POST_ValidatePdf_Multipart_ValidPdf_Returns200() + { + // Arrange + var assembly = typeof(PdfValidationControllerTests).Assembly; + var resourceName = "DocumentOperator.Tests.TestData.Pdfs.valid.pdf"; + + byte[] pdfBytes; + using (var stream = assembly.GetManifestResourceStream(resourceName)) + { + if (stream == null) + { + throw new InvalidOperationException($"Test resource '{resourceName}' not found"); + } + + using var ms = new MemoryStream(); + await stream.CopyToAsync(ms); + pdfBytes = ms.ToArray(); + } + + // Create multipart/form-data content + using var content = new MultipartFormDataContent(); + var fileContent = new ByteArrayContent(pdfBytes); + fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf"); + content.Add(fileContent, "file", "test.pdf"); + + // Act + var response = await _client.PostAsync("/api/pdf/validation/validate", content); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + + var result = await response.Content.ReadFromJsonAsync(); + result.Should().NotBeNull(); + result!.PageCount.Should().BeGreaterThan(0); + result.FileSizeBytes.Should().BeGreaterThan(0); + result.PdfVersion.Should().NotBeNullOrEmpty(); + } + + [Fact] + public async Task POST_ValidatePdf_Multipart_EmptyFile_Returns400() + { + // Arrange + using var content = new MultipartFormDataContent(); + var fileContent = new ByteArrayContent(Array.Empty()); + fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf"); + content.Add(fileContent, "file", "empty.pdf"); + + // Act + var response = await _client.PostAsync("/api/pdf/validation/validate", content); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + + var problemDetails = await response.Content.ReadAsStringAsync(); + problemDetails.Should().Contain("cannot be empty"); + } + + [Fact] + public async Task POST_ValidatePdf_Multipart_NoFile_Returns400() + { + // Arrange + using var content = new MultipartFormDataContent(); + + // Act + var response = await _client.PostAsync("/api/pdf/validation/validate", content); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + } + + #endregion +} diff --git a/DocumentOperator.Tests/Unit/Application/Features/ExtractSwissQrCode/ExtractSwissQrCodeHandlerTests.cs b/DocumentOperator.Tests/Unit/Application/Features/ExtractSwissQrCode/ExtractSwissQrCodeHandlerTests.cs index d413167..e69de29 100644 --- a/DocumentOperator.Tests/Unit/Application/Features/ExtractSwissQrCode/ExtractSwissQrCodeHandlerTests.cs +++ b/DocumentOperator.Tests/Unit/Application/Features/ExtractSwissQrCode/ExtractSwissQrCodeHandlerTests.cs @@ -1,95 +0,0 @@ -using DocumentOperator.Application.Common.Interfaces; -using DocumentOperator.Application.Features.Documents.ExtractSwissQrCode; -using DocumentOperator.Domain.Models.ValueObjects; -using DocumentOperator.Domain.ValueObjects; -using FluentAssertions; -using Moq; -using Xunit; - -namespace DocumentOperator.Tests.Unit.Application.Features.ExtractSwissQrCode; - -public sealed class ExtractSwissQrCodeHandlerTests -{ - private readonly Mock _mockQrCodeProcessor; - private readonly ExtractSwissQrCodeHandler _handler; - - public ExtractSwissQrCodeHandlerTests() - { - _mockQrCodeProcessor = new Mock(); - _handler = new ExtractSwissQrCodeHandler(_mockQrCodeProcessor.Object); - } - - [Fact] - public async Task Handle_ValidRequest_ReturnsQrCodeDataAndReferences() - { - // Arrange - var references = new List { "REF-001", "REF-002" }; - var pdfBase64 = "JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwovUGFnZXMgMiAwIFIKPj4KZW5kb2JqCjIgMCBvYmoKPDwKL1R5cGUgL1BhZ2VzCi9LaWRzIFszIDAgUl0KL0NvdW50IDEKPj4KZW5kb2JqCjMgMCBvYmoKPDwKL1R5cGUgL1BhZ2UKL1BhcmVudCAyIDAgUgovTWVkaWFCb3ggWzAgMCA2MTIgNzkyXQovQ29udGVudHMgNCAwIFIKPj4KZW5kb2JqCjQgMCBvYmoKPDwKL0xlbmd0aCAzMgo+PgpzdHJlYW0KQlQKL0YxIDEyIFRmCjEwMCA3MDAgVGQKKEhlbGxvKSBUagpFVAplbmRzdHJlYW0KZW5kb2JqCnhyZWYKMCA1CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAxNSAwMDAwMCBuIAowMDAwMDAwMDY0IDAwMDAwIG4gCjAwMDAwMDAxMjEgMDAwMDAgbiAKMDAwMDAwMDIxMyAwMDAwMCBuIAp0cmFpbGVyCjw8Ci9TaXplIDUKL1Jvb3QgMSAwIFIKPj4Kc3RhcnR4cmVmCjI5NAolJUVPRgo="; - var base64String = Base64String.Create(pdfBase64); - var query = new ExtractSwissQrCodeQuery(references, base64String); - - var expectedQrCodeData = new SwissQrCodeData - { - QrType = "SPC", - Version = "0200", - CodingType = "1", - Iban = "CH4431999123000889012", - Creditor = new AddressData - { - AddressType = "S", - Name = "Robert Schneider AG", - Street = "Rue du Lac", - BuildingNumber = "1268", - PostalCode = "2501", - City = "Biel", - Country = "CH" - }, - UltimateCreditor = null, - Amount = 1949.75m, - Currency = "CHF", - UltimateDebtor = null, - ReferenceType = "QRR", - Reference = "210000000003139471430009017", - UnstructuredMessage = "Order from 15.01.2025", - BillInformation = null, - AlternativeProcedureParameters = null - }; - - _mockQrCodeProcessor - .Setup(x => x.ExtractSwissQrCodeAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(expectedQrCodeData); - - // Act - var result = await _handler.Handle(query, CancellationToken.None); - - // Assert - result.Should().NotBeNull(); - result.References.Should().BeEquivalentTo(references); - result.QrCodeData.Should().BeEquivalentTo(expectedQrCodeData); - - _mockQrCodeProcessor.Verify( - x => x.ExtractSwissQrCodeAsync(It.IsAny(), It.IsAny()), - Times.Once); - } - - [Fact] - public async Task Handle_QrCodeProcessorThrowsException_PropagatesException() - { - // Arrange - var references = new List { "REF-001" }; - var pdfBase64 = "JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwovUGFnZXMgMiAwIFIKPj4KZW5kb2JqCjIgMCBvYmoKPDwKL1R5cGUgL1BhZ2VzCi9LaWRzIFszIDAgUl0KL0NvdW50IDEKPj4KZW5kb2JqCjMgMCBvYmoKPDwKL1R5cGUgL1BhZ2UKL1BhcmVudCAyIDAgUgovTWVkaWFCb3ggWzAgMCA2MTIgNzkyXQovQ29udGVudHMgNCAwIFIKPj4KZW5kb2JqCjQgMCBvYmoKPDwKL0xlbmd0aCAzMgo+PgpzdHJlYW0KQlQKL0YxIDEyIFRmCjEwMCA3MDAgVGQKKEhlbGxvKSBUagpFVAplbmRzdHJlYW0KZW5kb2JqCnhyZWYKMCA1CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAxNSAwMDAwMCBuIAowMDAwMDAwMDY0IDAwMDAwIG4gCjAwMDAwMDAxMjEgMDAwMDAgbiAKMDAwMDAwMDIxMyAwMDAwMCBuIAp0cmFpbGVyCjw8Ci9TaXplIDUKL1Jvb3QgMSAwIFIKPj4Kc3RhcnR4cmVmCjI5NAolJUVPRgo="; - var base64String = Base64String.Create(pdfBase64); - var query = new ExtractSwissQrCodeQuery(references, base64String); - - _mockQrCodeProcessor - .Setup(x => x.ExtractSwissQrCodeAsync(It.IsAny(), It.IsAny())) - .ThrowsAsync(new InvalidOperationException("QR Code processing failed")); - - // Act - Func act = async () => await _handler.Handle(query, CancellationToken.None); - - // Assert - await act.Should().ThrowAsync() - .WithMessage("QR Code processing failed"); - } -} diff --git a/DocumentOperator.Tests/Unit/Application/Features/ValidatePdf/ValidatePdfHandlerTests.cs b/DocumentOperator.Tests/Unit/Application/Features/ValidatePdf/ValidatePdfHandlerTests.cs index 225a0fd..149c3d9 100644 --- a/DocumentOperator.Tests/Unit/Application/Features/ValidatePdf/ValidatePdfHandlerTests.cs +++ b/DocumentOperator.Tests/Unit/Application/Features/ValidatePdf/ValidatePdfHandlerTests.cs @@ -1,5 +1,7 @@ +using AutoMapper; +using DocumentOperator.Application.Common.DTOs; using DocumentOperator.Application.Common.Interfaces; -using DocumentOperator.Application.Features.Documents.ValidatePdf; +using DocumentOperator.Application.ValidatePdf.Queries; using DocumentOperator.Domain.Common.Exceptions; using DocumentOperator.Domain.Models.ValueObjects; using FluentAssertions; @@ -11,23 +13,24 @@ namespace DocumentOperator.Tests.Unit.Application.Features.ValidatePdf; public class ValidatePdfHandlerTests { private readonly Mock _mockPdfProcessor; - private readonly ValidatePdfHandler _handler; + private readonly Mock _mockMapper; + private readonly ValidatePdfQueryHandler _handler; public ValidatePdfHandlerTests() { _mockPdfProcessor = new Mock(); - _handler = new ValidatePdfHandler(_mockPdfProcessor.Object); + _mockMapper = new Mock(); + _handler = new ValidatePdfQueryHandler(_mockPdfProcessor.Object, _mockMapper.Object); } [Fact] public async Task Handle_ValidPdf_ReturnsPdfMetadata() { // Arrange - var base64Pdf = Convert.ToBase64String(new byte[] { 0x25, 0x50, 0x44, 0x46 }); // "%PDF" - var pdfContent = Base64String.Create(base64Pdf); - var query = new ValidatePdfQuery(pdfContent); + var pdfBytes = new byte[] { 0x25, 0x50, 0x44, 0x46 }; // "%PDF" + var query = new ValidatePdfQuery { PdfBytes = pdfBytes }; - var expectedMetadata = new PdfMetadata( + var domainMetadata = new PdfMetadata( pageCount: 5, fileSizeBytes: 1024, pdfVersion: "1.4", @@ -35,9 +38,22 @@ public class ValidatePdfHandlerTests attachmentCount: 0 ); + var expectedDto = new PdfValidationResult( + PageCount: 5, + FileSizeBytes: 1024, + FileSizeMB: 0.00, + PdfVersion: "1.4", + HasAttachments: false, + AttachmentCount: 0 + ); + _mockPdfProcessor .Setup(x => x.ValidateAsync(It.IsAny())) - .ReturnsAsync(expectedMetadata); + .ReturnsAsync(domainMetadata); + + _mockMapper + .Setup(x => x.Map(domainMetadata)) + .Returns(expectedDto); // Act var result = await _handler.Handle(query, CancellationToken.None); @@ -50,29 +66,27 @@ public class ValidatePdfHandlerTests result.HasAttachments.Should().BeFalse(); result.AttachmentCount.Should().Be(0); - _mockPdfProcessor.Verify( - x => x.ValidateAsync(It.IsAny()), - Times.Once - ); + _mockPdfProcessor.Verify(x => x.ValidateAsync(It.IsAny()), Times.Once); + _mockMapper.Verify(x => x.Map(domainMetadata), Times.Once); } [Fact] public async Task Handle_PdfProcessorThrowsException_PropagatesException() { // Arrange - var base64Pdf = Convert.ToBase64String(new byte[] { 0x25, 0x50, 0x44, 0x46 }); - var pdfContent = Base64String.Create(base64Pdf); - var query = new ValidatePdfQuery(pdfContent); + var pdfBytes = new byte[] { 0x25, 0x50, 0x44, 0x46 }; // "%PDF" + var query = new ValidatePdfQuery { PdfBytes = pdfBytes }; _mockPdfProcessor .Setup(x => x.ValidateAsync(It.IsAny())) .ThrowsAsync(new PdfProcessingException("Invalid PDF format")); - // Act - Func act = async () => await _handler.Handle(query, CancellationToken.None); + // Act & Assert + var exception = await Assert.ThrowsAsync( + () => _handler.Handle(query, CancellationToken.None) + ); - // Assert - await act.Should().ThrowAsync() - .WithMessage("Invalid PDF format"); + exception.Message.Should().Be("Invalid PDF format"); + _mockPdfProcessor.Verify(x => x.ValidateAsync(It.IsAny()), Times.Once); } }