Compare commits

..

4 Commits

Author SHA1 Message Date
OlgunR
196f6d9cfb Update test project dependencies and add project references
Updated `Microsoft.NET.Test.Sdk` to version 17.11.1 and `xunit` to version 2.9.3. Updated `xunit.runner.visualstudio` to version 2.8.2 with additional metadata for asset inclusion and private asset behavior.

Added new dependencies: `FluentAssertions` (7.0.0) and `Moq` (4.20.72).

Introduced project references to `DocumentOperator.Domain`, `DocumentOperator.Application`, and `DocumentOperator.Infrastructure` to the test project.

No changes were made to the `coverlet.collector` dependency or the `<Using Include="Xunit" />` directive.
2026-06-18 16:35:56 +02:00
OlgunR
498b6758bf Add DocumentOperator.Tests project for unit testing
Added a new test project, `DocumentOperator.Tests`, to the solution targeting .NET 8.0. Configured the project as a non-packable test project with support for nullable reference types and implicit usings. Included dependencies for `xunit`, `Microsoft.NET.Test.Sdk`, and `coverlet.collector` for testing and code coverage.

Added a placeholder test class, `UnitTest1`, with a single test method, `Test1`, marked with the `[Fact]` attribute.
2026-06-18 16:23:41 +02:00
OlgunR
49d1f43822 Add IPdfProcessor interface and update ROADMAP.md
The `IPdfProcessor` interface was added to the `Application/Common/Interfaces/` directory. It includes the `ValidateAsync` method for validating PDFs and extracting metadata, with proper XML documentation and dependency on domain value objects.

Updated `ROADMAP.md` to mark Step 3.1 as completed, detailing the creation of the `IPdfProcessor` interface and its implementation status.

Removed the `<Folder Include="Common\Interfaces\" />` entry from `DocumentOperator.Application.csproj` to reflect the transition from a placeholder folder structure to actual implementation.
2026-06-18 16:02:32 +02:00
OlgunR
3a87ace144 Complete Phase 2: Domain Layer implementation
Updated ROADMAP.md to mark Phase 2 as completed and added detailed descriptions of completed tasks. Introduced three new value objects (`Base64String`, `TenantId`, and `PdfMetadata`) in the `DocumentOperator.Domain.Models.ValueObjects` namespace. These classes ensure type safety, immutability, and encapsulated validation.

- `Base64String`: Handles Base64 string creation, validation, and conversion.
- `TenantId`: Represents a tenant identifier with validation and normalization.
- `PdfMetadata`: Represents PDF metadata with computed properties.

Updated `DocumentOperator.Domain.csproj` to reflect the addition of these value objects. The project is now ready to begin Phase 3 (Infrastructure Layer).
2026-06-18 14:32:06 +02:00
10 changed files with 246 additions and 15 deletions

View File

