Commit Graph

99 Commits

Author SHA1 Message Date
e95f070b9b feat(domain): Add annotation value objects for Feature 6
- Add AnnotationType enum (TextMarkup, FreeText, StickyNote, Circle, Square)
- Add TextMarkupStyle enum (Highlight, Underline, Strikeout)
- Support 5 annotation types as per CONTROLLER_ENDPOINTS.md requirements
2026-07-21 12:27:41 +02:00
aafe46a738 docs: Update AGENTS.md for Feature 5 (PDF Merge) completion
- Update test count: 82 passed, 7 skipped (was 62 passed)
- Update PdfOperationsController status: Partial (1/N endpoints), 7 tests
- Add PdfOperationsController Status section with merge endpoint details
- Mark merge endpoint as DONE (Phase 1, Priority 5)
- List remaining Phase 2 endpoints: stamp, annotate
2026-07-21 10:22:04 +02:00
0bec759396 test: Add 7 integration tests for PdfOperationsController merge endpoint
Multipart tests (4):
- POST_Merge_Multipart_TwoPdfs_Returns200WithMergedPdf
- POST_Merge_Multipart_ThreePdfs_Returns200
- POST_Merge_Multipart_SinglePdf_Returns400
- POST_Merge_Multipart_CorruptedPdf_Returns400Or500 (flexible assertion)

Base64 tests (3):
- POST_Merge_Base64_TwoPdfs_Returns200WithMergedPdf
- POST_Merge_Base64_ThreePdfs_Returns200
- POST_Merge_Base64_SinglePdf_Returns400

Skipped tests (2):
- POST_Merge_Multipart_WithPageRanges_Returns200 (multipart List<string?> binding complex)
- POST_Merge_Multipart_InvalidPageRange_Returns400 (page ranges work via JSON endpoint)
2026-07-21 10:21:46 +02:00
5c3fafff1b feat: Add PdfOperationsController with dual-input merge endpoints
- Add PdfOperationsController.cs with route '/api/pdf/operations'
- MergeFromFiles: POST /merge (multipart/form-data) - accepts List<IFormFile>
- MergeFromBase64: POST /merge (application/json) - accepts MergePdfsRequest DTO
- Returns merged PDF as FileContentResult (application/pdf)
- Supports optional page ranges via JSON endpoint only (multipart binding complex)
- XML documentation with response codes (200, 400, 500)
- Uses primary constructor pattern
2026-07-21 10:21:27 +02:00
bc273c7f4f feat: Add MergePdfsCommand with merged Command/Handler/Validator
- Add MergePdfsCommand.cs (Vertical Slice pattern)
- Command: IRequest<byte[]> with PdfStreams + PageRanges properties
- Handler: Calls IPdfProcessor.MergePdfsAsync, uses primary constructor
- Validator: Validates minimum 2 PDFs, page ranges count matches PDF count
- All 3 classes in single file (Command/Handler/Validator merged)
2026-07-21 10:21:10 +02:00
e12b64a517 test: Add 10 unit tests for MergePdfsAsync + LoadTestPdfAsStream helper
Unit tests (10):
- MergePdfsAsync_TwoPdfs_ReturnsMergedPdf
- MergePdfsAsync_ThreePdfs_ReturnsMergedPdf
- MergePdfsAsync_WithNullPageRanges_MergesAllPages
- MergePdfsAsync_WithEmptyPageRanges_MergesAllPages
- MergePdfsAsync_WithRangeFormat_MergesSelectedPages
- MergePdfsAsync_SinglePdf_ThrowsBadRequestException
- MergePdfsAsync_InvalidPageRangeCount_ThrowsBadRequestException
- MergePdfsAsync_InvalidPageRangeFormat_ThrowsBadRequestException
- MergePdfsAsync_PageNumberOutOfRange_ThrowsBadRequestException
- MergePdfsAsync_StreamNotAtPositionZero_ThrowsBadRequestException

