--- 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*