Two plans for Phase 15: models + consolidator service (wave 1), unit tests + build verification (wave 2). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
12 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 15-consolidation-data-model | 02 | execute | 2 |
|
|
true |
|
|
Purpose: Prove that the consolidation logic handles all edge cases (empty input, single entry, merging, case-insensitivity, the 10-row/7-output scenario from requirements) and that adding the new files does not break existing code.
Output: One test file with 10 test methods covering all RPT-04 test requirements, plus a clean full-solution build.
<execution_context> @C:/Users/dev/.claude/get-shit-done/workflows/execute-plan.md @C:/Users/dev/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/15-consolidation-data-model/15-CONTEXT.md @.planning/phases/15-consolidation-data-model/15-RESEARCH.md @.planning/phases/15-consolidation-data-model/15-01-SUMMARY.md ```csharp // SharepointToolbox/Core/Models/LocationInfo.cs namespace SharepointToolbox.Core.Models; public record LocationInfo(string SiteUrl, string SiteTitle, string ObjectTitle, string ObjectUrl, string ObjectType);// SharepointToolbox/Core/Models/ConsolidatedPermissionEntry.cs namespace SharepointToolbox.Core.Models; public record ConsolidatedPermissionEntry( string UserDisplayName, string UserLogin, string PermissionLevel, AccessType AccessType, string GrantedThrough, bool IsHighPrivilege, bool IsExternalUser, IReadOnlyList Locations ) { public int LocationCount => Locations.Count; }
// SharepointToolbox/Core/Helpers/PermissionConsolidator.cs namespace SharepointToolbox.Core.Helpers; public static class PermissionConsolidator { internal static string MakeKey(UserAccessEntry entry); public static IReadOnlyList Consolidate(IReadOnlyList entries); }
// SharepointToolbox/Core/Models/UserAccessEntry.cs namespace SharepointToolbox.Core.Models; public enum AccessType { Direct, Group, Inherited } public record UserAccessEntry( string UserDisplayName, string UserLogin, string SiteUrl, string SiteTitle, string ObjectType, string ObjectTitle, string ObjectUrl, string PermissionLevel, AccessType AccessType, string GrantedThrough, bool IsHighPrivilege, bool IsExternalUser );
<!-- InternalsVisibleTo already configured in SharepointToolbox/AssemblyInfo.cs -->
<!-- Test conventions: xUnit, [Fact]/[Theory], snake_case or PascalCase_Condition_Expected naming -->
</interfaces>
</context>
<tasks>
<task type="auto" tdd="true">
<name>Task 1: Create PermissionConsolidatorTests with all 10 test cases</name>
<files>SharepointToolbox.Tests/Helpers/PermissionConsolidatorTests.cs</files>
<behavior>
- RPT-04-a: Empty input returns empty list
- RPT-04-b: Single entry produces 1 consolidated row with 1 location
- RPT-04-c: 3 entries with same key (same UserLogin+PermissionLevel+AccessType+GrantedThrough, different sites) produce 1 row with 3 locations
- RPT-04-d: Entries with different keys (different PermissionLevel) remain as separate rows
- RPT-04-e: Case-insensitive key — "ALICE@contoso.com" and "alice@contoso.com" merge into same group
- RPT-04-f: MakeKey produces pipe-delimited format "userlogin|permissionlevel|accesstype|grantedthrough" (all lowercase strings)
- RPT-04-g: 10-row input with 3 duplicate pairs produces exactly 7 consolidated rows
- RPT-04-h: LocationCount property equals Locations.Count for a merged entry
- RPT-04-i: IsHighPrivilege=true and IsExternalUser=true from first entry are preserved in consolidated result
- RPT-04-j: Full solution builds cleanly (verified by build command, not a test method)
</behavior>
<action>
Create `PermissionConsolidatorTests.cs` in `SharepointToolbox.Tests/Helpers/`.
Follow existing test conventions from `PermissionLevelMappingTests.cs`:
- Namespace: `SharepointToolbox.Tests.Helpers`
- Use `[Fact]` for each test
- PascalCase method names with descriptive names
Include a private helper factory method to reduce boilerplate:
```csharp
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);
}
Test implementations:
RPT-04-a Consolidate_EmptyInput_ReturnsEmptyList:
var result = PermissionConsolidator.Consolidate(Array.Empty<UserAccessEntry>());- Assert:
resultis empty.
RPT-04-b Consolidate_SingleEntry_ReturnsOneRowWithOneLocation:
- Create 1 entry via
MakeEntry(). - Assert: result count is 1, result[0].Locations.Count is 1, result[0].UserLogin matches.
RPT-04-c Consolidate_ThreeEntriesSameKey_ReturnsOneRowWithThreeLocations:
- Create 3 entries with same UserLogin/PermissionLevel/AccessType/GrantedThrough but different SiteUrl/SiteTitle.
- Assert: result count is 1, result[0].Locations.Count is 3.
RPT-04-d Consolidate_DifferentKeys_RemainSeparateRows:
- Create 2 entries with same UserLogin but different PermissionLevel ("Contribute" vs "Full Control").
- Assert: result count is 2.
RPT-04-e Consolidate_CaseInsensitiveKey_MergesCorrectly:
- Create 2 entries: one with UserLogin "ALICE@CONTOSO.COM" and one with "alice@contoso.com", same other key fields.
- Assert: result count is 1, result[0].Locations.Count is 2.
RPT-04-f MakeKey_ProducesPipeDelimitedLowercaseFormat:
- Create entry with UserLogin="Alice@Contoso.com", PermissionLevel="Full Control", AccessType=Direct, GrantedThrough="Direct Permissions".
- Call
PermissionConsolidator.MakeKey(entry). - Assert: result equals "alice@contoso.com|full control|Direct|direct permissions" (note: enum ToString() preserves case).
RPT-04-g Consolidate_TenRowsWithThreeDuplicatePairs_ReturnsSevenRows:
- Build exactly 10 entries:
- 3 entries for alice@contoso.com / Contribute / Direct / "Direct Permissions" (different sites) -> merges to 1
- 2 entries for bob@contoso.com / Full Control / Group / "SharePoint Group: Owners" (different sites) -> merges to 1
- 2 entries for carol@contoso.com / Read / Inherited / "Inherited Permissions" (different sites) -> merges to 1
- 1 entry for alice@contoso.com / Full Control / Direct / "Direct Permissions" (different key from alice's Contribute)
- 1 entry for dave@contoso.com / Contribute / Direct / "Direct Permissions"
- 1 entry for eve@contoso.com / Read / Direct / "Direct Permissions"
- Assert: result.Count is 7 (3 pairs merged to 1 each = 3, plus 4 unique = 7).
RPT-04-h Consolidate_MergedEntry_LocationCountMatchesLocationsCount:
- Create 3 entries with same key.
- Assert: result[0].LocationCount == result[0].Locations.Count && result[0].LocationCount == 3.
RPT-04-i Consolidate_PreservesIsHighPrivilegeAndIsExternalUser:
- Create 2 entries with same key, first has IsHighPrivilege=true, IsExternalUser=true.
- Assert: consolidated result has IsHighPrivilege=true and IsExternalUser=true.
Use using SharepointToolbox.Core.Helpers; and using SharepointToolbox.Core.Models;.
cd C:/Users/dev/Documents/projets/Sharepoint && dotnet test SharepointToolbox.Tests/SharepointToolbox.Tests.csproj --filter "FullyQualifiedName~PermissionConsolidatorTests" --no-restore -v n 2>&1 | tail -20
All 9 test methods pass (RPT-04-a through RPT-04-i). Tests cover empty input, single entry, merging, separate keys, case insensitivity, MakeKey format, the 10-row scenario, LocationCount, and preserved flags.
Run: dotnet build (full solution) and then dotnet test (all tests).
This satisfies success criterion 4: "Existing HTML export services compile and produce identical output when consolidation is not applied (opt-in, defaults off)." Since no existing files were modified and the new code is opt-in only, existing behavior is unchanged by definition.
If the build or any existing test fails, investigate and fix — the new files must not introduce any regressions. cd C:/Users/dev/Documents/projets/Sharepoint && dotnet build -v q 2>&1 | tail -5 && dotnet test --no-build -v n 2>&1 | tail -10 Full solution builds with 0 errors. All existing tests plus new PermissionConsolidatorTests pass. No existing files modified.
All tests pass including the new PermissionConsolidatorTests: ```bash cd C:/Users/dev/Documents/projets/Sharepoint && dotnet test -v n ```Specific verification of the 10-row scenario (success criterion 3):
cd C:/Users/dev/Documents/projets/Sharepoint && dotnet test --filter "FullyQualifiedName~TenRowsWithThreeDuplicatePairs" -v n
<success_criteria>
- PermissionConsolidatorTests.cs exists with 9 [Fact] test methods covering RPT-04-a through RPT-04-i
- All 9 tests pass
- The 10-row input test produces exactly 7 output rows
- Full solution build succeeds with 0 errors (RPT-04-j)
- All pre-existing tests continue to pass </success_criteria>