feat(13-01): extend GraphDirectoryUser with UserType and add includeGuests parameter to directory service
- 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>
This commit is contained in:
@@ -31,7 +31,8 @@ public class GraphUserDirectoryServiceTests
|
|||||||
UserPrincipalName = "alice@contoso.com",
|
UserPrincipalName = "alice@contoso.com",
|
||||||
Mail = "alice@contoso.com",
|
Mail = "alice@contoso.com",
|
||||||
Department = "Engineering",
|
Department = "Engineering",
|
||||||
JobTitle = "Senior Developer"
|
JobTitle = "Senior Developer",
|
||||||
|
UserType = "Member"
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = GraphUserDirectoryService.MapUser(user);
|
var result = GraphUserDirectoryService.MapUser(user);
|
||||||
@@ -41,6 +42,7 @@ public class GraphUserDirectoryServiceTests
|
|||||||
Assert.Equal("alice@contoso.com", result.Mail);
|
Assert.Equal("alice@contoso.com", result.Mail);
|
||||||
Assert.Equal("Engineering", result.Department);
|
Assert.Equal("Engineering", result.Department);
|
||||||
Assert.Equal("Senior Developer", result.JobTitle);
|
Assert.Equal("Senior Developer", result.JobTitle);
|
||||||
|
Assert.Equal("Member", result.UserType);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -52,7 +54,8 @@ public class GraphUserDirectoryServiceTests
|
|||||||
UserPrincipalName = "bob@contoso.com",
|
UserPrincipalName = "bob@contoso.com",
|
||||||
Mail = null,
|
Mail = null,
|
||||||
Department = null,
|
Department = null,
|
||||||
JobTitle = null
|
JobTitle = null,
|
||||||
|
UserType = "Guest"
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = GraphUserDirectoryService.MapUser(user);
|
var result = GraphUserDirectoryService.MapUser(user);
|
||||||
@@ -62,6 +65,7 @@ public class GraphUserDirectoryServiceTests
|
|||||||
Assert.Null(result.Mail);
|
Assert.Null(result.Mail);
|
||||||
Assert.Null(result.Department);
|
Assert.Null(result.Department);
|
||||||
Assert.Null(result.JobTitle);
|
Assert.Null(result.JobTitle);
|
||||||
|
Assert.Equal("Guest", result.UserType);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -120,6 +124,44 @@ public class GraphUserDirectoryServiceTests
|
|||||||
Assert.Null(result.JobTitle);
|
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) ──
|
// ── GetUsersAsync: integration-level scenarios (skipped without live tenant) ──
|
||||||
|
|
||||||
[Fact(Skip = "Requires integration test with real Graph client — PageIterator.CreatePageIterator " +
|
[Fact(Skip = "Requires integration test with real Graph client — PageIterator.CreatePageIterator " +
|
||||||
|
|||||||
@@ -9,4 +9,5 @@ public record GraphDirectoryUser(
|
|||||||
string UserPrincipalName,
|
string UserPrincipalName,
|
||||||
string? Mail,
|
string? Mail,
|
||||||
string? Department,
|
string? Department,
|
||||||
string? JobTitle);
|
string? JobTitle,
|
||||||
|
string? UserType);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ public class GraphUserDirectoryService : IGraphUserDirectoryService
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<IReadOnlyList<GraphDirectoryUser>> GetUsersAsync(
|
public async Task<IReadOnlyList<GraphDirectoryUser>> GetUsersAsync(
|
||||||
string clientId,
|
string clientId,
|
||||||
|
bool includeGuests = false,
|
||||||
IProgress<int>? progress = null,
|
IProgress<int>? progress = null,
|
||||||
CancellationToken ct = default)
|
CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
@@ -29,11 +30,12 @@ public class GraphUserDirectoryService : IGraphUserDirectoryService
|
|||||||
|
|
||||||
var response = await graphClient.Users.GetAsync(config =>
|
var response = await graphClient.Users.GetAsync(config =>
|
||||||
{
|
{
|
||||||
// Pending real-tenant verification — see STATE.md pending todos
|
config.QueryParameters.Filter = includeGuests
|
||||||
config.QueryParameters.Filter = "accountEnabled eq true and userType eq 'Member'";
|
? "accountEnabled eq true"
|
||||||
|
: "accountEnabled eq true and userType eq 'Member'";
|
||||||
config.QueryParameters.Select = new[]
|
config.QueryParameters.Select = new[]
|
||||||
{
|
{
|
||||||
"displayName", "userPrincipalName", "mail", "department", "jobTitle"
|
"displayName", "userPrincipalName", "mail", "department", "jobTitle", "userType"
|
||||||
};
|
};
|
||||||
config.QueryParameters.Top = 999;
|
config.QueryParameters.Top = 999;
|
||||||
// No ConsistencyLevel header: standard equality filter does not require eventual consistency
|
// No ConsistencyLevel header: standard equality filter does not require eventual consistency
|
||||||
@@ -74,5 +76,6 @@ public class GraphUserDirectoryService : IGraphUserDirectoryService
|
|||||||
UserPrincipalName: user.UserPrincipalName ?? string.Empty,
|
UserPrincipalName: user.UserPrincipalName ?? string.Empty,
|
||||||
Mail: user.Mail,
|
Mail: user.Mail,
|
||||||
Department: user.Department,
|
Department: user.Department,
|
||||||
JobTitle: user.JobTitle);
|
JobTitle: user.JobTitle,
|
||||||
|
UserType: user.UserType);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ public interface IGraphUserDirectoryService
|
|||||||
/// Iterates through all pages using the Graph SDK PageIterator until exhausted or cancelled.
|
/// Iterates through all pages using the Graph SDK PageIterator until exhausted or cancelled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="clientId">The client/tenant identifier used to obtain a Graph token.</param>
|
/// <param name="clientId">The client/tenant identifier used to obtain a Graph token.</param>
|
||||||
|
/// <param name="includeGuests">
|
||||||
|
/// When <c>false</c> (default), only member users are returned (userType eq 'Member').
|
||||||
|
/// When <c>true</c>, both members and guests are returned (no userType filter).
|
||||||
|
/// </param>
|
||||||
/// <param name="progress">
|
/// <param name="progress">
|
||||||
/// Optional progress reporter — receives the running count of users fetched so far.
|
/// Optional progress reporter — receives the running count of users fetched so far.
|
||||||
/// Phase 13's ViewModel uses this to show "Loading... X users" feedback.
|
/// Phase 13's ViewModel uses this to show "Loading... X users" feedback.
|
||||||
@@ -21,6 +25,7 @@ public interface IGraphUserDirectoryService
|
|||||||
/// <param name="ct">Cancellation token. Iteration stops when cancelled.</param>
|
/// <param name="ct">Cancellation token. Iteration stops when cancelled.</param>
|
||||||
Task<IReadOnlyList<GraphDirectoryUser>> GetUsersAsync(
|
Task<IReadOnlyList<GraphDirectoryUser>> GetUsersAsync(
|
||||||
string clientId,
|
string clientId,
|
||||||
|
bool includeGuests = false,
|
||||||
IProgress<int>? progress = null,
|
IProgress<int>? progress = null,
|
||||||
CancellationToken ct = default);
|
CancellationToken ct = default);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user