diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 518c11d..2f625d9 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -21,7 +21,7 @@ **v1.1 Enhanced Reports** - [x] **Phase 6: Global Site Selection** — Toolbar-level multi-site picker that all feature tabs consume as their default target (completed 2026-04-07) -- [ ] **Phase 7: User Access Audit** — New feature tab: export every SharePoint/Teams access a specific user holds across selected sites +- [x] **Phase 7: User Access Audit** — New feature tab: export every SharePoint/Teams access a specific user holds across selected sites (completed 2026-04-07) - [ ] **Phase 8: Simplified Permissions** — Plain-language labels, summary counts, color coding, and detail-level toggle on the permissions report - [ ] **Phase 9: Storage Visualization** — Charting dependency + pie/donut and bar chart views of storage by file type in the Storage Metrics tab @@ -53,7 +53,7 @@ Plans: 2. Running the audit returns a list of all access entries the user holds across the selected sites 3. Results distinguish between direct role assignments, SharePoint group memberships, and inherited access 4. Results can be exported to CSV or HTML in the same format established by v1.0 export patterns -**Plans:** 7/8 plans executed +**Plans:** 8/8 plans complete Plans: - [ ] 07-01-PLAN.md — UserAccessEntry model + service interfaces (Wave 1) - [ ] 07-02-PLAN.md — UserAccessAuditService implementation (Wave 2) @@ -96,6 +96,6 @@ Plans: | 4. Bulk Operations and Provisioning | v1.0 | 10/10 | Complete | 2026-04-03 | | 5. Distribution and Hardening | v1.0 | 3/3 | Complete | 2026-04-03 | | 6. Global Site Selection | 5/5 | Complete | 2026-04-07 | - | -| 7. User Access Audit | 7/8 | In Progress| | - | +| 7. User Access Audit | 8/8 | Complete | 2026-04-07 | - | | 8. Simplified Permissions | v1.1 | 0/? | Not started | - | | 9. Storage Visualization | v1.1 | 0/? | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index 35f7ba3..77dd7cb 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,14 +3,14 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone status: completed -stopped_at: Completed 07-07-PLAN.md -last_updated: "2026-04-07T10:54:46.890Z" +stopped_at: Completed 07-08-PLAN.md +last_updated: "2026-04-07T11:00:01.832Z" last_activity: 2026-04-07 — Roadmap created (Phases 6-9), 10/10 requirements mapped progress: total_phases: 4 - completed_phases: 1 + completed_phases: 2 total_plans: 13 - completed_plans: 12 + completed_plans: 13 --- # Project State @@ -54,6 +54,7 @@ Phase 6 [ ] → Phase 7 [ ] → Phase 8 [ ] → Phase 9 [ ] | Phase 07-user-access-audit P04 | 2 | 1 tasks | 1 files | | Phase 07-user-access-audit P05 | 4 | 2 tasks | 2 files | | Phase 07-user-access-audit P07 | 8 | 3 tasks | 7 files | +| Phase 07-user-access-audit P08 | 2 | 2 tasks | 4 files | ## Accumulated Context @@ -91,6 +92,8 @@ Decisions are logged in PROJECT.md Key Decisions table. - [Phase 07-05]: Simple ListBox autocomplete (not Popup) following plan's recommended simpler alternative — avoids Popup placement issues - [Phase 07-user-access-audit]: Dialog factory wiring in MainWindow.xaml.cs by casting auditView.DataContext to UserAccessAuditViewModel — matches PermissionsView pattern - [Phase 07-user-access-audit]: UserAccessAuditView created inline (Rule 3) when 07-05 found missing — follows 07-05 spec with two-panel layout +- [Phase 07-user-access-audit]: Used internal TestRunOperationAsync for ViewModel tests; Application.Current null in tests lets else branch run synchronously +- [Phase 07-user-access-audit]: WeakReferenceMessenger.Default.Reset() in test constructor prevents cross-test contamination from message registrations ### Pending Todos @@ -102,6 +105,6 @@ None. ## Session Continuity -Last session: 2026-04-07T10:54:46.888Z -Stopped at: Completed 07-07-PLAN.md +Last session: 2026-04-07T11:00:01.830Z +Stopped at: Completed 07-08-PLAN.md Resume file: None diff --git a/.planning/phases/07-user-access-audit/07-08-SUMMARY.md b/.planning/phases/07-user-access-audit/07-08-SUMMARY.md new file mode 100644 index 0000000..62aac88 --- /dev/null +++ b/.planning/phases/07-user-access-audit/07-08-SUMMARY.md @@ -0,0 +1,126 @@ +--- +phase: 07-user-access-audit +plan: 08 +subsystem: testing +tags: [unit-tests, xunit, moq, user-access-audit, csv-export, html-export, viewmodel] + +requires: + - phase: 07-02 + provides: [UserAccessAuditService] + - phase: 07-03 + provides: [GraphUserSearchService, IGraphUserSearchService] + - phase: 07-04 + provides: [UserAccessAuditViewModel] + - phase: 07-06 + provides: [UserAccessCsvExportService, UserAccessHtmlExportService] + +provides: + - Unit tests for UserAccessAuditService (12 tests) + - Unit tests for UserAccessCsvExportService (5 tests) + - Unit tests for UserAccessHtmlExportService (7 tests) + - Unit tests for UserAccessAuditViewModel (8 tests) + +affects: [] + +tech-stack: + added: [] + patterns: [Moq mock setup with ReturnsAsync, reflection for private field access in override guard tests, WeakReferenceMessenger.Reset in test constructor] + +key-files: + created: + - SharepointToolbox.Tests/Services/UserAccessAuditServiceTests.cs + - SharepointToolbox.Tests/Services/Export/UserAccessCsvExportServiceTests.cs + - SharepointToolbox.Tests/Services/Export/UserAccessHtmlExportServiceTests.cs + - SharepointToolbox.Tests/ViewModels/UserAccessAuditViewModelTests.cs + modified: [] + +key-decisions: + - "Used internal TestRunOperationAsync to exercise ViewModel business logic directly, consistent with PermissionsViewModelTests pattern" + - "Application.Current is null in tests — RunOperationAsync else branch executes synchronously, no Dispatcher mocking required" + - "WeakReferenceMessenger.Default.Reset() in test constructor prevents cross-test contamination from GlobalSitesChangedMessage and TenantSwitchedMessage registrations" + - "Reflection used to set _hasLocalSiteOverride for override guard tests, consistent with existing GlobalSiteSelectionTests pattern" + +patterns-established: + - "UserAccess test helpers: MakeEntry() factory for UserAccessEntry, CreateViewModel() factory returns (vm, mockAudit) tuple" + - "Service test pattern: CreateService() returns (svc, permMock, sessionMock) and sets up ScanSiteAsync/GetOrCreateContextAsync on all mocks" + +requirements-completed: [UACC-01, UACC-02] + +duration: 2min +completed: 2026-04-07 +--- + +# Phase 7 Plan 08: Unit Tests Summary + +**32 unit tests covering UserAccessAuditService (user filtering, claim matching, access classification), CSV/HTML export services (format correctness, encoding), and UserAccessAuditViewModel (audit invocation, result population, summary properties, tenant reset, site selection) — all passing with no regressions.** + +## Performance + +- **Duration:** ~2 min +- **Started:** 2026-04-07T11:16:30Z +- **Completed:** 2026-04-07T11:18:50Z +- **Tasks:** 2 +- **Files modified:** 4 + +## Accomplishments + +- UserAccessAuditServiceTests (12 tests): full coverage of user login filtering, claim format bidirectional matching, Direct/Group/Inherited classification, Full Control + Site Collection Administrator high-privilege detection, external user #EXT# flagging, semicolon-delimited user and permission level splitting, multi-site scan loop verification +- UserAccessCsvExportServiceTests (5 tests): summary section content, data header presence, RFC 4180 double-quote escaping, 7-column count enforcement, WriteSingleFileAsync multi-user combined output +- UserAccessHtmlExportServiceTests (7 tests): DOCTYPE prefix, stat-card presence, dual-view section identifiers (view-user/view-site), access-direct/group/inherited CSS badge classes, filterTable/toggleView JS functions, HTML entity encoding for XSS-risk content +- UserAccessAuditViewModelTests (8 tests): AuditUsersAsync mock invocation, Results population count, TotalAccessCount/SitesCount/HighPrivilegeCount computed properties, OnTenantSwitched full reset, GlobalSitesChangedMessage updates SelectedSites, override guard prevents global update, CanExport false/true states + +## Task Commits + +1. **Task 1: Write UserAccessAuditService unit tests** - `5df9503` (test) +2. **Task 2: Write export service and ViewModel tests** - `35b2c2a` (test) + +## Files Created/Modified + +- `SharepointToolbox.Tests/Services/UserAccessAuditServiceTests.cs` — 12 tests for audit service business logic +- `SharepointToolbox.Tests/Services/Export/UserAccessCsvExportServiceTests.cs` — 5 tests for CSV export formatting +- `SharepointToolbox.Tests/Services/Export/UserAccessHtmlExportServiceTests.cs` — 7 tests for HTML export content +- `SharepointToolbox.Tests/ViewModels/UserAccessAuditViewModelTests.cs` — 8 tests for ViewModel orchestration + +## Decisions Made + +1. **TestRunOperationAsync for ViewModel tests** — Used the internal `TestRunOperationAsync` method to exercise `RunOperationAsync` business logic directly. This avoids requiring a full WPF application pump (no Application.Current in tests). Since `Application.Current?.Dispatcher` returns null in the test runner, the else branch executes synchronously — Results and summary properties are set immediately. + +2. **WeakReferenceMessenger.Reset in constructor** — Test class constructor calls `WeakReferenceMessenger.Default.Reset()` to clear all registered receivers between tests. This prevents cross-test contamination where a GlobalSitesChangedMessage from one test bleeds into another. + +3. **Reflection for override guard test** — The `_hasLocalSiteOverride` field is private with no public setter. Using reflection to set it directly is the standard pattern established by GlobalSiteSelectionTests for PermissionsViewModel — consistent approach maintained. + +4. **No special WPF threading setup** — The `CollectionViewSource` and `ICollectionView` used in the ViewModel constructor work in a WPF-enabled test environment (the test project targets `net10.0-windows` with `UseWPF=true`). No mock dispatcher or `[STAThread]` annotation needed. + +## Deviations from Plan + +None — plan executed exactly as written. + +## Issues Encountered + +None. + +## User Setup Required + +None - no external service configuration required. + +## Next Phase Readiness + +- All Phase 7 unit tests complete. 32 new tests, 176 total passing, 22 skipped (pre-existing). +- Phase 7 is fully implemented: models (07-01), audit service (07-02), Graph search (07-03), ViewModel (07-04), view (07-05), exports (07-06), integration wiring (07-07), unit tests (07-08). +- Ready to proceed to Phase 8 or Phase 9. + +## Self-Check: PASSED + +Files confirmed present: +- FOUND: SharepointToolbox.Tests/Services/UserAccessAuditServiceTests.cs +- FOUND: SharepointToolbox.Tests/Services/Export/UserAccessCsvExportServiceTests.cs +- FOUND: SharepointToolbox.Tests/Services/Export/UserAccessHtmlExportServiceTests.cs +- FOUND: SharepointToolbox.Tests/ViewModels/UserAccessAuditViewModelTests.cs + +Commits confirmed: +- FOUND: 5df9503 +- FOUND: 35b2c2a + +--- +*Phase: 07-user-access-audit* +*Completed: 2026-04-07*