- GraphUserDirectoryService uses PageIterator<User, UserCollectionResponse> for pagination - Filter: accountEnabled eq true and userType eq 'Member' (no ConsistencyLevel header) - Cancellation checked in PageIterator callback (return false stops iteration) - Progress reported via IProgress<int> with running count per user - MapUser extracted as internal static for direct unit test coverage - Tests: 5 unit tests for MapUser field mapping and fallback logic - Integration-level tests (pagination/cancellation) skipped with rationale documented - Note: test project compilation blocked by pre-existing BrandingServiceTests.cs (10-01 artifact)
151 lines
5.6 KiB
C#
151 lines
5.6 KiB
C#
using Microsoft.Graph.Models;
|
|
using SharepointToolbox.Core.Models;
|
|
using SharepointToolbox.Services;
|
|
|
|
namespace SharepointToolbox.Tests.Services;
|
|
|
|
/// <summary>
|
|
/// Unit tests for <see cref="GraphUserDirectoryService"/> (Phase 10 Plan 02).
|
|
///
|
|
/// Testing strategy: GraphUserDirectoryService wraps Microsoft Graph SDK's PageIterator,
|
|
/// whose constructor is internal and cannot be mocked without a real GraphServiceClient.
|
|
/// Full pagination/cancellation tests therefore require integration-level setup.
|
|
///
|
|
/// We test what IS unit-testable:
|
|
/// 1. MapUser — the static mapping method that converts a Graph User to GraphDirectoryUser.
|
|
/// This covers all 5 required fields and the DisplayName fallback logic.
|
|
/// 2. GetUsersAsync integration paths are documented with Skip tests that explain the
|
|
/// constraint and serve as living documentation of intended behaviour.
|
|
/// </summary>
|
|
[Trait("Category", "Unit")]
|
|
public class GraphUserDirectoryServiceTests
|
|
{
|
|
// ── MapUser: field mapping ────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void MapUser_AllFieldsPresent_MapsCorrectly()
|
|
{
|
|
var user = new User
|
|
{
|
|
DisplayName = "Alice Smith",
|
|
UserPrincipalName = "alice@contoso.com",
|
|
Mail = "alice@contoso.com",
|
|
Department = "Engineering",
|
|
JobTitle = "Senior Developer"
|
|
};
|
|
|
|
var result = GraphUserDirectoryService.MapUser(user);
|
|
|
|
Assert.Equal("Alice Smith", result.DisplayName);
|
|
Assert.Equal("alice@contoso.com", result.UserPrincipalName);
|
|
Assert.Equal("alice@contoso.com", result.Mail);
|
|
Assert.Equal("Engineering", result.Department);
|
|
Assert.Equal("Senior Developer", result.JobTitle);
|
|
}
|
|
|
|
[Fact]
|
|
public void MapUser_NullDisplayName_FallsBackToUserPrincipalName()
|
|
{
|
|
var user = new User
|
|
{
|
|
DisplayName = null,
|
|
UserPrincipalName = "bob@contoso.com",
|
|
Mail = null,
|
|
Department = null,
|
|
JobTitle = null
|
|
};
|
|
|
|
var result = GraphUserDirectoryService.MapUser(user);
|
|
|
|
Assert.Equal("bob@contoso.com", result.DisplayName);
|
|
Assert.Equal("bob@contoso.com", result.UserPrincipalName);
|
|
Assert.Null(result.Mail);
|
|
Assert.Null(result.Department);
|
|
Assert.Null(result.JobTitle);
|
|
}
|
|
|
|
[Fact]
|
|
public void MapUser_NullDisplayNameAndNullUPN_FallsBackToEmptyString()
|
|
{
|
|
var user = new User
|
|
{
|
|
DisplayName = null,
|
|
UserPrincipalName = null,
|
|
Mail = null,
|
|
Department = null,
|
|
JobTitle = null
|
|
};
|
|
|
|
var result = GraphUserDirectoryService.MapUser(user);
|
|
|
|
Assert.Equal(string.Empty, result.DisplayName);
|
|
Assert.Equal(string.Empty, result.UserPrincipalName);
|
|
}
|
|
|
|
[Fact]
|
|
public void MapUser_NullUPN_ReturnsEmptyStringForUPN()
|
|
{
|
|
var user = new User
|
|
{
|
|
DisplayName = "Carol Jones",
|
|
UserPrincipalName = null,
|
|
Mail = "carol@contoso.com",
|
|
Department = "Marketing",
|
|
JobTitle = "Manager"
|
|
};
|
|
|
|
var result = GraphUserDirectoryService.MapUser(user);
|
|
|
|
Assert.Equal("Carol Jones", result.DisplayName);
|
|
Assert.Equal(string.Empty, result.UserPrincipalName);
|
|
Assert.Equal("carol@contoso.com", result.Mail);
|
|
}
|
|
|
|
[Fact]
|
|
public void MapUser_OptionalFieldsNull_ProducesNullableNullProperties()
|
|
{
|
|
var user = new User
|
|
{
|
|
DisplayName = "Dave Brown",
|
|
UserPrincipalName = "dave@contoso.com",
|
|
Mail = null,
|
|
Department = null,
|
|
JobTitle = null
|
|
};
|
|
|
|
var result = GraphUserDirectoryService.MapUser(user);
|
|
|
|
Assert.Null(result.Mail);
|
|
Assert.Null(result.Department);
|
|
Assert.Null(result.JobTitle);
|
|
}
|
|
|
|
// ── GetUsersAsync: integration-level scenarios (skipped without live tenant) ──
|
|
|
|
[Fact(Skip = "Requires integration test with real Graph client — PageIterator.CreatePageIterator " +
|
|
"uses internal GraphServiceClient request execution that cannot be mocked via Moq. " +
|
|
"Intended behaviour: returns all users matching filter across all pages, " +
|
|
"correctly mapping all 5 fields per user.")]
|
|
public Task GetUsersAsync_SinglePage_ReturnsMappedUsers()
|
|
=> Task.CompletedTask;
|
|
|
|
[Fact(Skip = "Requires integration test with real Graph client. " +
|
|
"Intended behaviour: IProgress<int>.Report is called once per user " +
|
|
"with an incrementing count (1, 2, 3, ...).")]
|
|
public Task GetUsersAsync_ReportsProgressWithIncrementingCount()
|
|
=> Task.CompletedTask;
|
|
|
|
[Fact(Skip = "Requires integration test with real Graph client. " +
|
|
"Intended behaviour: when CancellationToken is cancelled during iteration, " +
|
|
"the callback returns false and iteration stops, returning partial results " +
|
|
"(or OperationCanceledException if cancellation fires before first page).")]
|
|
public Task GetUsersAsync_CancelledToken_StopsIteration()
|
|
=> Task.CompletedTask;
|
|
|
|
[Fact(Skip = "Requires integration test with real Graph client. " +
|
|
"Intended behaviour: when Graph returns null response, " +
|
|
"GetUsersAsync returns an empty IReadOnlyList without throwing.")]
|
|
public Task GetUsersAsync_NullResponse_ReturnsEmptyList()
|
|
=> Task.CompletedTask;
|
|
}
|