Skip to content

Commit db8d01c

Browse files
committed
fix(taskitems): resolve assignee display names in all query handlers and cover with integration/unit tests
1 parent 66d5223 commit db8d01c

10 files changed

Lines changed: 102 additions & 6 deletions

File tree

src/TaskManagement.Api/Features/TaskItems/Queries/Handlers/GetTaskItemQueryHandler.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,18 @@ public class GetTaskItemQueryHandler : IRequestHandler<GetTaskItemQuery, TaskIte
1414
{
1515
private readonly TaskManagementDbContext _dbContext;
1616
private readonly ICurrentUserService _currentUserService;
17+
private readonly IUserDirectoryService _userDirectoryService;
1718
private readonly IMapper _mapper;
1819

1920
public GetTaskItemQueryHandler(
2021
TaskManagementDbContext dbContext,
2122
ICurrentUserService currentUserService,
23+
IUserDirectoryService userDirectoryService,
2224
IMapper mapper)
2325
{
2426
_dbContext = dbContext;
2527
_currentUserService = currentUserService;
28+
_userDirectoryService = userDirectoryService;
2629
_mapper = mapper;
2730
}
2831

@@ -50,6 +53,7 @@ public async Task<TaskItemDto> Handle(GetTaskItemQuery request, CancellationToke
5053
throw new NotFoundException($"TaskItem with ID {request.Id} not found or access denied.");
5154
}
5255

56+
await TaskAssigneeDisplayNameResolver.ApplyAsync([taskItemDto], _userDirectoryService, cancellationToken);
5357
return taskItemDto;
5458
}
5559
}

src/TaskManagement.Api/Features/TaskItems/Queries/Handlers/GetTasksForProjectQueryHandler.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,18 @@ public class GetTasksForProjectQueryHandler : IRequestHandler<GetTasksForProject
1414
{
1515
private readonly TaskManagementDbContext _dbContext;
1616
private readonly ICurrentUserService _currentUserService;
17+
private readonly IUserDirectoryService _userDirectoryService;
1718
private readonly IMapper _mapper;
1819

1920
public GetTasksForProjectQueryHandler(
2021
TaskManagementDbContext dbContext,
2122
ICurrentUserService currentUserService,
23+
IUserDirectoryService userDirectoryService,
2224
IMapper mapper)
2325
{
2426
_dbContext = dbContext;
2527
_currentUserService = currentUserService;
28+
_userDirectoryService = userDirectoryService;
2629
_mapper = mapper;
2730
}
2831

@@ -53,6 +56,7 @@ public async Task<IReadOnlyList<TaskItemDto>> Handle(GetTasksForProjectQuery req
5356
.ProjectTo<TaskItemDto>(_mapper.ConfigurationProvider)
5457
.ToListAsync(cancellationToken);
5558

59+
await TaskAssigneeDisplayNameResolver.ApplyAsync(taskDtos, _userDirectoryService, cancellationToken);
5660
return taskDtos;
5761
}
5862
}

src/TaskManagement.Api/Features/TaskItems/Queries/Handlers/GetTasksForUserQueryHandler.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,18 @@ public class GetTasksForUserQueryHandler : IRequestHandler<GetTasksForUserQuery,
1212
{
1313
private readonly TaskManagementDbContext _dbContext;
1414
private readonly ICurrentUserService _currentUserService;
15+
private readonly IUserDirectoryService _userDirectoryService;
1516
private readonly IMapper _mapper;
1617

1718
public GetTasksForUserQueryHandler(
1819
TaskManagementDbContext dbContext,
1920
ICurrentUserService currentUserService,
21+
IUserDirectoryService userDirectoryService,
2022
IMapper mapper)
2123
{
2224
_dbContext = dbContext;
2325
_currentUserService = currentUserService;
26+
_userDirectoryService = userDirectoryService;
2427
_mapper = mapper;
2528
}
2629

@@ -39,6 +42,7 @@ public async Task<IReadOnlyList<TaskItemDto>> Handle(GetTasksForUserQuery reques
3942
.ProjectTo<TaskItemDto>(_mapper.ConfigurationProvider)
4043
.ToListAsync(cancellationToken);
4144

45+
await TaskAssigneeDisplayNameResolver.ApplyAsync(taskDtos, _userDirectoryService, cancellationToken);
4246
return taskDtos;
4347
}
4448
}

