Adds 2 units and docs for unit 3. nfr-requirements plan

This commit is contained in:
2026-06-29 22:18:37 +02:00
parent 0e01ca1e1c
commit c156107cb1
126 changed files with 15204 additions and 80199 deletions
@@ -0,0 +1,207 @@
# Code Generation Plan — Unit 1: master-backend
## Unit Context
**Unit**: `master-backend`
**Workspace root**: `K:\Development\Projects\SlpModularCms`
**Project type**: Brownfield — new project added to existing solution
**New projects**:
- `src/SlpModularCms.Modules.Master/` (application code)
- `src/SlpModularCms.Modules.Master.Tests/` (test code)
**Modified files**:
- `src/SlpModularCms.Api/SlpModularCms.Api.csproj` — add project reference to Master module
- `SlpModularCms.sln` — add both new projects
**Test stack** (matching existing projects): xUnit, NSubstitute, FluentAssertions, EF Core InMemory
**New NuGet dependency**: `Microsoft.Extensions.Http.Resilience` (Polly integration)
**Requirements covered**: FR-MASTER-01/02/03/04/05/10/12/13/14; NFR-MASTER-03/04/05/06
---
## Generation Steps
### STEP 1 — Project files & solution wiring
- [x] 1a. Create `src/SlpModularCms.Modules.Master/SlpModularCms.Modules.Master.csproj`
- Target: `net10.0`; Nullable + ImplicitUsings enabled
- ProjectReference: `SlpModularCms.Core`
- PackageReference: `Microsoft.Extensions.Http.Resilience` (latest stable)
- [x] 1b. Create `src/SlpModularCms.Modules.Master.Tests/SlpModularCms.Modules.Master.Tests.csproj`
- Target: `net10.0`; `IsPackable = false`
- PackageReference: `xunit`, `xunit.runner.visualstudio`, `Microsoft.NET.Test.Sdk`, `coverlet.collector`, `NSubstitute`, `FluentAssertions`, `Microsoft.EntityFrameworkCore.InMemory`, `Microsoft.Extensions.Logging.Abstractions`
- ProjectReference: `SlpModularCms.Modules.Master`
- Global using: `Xunit`
- [x] 1c. Modify `src/SlpModularCms.Api/SlpModularCms.Api.csproj` — add `<ProjectReference Include="..\SlpModularCms.Modules.Master\SlpModularCms.Modules.Master.csproj" />`
- [x] 1d. Modify `SlpModularCms.sln` — add both new projects with correct GUIDs and folder placement
### STEP 2 — Configuration options
- [x] 2a. Create `src/SlpModularCms.Modules.Master/Options/MasterModuleOptions.cs`
- Properties: `IntegrityCheckIntervalMinutes` (int, 60), `HttpTimeoutSeconds` (int, 10), `MasterUrl` (string?), `CacheMinutes` (int, 60), `ApiKey` (string?)
### STEP 3 — Domain entities
- [x] 3a. Create `src/SlpModularCms.Modules.Master/Data/Entities/CmsInstanceStatus.cs`
- Enum: `Available = 0`, `NotAvailable = 1`, `Inactive = 2`
- [x] 3b. Create `src/SlpModularCms.Modules.Master/Data/Entities/CmsInstance.cs`
- All fields per domain-entities.md (including `LastIntegrityCheckFailedAt`)
- `[ExcludeFromCodeCoverage]` NOT applied — entity has no logic; covered by service tests
### STEP 4 — DTOs and request/response models
- [x] 4a. Create `src/SlpModularCms.Modules.Master/Models/CmsInstanceDto.cs``[ExcludeFromCodeCoverage]`
- [x] 4b. Create `src/SlpModularCms.Modules.Master/Models/CreateCmsInstanceRequest.cs``[ExcludeFromCodeCoverage]`
- [x] 4c. Create `src/SlpModularCms.Modules.Master/Models/UpdateStatusRequest.cs``[ExcludeFromCodeCoverage]`
- [x] 4d. Create `src/SlpModularCms.Modules.Master/Models/UpdateStatusResult.cs``[ExcludeFromCodeCoverage]`
### STEP 5 — EF Core DbContext
- [x] 5a. Create `src/SlpModularCms.Modules.Master/Data/MasterDbContext.cs`
- `DbSet<CmsInstance> CmsInstances`
- `OnModelCreating`: configure entity (table name, required fields, max lengths)
- Constructor: accepts `DbContextOptions<MasterDbContext>`
### STEP 6 — Repository
- [x] 6a. Create `src/SlpModularCms.Modules.Master/Repositories/ICmsInstanceRepository.cs`
- `GetAllAsync`, `GetActiveAsync`, `GetByIdAsync`, `AddAsync`, `UpdateAsync`, `SaveChangesAsync`
- [x] 6b. Create `src/SlpModularCms.Modules.Master/Repositories/CmsInstanceRepository.cs`
- Inject `MasterDbContext`; implement all methods
- `GetActiveAsync` filters `Status != CmsInstanceStatus.Inactive`
### STEP 7 — Security: ApiKeyProtector
- [x] 7a. Create `src/SlpModularCms.Modules.Master/Services/IApiKeyProtector.cs`
- `string Protect(string plainApiKey)`
- `string Unprotect(string encryptedApiKey)`
- [x] 7b. Create `src/SlpModularCms.Modules.Master/Services/ApiKeyProtector.cs`
- Inject `IDataProtectionProvider`; purpose string `"SlpModularCms.Master.ApiKey"`
- `[ExcludeFromCodeCoverage]` NOT applied — testable via unit test with real `EphemeralDataProtectionProvider`
### STEP 8 — HTTP client: SlaveApiClient
- [x] 8a. Create `src/SlpModularCms.Modules.Master/Services/ISlaveApiClient.cs`
- `RegisterMasterAsync`, `PushStatusAsync`, `GetRegisteredMasterUrlAsync`
- [x] 8b. Create `src/SlpModularCms.Modules.Master/Services/SlaveApiClient.cs`
- Inject `HttpClient` (typed client)
- Each method: build request with `X-Master-Api-Key` header; handle non-success responses; return `false` / `null` on failure
- JSON serialization: `System.Text.Json` (consistent with project)
### STEP 9 — Service dependencies record
- [x] 9a. Create `src/SlpModularCms.Modules.Master/Services/MasterServiceDependencies.cs`
- Record with 6 properties: `ICmsInstanceRepository`, `ISlaveApiClient`, `IApiKeyProtector`, `IOptions<MasterModuleOptions>`, `IHttpContextAccessor`, `ILogger<CmsInstanceService>`
- `[ExcludeFromCodeCoverage]`
### STEP 10 — CmsInstanceService
- [x] 10a. Create `src/SlpModularCms.Modules.Master/Services/ICmsInstanceService.cs`
- `GetAllAsync`, `AddAsync`, `UpdateStatusAsync`, `VerifyIntegrityAsync`
- [x] 10b. Create `src/SlpModularCms.Modules.Master/Services/CmsInstanceService.cs`
- Inject `MasterServiceDependencies`
- `GetAllAsync`: return all as `CmsInstanceDto` (no ApiKey)
- `AddAsync`: validate → encrypt → persist → determine masterUrl (HttpContext → config fallback) → register → update `LastContactedAt` if success
- `UpdateStatusAsync`: validate → load → validate DisableMessage → persist → for non-Inactive: decrypt → push → update `LastStatusPushedAt` if success → return `UpdateStatusResult`
- `VerifyIntegrityAsync`: get active → per slave: decrypt → get registered URL → compare → re-register if mismatch → update `LastIntegrityCheckFailedAt` / `LastContactedAt`
- Logging per business-rules.md (Error/Warning/Information)
### STEP 11 — Background service
- [x] 11a. Create `src/SlpModularCms.Modules.Master/BackgroundServices/IntegrityCheckBackgroundService.cs`
- Inject `IServiceScopeFactory`, `IOptions<MasterModuleOptions>`, `ILogger<IntegrityCheckBackgroundService>`
- `PeriodicTimer` with `IntegrityCheckIntervalMinutes`
- `CreateAsyncScope()` per tick; resolve `ICmsInstanceService`; call `VerifyIntegrityAsync()`
- Outer `try/catch` logs `Error` and continues
- `[ExcludeFromCodeCoverage]` NOT applied — test with mocked `IServiceScopeFactory`
### STEP 12 — Controller
- [x] 12a. Create `src/SlpModularCms.Modules.Master/Controllers/CmsInstanceController.cs`
- `[ApiController]`, `[Route("[controller]")]`, `[Authorize(Policy = "OwnerOnly")]`
- `GetAll()`: GET → `ICmsInstanceService.GetAllAsync()` → 200 OK
- `Add([FromBody] CreateCmsInstanceRequest)`: POST → `ICmsInstanceService.AddAsync()` → 201 Created
- `UpdateStatus(Guid id, [FromBody] UpdateStatusRequest)`: PUT `/{id}/status``ICmsInstanceService.UpdateStatusAsync()` → 200 OK with `UpdateStatusResult`
- Error handling: catch `KeyNotFoundException` → 404; catch `ArgumentException` (validation) → 400
### STEP 13 — Module registration
- [x] 13a. Create `src/SlpModularCms.Modules.Master/MasterModule.cs``[ExcludeFromCodeCoverage]`
- `Name = "Master"`, `Version = "1.0.0"`
- `RegisterServices`: AddDataProtection, AddSingleton IApiKeyProtector, Configure MasterModuleOptions, AddDbContext MasterDbContext, AddScoped repo + deps + service, AddHttpClient ISlaveApiClient + AddResilienceHandler, AddHostedService IntegrityCheckBackgroundService, AddHttpContextAccessor
- `UseModule`: migrate `MasterDbContext` at startup
### STEP 14 — EF Core migration
- [x] 14a. Create `src/SlpModularCms.Modules.Master/Migrations/` directory placeholder
- Document CLI command to generate initial migration:
```
dotnet ef migrations add InitialCreate --project src/SlpModularCms.Modules.Master --startup-project src/SlpModularCms.Api
```
- Note: Migration is created via CLI after Step 13 compile-succeeds; not hand-generated
### STEP 15 — Unit tests: Repository
- [x] 15a. Create `src/SlpModularCms.Modules.Master.Tests/Repositories/CmsInstanceRepositoryTests.cs`
- Use EF Core InMemory for `MasterDbContext`
- Test: `GetAllAsync`, `GetActiveAsync` (excludes Inactive), `GetByIdAsync` (found/not found), `AddAsync` + `SaveChangesAsync`, `UpdateAsync`
### STEP 16 — Unit tests: ApiKeyProtector
- [x] 16a. Create `src/SlpModularCms.Modules.Master.Tests/Services/ApiKeyProtectorTests.cs`
- Use `EphemeralDataProtectionProvider` (real provider, no mocks)
- Test: Protect returns non-plaintext; Unprotect(Protect(x)) == x; Unprotect with wrong key throws
### STEP 17 — Unit tests: SlaveApiClient
- [x] 17a. Create `src/SlpModularCms.Modules.Master.Tests/Services/SlaveApiClientTests.cs`
- Use `NSubstitute` `HttpMessageHandler` substitute or `MockHttpMessageHandler`
- Test: `RegisterMasterAsync` returns true on 200, false on 4xx/5xx/exception
- Test: `PushStatusAsync` returns true on 200, false on failure
- Test: `GetRegisteredMasterUrlAsync` returns URL from response body, null on failure
- Test: `X-Master-Api-Key` header is set on each request
### STEP 18 — Unit tests: CmsInstanceService
- [x] 18a. Create `src/SlpModularCms.Modules.Master.Tests/Services/CmsInstanceServiceTests.cs`
- Substitute all 6 dependencies via NSubstitute
- `AddAsync` tests: success path, registration failure (record still persisted), URL from HttpContext, URL from config fallback
- `UpdateStatusAsync` tests: not found → throws, missing DisableMessage → throws, Inactive → no push, Available → persists + pushes, push fails → SlaveContactSuccess=false
- `VerifyIntegrityAsync` tests: skip Inactive, URL match → clear flag, mismatch → re-register, unreachable → set LastIntegrityCheckFailedAt
- `GetAllAsync`: ApiKey not in DTO
### STEP 19 — Unit tests: IntegrityCheckBackgroundService
- [x] 19a. Create `src/SlpModularCms.Modules.Master.Tests/BackgroundServices/IntegrityCheckBackgroundServiceTests.cs`
- Test: service calls `VerifyIntegrityAsync` on tick
- Test: exceptions in `VerifyIntegrityAsync` are caught and logged (service does not crash)
- Use NSubstitute `IServiceScopeFactory` + `IServiceScope`
### STEP 20 — Unit tests: Controller
- [x] 20a. Create `src/SlpModularCms.Modules.Master.Tests/Controllers/CmsInstanceControllerTests.cs`
- Substitute `ICmsInstanceService` via NSubstitute
- `GetAll`: 200 with list
- `Add`: 201 Created with dto; 400 on validation exception; 400 on argument exception
- `UpdateStatus`: 200 with result; 404 on KeyNotFoundException; 400 on ArgumentException
### STEP 21 — Code documentation summary
- [x] 21a. Create `aidlc-docs/features/master-cms-module/construction/master-backend/code/code-summary.md`
- List all created/modified files with paths
- Note the EF Core migration CLI command
- Note NuGet package added
---
## File Summary
| File | Action | ExcludeFromCoverage |
|------|--------|-------------------|
| `SlpModularCms.Modules.Master.csproj` | Create | N/A |
| `SlpModularCms.Modules.Master.Tests.csproj` | Create | N/A |
| `SlpModularCms.Api.csproj` | Modify | N/A |
| `SlpModularCms.sln` | Modify | N/A |
| `Options/MasterModuleOptions.cs` | Create | No |
| `Data/Entities/CmsInstanceStatus.cs` | Create | No |
| `Data/Entities/CmsInstance.cs` | Create | No |
| `Models/CmsInstanceDto.cs` | Create | Yes |
| `Models/CreateCmsInstanceRequest.cs` | Create | Yes |
| `Models/UpdateStatusRequest.cs` | Create | Yes |
| `Models/UpdateStatusResult.cs` | Create | Yes |
| `Data/MasterDbContext.cs` | Create | No |
| `Repositories/ICmsInstanceRepository.cs` | Create | No |
| `Repositories/CmsInstanceRepository.cs` | Create | No |
| `Services/IApiKeyProtector.cs` | Create | No |
| `Services/ApiKeyProtector.cs` | Create | No |
| `Services/ISlaveApiClient.cs` | Create | No |
| `Services/SlaveApiClient.cs` | Create | No |
| `Services/MasterServiceDependencies.cs` | Create | Yes |
| `Services/ICmsInstanceService.cs` | Create | No |
| `Services/CmsInstanceService.cs` | Create | No |
| `BackgroundServices/IntegrityCheckBackgroundService.cs` | Create | No |
| `Controllers/CmsInstanceController.cs` | Create | No |
| `MasterModule.cs` | Create | Yes |
| Tests (6 files) | Create | N/A |
**Total**: 25 application files (24 new, 2 modified) + 6 test files + 1 code summary