test(17-01): add failing tests for SharePointGroupResolver static helpers and empty-list contract
This commit is contained in:
167
SharepointToolbox.Tests/Services/SharePointGroupResolverTests.cs
Normal file
167
SharepointToolbox.Tests/Services/SharePointGroupResolverTests.cs
Normal file
@@ -0,0 +1,167 @@
|
||||
using SharepointToolbox.Services;
|
||||
|
||||
namespace SharepointToolbox.Tests.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for <see cref="SharePointGroupResolver"/> (Phase 17 Plan 01).
|
||||
///
|
||||
/// Testing strategy:
|
||||
/// SharePointGroupResolver wraps CSOM (ClientContext) and Microsoft Graph SDK.
|
||||
/// Both require live infrastructure that cannot be mocked without heavy ceremony.
|
||||
///
|
||||
/// We test what IS unit-testable without live infrastructure:
|
||||
/// 1. IsAadGroup — static helper: login prefix pattern detection
|
||||
/// 2. ExtractAadGroupId — static helper: GUID extraction from AAD group login
|
||||
/// 3. StripClaims — static helper: UPN extraction after last pipe
|
||||
/// 4. ResolveGroupsAsync with empty list — returns empty dict (no CSOM calls made)
|
||||
///
|
||||
/// Integration tests requiring live tenant / CSOM context are skip-marked.
|
||||
/// </summary>
|
||||
[Trait("Category", "Unit")]
|
||||
public class SharePointGroupResolverTests
|
||||
{
|
||||
// ── IsAadGroup ─────────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void IsAadGroup_AadGroupLogin_ReturnsTrue()
|
||||
{
|
||||
var login = "c:0t.c|tenant|aaaabbbb-cccc-dddd-eeee-ffffgggghhhh";
|
||||
Assert.True(SharePointGroupResolver.IsAadGroup(login));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAadGroup_RegularUserLogin_ReturnsFalse()
|
||||
{
|
||||
var login = "i:0#.f|membership|user@contoso.com";
|
||||
Assert.False(SharePointGroupResolver.IsAadGroup(login));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAadGroup_SecurityGroupLogin_ReturnsFalse()
|
||||
{
|
||||
var login = "c:0(.s|true";
|
||||
Assert.False(SharePointGroupResolver.IsAadGroup(login));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAadGroup_EmptyString_ReturnsFalse()
|
||||
{
|
||||
Assert.False(SharePointGroupResolver.IsAadGroup(string.Empty));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAadGroup_CaseInsensitive_ReturnsTrue()
|
||||
{
|
||||
// Prefix check should be case-insensitive per OrdinalIgnoreCase
|
||||
var login = "C:0T.C|TENANT|aaaabbbb-cccc-dddd-eeee-ffffgggghhhh";
|
||||
Assert.True(SharePointGroupResolver.IsAadGroup(login));
|
||||
}
|
||||
|
||||
// ── ExtractAadGroupId ──────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void ExtractAadGroupId_ValidAadLogin_ExtractsGuid()
|
||||
{
|
||||
var login = "c:0t.c|tenant|aaaabbbb-cccc-dddd-eeee-ffffgggghhhh";
|
||||
var result = SharePointGroupResolver.ExtractAadGroupId(login);
|
||||
Assert.Equal("aaaabbbb-cccc-dddd-eeee-ffffgggghhhh", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtractAadGroupId_SingleSegment_ReturnsFullString()
|
||||
{
|
||||
// Edge: no pipe — LastIndexOf returns -1 so [(-1+1)..] = [0..] = whole string
|
||||
var login = "nopipe";
|
||||
var result = SharePointGroupResolver.ExtractAadGroupId(login);
|
||||
Assert.Equal("nopipe", result);
|
||||
}
|
||||
|
||||
// ── StripClaims ────────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void StripClaims_MembershipLogin_ReturnsUpn()
|
||||
{
|
||||
var login = "i:0#.f|membership|user@contoso.com";
|
||||
var result = SharePointGroupResolver.StripClaims(login);
|
||||
Assert.Equal("user@contoso.com", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StripClaims_NoClaimsPrefix_ReturnsFullString()
|
||||
{
|
||||
var login = "user@contoso.com";
|
||||
var result = SharePointGroupResolver.StripClaims(login);
|
||||
Assert.Equal("user@contoso.com", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StripClaims_MultiPipeLogin_ReturnsAfterLastPipe()
|
||||
{
|
||||
var login = "c:0t.c|tenant|some-guid-here";
|
||||
var result = SharePointGroupResolver.StripClaims(login);
|
||||
Assert.Equal("some-guid-here", result);
|
||||
}
|
||||
|
||||
// ── ResolveGroupsAsync — empty input ──────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveGroupsAsync_EmptyGroupNames_ReturnsEmptyDict()
|
||||
{
|
||||
// Arrange: create resolver without real dependencies — empty list triggers early return
|
||||
// No CSOM ClientContext or GraphClientFactory is called for empty input
|
||||
// We pass null! for the factory since it must not be invoked for an empty list
|
||||
var resolver = new SharePointGroupResolver(null!);
|
||||
|
||||
// Act
|
||||
var result = await resolver.ResolveGroupsAsync(
|
||||
ctx: null!,
|
||||
clientId: "ignored",
|
||||
groupNames: Array.Empty<string>(),
|
||||
ct: CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveGroupsAsync_EmptyGroupNames_DictUsesOrdinalIgnoreCase()
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new SharePointGroupResolver(null!);
|
||||
|
||||
// Act
|
||||
var result = await resolver.ResolveGroupsAsync(
|
||||
ctx: null!,
|
||||
clientId: "ignored",
|
||||
groupNames: Array.Empty<string>(),
|
||||
ct: CancellationToken.None);
|
||||
|
||||
// Assert: verify the returned dict is OrdinalIgnoreCase by inserting a value
|
||||
// and looking it up with different casing — this validates the comparer used
|
||||
// at construction time even on an empty dict
|
||||
var mutable = new Dictionary<string, int>(result.Comparer)
|
||||
{
|
||||
["Site Members"] = 1
|
||||
};
|
||||
Assert.True(mutable.ContainsKey("site members"),
|
||||
"Result dictionary comparer must be OrdinalIgnoreCase");
|
||||
}
|
||||
|
||||
// ── Integration tests (live SP tenant required) ────────────────────────────
|
||||
|
||||
[Fact(Skip = "Requires live SP tenant — run manually against a real ClientContext")]
|
||||
public async Task ResolveGroupsAsync_KnownGroup_ReturnsMembers()
|
||||
{
|
||||
// Integration test: create a real ClientContext, call with a known group name,
|
||||
// verify the returned list contains at least one ResolvedMember.
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "Requires live SP tenant — verify case-insensitive lookup with real data")]
|
||||
public async Task ResolveGroupsAsync_LookupDifferentCasing_FindsGroup()
|
||||
{
|
||||
// Integration test: resolver stores "Site Members" — lookup "site members" should succeed.
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user