--- phase: 08-simplified-permissions plan: 06 type: execute wave: 5 depends_on: ["08-05"] files_modified: - SharepointToolbox.Tests/Helpers/PermissionLevelMappingTests.cs - SharepointToolbox.Tests/Models/PermissionSummaryBuilderTests.cs - SharepointToolbox.Tests/ViewModels/PermissionsViewModelTests.cs autonomous: true requirements: - SIMP-01 - SIMP-02 - SIMP-03 must_haves: truths: - "PermissionLevelMapping maps all known role names correctly and handles unknown roles" - "PermissionSummaryBuilder produces 4 risk-level groups with correct counts" - "PermissionsViewModel toggle behavior is verified: IsSimplifiedMode rebuilds data, IsDetailView switches without re-scan" - "SimplifiedPermissionEntry wraps PermissionEntry correctly with computed labels and risk levels" artifacts: - path: "SharepointToolbox.Tests/Helpers/PermissionLevelMappingTests.cs" provides: "Unit tests for permission level mapping" contains: "class PermissionLevelMappingTests" - path: "SharepointToolbox.Tests/Models/PermissionSummaryBuilderTests.cs" provides: "Unit tests for summary aggregation" contains: "class PermissionSummaryBuilderTests" - path: "SharepointToolbox.Tests/ViewModels/PermissionsViewModelTests.cs" provides: "Extended ViewModel tests for simplified mode" contains: "IsSimplifiedMode" key_links: - from: "SharepointToolbox.Tests/Helpers/PermissionLevelMappingTests.cs" to: "SharepointToolbox/Core/Helpers/PermissionLevelMapping.cs" via: "Direct static method calls" pattern: "PermissionLevelMapping\\.Get" - from: "SharepointToolbox.Tests/ViewModels/PermissionsViewModelTests.cs" to: "SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs" via: "Test constructor + property assertions" pattern: "IsSimplifiedMode" --- Add unit tests for the simplified permissions feature: PermissionLevelMapping, PermissionSummaryBuilder, SimplifiedPermissionEntry wrapping, and PermissionsViewModel toggle behavior. Purpose: Validates the core logic of all three SIMP requirements. Mapping correctness (SIMP-01), summary aggregation (SIMP-02), and toggle-without-rescan behavior (SIMP-03). Output: PermissionLevelMappingTests.cs, PermissionSummaryBuilderTests.cs, updated PermissionsViewModelTests.cs @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/08-simplified-permissions/08-01-SUMMARY.md @.planning/phases/08-simplified-permissions/08-02-SUMMARY.md From PermissionLevelMapping: ```csharp public static class PermissionLevelMapping { public record MappingResult(string Label, RiskLevel RiskLevel); public static MappingResult GetMapping(string roleName); public static IReadOnlyList GetMappings(string permissionLevels); public static RiskLevel GetHighestRisk(string permissionLevels); public static string GetSimplifiedLabels(string permissionLevels); } ``` From PermissionSummaryBuilder: ```csharp public static class PermissionSummaryBuilder { public static IReadOnlyList Build(IEnumerable entries); } ``` From SimplifiedPermissionEntry: ```csharp public class SimplifiedPermissionEntry { public PermissionEntry Inner { get; } public string SimplifiedLabels { get; } public RiskLevel RiskLevel { get; } public static IReadOnlyList WrapAll(IEnumerable entries); } ``` ```csharp public class PermissionsViewModelTests { [Fact] public async Task StartScanAsync_WithMultipleSiteUrls_CallsServiceOncePerUrl() { var vm = new PermissionsViewModel( mockPermissionsService.Object, mockSiteListService.Object, mockSessionManager.Object, new NullLogger()); // ... test ... } } ``` Task 1: Create PermissionLevelMapping and PermissionSummaryBuilder tests SharepointToolbox.Tests/Helpers/PermissionLevelMappingTests.cs, SharepointToolbox.Tests/Models/PermissionSummaryBuilderTests.cs Create `SharepointToolbox.Tests/Helpers/PermissionLevelMappingTests.cs`: ```csharp using SharepointToolbox.Core.Helpers; using SharepointToolbox.Core.Models; namespace SharepointToolbox.Tests.Helpers; public class PermissionLevelMappingTests { [Theory] [InlineData("Full Control", RiskLevel.High)] [InlineData("Site Collection Administrator", RiskLevel.High)] [InlineData("Contribute", RiskLevel.Medium)] [InlineData("Edit", RiskLevel.Medium)] [InlineData("Design", RiskLevel.Medium)] [InlineData("Approve", RiskLevel.Medium)] [InlineData("Manage Hierarchy", RiskLevel.Medium)] [InlineData("Read", RiskLevel.Low)] [InlineData("Restricted Read", RiskLevel.Low)] [InlineData("View Only", RiskLevel.ReadOnly)] [InlineData("Restricted View", RiskLevel.ReadOnly)] public void GetMapping_KnownRoles_ReturnsCorrectRiskLevel(string roleName, RiskLevel expected) { var result = PermissionLevelMapping.GetMapping(roleName); Assert.Equal(expected, result.RiskLevel); Assert.NotEmpty(result.Label); } [Fact] public void GetMapping_UnknownRole_ReturnsMediumRiskWithRawName() { var result = PermissionLevelMapping.GetMapping("Custom Permission Level"); Assert.Equal(RiskLevel.Medium, result.RiskLevel); Assert.Equal("Custom Permission Level", result.Label); } [Fact] public void GetMapping_CaseInsensitive() { var lower = PermissionLevelMapping.GetMapping("full control"); var upper = PermissionLevelMapping.GetMapping("FULL CONTROL"); Assert.Equal(RiskLevel.High, lower.RiskLevel); Assert.Equal(RiskLevel.High, upper.RiskLevel); } [Fact] public void GetMappings_SemicolonDelimited_SplitsAndMaps() { var results = PermissionLevelMapping.GetMappings("Full Control; Read"); Assert.Equal(2, results.Count); Assert.Equal(RiskLevel.High, results[0].RiskLevel); Assert.Equal(RiskLevel.Low, results[1].RiskLevel); } [Fact] public void GetMappings_EmptyString_ReturnsEmpty() { var results = PermissionLevelMapping.GetMappings(""); Assert.Empty(results); } [Fact] public void GetHighestRisk_MultipleLevels_ReturnsHighest() { // Full Control (High) + Read (Low) => High var risk = PermissionLevelMapping.GetHighestRisk("Full Control; Read"); Assert.Equal(RiskLevel.High, risk); } [Fact] public void GetHighestRisk_SingleReadOnly_ReturnsReadOnly() { var risk = PermissionLevelMapping.GetHighestRisk("View Only"); Assert.Equal(RiskLevel.ReadOnly, risk); } [Fact] public void GetSimplifiedLabels_JoinsLabels() { var labels = PermissionLevelMapping.GetSimplifiedLabels("Contribute; Read"); Assert.Contains("Can edit files and list items", labels); Assert.Contains("Can view files and pages", labels); } } ``` Create `SharepointToolbox.Tests/Models/PermissionSummaryBuilderTests.cs`: ```csharp using SharepointToolbox.Core.Models; namespace SharepointToolbox.Tests.Models; public class PermissionSummaryBuilderTests { private static PermissionEntry MakeEntry(string permLevels, string users = "User1", string logins = "user1@test.com") => new PermissionEntry( ObjectType: "Site", Title: "Test", Url: "https://test.sharepoint.com", HasUniquePermissions: true, Users: users, UserLogins: logins, PermissionLevels: permLevels, GrantedThrough: "Direct Permissions", PrincipalType: "User"); [Fact] public void Build_ReturnsAllFourRiskLevels() { var entries = SimplifiedPermissionEntry.WrapAll(new[] { MakeEntry("Full Control"), MakeEntry("Contribute"), MakeEntry("Read"), MakeEntry("View Only") }); var summaries = PermissionSummaryBuilder.Build(entries); Assert.Equal(4, summaries.Count); Assert.Contains(summaries, s => s.RiskLevel == RiskLevel.High && s.Count == 1); Assert.Contains(summaries, s => s.RiskLevel == RiskLevel.Medium && s.Count == 1); Assert.Contains(summaries, s => s.RiskLevel == RiskLevel.Low && s.Count == 1); Assert.Contains(summaries, s => s.RiskLevel == RiskLevel.ReadOnly && s.Count == 1); } [Fact] public void Build_EmptyCollection_ReturnsZeroCounts() { var summaries = PermissionSummaryBuilder.Build(Array.Empty()); Assert.Equal(4, summaries.Count); Assert.All(summaries, s => Assert.Equal(0, s.Count)); } [Fact] public void Build_CountsDistinctUsers() { var entries = SimplifiedPermissionEntry.WrapAll(new[] { MakeEntry("Full Control", "Alice", "alice@test.com"), MakeEntry("Full Control", "Bob", "bob@test.com"), MakeEntry("Full Control", "Alice", "alice@test.com"), // duplicate user }); var summaries = PermissionSummaryBuilder.Build(entries); var high = summaries.Single(s => s.RiskLevel == RiskLevel.High); Assert.Equal(3, high.Count); // 3 entries Assert.Equal(2, high.DistinctUsers); // 2 distinct users } [Fact] public void SimplifiedPermissionEntry_WrapAll_PreservesInner() { var original = MakeEntry("Contribute"); var wrapped = SimplifiedPermissionEntry.WrapAll(new[] { original }); Assert.Single(wrapped); Assert.Same(original, wrapped[0].Inner); Assert.Equal("Contribute", wrapped[0].PermissionLevels); Assert.Equal(RiskLevel.Medium, wrapped[0].RiskLevel); Assert.Contains("Can edit", wrapped[0].SimplifiedLabels); } } ``` Create the `SharepointToolbox.Tests/Helpers/` and `SharepointToolbox.Tests/Models/` directories if they don't exist. cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet test SharepointToolbox.Tests/ --filter "PermissionLevelMappingTests|PermissionSummaryBuilderTests" --no-restore 2>&1 | tail -10 PermissionLevelMappingTests covers: all 11 known roles, unknown role fallback, case insensitivity, semicolon splitting, highest risk, simplified labels. PermissionSummaryBuilderTests covers: 4 risk levels, empty input, distinct user counting, SimplifiedPermissionEntry wrapping. All tests pass. Task 2: Add simplified mode tests to PermissionsViewModelTests SharepointToolbox.Tests/ViewModels/PermissionsViewModelTests.cs Add the following test methods to the existing `PermissionsViewModelTests` class in `SharepointToolbox.Tests/ViewModels/PermissionsViewModelTests.cs`. Add any needed using statements at the top: ```csharp using CommunityToolkit.Mvvm.Messaging; ``` Add a helper method and new tests after the existing test: ```csharp /// /// Creates a PermissionsViewModel with mocked services and pre-populated results. /// private static PermissionsViewModel CreateViewModelWithResults(IReadOnlyList results) { var mockPermissionsService = new Mock(); mockPermissionsService .Setup(s => s.ScanSiteAsync( It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) .ReturnsAsync(results.ToList()); var mockSiteListService = new Mock(); var mockSessionManager = new Mock(); mockSessionManager .Setup(s => s.GetOrCreateContextAsync(It.IsAny(), It.IsAny())) .ReturnsAsync((ClientContext)null!); var vm = new PermissionsViewModel( mockPermissionsService.Object, mockSiteListService.Object, mockSessionManager.Object, new NullLogger()); return vm; } [Fact] public void IsSimplifiedMode_Default_IsFalse() { WeakReferenceMessenger.Default.Reset(); var vm = CreateViewModelWithResults(Array.Empty()); Assert.False(vm.IsSimplifiedMode); } [Fact] public async Task IsSimplifiedMode_WhenToggled_RebuildSimplifiedResults() { WeakReferenceMessenger.Default.Reset(); var entries = new List { new("Site", "Test", "https://test.sharepoint.com", true, "User1", "user1@test.com", "Full Control", "Direct Permissions", "User"), new("List", "Docs", "https://test.sharepoint.com/docs", false, "User2", "user2@test.com", "Read", "Direct Permissions", "User"), }; var vm = CreateViewModelWithResults(entries); // Simulate scan completing vm.SelectedSites.Add(new SiteInfo("https://test.sharepoint.com", "Test")); vm.SetCurrentProfile(new TenantProfile { Name = "Test", TenantUrl = "https://test.sharepoint.com", ClientId = "cid" }); await vm.TestRunOperationAsync(CancellationToken.None, new Progress()); // Before toggle: simplified results empty Assert.Empty(vm.SimplifiedResults); // Toggle on vm.IsSimplifiedMode = true; // After toggle: simplified results populated Assert.Equal(2, vm.SimplifiedResults.Count); Assert.Equal(4, vm.Summaries.Count); } [Fact] public async Task IsDetailView_Toggle_DoesNotChangeCounts() { WeakReferenceMessenger.Default.Reset(); var entries = new List { new("Site", "Test", "https://test.sharepoint.com", true, "User1", "user1@test.com", "Contribute", "Direct Permissions", "User"), }; var vm = CreateViewModelWithResults(entries); vm.SelectedSites.Add(new SiteInfo("https://test.sharepoint.com", "Test")); vm.SetCurrentProfile(new TenantProfile { Name = "Test", TenantUrl = "https://test.sharepoint.com", ClientId = "cid" }); await vm.TestRunOperationAsync(CancellationToken.None, new Progress()); vm.IsSimplifiedMode = true; var countBefore = vm.SimplifiedResults.Count; vm.IsDetailView = false; Assert.Equal(countBefore, vm.SimplifiedResults.Count); // No re-computation vm.IsDetailView = true; Assert.Equal(countBefore, vm.SimplifiedResults.Count); // Still the same } [Fact] public async Task Summaries_ContainsCorrectRiskBreakdown() { WeakReferenceMessenger.Default.Reset(); var entries = new List { new("Site", "S1", "https://s1", true, "Admin", "admin@t.com", "Full Control", "Direct", "User"), new("Site", "S2", "https://s2", true, "Editor", "ed@t.com", "Contribute", "Direct", "User"), new("List", "L1", "https://l1", false, "Reader", "read@t.com", "Read", "Direct", "User"), }; var vm = CreateViewModelWithResults(entries); vm.SelectedSites.Add(new SiteInfo("https://s1", "S1")); vm.SetCurrentProfile(new TenantProfile { Name = "Test", TenantUrl = "https://s1", ClientId = "cid" }); await vm.TestRunOperationAsync(CancellationToken.None, new Progress()); vm.IsSimplifiedMode = true; var high = vm.Summaries.Single(s => s.RiskLevel == RiskLevel.High); var medium = vm.Summaries.Single(s => s.RiskLevel == RiskLevel.Medium); var low = vm.Summaries.Single(s => s.RiskLevel == RiskLevel.Low); Assert.Equal(1, high.Count); Assert.Equal(1, medium.Count); Assert.Equal(1, low.Count); } ``` Add the RiskLevel using statement: ```csharp using SharepointToolbox.Core.Models; // Already present (for PermissionEntry) ``` cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet test SharepointToolbox.Tests/ --filter "PermissionsViewModelTests" --no-restore 2>&1 | tail -10 PermissionsViewModelTests has 5 tests total (1 existing + 4 new). Tests verify: IsSimplifiedMode default false, toggle rebuilds SimplifiedResults, IsDetailView toggle doesn't re-compute, Summaries has correct risk breakdown. All tests pass. - `dotnet test SharepointToolbox.Tests/ --no-restore` passes all tests - PermissionLevelMappingTests: 9 test methods covering known roles, unknown fallback, case insensitivity, splitting, risk ranking - PermissionSummaryBuilderTests: 4 test methods covering risk levels, empty input, distinct users, wrapping - PermissionsViewModelTests: 5 test methods (1 existing + 4 new) covering simplified mode toggle, detail toggle, summary breakdown All simplified permissions logic is covered by automated tests. Mapping correctness (SIMP-01), summary aggregation (SIMP-02), and toggle-without-rescan behavior (SIMP-03) are all verified. The test suite catches regressions in the core mapping layer and ViewModel behavior. After completion, create `.planning/phases/08-simplified-permissions/08-06-SUMMARY.md`