- Add string? UserType as last positional parameter to GraphDirectoryUser record - Add bool includeGuests = false parameter to IGraphUserDirectoryService.GetUsersAsync - Branch Graph filter: members-only (default) vs all users when includeGuests=true - Add userType to Graph Select array for MapUser population - Update MapUser to include UserType from Graph User object - Add MapUser_PopulatesUserType and MapUser_NullUserType tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
193 lines
6.8 KiB
C#
193 lines
6.8 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",
|
|
UserType = "Member"
|
|
};
|
|
|
|
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);
|
|
Assert.Equal("Member", result.UserType);
|
|
}
|
|
|
|
[Fact]
|
|
public void MapUser_NullDisplayName_FallsBackToUserPrincipalName()
|
|
{
|
|
var user = new User
|
|
{
|
|
DisplayName = null,
|
|
UserPrincipalName = "bob@contoso.com",
|
|
Mail = null,
|
|
Department = null,
|
|
JobTitle = null,
|
|
UserType = "Guest"
|
|
};
|
|
|
|
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);
|
|
Assert.Equal("Guest", result.UserType);
|
|
}
|
|
|
|
[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);
|
|
}
|
|
|
|
// ── MapUser: UserType mapping ──────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void MapUser_PopulatesUserType()
|
|
{
|
|
var user = new User
|
|
{
|
|
DisplayName = "Eve Wilson",
|
|
UserPrincipalName = "eve@contoso.com",
|
|
Mail = "eve@contoso.com",
|
|
Department = "Sales",
|
|
JobTitle = "Account Executive",
|
|
UserType = "Member"
|
|
};
|
|
|
|
var result = GraphUserDirectoryService.MapUser(user);
|
|
|
|
Assert.Equal("Member", result.UserType);
|
|
}
|
|
|
|
[Fact]
|
|
public void MapUser_NullUserType_ReturnsNull()
|
|
{
|
|
var user = new User
|
|
{
|
|
DisplayName = "Frank Lee",
|
|
UserPrincipalName = "frank@contoso.com",
|
|
Mail = null,
|
|
Department = null,
|
|
JobTitle = null,
|
|
UserType = null
|
|
};
|
|
|
|
var result = GraphUserDirectoryService.MapUser(user);
|
|
|
|
Assert.Null(result.UserType);
|
|
}
|
|
|
|
// ── 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;
|
|
}
|