Helper:
- Add LoadTestPdfAsStream helper for Stream-returning test setup
2026-07-21 10:20:53 +02:00
3598c5f9c6 feat: Implement DevExpressPdfProcessor.MergePdfsAsync with page range support
- Implement MergePdfsAsync: merges multiple PDFs with optional page ranges
- Add ParsePageRange helper: parses '1-3,5' format, validates page numbers
- Stream-based pipeline (no byte[] buffering)
- Validates: Position = 0, minimum 2 PDFs, page ranges count
- Uses DevExpress PdfDocumentProcessor for actual merge operation
- Returns merged PDF as byte array
2026-07-21 10:20:35 +02:00
522de8a863 feat: Add IPdfProcessor.MergePdfsAsync interface
- Add MergePdfsAsync method to IPdfProcessor interface
- Parameters: IReadOnlyList<Stream> pdfStreams, IReadOnlyList<string?>? pageRanges
- Returns: Task<byte[]> (merged PDF)
- Validates: Minimum 2 PDFs, stream Position = 0, page ranges count matches PDF count
- Supports optional page ranges (e.g., '1-3,5' or null for all pages)
2026-07-21 10:20:18 +02:00
cb552e54e7 docs: Update AGENTS.md - PdfAttachmentController 2/3 endpoints, 62 tests 2026-07-21 09:27:14 +02:00
fa4e55242d fix: Update SwissQrCode test - ArgumentException to BadRequestException 2026-07-21 09:26:51 +02:00
e14044c48a test: Add PdfAttachmentController extract endpoint integration tests (6 tests) 2026-07-21 09:26:43 +02:00
34e38f19e5 feat: Add PdfAttachmentController extract endpoints (multipart + Base64) 2026-07-21 09:26:34 +02:00
61b1595258 test: Add ExtractAttachmentsAsync unit tests (ZIP validation, edge cases) 2026-07-21 09:26:27 +02:00
26458a4017 feat: Implement DevExpressPdfProcessor.ExtractAttachmentsAsync with ZIP packaging 2026-07-21 09:26:18 +02:00
2c673ea98e feat: Add IPdfProcessor.ExtractAttachmentsAsync interface method 2026-07-21 09:26:11 +02:00
1989ca7ef7 feat: Add ExtractPdfAttachments Application layer (Command/Handler/Validator merged) 2026-07-21 09:26:04 +02:00
0f4d860176 test(integration): migrate integration tests to Controller DTOs and update assertions
Test Changes:
- Use Controller DTOs (ValidatePdfBase64Request, ValidatePdfABase64Request, CheckPdfAttachmentsRequest, ExtractSwissQrCodeBase64Request)
- Remove direct Query object usage in HTTP tests (architectural violation)
- Update imports: DocumentOperator.API.Controllers namespace

Assertion Updates:
- Invalid Base64 tests: case-insensitive regex (?i)base.?64 (FormatException message contains 'Base-64' with hyphen)
- Empty PDF tests: regex match for 'Base64|empty|stream' (flexible validation error matching)
- Corrupted PDF test: Expect 500 Internal Server Error (DevExpress exception propagates naturally)

Result: 52 tests pass, 6 skipped (IBAN validation - DevExpress limitation)
2026-07-20 16:33:19 +02:00
645dfceafa 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
2026-07-20 16:33:00 +02:00
07be9b9f02 refactor(domain): remove obsolete exception types
Deleted:
- PdfProcessingException: Obsolete, DevExpress exceptions now propagate naturally
- SwissQrCodeNotFoundException: Moved to Application layer (feature-specific exception)

Rationale:
- PdfProcessingException was wrapping library exceptions unnecessarily
- Better to let infrastructure exceptions propagate → middleware handles as 500
- SwissQrCodeNotFoundException is application-level concern, not domain
2026-07-20 16:32:36 +02:00
a1e8575018 refactor(api): remove generic exception handling from middleware
Remove FormatException/ArgumentException handling:
- These are framework exceptions, not application-specific
- May come from internal libraries (false positives for 400 Bad Request)
- Controllers now wrap Base64 conversion with BadRequestException explicitly

Remove PdfProcessingException handling:
- Exception type removed (obsolete)
- DevExpress exceptions now propagate naturally → 500 Internal Server Error