@@ -726,10 +726,12 @@ DocumentOperator.Tests/
--- ---
### ?? PHASE 2: Domain Layer (Minimal) - **IN PROGRESS** ### ? PHASE 2: Domain Layer (Minimal) - **COMPLETED**
**Ziel:** Nur was wirklich gebraucht wird! **Ziel:** Nur was wirklich gebraucht wird!
**Status:** ? **Alle Steps abgeschlossen!**
--- ---
#### ? Step 2.1: Domain Exceptions erstellen - **COMPLETED** #### ? Step 2.1: Domain Exceptions erstellen - **COMPLETED**
@@ -802,7 +804,7 @@ DocumentOperator.Tests/
--- ---
#### ?? Step 2.3: Value Objects erstellen - **NEXT** #### ? Step 2.3: Value Objects erstellen - **COMPLETED**
**Aufgabe:** Typsichere, selbst-validierende Wert-Objekte **Aufgabe:** Typsichere, selbst-validierende Wert-Objekte
@@ -812,49 +814,58 @@ DocumentOperator.Tests/
- ? Immutable (keine Änderungen nach Erstellung) - ? Immutable (keine Änderungen nach Erstellung)
- ? Wiederverwendbar (in Domain, Application, Infrastructure) - ? Wiederverwendbar (in Domain, Application, Infrastructure)
**Was du erstellen wirst:** **Was du erstellt hast:**
1. **Base64String.cs** 1. **Base64String.cs** ?
- Factory Method: `Create(string value)` - Factory Method: `Create(string value)`
- Validierung: Gültiges Base64-Format - Validierung: Gültiges Base64-Format
- Konvertierung: `ToByteArray()`, `FromByteArray(byte[])` - Konvertierung: `ToByteArray()`, `FromByteArray(byte[])`
- Wirft `DomainValidationException` bei Fehler - Wirft `DomainValidationException` bei Fehler
2. **TenantId.cs** 2. **TenantId.cs** ?
- Factory Method: `Create(string value)` - Factory Method: `Create(string value)`
- Validierung: Nicht leer, Max 100 Zeichen - Validierung: Nicht leer, Max 100 Zeichen
- Normalisierung: `.ToLowerInvariant()` - Normalisierung: `.ToLowerInvariant()`
- Wirft `DomainValidationException` bei Fehler - Wirft `DomainValidationException` bei Fehler
3. **PdfMetadata.cs** 3. **PdfMetadata.cs** ?
- Properties: PageCount, FileSizeBytes, PdfVersion, HasAttachments, AttachmentCount - Properties: PageCount, FileSizeBytes, PdfVersion, HasAttachments, AttachmentCount
- Computed Property: `FileSizeMB` - Computed Property: `FileSizeMB`
- Keine Validierung (nur Daten-Container) - Keine Validierung (nur Daten-Container)
**Wo:** `Domain/Models/ValueObjects/` **Wo:** `Domain/Models/ValueObjects/`
**Detaillierte Anleitung kommt in Step 2.3!** **Status:** ? **COMPLETED** (17.01.2025)
- ? Base64String.cs erstellt (sealed, Factory Methods, Validierung, Equality)
- ? TenantId.cs erstellt (sealed, Normalisierung, Validierung, Equality)
- ? PdfMetadata.cs erstellt (sealed, Computed Property, ToString())
- ? Build erfolgreich
**?? Phase 2 (Domain Layer) komplett abgeschlossen!**
--- ---
### ? PHASE 3: Infrastructure Layer (Outside-In!) ### ? PHASE 3: Infrastructure Layer (Outside-In!) - **NEXT**
**Ziel:** DevExpress Services implementieren (wir sehen **echten** Code!) **Ziel:** DevExpress Services implementieren (wir sehen **echten** Code!)
--- ---
#### ? Step 3.1: IPdfProcessor Interface erstellen #### ? Step 3.1: IPdfProcessor Interface erstellen - **COMPLETED**
**Aufgabe:** Abstraction für PDF-Operationen **Aufgabe:** Abstraction für PDF-Operationen
**Was du erstellen wirst:** **Was du erstellt hast:**
- **Wo:** `Application/Common/Interfaces/IPdfProcessor.cs` - **Wo:** `Application/Common/Interfaces/IPdfProcessor.cs` ?
- **Inhalt:** - **Inhalt:**
```csharp ```csharp
using DocumentOperator.Domain.Models.ValueObjects;
namespace DocumentOperator.Application.Common.Interfaces;
public interface IPdfProcessor public interface IPdfProcessor
{ {
Task<PdfMetadata> ValidateAsync(byte[] pdfBytes); Task<PdfMetadata> ValidateAsync(byte[] pdfBytes);
// Weitere Methoden später (YAGNI!)
} }
``` ```
@@ -863,9 +874,15 @@ DocumentOperator.Tests/
- Infrastructure implementiert - Infrastructure implementiert
- TDD: Test ? Interface ? Implementation - TDD: Test ? Interface ? Implementation
**Status:** ? **COMPLETED** (17.01.2025)
- ? Interface erstellt mit XML Comments
- ? Using Statement korrekt
- ? Namespace korrekt
- ? Build erfolgreich
--- ---
#### ? Step 3.2: DevExpressPdfProcessor implementieren (mit TDD!) #### ?? Step 3.2: DevExpressPdfProcessor implementieren (mit TDD!) - **NEXT**
**Aufgabe:** DevExpress Integration **Aufgabe:** DevExpress Integration
@@ -1271,6 +1288,23 @@ public async Task POST_ValidatePdf_InvalidPdf_Returns400() { }
## ?? CURRENT STATUS ## ?? CURRENT STATUS
### ? Completed ### ? Completed
- **Phase 1:** Foundation & Clean Architecture Setup ?
- Dependencies ?
- Packages ?
- Folder Structure ?
- Configuration ?
- Serilog ?
- **Phase 2:** Domain Layer (Minimal) ?
- ? Step 2.1 - Domain Exceptions (4 Exceptions erstellt)
- ? Step 2.2 - Enums (DocumentOperationType, ProcessingStatus)
- ? Step 2.3 - Value Objects (Base64String, TenantId, PdfMetadata)
### ?? In Progress
- **Phase 3:** Infrastructure Layer (Outside-In!)
- **NEXT:** Step 3.1 - IPdfProcessor Interface erstellen
### ? Pending
- **Phase 1:** Foundation & Clean Architecture Setup - **Phase 1:** Foundation & Clean Architecture Setup
- Dependencies ? - Dependencies ?
- Packages ? - Packages ?

