diff --git a/src/Web.Api.Tests/Features/Users/AdminUsersControllerTests.cs b/src/Web.Api.Tests/Features/Users/AdminUsersControllerTests.cs new file mode 100644 index 00000000..b4d93cff --- /dev/null +++ b/src/Web.Api.Tests/Features/Users/AdminUsersControllerTests.cs @@ -0,0 +1,132 @@ +using System.Linq; +using System.Threading.Tasks; +using Domain.Enums; +using TestUtils.Auth; +using TestUtils.Db; +using TestUtils.Fakes; +using Web.Api.Features.Users; +using Web.Api.Features.Users.SearchUsersForAdmin; +using Xunit; + +namespace Web.Api.Tests.Features.Users; + +public class AdminUsersControllerTests +{ + [Fact] + public async Task All_WithEmailFilter_ReturnsMatchingUsers() + { + await using var context = new InMemoryDatabaseContext(); + var admin = await new UserFake(Role.Admin).PleaseAsync(context); + var user1 = await new UserFake(Role.Interviewer, userName: "john.doe@example.com").PleaseAsync(context); + var user2 = await new UserFake(Role.Interviewer, userName: "jane.smith@example.com").PleaseAsync(context); + + var controller = new AdminUsersController( + new FakeAuth(admin), + context); + + var queryParams = new SearchUsersForAdminQueryParams + { + Email = "john" + }; + + context.ChangeTracker.Clear(); + var result = await controller.All(queryParams); + + Assert.Single(result.Results); + Assert.Equal(user1.Id, result.Results.First().Id); + } + + [Fact] + public async Task All_WithUnsubscribeFilter_ReturnsMatchingUsers() + { + await using var context = new InMemoryDatabaseContext(); + var admin = await new UserFake(Role.Admin).PleaseAsync(context); + var user1 = await new UserFake(Role.Interviewer).WithUnsubscribeMeFromAll(true).PleaseAsync(context); + var user2 = await new UserFake(Role.Interviewer).WithUnsubscribeMeFromAll(false).PleaseAsync(context); + + var controller = new AdminUsersController( + new FakeAuth(admin), + context); + + var queryParams = new SearchUsersForAdminQueryParams + { + UnsubscribeMeFromAll = true + }; + + context.ChangeTracker.Clear(); + var result = await controller.All(queryParams); + + Assert.Single(result.Results); + Assert.Equal(user1.Id, result.Results.First().Id); + } + + [Fact] + public async Task All_WithBothFilters_ReturnsMatchingUsers() + { + await using var context = new InMemoryDatabaseContext(); + var admin = await new UserFake(Role.Admin).PleaseAsync(context); + var user1 = await new UserFake(Role.Interviewer, userName: "john.doe@example.com").WithUnsubscribeMeFromAll(true).PleaseAsync(context); + var user2 = await new UserFake(Role.Interviewer, userName: "john.smith@example.com").WithUnsubscribeMeFromAll(false).PleaseAsync(context); + var user3 = await new UserFake(Role.Interviewer, userName: "jane.doe@example.com").WithUnsubscribeMeFromAll(true).PleaseAsync(context); + + var controller = new AdminUsersController( + new FakeAuth(admin), + context); + + var queryParams = new SearchUsersForAdminQueryParams + { + Email = "john", + UnsubscribeMeFromAll = true + }; + + context.ChangeTracker.Clear(); + var result = await controller.All(queryParams); + + Assert.Single(result.Results); + Assert.Equal(user1.Id, result.Results.First().Id); + } + + [Fact] + public async Task All_WithNoFilters_ReturnsAllActiveUsers() + { + await using var context = new InMemoryDatabaseContext(); + var admin = await new UserFake(Role.Admin).PleaseAsync(context); + var user1 = await new UserFake(Role.Interviewer).PleaseAsync(context); + var user2 = await new UserFake(Role.Interviewer).PleaseAsync(context); + + var controller = new AdminUsersController( + new FakeAuth(admin), + context); + + var queryParams = new SearchUsersForAdminQueryParams(); + + context.ChangeTracker.Clear(); + var result = await controller.All(queryParams); + + // Should return all 3 users (admin + 2 test users) + Assert.Equal(3, result.Results.Count); + } + + [Fact] + public async Task All_WithEmptyEmailFilter_ReturnsAllActiveUsers() + { + await using var context = new InMemoryDatabaseContext(); + var admin = await new UserFake(Role.Admin).PleaseAsync(context); + var user1 = await new UserFake(Role.Interviewer).PleaseAsync(context); + + var controller = new AdminUsersController( + new FakeAuth(admin), + context); + + var queryParams = new SearchUsersForAdminQueryParams + { + Email = string.Empty // Empty string should not filter + }; + + context.ChangeTracker.Clear(); + var result = await controller.All(queryParams); + + // Should return all users since empty email filter is ignored + Assert.Equal(2, result.Results.Count); + } +} \ No newline at end of file diff --git a/src/Web.Api/Features/Users/AdminUsersController.cs b/src/Web.Api/Features/Users/AdminUsersController.cs index 67648a93..630740b9 100644 --- a/src/Web.Api/Features/Users/AdminUsersController.cs +++ b/src/Web.Api/Features/Users/AdminUsersController.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Web.Api.Features.Users.Models; +using Web.Api.Features.Users.SearchUsersForAdmin; using Web.Api.Setup.Attributes; namespace Web.Api.Features.Users; @@ -35,19 +36,23 @@ public AdminUsersController( [HttpGet("")] public async Task> All( - [FromQuery] PageModel pageParams = null) + [FromQuery] SearchUsersForAdminQueryParams queryParams = null) { await _auth.HasRoleOrFailAsync(Role.Admin); - pageParams ??= PageModel.Default; + queryParams ??= new SearchUsersForAdminQueryParams(); + + var emailFilter = queryParams.Email?.Trim().ToLowerInvariant(); return await _context.Users .AsNoTracking() .Include(x => x.UserRoles) .Include(x => x.Salaries) .Where(x => x.DeletedAt == null) + .When(queryParams.HasEmailFilter(), x => x.Email != null && x.Email.ToLower().Contains(emailFilter)) + .When(queryParams.HasUnsubscribeFilter(), x => x.UnsubscribeMeFromAll == queryParams.UnsubscribeMeFromAll.Value) .OrderBy(x => x.CreatedAt) .Select(UserDto.Transformation) - .AsPaginatedAsync(pageParams); + .AsPaginatedAsync(queryParams); } [HttpGet("{id:long}")] diff --git a/src/Web.Api/Features/Users/SearchUsersForAdmin/SearchUsersForAdminQueryParams.cs b/src/Web.Api/Features/Users/SearchUsersForAdmin/SearchUsersForAdminQueryParams.cs new file mode 100644 index 00000000..c1109279 --- /dev/null +++ b/src/Web.Api/Features/Users/SearchUsersForAdmin/SearchUsersForAdminQueryParams.cs @@ -0,0 +1,16 @@ +using Domain.ValueObjects.Pagination; + +namespace Web.Api.Features.Users.SearchUsersForAdmin; + +public record SearchUsersForAdminQueryParams : PageModel +{ + public string Email { get; init; } = string.Empty; + + public bool? UnsubscribeMeFromAll { get; init; } + + public bool HasEmailFilter() + => !string.IsNullOrWhiteSpace(Email); + + public bool HasUnsubscribeFilter() + => UnsubscribeMeFromAll.HasValue; +} \ No newline at end of file