208 lines
12 KiB
Markdown
208 lines
12 KiB
Markdown
# 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
|