From 7b9f3e17aa1639557e4eabbe84770de90a1b4f23 Mon Sep 17 00:00:00 2001 From: Dev Date: Thu, 9 Apr 2026 11:45:22 +0200 Subject: [PATCH] test(15-02): add PermissionConsolidatorTests with 9 test cases (RPT-04-a through RPT-04-i) - RPT-04-a: empty input returns empty list - RPT-04-b: single entry -> 1 row with 1 location - RPT-04-c: 3 entries same key -> 1 row with 3 locations - RPT-04-d: different PermissionLevel -> separate rows - RPT-04-e: case-insensitive key merges ALICE@ and alice@ - RPT-04-f: MakeKey produces pipe-delimited lowercase format - RPT-04-g: 11-row input with 3 merge groups -> 7 consolidated rows - RPT-04-h: LocationCount equals Locations.Count - RPT-04-i: IsHighPrivilege/IsExternalUser preserved from first entry --- .../Helpers/PermissionConsolidatorTests.cs | 256 ++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 SharepointToolbox.Tests/Helpers/PermissionConsolidatorTests.cs diff --git a/SharepointToolbox.Tests/Helpers/PermissionConsolidatorTests.cs b/SharepointToolbox.Tests/Helpers/PermissionConsolidatorTests.cs new file mode 100644 index 0000000..8da120b --- /dev/null +++ b/SharepointToolbox.Tests/Helpers/PermissionConsolidatorTests.cs @@ -0,0 +1,256 @@ +using SharepointToolbox.Core.Helpers; +using SharepointToolbox.Core.Models; + +namespace SharepointToolbox.Tests.Helpers; + +/// +/// Unit tests for PermissionConsolidator static helper. +/// RPT-04: Validates consolidation logic for empty input, single entry, merging, +/// case-insensitivity, MakeKey format, the 10-row/7-row scenario, LocationCount, +/// and preservation of IsHighPrivilege / IsExternalUser flags. +/// +public class PermissionConsolidatorTests +{ + // --------------------------------------------------------------------------- + // Helper factory — reduces boilerplate across all test methods + // --------------------------------------------------------------------------- + + private static UserAccessEntry MakeEntry( + string userLogin = "alice@contoso.com", + string siteUrl = "https://contoso.sharepoint.com/sites/hr", + string siteTitle = "HR Site", + string objectType = "List", + string objectTitle = "Documents", + string objectUrl = "https://contoso.sharepoint.com/sites/hr/Documents", + string permissionLevel = "Contribute", + AccessType accessType = AccessType.Direct, + string grantedThrough = "Direct Permissions", + string userDisplayName = "Alice Smith", + bool isHighPrivilege = false, + bool isExternalUser = false) + { + return new UserAccessEntry( + userDisplayName, userLogin, siteUrl, siteTitle, + objectType, objectTitle, objectUrl, + permissionLevel, accessType, grantedThrough, + isHighPrivilege, isExternalUser); + } + + // --------------------------------------------------------------------------- + // RPT-04-a: Empty input returns empty list + // --------------------------------------------------------------------------- + + [Fact] + public void Consolidate_EmptyInput_ReturnsEmptyList() + { + var result = PermissionConsolidator.Consolidate(Array.Empty()); + + Assert.Empty(result); + } + + // --------------------------------------------------------------------------- + // RPT-04-b: Single entry produces 1 consolidated row with 1 location + // --------------------------------------------------------------------------- + + [Fact] + public void Consolidate_SingleEntry_ReturnsOneRowWithOneLocation() + { + var entry = MakeEntry(); + + var result = PermissionConsolidator.Consolidate(new[] { entry }); + + var row = Assert.Single(result); + Assert.Single(row.Locations); + Assert.Equal("alice@contoso.com", row.UserLogin); + } + + // --------------------------------------------------------------------------- + // RPT-04-c: 3 entries with same key (different sites) merge to 1 row with 3 locations + // --------------------------------------------------------------------------- + + [Fact] + public void Consolidate_ThreeEntriesSameKey_ReturnsOneRowWithThreeLocations() + { + var entries = new[] + { + MakeEntry(siteUrl: "https://contoso.sharepoint.com/sites/hr", siteTitle: "HR Site"), + MakeEntry(siteUrl: "https://contoso.sharepoint.com/sites/fin", siteTitle: "Finance Site"), + MakeEntry(siteUrl: "https://contoso.sharepoint.com/sites/mkt", siteTitle: "Marketing Site"), + }; + + var result = PermissionConsolidator.Consolidate(entries); + + Assert.Single(result); + Assert.Equal(3, result[0].Locations.Count); + } + + // --------------------------------------------------------------------------- + // RPT-04-d: Entries with different keys remain as separate rows + // --------------------------------------------------------------------------- + + [Fact] + public void Consolidate_DifferentKeys_RemainSeparateRows() + { + var entries = new[] + { + MakeEntry(permissionLevel: "Contribute"), + MakeEntry(permissionLevel: "Full Control"), + }; + + var result = PermissionConsolidator.Consolidate(entries); + + Assert.Equal(2, result.Count); + } + + // --------------------------------------------------------------------------- + // RPT-04-e: Case-insensitive key — "ALICE@CONTOSO.COM" and "alice@contoso.com" merge + // --------------------------------------------------------------------------- + + [Fact] + public void Consolidate_CaseInsensitiveKey_MergesCorrectly() + { + var entries = new[] + { + MakeEntry(userLogin: "ALICE@CONTOSO.COM", siteUrl: "https://contoso.sharepoint.com/sites/hr"), + MakeEntry(userLogin: "alice@contoso.com", siteUrl: "https://contoso.sharepoint.com/sites/fin"), + }; + + var result = PermissionConsolidator.Consolidate(entries); + + Assert.Single(result); + Assert.Equal(2, result[0].Locations.Count); + } + + // --------------------------------------------------------------------------- + // RPT-04-f: MakeKey produces pipe-delimited lowercase format + // --------------------------------------------------------------------------- + + [Fact] + public void MakeKey_ProducesPipeDelimitedLowercaseFormat() + { + var entry = MakeEntry( + userLogin: "Alice@Contoso.com", + permissionLevel: "Full Control", + accessType: AccessType.Direct, + grantedThrough: "Direct Permissions"); + + var key = PermissionConsolidator.MakeKey(entry); + + // AccessType.ToString() preserves casing ("Direct"); all string fields are lowercased + Assert.Equal("alice@contoso.com|full control|Direct|direct permissions", key); + } + + // --------------------------------------------------------------------------- + // RPT-04-g: 10-row input with 3 duplicate pairs produces 7 consolidated rows + // --------------------------------------------------------------------------- + + [Fact] + public void Consolidate_TenRowsWithThreeDuplicatePairs_ReturnsSevenRows() + { + var entries = new[] + { + // alice / Contribute / Direct — 3 entries -> merges to 1 + MakeEntry(userLogin: "alice@contoso.com", permissionLevel: "Contribute", + accessType: AccessType.Direct, grantedThrough: "Direct Permissions", + siteUrl: "https://contoso.sharepoint.com/sites/hr", siteTitle: "HR"), + MakeEntry(userLogin: "alice@contoso.com", permissionLevel: "Contribute", + accessType: AccessType.Direct, grantedThrough: "Direct Permissions", + siteUrl: "https://contoso.sharepoint.com/sites/fin", siteTitle: "Finance"), + MakeEntry(userLogin: "alice@contoso.com", permissionLevel: "Contribute", + accessType: AccessType.Direct, grantedThrough: "Direct Permissions", + siteUrl: "https://contoso.sharepoint.com/sites/mkt", siteTitle: "Marketing"), + + // bob / Full Control / Group — 2 entries -> merges to 1 + MakeEntry(userLogin: "bob@contoso.com", userDisplayName: "Bob Jones", + permissionLevel: "Full Control", accessType: AccessType.Group, + grantedThrough: "SharePoint Group: Owners", + siteUrl: "https://contoso.sharepoint.com/sites/hr", siteTitle: "HR"), + MakeEntry(userLogin: "bob@contoso.com", userDisplayName: "Bob Jones", + permissionLevel: "Full Control", accessType: AccessType.Group, + grantedThrough: "SharePoint Group: Owners", + siteUrl: "https://contoso.sharepoint.com/sites/fin", siteTitle: "Finance"), + + // carol / Read / Inherited — 2 entries -> merges to 1 + MakeEntry(userLogin: "carol@contoso.com", userDisplayName: "Carol White", + permissionLevel: "Read", accessType: AccessType.Inherited, + grantedThrough: "Inherited Permissions", + siteUrl: "https://contoso.sharepoint.com/sites/hr", siteTitle: "HR"), + MakeEntry(userLogin: "carol@contoso.com", userDisplayName: "Carol White", + permissionLevel: "Read", accessType: AccessType.Inherited, + grantedThrough: "Inherited Permissions", + siteUrl: "https://contoso.sharepoint.com/sites/fin", siteTitle: "Finance"), + + // alice / Full Control / Direct — different key from alice's Contribute -> unique row + MakeEntry(userLogin: "alice@contoso.com", permissionLevel: "Full Control", + accessType: AccessType.Direct, grantedThrough: "Direct Permissions", + siteUrl: "https://contoso.sharepoint.com/sites/hr", siteTitle: "HR"), + + // dave — unique + MakeEntry(userLogin: "dave@contoso.com", userDisplayName: "Dave Brown", + permissionLevel: "Contribute", accessType: AccessType.Direct, + grantedThrough: "Direct Permissions", + siteUrl: "https://contoso.sharepoint.com/sites/hr", siteTitle: "HR"), + + // eve — unique + MakeEntry(userLogin: "eve@contoso.com", userDisplayName: "Eve Green", + permissionLevel: "Read", accessType: AccessType.Direct, + grantedThrough: "Direct Permissions", + siteUrl: "https://contoso.sharepoint.com/sites/hr", siteTitle: "HR"), + + // frank — unique (4th unique row) + MakeEntry(userLogin: "frank@contoso.com", userDisplayName: "Frank Black", + permissionLevel: "Contribute", accessType: AccessType.Group, + grantedThrough: "SharePoint Group: Members", + siteUrl: "https://contoso.sharepoint.com/sites/hr", siteTitle: "HR"), + }; + + var result = PermissionConsolidator.Consolidate(entries); + + // 3 merged groups (alice-Contribute 3->1, bob 2->1, carol 2->1) + 4 unique rows + // (alice-FullControl, dave, eve, frank) = 7 total + Assert.Equal(7, result.Count); + } + + // --------------------------------------------------------------------------- + // RPT-04-h: LocationCount property equals Locations.Count for a merged entry + // --------------------------------------------------------------------------- + + [Fact] + public void Consolidate_MergedEntry_LocationCountMatchesLocationsCount() + { + var entries = new[] + { + MakeEntry(siteUrl: "https://contoso.sharepoint.com/sites/hr", siteTitle: "HR"), + MakeEntry(siteUrl: "https://contoso.sharepoint.com/sites/fin", siteTitle: "Finance"), + MakeEntry(siteUrl: "https://contoso.sharepoint.com/sites/mkt", siteTitle: "Marketing"), + }; + + var result = PermissionConsolidator.Consolidate(entries); + + Assert.Single(result); + Assert.Equal(result[0].Locations.Count, result[0].LocationCount); + Assert.Equal(3, result[0].LocationCount); + } + + // --------------------------------------------------------------------------- + // RPT-04-i: IsHighPrivilege and IsExternalUser from first entry are preserved + // --------------------------------------------------------------------------- + + [Fact] + public void Consolidate_PreservesIsHighPrivilegeAndIsExternalUser() + { + var entries = new[] + { + MakeEntry(isHighPrivilege: true, isExternalUser: true, + siteUrl: "https://contoso.sharepoint.com/sites/hr"), + MakeEntry(isHighPrivilege: false, isExternalUser: false, + siteUrl: "https://contoso.sharepoint.com/sites/fin"), + }; + + var result = PermissionConsolidator.Consolidate(entries); + + Assert.Single(result); + Assert.True(result[0].IsHighPrivilege); + Assert.True(result[0].IsExternalUser); + } +}