xiangning 7479521546 feat(rag): 实现RAG模块的Hybrid检索和向量索引优化
- 添加HNSW向量索引提升检索性能
- 实现Hybrid检索模式支持向量和关键词双路召回
- 采用RRF算法融合多路召回结果
- 支持ParentChild分块模式的上下文回填
- 优化向量写入采用批量SQL操作提升性能
- 添加RAG PostgreSQL连接字符串配置
- 更新部署脚本增加PGVector集成测试
- 修正表名引用从document_chunks到b_document_chunks
- 重构RagRetrievalService实现完整的检索流程
- 增加重排策略支持关键词和LLM重排
- 修改服务器部署地址配置
2026-08-11 10:59:09 +08:00

180 lines
6.3 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.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.Should().ContainSingle();
result[0].Username.Should().Be("searchme");
}
#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
}