using Microsoft.Graph.Models; using SharepointToolbox.Core.Models; using SharepointToolbox.Services; namespace SharepointToolbox.Tests.Services; /// /// Unit tests for (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. /// [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.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; }