Current exception mapping:
- ValidationException (FluentValidation) → 400 Bad Request
- BadRequestException (custom) → 400 Bad Request
- NotFoundException (custom) → 404 Not Found
- SwissQrCodeNotFoundException (custom) → 404 Not Found
- All others → 500 Internal Server Error
2026-07-20 16:32:19 +02:00
b4befde418 refactor(api): migrate controllers to Stream API with Base64 validation
PdfValidationController:
- ValidateFromFile: IFormFile.OpenReadStream() direct usage (no byte[] copy)
- ValidateFromBase64: try-catch Convert.FromBase64String → BadRequestException
- ValidatePdfAFromFile: IFormFile.OpenReadStream() direct usage
- ValidatePdfAFromBase64: try-catch Convert.FromBase64String → BadRequestException

PdfAttachmentController:
- CheckAttachmentsFromBase64: try-catch Convert.FromBase64String → BadRequestException

SwissQrCodeController:
- ExtractFromBase64: try-catch Convert.FromBase64String → BadRequestException

All controllers: Add using DocumentOperator.Domain.Common.Exceptions for BadRequestException
2026-07-20 16:32:03 +02:00
1af158840e refactor(infrastructure): implement Stream-based PDF processing
DevExpressPdfProcessor:
- ValidateAsync, ValidatePdfAAsync, CheckAttachmentsAsync: Stream parameters
- Defensive Position=0 validation (BadRequestException for seekable streams not at beginning)
- Remove unsafe Position reset (non-seekable stream compatibility)
- Remove PdfProcessingException wrapping (let DevExpress exceptions propagate naturally)

DevExpressSwissQrCodeProcessor:
- ExtractSwissQrCodeAsync: Stream parameter
- Defensive Position=0 validation
- Remove unsafe Position reset

