218 lines
7.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}