--- phase: 01-foundation plan: 03 subsystem: persistence tags: [dotnet10, csharp, system-text-json, semaphoreslim, write-then-replace, unit-tests, xunit] # Dependency graph requires: - 01-01 (solution scaffold, test project) - 01-02 (TenantProfile model) provides: - ProfileRepository: file I/O for profiles JSON with SemaphoreSlim write lock and write-then-replace - ProfileService: CRUD (GetProfiles/AddProfile/RenameProfile/DeleteProfile) with input validation - SettingsRepository: file I/O for settings JSON with same write-then-replace safety pattern - SettingsService: GetSettings/SetLanguage/SetDataFolder with supported-language validation - AppSettings model: DataFolder + Lang with camelCase JSON compatibility affects: - 01-04 (MsalClientFactory may use ProfileService for tenant list) - 01-05 (TranslationSource uses SettingsService for lang) - 01-06 (FeatureViewModelBase may use ProfileService/SettingsService) - all feature plans (profile and settings are the core data contracts) # Tech tracking tech-stack: added: [] patterns: - Write-then-replace: write to .tmp, validate JSON round-trip via JsonDocument.Parse, then File.Move(overwrite:true) - SemaphoreSlim(1,1) for async exclusive write access on per-repository basis - System.Text.Json with PropertyNamingPolicy.CamelCase for schema-compatible serialization - PropertyNameCaseInsensitive=true for deserialization to handle both old and new JSON - TDD with IDisposable temp file pattern for isolated unit tests key-files: created: - SharepointToolbox/Core/Models/AppSettings.cs - SharepointToolbox/Infrastructure/Persistence/ProfileRepository.cs - SharepointToolbox/Infrastructure/Persistence/SettingsRepository.cs - SharepointToolbox/Services/ProfileService.cs - SharepointToolbox/Services/SettingsService.cs - SharepointToolbox.Tests/Services/ProfileServiceTests.cs - SharepointToolbox.Tests/Services/SettingsServiceTests.cs modified: [] key-decisions: - "Explicit System.IO using required in WPF project — WPF temp build project does not include System.IO in implicit usings; all file I/O classes need explicit namespace import" - "SettingsService validates only 'en' and 'fr' — matches app's supported locales; throws ArgumentException for any other code" - "LoadAsync on corrupt JSON throws InvalidDataException (not silent empty) — explicit failure is safer than silently discarding user data" patterns-established: - "Write-then-replace: all file persistence uses .tmp write + JsonDocument.Parse validation + File.Move(overwrite:true) to protect against crash-corruption" - "IDisposable test pattern: unit tests use Path.GetTempFileName() + Dispose() for clean isolated file I/O tests" requirements-completed: - FOUND-02 - FOUND-10 - FOUND-12 # Metrics duration: 8min completed: 2026-04-02 --- # Phase 1 Plan 03: Persistence Layer Summary **ProfileRepository + SettingsRepository with write-then-replace safety, ProfileService + SettingsService with validation, 18 unit tests covering round-trips, corrupt-file recovery, concurrency, and JSON schema compatibility** ## Performance - **Duration:** 8 min - **Started:** 2026-04-02T10:09:13Z - **Completed:** 2026-04-02T10:17:00Z - **Tasks:** 2 - **Files modified:** 7 ## Accomplishments - ProfileRepository and SettingsRepository both implement write-then-replace (tmp file → JSON validation → File.Move) with SemaphoreSlim(1,1) preventing concurrent write corruption - JSON serialization uses camelCase (PropertyNamingPolicy.CamelCase) — preserves existing user data field names: `profiles`, `name`, `tenantUrl`, `clientId`, `dataFolder`, `lang` - ProfileService provides full CRUD with input validation (Name not empty, TenantUrl valid absolute URL, ClientId not empty) - SettingsService validates language codes against supported set (en/fr only), allows empty dataFolder - All 18 unit tests pass (10 ProfileServiceTests + 8 SettingsServiceTests); no skips ## Task Commits Each task was committed atomically: 1. **Task 1: ProfileRepository and ProfileService with write-then-replace** - `769196d` (feat) 2. **Task 2: SettingsRepository and SettingsService** - `ac3fa5c` (feat) **Plan metadata:** (docs commit follows) ## Files Created/Modified - `SharepointToolbox/Core/Models/AppSettings.cs` - AppSettings model; DataFolder + Lang with camelCase JSON - `SharepointToolbox/Infrastructure/Persistence/ProfileRepository.cs` - File I/O; SemaphoreSlim; write-then-replace; camelCase - `SharepointToolbox/Infrastructure/Persistence/SettingsRepository.cs` - Same pattern as ProfileRepository for settings - `SharepointToolbox/Services/ProfileService.cs` - CRUD on profiles; validates Name/TenantUrl/ClientId; throws KeyNotFoundException - `SharepointToolbox/Services/SettingsService.cs` - Get/SetLanguage/SetDataFolder; validates language codes - `SharepointToolbox.Tests/Services/ProfileServiceTests.cs` - 10 tests: round-trip, missing file, corrupt JSON, concurrency, schema keys - `SharepointToolbox.Tests/Services/SettingsServiceTests.cs` - 8 tests: defaults, round-trip, JSON keys, tmp file, language/folder persistence ## Decisions Made - Explicit `using System.IO;` required in WPF main project — the WPF temp build project does not include `System.IO` in its implicit usings, unlike the standard non-WPF SDK. All repositories need explicit namespace imports. - `SettingsService.SetLanguageAsync` validates only "en" and "fr" using a case-insensitive `HashSet`. Other codes throw `ArgumentException` immediately. - `LoadAsync` on corrupt JSON throws `InvalidDataException` (not silent empty list/default) — this is an explicit safety decision: silently discarding corrupt data could mask accidental overwrites. ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 3 - Blocking] Added explicit System.IO using to WPF project files** - **Found during:** Task 1 (dotnet test — first GREEN attempt) - **Issue:** WPF temporary build project does not include `System.IO` in its implicit usings. `File`, `Path`, `Directory`, `IOException`, `InvalidDataException` all unresolved in the main project and test project. - **Fix:** Added `using System.IO;` at the top of ProfileRepository.cs, SettingsRepository.cs, ProfileServiceTests.cs, and SettingsServiceTests.cs - **Files modified:** All 4 implementation and test files - **Verification:** Build succeeded with 0 errors, 18/18 tests pass - **Committed in:** 769196d and ac3fa5c (inline with respective task commits) --- **Total deviations:** 1 auto-fixed (Rule 3 — blocking build issue) **Impact on plan:** One-line fix per file, no logic changes, no scope creep. ## Issues Encountered None beyond the auto-fixed deviation above. ## User Setup Required None - no external service configuration required. ## Next Phase Readiness - ProfileService and SettingsService ready for injection in plan 01-04 (MsalClientFactory may need tenant list from ProfileService) - SettingsService.SetLanguageAsync ready for TranslationSource in plan 01-05 - Both services follow the same constructor injection pattern — ready for DI container registration in plan 01-06 or 01-07 - JSON schema contracts locked: field names are tested and verified camelCase --- *Phase: 01-foundation* *Completed: 2026-04-02*