using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using CommunityToolkit.Mvvm.Messaging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.SharePoint.Client; using Moq; using SharepointToolbox.Core.Messages; using SharepointToolbox.Core.Models; using SharepointToolbox.Services; using SharepointToolbox.ViewModels; using SharepointToolbox.ViewModels.Tabs; namespace SharepointToolbox.Tests.ViewModels; /// /// Unit tests for PermissionsViewModel. /// PERM-02: multi-site scan loop invokes ScanSiteAsync once per URL. /// public class PermissionsViewModelTests { [Fact] public async Task StartScanAsync_WithMultipleSiteUrls_CallsServiceOncePerUrl() { // Arrange var mockPermissionsService = new Mock(); mockPermissionsService .Setup(s => s.ScanSiteAsync( It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) .ReturnsAsync(new List()); 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()); // Set up two site URLs via global site selection WeakReferenceMessenger.Default.Send(new GlobalSitesChangedMessage( new List { new("https://tenant1.sharepoint.com/sites/alpha", "Alpha"), new("https://tenant1.sharepoint.com/sites/beta", "Beta") }.AsReadOnly())); vm.SetCurrentProfile(new TenantProfile { Name = "Test", TenantUrl = "https://tenant1.sharepoint.com", ClientId = "client-id" }); // Act await vm.TestRunOperationAsync(CancellationToken.None, new Progress()); // Assert: ScanSiteAsync called exactly twice (once per URL) mockPermissionsService.Verify( s => s.ScanSiteAsync( It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny()), Times.Exactly(2)); } /// /// Creates a PermissionsViewModel with mocked services where ScanSiteAsync returns the given 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_RebuildsSimplifiedResults() { 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 WeakReferenceMessenger.Default.Send(new GlobalSitesChangedMessage( new List { new("https://test.sharepoint.com", "Test") }.AsReadOnly())); 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); WeakReferenceMessenger.Default.Send(new GlobalSitesChangedMessage( new List { new("https://test.sharepoint.com", "Test") }.AsReadOnly())); 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); WeakReferenceMessenger.Default.Send(new GlobalSitesChangedMessage( new List { new("https://s1", "S1") }.AsReadOnly())); 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); } }