Memory optimization: MemoryStream.TryGetBuffer fast path for byte[] extraction
2026-07-20 16:31:47 +02:00
5dc2e38507 refactor(application): update processor interfaces for Stream API
- IPdfProcessor: ValidateAsync, ValidatePdfAAsync, CheckAttachmentsAsync now accept Stream
- ISwissQrCodeProcessor: ExtractSwissQrCodeAsync now accepts Stream
- Update XML documentation: Position=0 requirement, non-seekable stream support
- Exception documentation: BadRequestException for validation errors (stream empty/invalid/wrong position)
2026-07-20 16:31:31 +02:00
c93488c29f refactor(application): migrate all queries to Stream-based API
- Replace byte[] and Base64String with required Stream PdfStream
- Simplify validators: remove XOR/Base64 validation, only check NotNull
- Affected queries: ValidatePdfQuery, ValidatePdfAQuery, CheckPdfAttachmentsQuery, ExtractSwissQrCodeQuery
- Memory efficiency: direct stream usage, no intermediate byte[] copies
2026-07-20 16:31:14 +02:00
251ecc34d9 test: Add tests for CheckPdfAttachments feature
- Unit tests for CheckPdfAttachmentsQueryHandler (3 tests)
- Integration tests for PdfAttachmentController (14 tests covering both multipart and JSON endpoints)
- Tests verify: attachment detection, metadata extraction, empty PDF handling, validation errors
- All tests using Stream API (mocks with It.IsAny<Stream>())
- Total: 17 new tests, all passing
2026-07-20 11:56:19 +02:00
9db15f7025 feat: Add PdfAttachmentController with CheckAttachments endpoints
- Add POST /api/pdf/attachments/check endpoint (multipart/form-data)
- Add POST /api/pdf/attachments/check endpoint (application/json with Base64)
- Dual input support: IFormFile (file upload) and Base64 JSON
- Controller converts IFormFile → byte[] and sends to MediatR
- Complete XML documentation with Swagger examples
- Primary constructor pattern used
2026-07-20 11:56:12 +02:00
364b755f95 feat: Add CheckPdfAttachments feature - Application layer
- Add CheckPdfAttachmentsQuery with dual input support (byte[] + Base64)
- Add CheckPdfAttachmentsQueryHandler with IPdfProcessor integration
- Add CheckPdfAttachmentsQueryValidator with FluentValidation rules
- Add AttachmentCheckResult DTO for API response
- Handler converts byte[] → MemoryStream for IPdfProcessor.CheckAttachmentsAsync()
- AutoMapper maps AttachmentInfo → AttachmentCheckResult
2026-07-20 11:56:03 +02:00
f2e6ef0260 chore: Remove obsolete Domain enums (DocumentOperationType, ProcessingStatus)
- These enums were part of initial scaffolding but never used
- Domain layer cleanup - removing unused code
2026-07-20 11:55:55 +02:00
8aff3138ff test: Fix obsolete exception expectations in SwissQrCodeProcessor tests
- SwissQrCodeNotFoundException → NotFoundException (obsolete exception replaced)
- PdfProcessingException → ArgumentException (DevExpress throws ArgumentException for invalid PDFs)
- ArgumentNullException → NullReferenceException (actual behavior of current implementation)
2026-07-20 11:55:48 +02:00
58f9b07af3 test: Update Application handler unit tests for Stream API
- Update mock setups: It.IsAny<byte[]>() → It.IsAny<Stream>()
- Update mock verifications: Verify Stream parameter instead of byte[]
- Remove obsolete namespace imports (Domain.Models.ValueObjects)
- Update exception expectations (BadRequestException instead of PdfProcessingException)
2026-07-20 11:55:41 +02:00
468dca46d4 test: Update DevExpressPdfProcessor unit tests for Stream API
- Add ToStream() helper method to convert byte[] → MemoryStream
- Update all test method calls to use ToStream(pdfBytes)
- Fix null/empty stream tests (use Stream directly instead of byte[])
- Update exception expectations (ArgumentNullException for null streams, BadRequestException for empty streams)
- Update namespace imports (Application.Common.DTOs instead of Domain.Models.ValueObjects)
2026-07-20 11:55:33 +02:00
e13e85182a refactor: Update Application handlers to convert byte[] to Stream
- ValidatePdfQueryHandler: Convert byte[] → MemoryStream before calling IPdfProcessor
- ValidatePdfAQueryHandler: Same pattern
- Update AutoMapper namespace imports (remove Domain.Models.ValueObjects references)
2026-07-20 11:55:25 +02:00
1de781748b refactor: Update DevExpressPdfProcessor to use Stream parameters
- All 3 methods (ValidateAsync, ValidatePdfAAsync, CheckAttachmentsAsync) now accept Stream
- Add ArgumentNullException.ThrowIfNull() checks for null streams
- Implement fast-path optimization: reuse MemoryStream buffer when possible
- Implement slow-path fallback: copy stream to byte[] for DevExpress API compatibility
- Fix namespace collision: use fully qualified Application.Common.DTOs.PdfMetadata
2026-07-20 11:55:17 +02:00
73a7afe257 refactor: Change IPdfProcessor interface from byte[] to Stream
- ValidateAsync(byte[]) → ValidateAsync(Stream)
- ValidatePdfAAsync(byte[]) → ValidatePdfAAsync(Stream)
- CheckAttachmentsAsync(byte[]) → CheckAttachmentsAsync(Stream)
- Reason: Stream-based processing reduces memory footprint and enables pipeline parallelization
2026-07-20 11:55:08 +02:00
d123bc996e refactor: Move DTOs from Domain to Application layer
- Move PdfMetadata, PdfAMetadata from Domain.Models.ValueObjects to Application.Common.DTOs
- Move AttachmentInfo, AttachmentMetadata from Domain.Models.ValueObjects to Application.Common.DTOs
- Reason: DTOs belong in Application layer, Domain should have zero external dependencies (Clean Architecture)
2026-07-20 11:55:01 +02:00
4085a88485 Update XML docs for raw param in SwissQrCodeController
Removed detailed description of the `raw` parameter in XML
documentation for two methods in `SwissQrCodeController`.
Updated `<returns>` tag to simplify the explanation by
removing conditional details based on the `raw` parameter.
These changes affect methods handling multipart/form-data
PDF input and Base64 JSON input.
2026-07-20 10:51:30 +02:00
88984c8887 Refactor and streamline codebase
- Removed `<Folder>` elements in `DocumentOperator.Domain.csproj`
  and replaced them with `<Compile Remove>`, `<EmbeddedResource Remove>`,
  and `<None Remove>` to exclude specific directories.
