diff --git a/.planning/phases/02-permissions/02-03-SUMMARY.md b/.planning/phases/02-permissions/02-03-SUMMARY.md new file mode 100644 index 0000000..71c31bd --- /dev/null +++ b/.planning/phases/02-permissions/02-03-SUMMARY.md @@ -0,0 +1,137 @@ +--- +phase: 02-permissions +plan: 03 +subsystem: api +tags: [sharepoint, pnp-framework, tenant-admin, site-listing, csharp] + +# Dependency graph +requires: + - phase: 01-foundation + provides: SessionManager.GetOrCreateContextAsync, TenantProfile, OperationProgress +provides: + - ISiteListService interface for ViewModel mocking + - SiteListService tenant admin API wrapper enumerating all sites + - SiteInfo record model (Url, Title) +affects: [02-06-site-picker-dialog, 02-permissions-viewmodel] + +# Tech tracking +tech-stack: + added: [] + patterns: + - "Admin URL derivation: Regex transform contoso.sharepoint.com → contoso-admin.sharepoint.com" + - "ServerException wrapping: Access denied → InvalidOperationException with actionable message" + - "InternalsVisibleTo pattern for testing internal static helpers without making them public" + +key-files: + created: + - SharepointToolbox/Core/Models/SiteInfo.cs + - SharepointToolbox/Services/ISiteListService.cs + - SharepointToolbox/Services/SiteListService.cs + modified: + - SharepointToolbox/AssemblyInfo.cs + - SharepointToolbox.Tests/Services/SiteListServiceTests.cs + +key-decisions: + - "DeriveAdminUrl is internal static (not private) so tests can call it directly without a live tenant" + - "InternalsVisibleTo added to AssemblyInfo.cs — plan specified internal visibility but omitted the assembly attribute needed to test it" + - "OneDrive personal sites filtered by -my.sharepoint.com URL pattern in addition to Active status check" + +patterns-established: + - "Admin URL derivation: use Regex.Replace with (https://[^.]+)(\\.sharepoint\\.com) pattern" + - "Tenant admin access: pass synthetic TenantProfile with admin URL to SessionManager.GetOrCreateContextAsync" + +requirements-completed: [PERM-02] + +# Metrics +duration: 1min +completed: 2026-04-02 +--- + +# Phase 2 Plan 3: SiteListService Summary + +**ISiteListService + SiteListService wrapper for SharePoint tenant admin API using PnP.Framework Tenant.GetSitePropertiesFromSharePoint, with admin URL regex derivation and ServerException-to-InvalidOperationException wrapping** + +## Performance + +- **Duration:** 1 min +- **Started:** 2026-04-02T11:48:57Z +- **Completed:** 2026-04-02T11:50:40Z +- **Tasks:** 1 (TDD: RED + GREEN) +- **Files modified:** 5 + +## Accomplishments + +- SiteInfo record model created in Core/Models +- ISiteListService interface defined — enables ViewModel mocking in Plan 06 (SitePickerDialog) +- SiteListService derives admin URL via Regex, connects via SessionManager to tenant admin endpoint +- Active-only filtering with OneDrive personal site exclusion (-my.sharepoint.com) +- DeriveAdminUrl tested with 2 unit tests (standard URL, trailing-slash URL) + +## Task Commits + +Each task was committed atomically: + +1. **Task 1 RED: SiteListServiceTests (failing)** - `5c10840` (test) +2. **Task 1 GREEN: ISiteListService, SiteListService, SiteInfo** - `78b3d4f` (feat) + +**Plan metadata:** _(pending)_ + +_Note: TDD task has two commits (test RED → feat GREEN); no REFACTOR step needed — code is clean as written_ + +## Files Created/Modified + +- `SharepointToolbox/Core/Models/SiteInfo.cs` - Simple record with Url and Title properties +- `SharepointToolbox/Services/ISiteListService.cs` - Interface contract for GetSitesAsync +- `SharepointToolbox/Services/SiteListService.cs` - Implementation: admin URL derivation, tenant query, filtering, error wrapping +- `SharepointToolbox/AssemblyInfo.cs` - Added InternalsVisibleTo("SharepointToolbox.Tests") +- `SharepointToolbox.Tests/Services/SiteListServiceTests.cs` - Two unit tests for DeriveAdminUrl + +## Decisions Made + +- DeriveAdminUrl marked `internal static` rather than `private static` to allow direct unit testing without mocking a full SessionManager +- `InternalsVisibleTo("SharepointToolbox.Tests")` added to AssemblyInfo.cs — this is the standard .NET approach for testing internal members (Rule 3 deviation, see below) +- OneDrive sites excluded by URL pattern (`-my.sharepoint.com`) in addition to `Status == "Active"` to avoid returning personal storage sites in the picker + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 3 - Blocking] Added InternalsVisibleTo to expose internal DeriveAdminUrl to test project** +- **Found during:** Task 1 GREEN (test compilation failed with CS0117) +- **Issue:** Plan specified `internal static` for DeriveAdminUrl for testability, but did not include the `InternalsVisibleTo` assembly attribute required for the test project to access it +- **Fix:** Added `[assembly: InternalsVisibleTo("SharepointToolbox.Tests")]` to AssemblyInfo.cs +- **Files modified:** SharepointToolbox/AssemblyInfo.cs +- **Verification:** Tests compile and both DeriveAdminUrl tests pass (2/2) +- **Committed in:** 78b3d4f (GREEN commit) + +--- + +**Total deviations:** 1 auto-fixed (1 blocking) +**Impact on plan:** Necessary for test infrastructure — plan's intent was clearly to test internal method; InternalsVisibleTo is the standard mechanism. No scope creep. + +## Issues Encountered + +None + +## User Setup Required + +None - no external service configuration required. + +## Next Phase Readiness + +- ISiteListService ready for injection into SitePickerDialog (Plan 06) +- SiteListService compiles and DeriveAdminUrl verified; live tenant testing requires admin credentials (handled at runtime via SessionManager interactive login) +- Full test suite: 53 pass, 4 skip, 0 fail + +## Self-Check: PASSED + +- FOUND: SharepointToolbox/Core/Models/SiteInfo.cs +- FOUND: SharepointToolbox/Services/ISiteListService.cs +- FOUND: SharepointToolbox/Services/SiteListService.cs +- FOUND: .planning/phases/02-permissions/02-03-SUMMARY.md +- FOUND: commit 5c10840 (test RED) +- FOUND: commit 78b3d4f (feat GREEN) + +--- +*Phase: 02-permissions* +*Completed: 2026-04-02*