# 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 `` - [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 CmsInstances` - `OnModelCreating`: configure entity (table name, required fields, max lengths) - Constructor: accepts `DbContextOptions` ### 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`, `IHttpContextAccessor`, `ILogger` - `[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`, `ILogger` - `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