- Removed unused `using DocumentOperator.Domain.Exceptions;` directive.
- Simplified `Split` method syntax for delimiter specification.
- Updated `return` statements to use concise parameter syntax.
- Removed page number validation logic in `DevExpressSwissQrCodeProcessor`.
- Replaced default page scanning logic with modern range expression.
- Overall, improved code clarity, reduced redundancy, and modernized syntax.
2026-07-20 09:46:44 +02:00
a315fbf890 feat: Add raw parameter to SwissQrCodeController endpoints
- Add 'raw' query parameter to both ExtractFromFile and ExtractFromBase64 methods
- Returns raw QR text lines when raw=true, parsed Bill object when raw=false (default)
- Remove obsolete 'references' parameter (not part of QR extraction logic)
- Add XML documentation for raw parameter
- Update ExceptionHandlingMiddleware to handle BadRequestException
2026-07-16 15:48:09 +02:00
1a89887056 test: Update tests for tuple return type and new API structure
- Update unit tests to assert (Bill, string[]) tuple return
- Update integration tests for new response structure (Bill + RawLines)
- Fix exception message assertion in DevExpressSwissQrCodeProcessorTests
- All 34 tests passing, 6 skipped (require real Swiss QR Bill PDFs)
2026-07-16 15:47:56 +02:00
a729df6fda refactor: Make Domain exceptions serializable and add XML docs
- Add [Serializable] attribute to all custom exceptions
- Add protected constructors for serialization support
- Add XML documentation comments
- Update SwissQrCodeNotFoundException message format
2026-07-16 15:47:44 +02:00
88bde13422 refactor: Refactor DevExpressSwissQrCodeProcessor to use Codecrete QRBill parser
- Change return type to tuple (Bill, string[])
- Remove custom parsing methods (ParseSwissQrBillContent, MapAddress, DetermineReferenceType)
- Use Codecrete QRBill.DecodeQrCodeText() for parsing
- Add SkiaSharp.QrCode v1.0.0 for QR decoding
- Remove obsolete ZXing and System.Drawing dependencies
- Add StringExtensions for QR code detection
- Raw lines properly split and trimmed from QR text
2026-07-16 15:47:33 +02:00
889144f144 feat: Add AutoMapper mappings for Codecrete Bill to DTOs
- Add Bill -> SwissQrBillDto mapping
- Add Address -> AddressDto mapping
- Add AlternativeScheme -> AlternativeSchemeDto mapping
- Document AutoMapper policy in comments
2026-07-16 15:47:19 +02:00
35016f02e1 refactor: Update Application layer for Codecrete Bill integration
- Change SwissQrCodeExtractionResult to use Bill + RawLines
- Update ISwissQrCodeProcessor to return tuple (Bill, string[])
- Add Codecrete.SwissQRBill.Generator v3.4.0 package reference
- Update ExtractSwissQrCodeQuery handler to use AutoMapper for Bill->DTO mapping
- Remove References property from query (not needed for QR extraction)
2026-07-16 15:47:09 +02:00
e321963487 refactor: Remove SwissQrCodeData domain value object
- Delete SwissQrCodeData.cs and AddressData
- Migrating to direct use of Codecrete Bill class
2026-07-16 15:46:55 +02:00
711f1a2660 feat: Add SwissQrBillDto and BadRequestException
- Add SwissQrBillDto, AddressDto, AlternativeSchemeDto for Codecrete Bill mapping
- Add BadRequestException to Domain exceptions
- DTOs include [Obsolete] warnings for deprecated fields (AddressLine1/2)
2026-07-16 15:46:44 +02:00
386a124a4e Add DualInputDocumentFilter for Swagger content merging
Introduced the `DualInputDocumentFilter` class to merge Swagger operations with the same path but different `[Consumes]` attributes (`multipart/form-data` and `application/json`) into a single operation. This ensures both content types are visible in the Swagger UI.