View File

@@ -0,0 +1,16 @@
using DocumentOperator.Domain.Models.ValueObjects;
namespace DocumentOperator.Application.Common.Interfaces;
public interface IPdfProcessor
{
/// <summary>
/// Validates a PDF and extracts metadata.
/// </summary>
/// <param name="pdfBytes">PDF content as byte array</param>
/// <returns>PDF metadata (page count, size, version, attachments)</returns>
/// <exception cref="Domain.Common.Exceptions.PdfProcessingException">
/// Thrown when PDF is corrupted or cannot be processed
/// </exception>
Task<PdfMetadata> ValidateAsync(byte[] pdfBytes);
}

View File

@@ -17,7 +17,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Common\Interfaces\" />
<Folder Include="Common\Behaviors\" /> <Folder Include="Common\Behaviors\" />
<Folder Include="Common\DTOs\" /> <Folder Include="Common\DTOs\" />
<Folder Include="Common\Mappings\" /> <Folder Include="Common\Mappings\" />

View File

@@ -9,7 +9,6 @@
<ItemGroup> <ItemGroup>
<Folder Include="Common\Results\" /> <Folder Include="Common\Results\" />
<Folder Include="Constants\" /> <Folder Include="Constants\" />
<Folder Include="Models\ValueObjects\" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -0,0 +1,59 @@
namespace DocumentOperator.Domain.Models.ValueObjects;
public sealed class Base64String
{
public string Value { get; }
private Base64String(string value)
{
Value = value;
}
public static Base64String Create(string value)
{
if (string.IsNullOrWhiteSpace(value))
throw new Common.Exceptions.DomainValidationException("Base64 string cannot be empty.");
// Validierung: Ist es gültiges Base64?
try
{
Convert.FromBase64String(value);
}
catch (FormatException)
{
throw new Common.Exceptions.DomainValidationException("Invalid Base64 format.");
}
return new Base64String(value);
}
public static Base64String FromByteArray(byte[] bytes)
{
if (bytes == null || bytes.Length == 0)
throw new Common.Exceptions.DomainValidationException("Byte array cannot be null or empty.");
var base64 = Convert.ToBase64String(bytes);
return new Base64String(base64);
}
public byte[] ToByteArray()
{
return Convert.FromBase64String(Value);
}
public override string ToString() => Value;
// Equality (wichtig für Value Objects!)
public override bool Equals(object? obj)
{
if (obj is not Base64String other)
return false;
return Value == other.Value;
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
}

View File

@@ -0,0 +1,32 @@
namespace DocumentOperator.Domain.Models.ValueObjects;
public sealed class PdfMetadata
{
public int PageCount { get; }
public long FileSizeBytes { get; }
public string PdfVersion { get; }
public bool HasAttachments { get; }
public int AttachmentCount { get; }
// Computed Property (berechnet aus FileSizeBytes)
public double FileSizeMB => FileSizeBytes / 1024.0 / 1024.0;
public PdfMetadata(
int pageCount,
long fileSizeBytes,
string pdfVersion,
bool hasAttachments,
int attachmentCount)
{
PageCount = pageCount;
FileSizeBytes = fileSizeBytes;
PdfVersion = pdfVersion;
HasAttachments = hasAttachments;
AttachmentCount = attachmentCount;
}
public override string ToString()
{
return $"PDF: {PageCount} pages, {FileSizeMB:F2} MB, Version {PdfVersion}, Attachments: {AttachmentCount}";
}
}

View File

@@ -0,0 +1,41 @@
namespace DocumentOperator.Domain.Models.ValueObjects;
public sealed class TenantId
{
public string Value { get; }
private TenantId(string value)
{
Value = value;
}
public static TenantId Create(string value)
{
if (string.IsNullOrWhiteSpace(value))
throw new Common.Exceptions.DomainValidationException("TenantId cannot be empty.");
if (value.Length > 100)
throw new Common.Exceptions.DomainValidationException("TenantId cannot exceed 100 characters.");
// Normalisierung: Lowercase
var normalized = value.Trim().ToLowerInvariant();
return new TenantId(normalized);
}
public override string ToString() => Value;
// Equality
public override bool Equals(object? obj)
{
if (obj is not TenantId other)
return false;
return Value == other.Value;
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
}

View File

@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="FluentAssertions" Version="7.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DocumentOperator.Domain\DocumentOperator.Domain.csproj" />
<ProjectReference Include="..\DocumentOperator.Application\DocumentOperator.Application.csproj" />
<ProjectReference Include="..\DocumentOperator.Infrastructure\DocumentOperator.Infrastructure.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,11 @@
namespace DocumentOperator.Tests
{
public class UnitTest1
{
[Fact]
public void Test1()
{
}
}
}

View File

@@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DocumentOperator.Infrastruc
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DocumentOperator.Domain", "DocumentOperator.Domain\DocumentOperator.Domain.csproj", "{B4C1C3ED-D3E8-4272-8704-2E73CEA6A0DD}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DocumentOperator.Domain", "DocumentOperator.Domain\DocumentOperator.Domain.csproj", "{B4C1C3ED-D3E8-4272-8704-2E73CEA6A0DD}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DocumentOperator.Tests", "DocumentOperator.Tests\DocumentOperator.Tests.csproj", "{32D2E997-3DA7-4061-8A50-DBB34BBC3E5A}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -33,6 +35,10 @@ Global
{B4C1C3ED-D3E8-4272-8704-2E73CEA6A0DD}.Debug|Any CPU.Build.0 = Debug|Any CPU {B4C1C3ED-D3E8-4272-8704-2E73CEA6A0DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B4C1C3ED-D3E8-4272-8704-2E73CEA6A0DD}.Release|Any CPU.ActiveCfg = Release|Any CPU {B4C1C3ED-D3E8-4272-8704-2E73CEA6A0DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B4C1C3ED-D3E8-4272-8704-2E73CEA6A0DD}.Release|Any CPU.Build.0 = Release|Any CPU {B4C1C3ED-D3E8-4272-8704-2E73CEA6A0DD}.Release|Any CPU.Build.0 = Release|Any CPU
{32D2E997-3DA7-4061-8A50-DBB34BBC3E5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32D2E997-3DA7-4061-8A50-DBB34BBC3E5A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32D2E997-3DA7-4061-8A50-DBB34BBC3E5A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32D2E997-3DA7-4061-8A50-DBB34BBC3E5A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE