Files
slp-modular-cms/aidlc-docs/features/master-cms-module/construction/plans/master-backend-code-generation-plan.md
T

12 KiB

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

  • 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)
  • 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
  • 1c. Modify src/SlpModularCms.Api/SlpModularCms.Api.csproj — add <ProjectReference Include="..\SlpModularCms.Modules.Master\SlpModularCms.Modules.Master.csproj" />
  • 1d. Modify SlpModularCms.sln — add both new projects with correct GUIDs and folder placement

STEP 2 — Configuration options

  • 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

  • 3a. Create src/SlpModularCms.Modules.Master/Data/Entities/CmsInstanceStatus.cs
    • Enum: Available = 0, NotAvailable = 1, Inactive = 2
  • 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

  • 4a. Create src/SlpModularCms.Modules.Master/Models/CmsInstanceDto.cs[ExcludeFromCodeCoverage]
  • 4b. Create src/SlpModularCms.Modules.Master/Models/CreateCmsInstanceRequest.cs[ExcludeFromCodeCoverage]
  • 4c. Create src/SlpModularCms.Modules.Master/Models/UpdateStatusRequest.cs[ExcludeFromCodeCoverage]
  • 4d. Create src/SlpModularCms.Modules.Master/Models/UpdateStatusResult.cs[ExcludeFromCodeCoverage]

STEP 5 — EF Core DbContext

  • 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

  • 6a. Create src/SlpModularCms.Modules.Master/Repositories/ICmsInstanceRepository.cs
    • GetAllAsync, GetActiveAsync, GetByIdAsync, AddAsync, UpdateAsync, SaveChangesAsync
  • 6b. Create src/SlpModularCms.Modules.Master/Repositories/CmsInstanceRepository.cs
    • Inject MasterDbContext; implement all methods
    • GetActiveAsync filters Status != CmsInstanceStatus.Inactive

STEP 7 — Security: ApiKeyProtector

  • 7a. Create src/SlpModularCms.Modules.Master/Services/IApiKeyProtector.cs
    • string Protect(string plainApiKey)
    • string Unprotect(string encryptedApiKey)
  • 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

  • 8a. Create src/SlpModularCms.Modules.Master/Services/ISlaveApiClient.cs
    • RegisterMasterAsync, PushStatusAsync, GetRegisteredMasterUrlAsync
  • 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

  • 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

  • 10a. Create src/SlpModularCms.Modules.Master/Services/ICmsInstanceService.cs
    • GetAllAsync, AddAsync, UpdateStatusAsync, VerifyIntegrityAsync
  • 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

  • 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

  • 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}/statusICmsInstanceService.UpdateStatusAsync() → 200 OK with UpdateStatusResult
    • Error handling: catch KeyNotFoundException → 404; catch ArgumentException (validation) → 400

STEP 13 — Module registration

  • 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

  • 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

  • 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

  • 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

  • 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

  • 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

  • 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

  • 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

  • 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