fix(scale): page mail and batch roles
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using System.Security.Claims;
|
||||
using System.Linq.Expressions;
|
||||
using System.Data.Common;
|
||||
using JobTrackerApi.Controllers;
|
||||
using JobTrackerApi.Models;
|
||||
using JobTrackerApi.Services;
|
||||
@@ -11,6 +12,10 @@ using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.EntityFrameworkCore.Query;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using JobTrackerApi.Data;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
@@ -102,6 +107,42 @@ public sealed class UsersControllerTests
|
||||
Assert.True(Assert.Single(rows, row => row.Id == member.Id).CanRemoveAdmin);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task List_uses_a_fixed_number_of_role_queries_for_many_users()
|
||||
{
|
||||
var counter = new CommandCounter();
|
||||
await using var connection = new SqliteConnection("Data Source=:memory:");
|
||||
await connection.OpenAsync();
|
||||
var currentUser = new Mock<ICurrentUserService>();
|
||||
currentUser.SetupGet(service => service.UserId).Returns("admin-1");
|
||||
var options = new DbContextOptionsBuilder<JobTrackerContext>()
|
||||
.UseSqlite(connection)
|
||||
.AddInterceptors(counter)
|
||||
.Options;
|
||||
await using var db = new JobTrackerContext(options, currentUser.Object);
|
||||
await db.Database.EnsureCreatedAsync();
|
||||
var adminRole = new IdentityRole("Admin") { Id = "role-admin", NormalizedName = "ADMIN" };
|
||||
var memberRole = new IdentityRole("Member") { Id = "role-member", NormalizedName = "MEMBER" };
|
||||
db.Roles.AddRange(adminRole, memberRole);
|
||||
var usersToAdd = Enumerable.Range(1, 75).Select(index => User(index == 1 ? "admin-1" : $"user-{index}")).ToList();
|
||||
db.Users.AddRange(usersToAdd);
|
||||
db.UserRoles.Add(new IdentityUserRole<string> { UserId = "admin-1", RoleId = adminRole.Id });
|
||||
foreach (var user in usersToAdd.Skip(1))
|
||||
db.UserRoles.Add(new IdentityUserRole<string> { UserId = user.Id, RoleId = memberRole.Id });
|
||||
await db.SaveChangesAsync();
|
||||
counter.Reset();
|
||||
|
||||
var manager = TestHostFactory.CreateUserManager();
|
||||
var controller = CreateController(manager, "admin-1", db);
|
||||
var action = await controller.List(CancellationToken.None);
|
||||
|
||||
var rows = Assert.IsType<List<UsersController.UserDto>>(Assert.IsType<OkObjectResult>(action.Result).Value);
|
||||
Assert.Equal(75, rows.Count);
|
||||
Assert.Equal(2, counter.ReaderCount);
|
||||
manager.Verify(userManager => userManager.GetRolesAsync(It.IsAny<ApplicationUser>()), Times.Never);
|
||||
manager.Verify(userManager => userManager.GetUsersInRoleAsync(It.IsAny<string>()), Times.Never);
|
||||
}
|
||||
|
||||
private static ApplicationUser User(string id) => new()
|
||||
{
|
||||
Id = id,
|
||||
@@ -109,7 +150,7 @@ public sealed class UsersControllerTests
|
||||
UserName = $"{id}@example.com"
|
||||
};
|
||||
|
||||
private static UsersController CreateController(Mock<UserManager<ApplicationUser>> users, string currentUserId)
|
||||
private static UsersController CreateController(Mock<UserManager<ApplicationUser>> users, string currentUserId, JobTrackerContext? db = null)
|
||||
{
|
||||
var roleStore = new Mock<IRoleStore<IdentityRole>>();
|
||||
var roles = new Mock<RoleManager<IdentityRole>>(
|
||||
@@ -125,7 +166,8 @@ public sealed class UsersControllerTests
|
||||
Mock.Of<IAppEmailSender>(),
|
||||
new ConfigurationBuilder().Build(),
|
||||
new NullLogger<UsersController>(),
|
||||
ExternalOrigin.Parse("http://localhost:3000", production: false));
|
||||
ExternalOrigin.Parse("http://localhost:3000", production: false),
|
||||
db: db);
|
||||
|
||||
var identity = new ClaimsIdentity([new Claim(ClaimTypes.NameIdentifier, currentUserId)], "test");
|
||||
controller.ControllerContext = new ControllerContext
|
||||
@@ -135,6 +177,23 @@ public sealed class UsersControllerTests
|
||||
return controller;
|
||||
}
|
||||
|
||||
private sealed class CommandCounter : DbCommandInterceptor
|
||||
{
|
||||
public int ReaderCount { get; private set; }
|
||||
|
||||
public void Reset() => ReaderCount = 0;
|
||||
|
||||
public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(
|
||||
DbCommand command,
|
||||
CommandEventData eventData,
|
||||
InterceptionResult<DbDataReader> result,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ReaderCount++;
|
||||
return base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TestAsyncQueryProvider<TEntity>(IQueryProvider inner) : IAsyncQueryProvider
|
||||
{
|
||||
public IQueryable CreateQuery(Expression expression) => new TestAsyncEnumerable<TEntity>(expression);
|
||||
|
||||
Reference in New Issue
Block a user