Updated `SwaggerConfiguration.cs` to:
- Resolve conflicting actions by keeping the first variant.
- Register the `DualInputDocumentFilter` to enable content type merging.
2026-07-13 16:04:55 +02:00
f7433111a7 test: Add PDF/A validation tests and manual testing guide
- Add unit tests for ValidatePdfAQueryHandler (4 tests)
- Add integration tests for PDF/A validation endpoint (6 tests)
- Fix FluentValidation: Add Base64 format validation to ValidatePdfAQueryValidator
- Update AGENTS.md: Document 3-folder test structure rationale and test count (30 tests)
- Add DocumentOperator.API/README.md: 16 manual test scenarios for all endpoints

Test coverage:
- Unit: ValidatePdfAQueryHandler (compliant, non-compliant, encrypted, exceptions)
- Integration: PDF/A endpoint (multipart + Base64, validation, error handling)
- Manual: Step-by-step Swagger UI testing guide for all features

All 30 automated tests passing.
2026-07-09 14:01:57 +02:00
cd50d45bd5 feat: Add PDF/A validation endpoint (Feature 3)
Domain layer:

  - PdfAMetadata value object (isValid, pdfVersion, pageCount, encrypted, pdfaVersion, pdfaCompliant, errors, warnings)

Infrastructure layer:

  - IPdfProcessor.ValidatePdfAAsync() interface method

  - DevExpressPdfProcessor.ValidatePdfAAsync() implementation

  - DetectEncryption() - scans PDF raw data for /Encrypt keyword

  - DetectPdfAConformance() - parses XMP metadata (pdfaid:part, pdfaid:conformance)

  - Validation: encrypted PDF cannot be PDF/A compliant

Application layer:

  - ValidatePdfAQuery + ValidatePdfAQueryHandler (co-located)

  - ValidatePdfAQueryValidator (FluentValidation: PdfBytes XOR Base64Pdf)

  - PdfAValidationResult DTO

  - AutoMapper: PdfAMetadata -> PdfAValidationResult

API layer:

  - PdfValidationController.ValidatePdfAFromFile() (multipart/form-data)

  - PdfValidationController.ValidatePdfAFromBase64() (application/json)

  - XML documentation with response codes

Result:

  - POST /api/pdf/validation/validate-pdfa (both multipart and JSON)

  - Returns: conformance level, errors, warnings

  - Build: 0 errors, 4 warnings (DevExpress eval)

  - Tests: 20/20 passing

Next: Integration tests + Swagger test case
2026-07-09 12:59:24 +02:00
1ff7cbea11 add example PDFs 2026-07-09 12:53:49 +02:00
dc0af68d26 docs: Update API specification based on Marvin/Marlon feedback
CONTROLLER_ENDPOINTS.md changes:

  - Binary stream output for all operations (NO outputPath, NO base64)

  - PDF Operations: merge, stamp, annotate → return application/pdf stream

  - PDF Conversion: to-pdfa, from-pdfa → return application/pdf stream

  - Attachment Extraction: extract → return application/zip stream

  - Add Attachment endpoint (Phase 2): embed files in PDF/PDF/A-3

  - Swiss QR Code endpoint documented (already implemented)

  - PdfRenderController REMOVED (moved to .NET client library)

AGENTS.md changes:

  - Current Status: SwissQrCodeController  DONE (2 tests)

  - Current Status: PdfValidationController  Partial (4 tests)

  - Phase reorganization:

    - Phase 1: validate, validate-pdfa, check, extract (SwissQR), extract (attachments), merge

    - Phase 2: stamp, annotate, add-attachment

    - Phase 3: to-pdfa, from-pdfa

  - Removed PdfRenderController from all phases

Design decisions (team consensus):

  - Server endpoints stay granular (validate, check, extract separate)

  - Combined operations (validateANDextract) → .NET client library

  - Binary streams avoid filesystem dependencies

  - No base64 overhead (~33%), client library handles conversions

Result: Clean API spec, memory-based operations, client convenience layer
2026-07-08 15:46:10 +02:00