--- phase: 07-user-access-audit plan: 08 type: execute wave: 5 depends_on: ["07-02", "07-03", "07-04", "07-06"] files_modified: - SharepointToolbox.Tests/Services/UserAccessAuditServiceTests.cs - SharepointToolbox.Tests/Services/Export/UserAccessCsvExportServiceTests.cs - SharepointToolbox.Tests/Services/Export/UserAccessHtmlExportServiceTests.cs - SharepointToolbox.Tests/ViewModels/UserAccessAuditViewModelTests.cs autonomous: true requirements: - UACC-01 - UACC-02 must_haves: truths: - "UserAccessAuditService tests verify: user filtering, access type classification, high-privilege detection, external user detection, multi-user splitting" - "CSV export tests verify: summary section presence, correct column count, RFC 4180 escaping, per-user file naming" - "HTML export tests verify: contains stats cards, both view sections, access type badges, filter script" - "ViewModel tests verify: debounced search triggers service, run audit populates results, tenant switch resets state, global sites override pattern" artifacts: - path: "SharepointToolbox.Tests/Services/UserAccessAuditServiceTests.cs" provides: "Unit tests for audit service business logic" contains: "UserAccessAuditServiceTests" - path: "SharepointToolbox.Tests/Services/Export/UserAccessCsvExportServiceTests.cs" provides: "Unit tests for CSV export" contains: "UserAccessCsvExportServiceTests" - path: "SharepointToolbox.Tests/Services/Export/UserAccessHtmlExportServiceTests.cs" provides: "Unit tests for HTML export" contains: "UserAccessHtmlExportServiceTests" - path: "SharepointToolbox.Tests/ViewModels/UserAccessAuditViewModelTests.cs" provides: "Unit tests for ViewModel logic" contains: "UserAccessAuditViewModelTests" key_links: - from: "SharepointToolbox.Tests/Services/UserAccessAuditServiceTests.cs" to: "SharepointToolbox/Services/UserAccessAuditService.cs" via: "Tests TransformEntries logic with mock IPermissionsService" pattern: "AuditUsersAsync" --- Write unit tests for the core Phase 7 business logic: UserAccessAuditService (filtering, classification), export services (CSV/HTML output), and ViewModel (search, audit, state management). Purpose: Verify the critical behavior of user filtering, access type classification, export formatting, and ViewModel orchestration. Output: 4 test files covering services, exports, and ViewModel @C:/Users/dev/.claude/get-shit-done/workflows/execute-plan.md @C:/Users/dev/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/07-user-access-audit/07-CONTEXT.md @.planning/phases/07-user-access-audit/07-02-SUMMARY.md @.planning/phases/07-user-access-audit/07-04-SUMMARY.md @.planning/phases/07-user-access-audit/07-06-SUMMARY.md From SharepointToolbox/Services/UserAccessAuditService.cs: ```csharp public class UserAccessAuditService : IUserAccessAuditService { public UserAccessAuditService(IPermissionsService permissionsService) { } public async Task> AuditUsersAsync( ISessionManager sessionManager, IReadOnlyList targetUserLogins, IReadOnlyList sites, ScanOptions options, IProgress progress, CancellationToken ct); } ``` From SharepointToolbox/Services/Export/UserAccessCsvExportService.cs: ```csharp public class UserAccessCsvExportService { public string BuildCsv(string userDisplayName, string userLogin, IReadOnlyList entries); public async Task WriteSingleFileAsync(IReadOnlyList entries, string filePath, CancellationToken ct); } ``` From SharepointToolbox/Services/Export/UserAccessHtmlExportService.cs: ```csharp public class UserAccessHtmlExportService { public string BuildHtml(IReadOnlyList entries); public async Task WriteAsync(IReadOnlyList entries, string filePath, CancellationToken ct); } ``` From SharepointToolbox.Tests (uses xUnit + NSubstitute): ```csharp using NSubstitute; using Xunit; ``` ```csharp var mockPermService = Substitute.For(); var mockSessionMgr = Substitute.For(); mockSessionMgr.GetOrCreateContextAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult(null!)); // service creates context, tests mock it mockPermService.ScanSiteAsync(Arg.Any(), Arg.Any(), Arg.Any>(), Arg.Any()) .Returns(Task.FromResult>(testEntries)); ``` Task 1: Write UserAccessAuditService unit tests SharepointToolbox.Tests/Services/UserAccessAuditServiceTests.cs Create `SharepointToolbox.Tests/Services/UserAccessAuditServiceTests.cs` with xUnit + NSubstitute. Test cases for AuditUsersAsync: 1. **Filters_by_target_user_login**: Mock IPermissionsService returning entries for 3 users. Audit for 1 user. Assert only that user's entries returned. 2. **Matches_user_by_email_in_claim_format**: PermissionEntry.UserLogins = "i:0#.f|membership|alice@contoso.com". Target = "alice@contoso.com". Assert match found. 3. **Classifies_direct_access**: Entry with HasUniquePermissions=true, GrantedThrough="Direct Permissions". Assert AccessType.Direct. 4. **Classifies_group_access**: Entry with HasUniquePermissions=true, GrantedThrough="SharePoint Group: Members". Assert AccessType.Group. 5. **Classifies_inherited_access**: Entry with HasUniquePermissions=false. Assert AccessType.Inherited. 6. **Detects_high_privilege**: Entry with PermissionLevels="Full Control". Assert IsHighPrivilege=true. 7. **Detects_high_privilege_site_admin**: Entry with PermissionLevels="Site Collection Administrator". Assert IsHighPrivilege=true. 8. **Flags_external_user**: Entry with UserLogins containing "#EXT#". Assert IsExternalUser=true. 9. **Splits_semicolon_users**: Entry with Users="Alice;Bob", UserLogins="alice@x.com;bob@x.com". Target both. Assert 2 separate UserAccessEntry rows per permission level. 10. **Splits_semicolon_permission_levels**: Entry with PermissionLevels="Read;Contribute". Assert 2 UserAccessEntry rows (one per level). 11. **Empty_targets_returns_empty**: Pass empty targetUserLogins. Assert empty result. 12. **Scans_multiple_sites**: Pass 2 sites. Assert both site entries appear in results. Mock setup pattern: ```csharp private static PermissionEntry MakeEntry( string users = "Alice", string logins = "alice@contoso.com", string levels = "Read", string grantedThrough = "Direct Permissions", bool hasUnique = true, string objectType = "List", string title = "Docs", string url = "https://contoso.sharepoint.com/Docs", string principalType = "User") => new(objectType, title, url, hasUnique, users, logins, levels, grantedThrough, principalType); ``` For the SessionManager mock, the service passes TenantProfile objects to GetOrCreateContextAsync. The mock should return null for ClientContext since the PermissionsService is also mocked (it never actually uses the context in tests). cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet test SharepointToolbox.Tests/SharepointToolbox.Tests.csproj --filter "FullyQualifiedName~UserAccessAuditServiceTests" --no-build 2>&1 | tail -10 All UserAccessAuditService tests pass: user filtering, claim format matching, access type classification (Direct/Group/Inherited), high-privilege detection, external user flagging, semicolon splitting, multi-site scanning. Task 2: Write export service and ViewModel tests SharepointToolbox.Tests/Services/Export/UserAccessCsvExportServiceTests.cs, SharepointToolbox.Tests/Services/Export/UserAccessHtmlExportServiceTests.cs, SharepointToolbox.Tests/ViewModels/UserAccessAuditViewModelTests.cs **UserAccessCsvExportServiceTests.cs**: 1. **BuildCsv_includes_summary_section**: Assert output starts with "User Access Audit Report" and includes user name, total, sites count. 2. **BuildCsv_includes_data_header**: Assert DataHeader line present after summary. 3. **BuildCsv_escapes_quotes**: Entry with title containing double quotes. Assert RFC 4180 escaping. 4. **BuildCsv_correct_column_count**: Assert each data row has 7 comma-separated fields. 5. **WriteSingleFileAsync_includes_all_users**: Pass entries for 2 users. Assert both appear in output. **UserAccessHtmlExportServiceTests.cs**: 1. **BuildHtml_contains_doctype**: Assert starts with "". 2. **BuildHtml_has_stats_cards**: Assert contains "Total Accesses" and stat-card CSS class. 3. **BuildHtml_has_both_views**: Assert contains "by-user" and "by-site" div/section identifiers. 4. **BuildHtml_has_access_type_badges**: Assert contains "access-direct", "access-group", "access-inherited" CSS classes. 5. **BuildHtml_has_filter_script**: Assert contains "filterTable" JS function. 6. **BuildHtml_has_toggle_script**: Assert contains "toggleView" JS function. 7. **BuildHtml_encodes_html_entities**: Entry with title containing "