src/TaskManagement.Api/Features/TaskItems/Queries/Handlers/GetTasksQueryHandler.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,18 @@ public class GetTasksQueryHandler : IRequestHandler<GetTasksQuery, IReadOnlyList
1515
private const int MaxPageSize = 500;
1616
private readonly TaskManagementDbContext _dbContext;
1717
private readonly ICurrentUserService _currentUserService;
18+
private readonly IUserDirectoryService _userDirectoryService;
1819
private readonly IMapper _mapper;
1920

2021
public GetTasksQueryHandler(
2122
TaskManagementDbContext dbContext,
2223
ICurrentUserService currentUserService,
24+
IUserDirectoryService userDirectoryService,
2325
IMapper mapper)
2426
{
2527
_dbContext = dbContext;
2628
_currentUserService = currentUserService;
29+
_userDirectoryService = userDirectoryService;
2730
_mapper = mapper;
2831
}
2932

@@ -111,13 +114,16 @@ public async Task<IReadOnlyList<TaskItemDto>> Handle(GetTasksQuery request, Canc
111114

112115
var skip = (page - 1) * pageSize;
113116

114-
return await query
117+
var taskDtos = await query
115118
.OrderByDescending(t => t.LastModifiedAt)
116119
.ThenBy(t => t.Title)
117120
.Skip(skip)
118121
.Take(pageSize)
119122
.ProjectTo<TaskItemDto>(_mapper.ConfigurationProvider)
120123
.ToListAsync(cancellationToken);
124+
125+
await TaskAssigneeDisplayNameResolver.ApplyAsync(taskDtos, _userDirectoryService, cancellationToken);
126+
return taskDtos;
121127
}
122128
}
123129
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using TaskManagement.Api.Features.TaskItems.Models.DTOs;
2+
using TaskManagement.Api.Features.Users.Services.Interfaces;
3+
4+
namespace TaskManagement.Api.Features.TaskItems.Queries.Handlers
5+
{
6+
internal static class TaskAssigneeDisplayNameResolver
7+
{
8+
public static async Task ApplyAsync(IReadOnlyCollection<TaskItemDto> taskItems, IUserDirectoryService userDirectoryService, CancellationToken cancellationToken)
9+
{
10+
var assignedUserIds = taskItems
11+
.Select(task => task.AssignedUserId)
12+
.Where(userId => !string.IsNullOrWhiteSpace(userId))
13+
.Select(userId => userId!.Trim())
14+
.Distinct(StringComparer.Ordinal)
15+
.ToList();
16+
17+
var displayNamesByUserId = new Dictionary<string, string>(StringComparer.Ordinal);
18+
foreach (var userId in assignedUserIds)
19+
{
20+
var displayName = await userDirectoryService.GetDisplayNameAsync(userId, cancellationToken);
21+
displayNamesByUserId[userId] = string.IsNullOrWhiteSpace(displayName) ? userId : displayName;
22+
}
23+
24+
foreach (var task in taskItems)
25+
{
26+
var assignedUserId = task.AssignedUserId?.Trim();
27+
if (string.IsNullOrWhiteSpace(assignedUserId))
28+
{
29+
task.AssignedUserName = "Unassigned";
30+
continue;
31+
}
32+
33+
task.AssignedUserName = displayNamesByUserId.TryGetValue(assignedUserId, out var displayName)
34+
? displayName
35+
: assignedUserId;
36+
}
37+
}
38+
}
39+
}

tests/TaskManagement.Api.Tests/IntegrationTests/Features/TaskItems/GetTaskItemEndpointTests.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,23 @@ public async Task GetTasksForProject_WhenUserIsOwner_ShouldReturnTasksList()
256256
tasks.Should().Contain(t => t.Id == _task2InProject1Id);
257257
}
258258

