Some checks failed
Release SharePoint Toolbox v2 / release (push) Failing after 14s
v1.1 shipped with 4 phases (25 plans), 10/10 requirements complete: - Global site selection (toolbar picker, all tabs consume) - User access audit (Graph people-picker, direct/group/inherited) - Simplified permissions (plain-language labels, risk levels, detail toggle) - Storage visualization (LiveCharts2 pie/donut + bar charts) Post-phase polish: centralized site selection (removed per-tab pickers), claims prefix stripping, StorageMetrics backfill, chart tooltip fix, summary stats in app + HTML exports. 205 tests passing, 10,484 LOC. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
195 lines
7.7 KiB
C#
195 lines
7.7 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Unit tests for PermissionsViewModel.
|
|
/// PERM-02: multi-site scan loop invokes ScanSiteAsync once per URL.
|
|
/// </summary>
|
|
public class PermissionsViewModelTests
|
|
{
|
|
[Fact]
|
|
public async Task StartScanAsync_WithMultipleSiteUrls_CallsServiceOncePerUrl()
|
|
{
|
|
// Arrange
|
|
var mockPermissionsService = new Mock<IPermissionsService>();
|
|
mockPermissionsService
|
|
.Setup(s => s.ScanSiteAsync(
|
|
It.IsAny<ClientContext>(),
|
|
It.IsAny<ScanOptions>(),
|
|
It.IsAny<IProgress<OperationProgress>>(),
|
|
It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(new List<PermissionEntry>());
|
|
|
|
var mockSiteListService = new Mock<ISiteListService>();
|
|
|
|
var mockSessionManager = new Mock<ISessionManager>();
|
|
mockSessionManager
|
|
.Setup(s => s.GetOrCreateContextAsync(It.IsAny<TenantProfile>(), It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync((ClientContext)null!);
|
|
|
|
var vm = new PermissionsViewModel(
|
|
mockPermissionsService.Object,
|
|
mockSiteListService.Object,
|
|
mockSessionManager.Object,
|
|
new NullLogger<FeatureViewModelBase>());
|
|
|
|
// Set up two site URLs via global site selection
|
|
WeakReferenceMessenger.Default.Send(new GlobalSitesChangedMessage(
|
|
new List<SiteInfo>
|
|
{
|
|
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<OperationProgress>());
|
|
|
|
// Assert: ScanSiteAsync called exactly twice (once per URL)
|
|
mockPermissionsService.Verify(
|
|
s => s.ScanSiteAsync(
|
|
It.IsAny<ClientContext>(),
|
|
It.IsAny<ScanOptions>(),
|
|
It.IsAny<IProgress<OperationProgress>>(),
|
|
It.IsAny<CancellationToken>()),
|
|
Times.Exactly(2));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a PermissionsViewModel with mocked services where ScanSiteAsync returns the given results.
|
|
/// </summary>
|
|
private static PermissionsViewModel CreateViewModelWithResults(IReadOnlyList<PermissionEntry> results)
|
|
{
|
|
var mockPermissionsService = new Mock<IPermissionsService>();
|
|
mockPermissionsService
|
|
.Setup(s => s.ScanSiteAsync(
|
|
It.IsAny<ClientContext>(),
|
|
It.IsAny<ScanOptions>(),
|
|
It.IsAny<IProgress<OperationProgress>>(),
|
|
It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(results.ToList());
|
|
|
|
var mockSiteListService = new Mock<ISiteListService>();
|
|
|
|
var mockSessionManager = new Mock<ISessionManager>();
|
|
mockSessionManager
|
|
.Setup(s => s.GetOrCreateContextAsync(It.IsAny<TenantProfile>(), It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync((ClientContext)null!);
|
|
|
|
var vm = new PermissionsViewModel(
|
|
mockPermissionsService.Object,
|
|
mockSiteListService.Object,
|
|
mockSessionManager.Object,
|
|
new NullLogger<FeatureViewModelBase>());
|
|
|
|
return vm;
|
|
}
|
|
|
|
[Fact]
|
|
public void IsSimplifiedMode_Default_IsFalse()
|
|
{
|
|
WeakReferenceMessenger.Default.Reset();
|
|
var vm = CreateViewModelWithResults(Array.Empty<PermissionEntry>());
|
|
Assert.False(vm.IsSimplifiedMode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task IsSimplifiedMode_WhenToggled_RebuildsSimplifiedResults()
|
|
{
|
|
WeakReferenceMessenger.Default.Reset();
|
|
var entries = new List<PermissionEntry>
|
|
{
|
|
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<SiteInfo> { 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<OperationProgress>());
|
|
|
|
// 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<PermissionEntry>
|
|
{
|
|
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<SiteInfo> { 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<OperationProgress>());
|
|
|
|
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<PermissionEntry>
|
|
{
|
|
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<SiteInfo> { new("https://s1", "S1") }.AsReadOnly()));
|
|
vm.SetCurrentProfile(new TenantProfile { Name = "Test", TenantUrl = "https://s1", ClientId = "cid" });
|
|
await vm.TestRunOperationAsync(CancellationToken.None, new Progress<OperationProgress>());
|
|
|
|
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);
|
|
}
|
|
}
|