test(02-01): scaffold PermissionsService, ViewModel, and classification test stubs
- PermissionEntryHelper.cs: pure static IsExternalUser, FilterPermissionLevels, IsSharingLinksGroup - PermissionEntryClassificationTests.cs: 7 real [Fact] tests — all passing immediately - PermissionsServiceTests.cs: 2 stubs (PERM-01, PERM-04) skipped until Plan 02 CSOM impl - PermissionsViewModelTests.cs: 1 stub (PERM-02) skipped until Plan 02 ViewModel impl
This commit is contained in:
@@ -0,0 +1,63 @@
|
|||||||
|
using SharepointToolbox.Core.Helpers;
|
||||||
|
|
||||||
|
namespace SharepointToolbox.Tests.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests for PERM-03: external user detection and permission-level filtering.
|
||||||
|
/// Pure static logic — runs immediately without stubs.
|
||||||
|
/// </summary>
|
||||||
|
public class PermissionEntryClassificationTests
|
||||||
|
{
|
||||||
|
// ── IsExternalUser ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsExternalUser_WithExtHashInLoginName_ReturnsTrue()
|
||||||
|
{
|
||||||
|
// B2B guest login names contain the literal "#EXT#" fragment
|
||||||
|
Assert.True(PermissionEntryHelper.IsExternalUser("ext_user_domain.com#EXT#@contoso.onmicrosoft.com"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsExternalUser_WithNormalLoginName_ReturnsFalse()
|
||||||
|
{
|
||||||
|
Assert.False(PermissionEntryHelper.IsExternalUser("i:0#.f|membership|alice@contoso.com"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── FilterPermissionLevels ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PermissionEntry_FiltersOutLimitedAccess_WhenOnlyPermissionIsLimitedAccess()
|
||||||
|
{
|
||||||
|
// A principal whose sole permission level is "Limited Access" should produce
|
||||||
|
// an empty list after filtering — used to decide whether to include the entry.
|
||||||
|
var result = PermissionEntryHelper.FilterPermissionLevels(new[] { "Limited Access" });
|
||||||
|
Assert.Empty(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FilterPermissionLevels_RetainsOtherLevels_WhenMixedWithLimitedAccess()
|
||||||
|
{
|
||||||
|
var result = PermissionEntryHelper.FilterPermissionLevels(new[] { "Limited Access", "Contribute" });
|
||||||
|
Assert.Equal(new[] { "Contribute" }, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── IsSharingLinksGroup ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsSharingLinksGroup_WithSharingLinksPrefix_ReturnsTrue()
|
||||||
|
{
|
||||||
|
Assert.True(PermissionEntryHelper.IsSharingLinksGroup("SharingLinks.abc123.Edit"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsSharingLinksGroup_WithLimitedAccessSystemGroup_ReturnsTrue()
|
||||||
|
{
|
||||||
|
Assert.True(PermissionEntryHelper.IsSharingLinksGroup("Limited Access System Group"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsSharingLinksGroup_WithNormalGroup_ReturnsFalse()
|
||||||
|
{
|
||||||
|
Assert.False(PermissionEntryHelper.IsSharingLinksGroup("Owners"));
|
||||||
|
}
|
||||||
|
}
|
||||||
31
SharepointToolbox.Tests/Services/PermissionsServiceTests.cs
Normal file
31
SharepointToolbox.Tests/Services/PermissionsServiceTests.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using SharepointToolbox.Core.Models;
|
||||||
|
using SharepointToolbox.Services;
|
||||||
|
|
||||||
|
namespace SharepointToolbox.Tests.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test stubs for PERM-01 and PERM-04.
|
||||||
|
/// These tests are skipped until IPermissionsService is implemented in Plan 02.
|
||||||
|
/// </summary>
|
||||||
|
public class PermissionsServiceTests
|
||||||
|
{
|
||||||
|
[Fact(Skip = "Requires live CSOM context — covered by Plan 02 implementation")]
|
||||||
|
public async Task ScanSiteAsync_ReturnsPermissionEntries_ForMockedSite()
|
||||||
|
{
|
||||||
|
// PERM-01: ScanSiteAsync returns a list of PermissionEntry records
|
||||||
|
// Arrange — requires a real or mocked ClientContext (CSOM)
|
||||||
|
// Act
|
||||||
|
// Assert
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact(Skip = "Requires live CSOM context — covered by Plan 02 implementation")]
|
||||||
|
public async Task ScanSiteAsync_WithIncludeInheritedFalse_SkipsItemsWithoutUniquePermissions()
|
||||||
|
{
|
||||||
|
// PERM-04: When IncludeInherited = false, items without unique permissions are excluded
|
||||||
|
// Arrange — requires a real or mocked ClientContext (CSOM)
|
||||||
|
// Act
|
||||||
|
// Assert
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using SharepointToolbox.Core.Models;
|
||||||
|
using SharepointToolbox.Services;
|
||||||
|
|
||||||
|
namespace SharepointToolbox.Tests.ViewModels;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test stubs for PERM-02 (multi-site scan loop).
|
||||||
|
/// Skipped until PermissionsViewModel is implemented in Plan 02.
|
||||||
|
/// </summary>
|
||||||
|
public class PermissionsViewModelTests
|
||||||
|
{
|
||||||
|
[Fact(Skip = "Requires live CSOM context — covered by Plan 02 implementation")]
|
||||||
|
public async Task StartScanAsync_WithMultipleSiteUrls_CallsServiceOncePerUrl()
|
||||||
|
{
|
||||||
|
// PERM-02: When the user supplies N site URLs, IPermissionsService.ScanSiteAsync
|
||||||
|
// is invoked exactly once per URL (sequential, not parallel).
|
||||||
|
// Arrange — requires PermissionsViewModel and a mock IPermissionsService
|
||||||
|
// Act
|
||||||
|
// Assert
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
SharepointToolbox/Core/Helpers/PermissionEntryHelper.cs
Normal file
30
SharepointToolbox/Core/Helpers/PermissionEntryHelper.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
namespace SharepointToolbox.Core.Helpers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pure static helpers for classifying SharePoint permission entries.
|
||||||
|
/// </summary>
|
||||||
|
public static class PermissionEntryHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true when the login name is a B2B guest (contains #EXT#).
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsExternalUser(string loginName) =>
|
||||||
|
loginName.Contains("#EXT#", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes "Limited Access" from the supplied permission levels.
|
||||||
|
/// Returns the remaining levels; returns an empty list when all are removed.
|
||||||
|
/// </summary>
|
||||||
|
public static IReadOnlyList<string> FilterPermissionLevels(IEnumerable<string> levels) =>
|
||||||
|
levels
|
||||||
|
.Where(l => !string.Equals(l, "Limited Access", StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true when the login name represents an internal sharing-link group
|
||||||
|
/// or the "Limited Access System Group" pseudo-principal.
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsSharingLinksGroup(string loginName) =>
|
||||||
|
loginName.StartsWith("SharingLinks.", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| loginName.Equals("Limited Access System Group", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user