259+
[Fact]
260+
public async Task GetTasks_WhenTaskHasAssignee_ShouldReturnResolvedAssignedUserName()
261+
{
262+
// Arrange
263+
SetAuthenticatedUser(_projectOwnerId);
264+
265+
// Act
266+
var response = await _client.GetAsync($"/api/taskitems?projectId={_project1Id}&page=1&pageSize=50");
267+
268+
// Assert
269+
response.StatusCode.Should().Be(HttpStatusCode.OK);
270+
var tasks = await response.Content.ReadFromJsonAsync<List<TaskItemDto>>();
271+
tasks.Should().NotBeNull();
272+
tasks.Should().Contain(t => t.Id == _task1InProject1Id && t.AssignedUserName == $"Test User {_taskAssigneeInProject1Id}");
273+
tasks.Should().Contain(t => t.Id == _task2InProject1Id && t.AssignedUserName == $"Test User {_projectOwnerId}");
274+
}
275+
259276
[Fact]
260277
public async Task GetTasksForProject_WhenUserIsMember_ShouldReturnTasksList()
261278
{

tests/TaskManagement.Api.Tests/UnitTests/Features/TaskItems/Queries/GetTaskItemQueryHandlerTests.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public class GetTaskItemQueryHandlerTests : IDisposable
2222
private readonly TaskManagementDbContext _dbContext;
2323
private readonly IMapper _mapper;
2424
private readonly Mock<ICurrentUserService> _mockCurrentUser;
25+
private readonly Mock<IUserDirectoryService> _mockUserDirectoryService;
2526
private readonly GetTaskItemQueryHandler _handler;
2627

2728
private readonly Guid _existingTaskId = Guid.NewGuid();
@@ -42,10 +43,14 @@ public GetTaskItemQueryHandlerTests()
4243
_mapper = mappingConfig.CreateMapper();
4344

4445
_mockCurrentUser = new Mock<ICurrentUserService>();
46+
_mockUserDirectoryService = new Mock<IUserDirectoryService>();
47+
_mockUserDirectoryService
48+
.Setup(service => service.GetDisplayNameAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
49+
.ReturnsAsync((string userId, CancellationToken _) => $"Test User {userId}");
4550

4651
SeedDatabase();
4752

48-
_handler = new GetTaskItemQueryHandler(_dbContext, _mockCurrentUser.Object, _mapper);
53+
_handler = new GetTaskItemQueryHandler(_dbContext, _mockCurrentUser.Object, _mockUserDirectoryService.Object, _mapper);
4954
}
5055

5156
private void SeedDatabase()
@@ -97,6 +102,7 @@ public async Task Handle_ShouldReturnTaskItemDto_WhenTaskExistsAndUserIsProjectO
97102
.Excluding(dto => dto.CreatedAt)
98103
.Excluding(dto => dto.LastModifiedAt)
99104
.Excluding(dto => dto.ProjectName)
105+
.Excluding(dto => dto.AssignedUserName)
100106
.Excluding(dto => dto.CreatedByUserId)
101107
.Excluding(dto => dto.LastModifiedByUserId));
102108
_mockCurrentUser.Verify(u => u.Id, Times.Once);
@@ -119,6 +125,7 @@ public async Task Handle_ShouldReturnTaskItemDto_WhenTaskExistsAndUserIsProjectM
119125
.Excluding(dto => dto.CreatedAt)
120126
.Excluding(dto => dto.LastModifiedAt)
121127
.Excluding(dto => dto.ProjectName)
128+
.Excluding(dto => dto.AssignedUserName)
122129
.Excluding(dto => dto.CreatedByUserId)
123130
.Excluding(dto => dto.LastModifiedByUserId));
124131
_mockCurrentUser.Verify(u => u.Id, Times.Once);

tests/TaskManagement.Api.Tests/UnitTests/Features/TaskItems/Queries/GetTasksForProjectQueryHandlerTests.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public class GetTasksForProjectQueryHandlerTests : IDisposable
2020
private readonly TaskManagementDbContext _dbContext;
2121
private readonly IMapper _mapper;
2222
private readonly Mock<ICurrentUserService> _mockCurrentUser;
23+
private readonly Mock<IUserDirectoryService> _mockUserDirectoryService;
2324
private readonly GetTasksForProjectQueryHandler _handler;
2425

