218 lines
7.9 KiB
C#
218 lines
7.9 KiB
C#
using BuildFlow.Platform.Application.Roles.Commands;
|
||
using BuildFlow.Platform.Application.Roles.Handlers;
|
||
using BuildFlow.Platform.Application.Users.Commands;
|
||
using BuildFlow.Platform.Application.Users.Handlers;
|
||
using BuildFlow.Platform.Application.Users.Queries;
|
||
using BuildFlow.Platform.Infrastructure.Persistence;
|
||
using BuildFlow.Shared.Domain.Errors;
|
||
using BuildFlow.Shared.Domain.Exceptions;
|
||
using FluentAssertions;
|
||
using Microsoft.EntityFrameworkCore;
|
||
using Xunit;
|
||
|
||
namespace BuildFlow.Platform.Tests.Users;
|
||
|
||
/// <summary>
|
||
/// [SOP-PLATFORM] Users 模块 6 个 Handler 覆盖测试。
|
||
/// 覆盖: GetList / GetById / GetNames / Create / Update / Delete。
|
||
/// 飞书矩阵: platform/Users 子域(6 Handler 零覆盖→全补)。
|
||
/// </summary>
|
||
public class UsersHandlerTests
|
||
{
|
||
private static PlatformDbContext CreateDb([System.Runtime.CompilerServices.CallerMemberName] string name = "")
|
||
=> PlatformTestDb.CreateDbContext(name);
|
||
|
||
private static async Task SeedUserRole(PlatformDbContext db)
|
||
=> await new CreateRoleCommandHandler(db).Handle(new CreateRoleCommand("User", null), CancellationToken.None);
|
||
|
||
#region GetUserList
|
||
|
||
[Fact]
|
||
public async Task GetUserList_ReturnsAll()
|
||
{
|
||
var db = CreateDb();
|
||
await PlatformTestDb.SeedUserAsync(db, Guid.NewGuid(), "u1");
|
||
await PlatformTestDb.SeedUserAsync(db, Guid.NewGuid(), "u2");
|
||
var result = await new GetUserListQueryHandler(db)
|
||
.Handle(new GetUserListQuery(null), CancellationToken.None);
|
||
result.Total.Should().Be(2);
|
||
result.Items.Should().HaveCount(2);
|
||
}
|
||
|
||
[Fact]
|
||
public async Task GetUserList_SearchFilters()
|
||
{
|
||
var db = CreateDb();
|
||
await PlatformTestDb.SeedUserAsync(db, Guid.NewGuid(), "searchme");
|
||
await PlatformTestDb.SeedUserAsync(db, Guid.NewGuid(), "otherguy");
|
||
var result = await new GetUserListQueryHandler(db)
|
||
.Handle(new GetUserListQuery("searchme"), CancellationToken.None);
|
||
result.Total.Should().Be(1);
|
||
result.Items.Should().ContainSingle();
|
||
result.Items[0].Username.Should().Be("searchme");
|
||
}
|
||
|
||
[Fact]
|
||
public async Task GetUserList_Pagination_SlicesDeterministically()
|
||
{
|
||
var db = CreateDb();
|
||
foreach (var name in new[] { "page-a", "page-b", "page-c" })
|
||
await PlatformTestDb.SeedUserAsync(db, Guid.NewGuid(), name);
|
||
|
||
var handler = new GetUserListQueryHandler(db);
|
||
var full = await handler.Handle(new GetUserListQuery(null, 1, 100), CancellationToken.None);
|
||
var page1 = await handler.Handle(new GetUserListQuery(null, 1, 2), CancellationToken.None);
|
||
var page2 = await handler.Handle(new GetUserListQuery(null, 2, 2), CancellationToken.None);
|
||
|
||
full.Total.Should().Be(3);
|
||
page1.Total.Should().Be(3);
|
||
page1.PageIndex.Should().Be(1);
|
||
page1.PageSize.Should().Be(2);
|
||
page2.Items.Should().ContainSingle();
|
||
page1.Items.Concat(page2.Items).Select(u => u.Username)
|
||
.Should().Equal(full.Items.Select(u => u.Username));
|
||
}
|
||
|
||
[Fact]
|
||
public async Task GetUserList_OutOfRangeParams_Clamped()
|
||
{
|
||
var db = CreateDb();
|
||
await PlatformTestDb.SeedUserAsync(db, Guid.NewGuid(), "clamp-u");
|
||
|
||
// PageIndex=0 → 钳为 1;PageSize=99999 → 钳到上限 100
|
||
var result = await new GetUserListQueryHandler(db)
|
||
.Handle(new GetUserListQuery(null, PageIndex: 0, PageSize: 99999), CancellationToken.None);
|
||
|
||
result.PageIndex.Should().Be(1);
|
||
result.PageSize.Should().Be(100);
|
||
result.Items.Should().ContainSingle();
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region GetUserById
|
||
|
||
[Fact]
|
||
public async Task GetUserById_Existing_ReturnsDto()
|
||
{
|
||
var db = CreateDb();
|
||
var user = await PlatformTestDb.SeedUserAsync(db, Guid.NewGuid(), "bob");
|
||
var result = await new GetUserByIdQueryHandler(db)
|
||
.Handle(new GetUserByIdQuery(user.Id), CancellationToken.None);
|
||
result.Username.Should().Be("bob");
|
||
}
|
||
|
||
[Fact]
|
||
public async Task GetUserById_NonExistent_ThrowsNotFound()
|
||
{
|
||
var db = CreateDb();
|
||
var act = async () => await new GetUserByIdQueryHandler(db)
|
||
.Handle(new GetUserByIdQuery(Guid.NewGuid()), CancellationToken.None);
|
||
var ex = await act.Should().ThrowAsync<NotFoundException>();
|
||
ex.Which.Code.Should().Be(ErrorCode.NotFound);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region GetUserNames
|
||
|
||
[Fact]
|
||
public async Task GetUserNames_EmptyIds_ReturnsEmpty()
|
||
{
|
||
var db = CreateDb();
|
||
var result = await new GetUserNamesQueryHandler(db)
|
||
.Handle(new GetUserNamesQuery(new List<Guid>()), CancellationToken.None);
|
||
result.Should().BeEmpty();
|
||
}
|
||
|
||
[Fact]
|
||
public async Task GetUserNames_WithIds_ReturnsMap()
|
||
{
|
||
var db = CreateDb();
|
||
var user = await PlatformTestDb.SeedUserAsync(db, Guid.NewGuid(), "namer");
|
||
var result = await new GetUserNamesQueryHandler(db)
|
||
.Handle(new GetUserNamesQuery(new List<Guid> { user.Id }), CancellationToken.None);
|
||
result.Should().ContainKey(user.Id);
|
||
result[user.Id].Should().Be("namer");
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region CreateUser
|
||
|
||
[Fact]
|
||
public async Task CreateUser_Valid_ReturnsDtoAndAssignsUserRole()
|
||
{
|
||
var db = CreateDb();
|
||
await SeedUserRole(db);
|
||
var result = await new CreateUserCommandHandler(db)
|
||
.Handle(new CreateUserCommand("alice", "alice@test.local", "Pass@123"), CancellationToken.None);
|
||
result.Username.Should().Be("alice");
|
||
result.Roles.Should().Contain("User");
|
||
(await db.Users.CountAsync()).Should().Be(1);
|
||
}
|
||
|
||
[Fact]
|
||
public async Task CreateUser_DuplicateUsername_ThrowsBusiness()
|
||
{
|
||
var db = CreateDb();
|
||
await SeedUserRole(db);
|
||
await new CreateUserCommandHandler(db)
|
||
.Handle(new CreateUserCommand("alice", "alice@test.local", "Pass@123"), CancellationToken.None);
|
||
var act = async () => await new CreateUserCommandHandler(db)
|
||
.Handle(new CreateUserCommand("alice", "other@test.local", "Pass@123"), CancellationToken.None);
|
||
var ex = await act.Should().ThrowAsync<BusinessException>();
|
||
ex.Which.Code.Should().Be(ErrorCode.AlreadyExists);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region UpdateUser
|
||
|
||
[Fact]
|
||
public async Task UpdateUser_Existing_UpdatesFields()
|
||
{
|
||
var db = CreateDb();
|
||
var user = await PlatformTestDb.SeedUserAsync(db, Guid.NewGuid(), "carol");
|
||
var result = await new UpdateUserCommandHandler(db)
|
||
.Handle(new UpdateUserCommand(user.Id, "carol2@test.local", null, false), CancellationToken.None);
|
||
result.Email.Should().Be("carol2@test.local");
|
||
result.IsActive.Should().BeFalse();
|
||
}
|
||
|
||
[Fact]
|
||
public async Task UpdateUser_NonExistent_ThrowsNotFound()
|
||
{
|
||
var db = CreateDb();
|
||
var act = async () => await new UpdateUserCommandHandler(db)
|
||
.Handle(new UpdateUserCommand(Guid.NewGuid(), null, null, null), CancellationToken.None);
|
||
var ex = await act.Should().ThrowAsync<NotFoundException>();
|
||
ex.Which.Code.Should().Be(ErrorCode.NotFound);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region DeleteUser
|
||
|
||
[Fact]
|
||
public async Task DeleteUser_Existing_Removes()
|
||
{
|
||
var db = CreateDb();
|
||
var user = await PlatformTestDb.SeedUserAsync(db, Guid.NewGuid(), "dave");
|
||
await new DeleteUserCommandHandler(db).Handle(new DeleteUserCommand(user.Id), CancellationToken.None);
|
||
(await db.Users.CountAsync()).Should().Be(0);
|
||
}
|
||
|
||
[Fact]
|
||
public async Task DeleteUser_NonExistent_ThrowsNotFound()
|
||
{
|
||
var db = CreateDb();
|
||
var act = async () => await new DeleteUserCommandHandler(db)
|
||
.Handle(new DeleteUserCommand(Guid.NewGuid()), CancellationToken.None);
|
||
var ex = await act.Should().ThrowAsync<NotFoundException>();
|
||
ex.Which.Code.Should().Be(ErrorCode.NotFound);
|
||
}
|
||
|
||
#endregion
|
||
}
|