2526
private readonly Guid _projectIdWithTasks = Guid.NewGuid();
@@ -43,10 +44,14 @@ public GetTasksForProjectQueryHandlerTests()
4344
_mapper = mappingConfig.CreateMapper();
4445

4546
_mockCurrentUser = new Mock<ICurrentUserService>();
47+
_mockUserDirectoryService = new Mock<IUserDirectoryService>();
48+
_mockUserDirectoryService
49+
.Setup(service => service.GetDisplayNameAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
50+
.ReturnsAsync((string userId, CancellationToken _) => $"Test User {userId}");
4651

4752
SeedDatabase();
4853

49-
_handler = new GetTasksForProjectQueryHandler(_dbContext, _mockCurrentUser.Object, _mapper);
54+
_handler = new GetTasksForProjectQueryHandler(_dbContext, _mockCurrentUser.Object, _mockUserDirectoryService.Object, _mapper);
5055
}
5156

5257
private void SeedDatabase()

tests/TaskManagement.Api.Tests/UnitTests/Features/TaskItems/Queries/GetTasksForUserQueryHandlerTests.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public class GetTasksForUserQueryHandlerTests : IDisposable
1717
private readonly TaskManagementDbContext _dbContext;
1818
private readonly IMapper _mapper;
1919
private readonly Mock<ICurrentUserService> _mockCurrentUser;
20+
private readonly Mock<IUserDirectoryService> _mockUserDirectoryService;
2021
private readonly GetTasksForUserQueryHandler _handler;
2122

2223
private readonly string _testUserId1 = "user-assignee-1";
@@ -38,10 +39,14 @@ public GetTasksForUserQueryHandlerTests()
3839
_mapper = mappingConfig.CreateMapper();
3940

4041
_mockCurrentUser = new Mock<ICurrentUserService>();
42+
_mockUserDirectoryService = new Mock<IUserDirectoryService>();
43+
_mockUserDirectoryService
44+
.Setup(service => service.GetDisplayNameAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
45+
.ReturnsAsync((string userId, CancellationToken _) => $"Test User {userId}");
4146

4247
SeedDatabase();
4348

44-
_handler = new GetTasksForUserQueryHandler(_dbContext, _mockCurrentUser.Object, _mapper);
49+
_handler = new GetTasksForUserQueryHandler(_dbContext, _mockCurrentUser.Object, _mockUserDirectoryService.Object, _mapper);
4550
}
4651

4752
private void SeedDatabase()
@@ -114,4 +119,4 @@ public void Dispose()
114119
GC.SuppressFinalize(this);
115120
}
116121
}
117-
}
122+
}

tests/TaskManagement.Api.Tests/UnitTests/Features/TaskItems/Queries/GetTasksQueryHandlerTests.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public class GetTasksQueryHandlerTests : IDisposable
2020
{
2121
private readonly TaskManagementDbContext _dbContext;
2222
private readonly Mock<ICurrentUserService> _mockCurrentUser;
23+
private readonly Mock<IUserDirectoryService> _mockUserDirectoryService;
2324
private readonly GetTasksQueryHandler _handler;
2425

2526
private readonly Guid _projectId = Guid.NewGuid();
@@ -35,11 +36,15 @@ public GetTasksQueryHandlerTests()
3536
.Options;
3637

3738
_mockCurrentUser = new Mock<ICurrentUserService>();
39+
_mockUserDirectoryService = new Mock<IUserDirectoryService>();
40+
_mockUserDirectoryService
41+
.Setup(service => service.GetDisplayNameAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
42+
.ReturnsAsync((string userId, CancellationToken _) => $"Test User {userId}");
3843
_dbContext = new TaskManagementDbContext(options, _mockCurrentUser.Object);
3944
SeedDatabase();
4045

4146
var mapper = new MapperConfiguration(cfg => cfg.AddProfile<TaskItemMappingProfile>()).CreateMapper();
42-
_handler = new GetTasksQueryHandler(_dbContext, _mockCurrentUser.Object, mapper);
47+
_handler = new GetTasksQueryHandler(_dbContext, _mockCurrentUser.Object, _mockUserDirectoryService.Object, mapper);
4348
}
4449

4550
private void SeedDatabase()

0 commit comments

Comments
 (0)