- 添加HNSW向量索引提升检索性能 - 实现Hybrid检索模式支持向量和关键词双路召回 - 采用RRF算法融合多路召回结果 - 支持ParentChild分块模式的上下文回填 - 优化向量写入采用批量SQL操作提升性能 - 添加RAG PostgreSQL连接字符串配置 - 更新部署脚本增加PGVector集成测试 - 修正表名引用从document_chunks到b_document_chunks - 重构RagRetrievalService实现完整的检索流程 - 增加重排策略支持关键词和LLM重排 - 修改服务器部署地址配置
229 lines
7.2 KiB
C#
229 lines
7.2 KiB
C#
using BuildFlow.Shared.Domain.Errors;
|
|
using FluentAssertions;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Workflow.Application.Form.ComponentRegistry.Commands;
|
|
using Workflow.Application.Form.ComponentRegistry.Handlers;
|
|
using Workflow.Application.Form.ComponentRegistry.Queries;
|
|
using Workflow.Domain.Entities;
|
|
using Xunit;
|
|
|
|
namespace BuildFlow.Workflow.Tests.Form;
|
|
|
|
public class FormComponentHandlerTests
|
|
{
|
|
private static WorkflowDbContext CreateDbContext()
|
|
{
|
|
var options = new DbContextOptionsBuilder<WorkflowDbContext>()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options;
|
|
var db = new WorkflowDbContext(options);
|
|
db.SavingChanges += AuditStub.OnSave;
|
|
return db;
|
|
}
|
|
|
|
private static FormComponentRegistry SeedComponent(WorkflowDbContext db, string name, string category)
|
|
{
|
|
var entity = new FormComponentRegistry
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Name = name,
|
|
DisplayName = name,
|
|
Category = category,
|
|
Icon = "icon",
|
|
DefaultSchema = "{}",
|
|
SupportedProps = "{}"
|
|
};
|
|
db.FormComponentRegistries.Add(entity);
|
|
return entity;
|
|
}
|
|
|
|
#region CreateFormComponent
|
|
|
|
[Fact]
|
|
public async Task CreateFormComponent_SavesAndReturnsDto()
|
|
{
|
|
await using var db = CreateDbContext();
|
|
var handler = new CreateFormComponentCommandHandler(db);
|
|
var command = new CreateFormComponentCommand(
|
|
Name: "input",
|
|
DisplayName: "Input",
|
|
Category: "basic",
|
|
Icon: "i",
|
|
DefaultSchema: "{}",
|
|
SupportedProps: "{}");
|
|
|
|
var result = await handler.Handle(command, CancellationToken.None);
|
|
|
|
result.Should().NotBeNull();
|
|
result.Name.Should().Be("input");
|
|
result.Category.Should().Be("basic");
|
|
(await db.FormComponentRegistries.CountAsync()).Should().Be(1);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateFormComponent_DuplicateName_ThrowsAlreadyExists()
|
|
{
|
|
await using var db = CreateDbContext();
|
|
SeedComponent(db, "input", "basic");
|
|
await db.SaveChangesAsync();
|
|
|
|
var handler = new CreateFormComponentCommandHandler(db);
|
|
var command = new CreateFormComponentCommand(
|
|
Name: "input",
|
|
DisplayName: "Input",
|
|
Category: "basic",
|
|
Icon: "i",
|
|
DefaultSchema: "{}",
|
|
SupportedProps: "{}");
|
|
|
|
var act = async () => await handler.Handle(command, CancellationToken.None);
|
|
|
|
var ex = await act.Should().ThrowAsync<BusinessException>();
|
|
ex.Which.Code.Should().Be(ErrorCode.AlreadyExists);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region UpdateFormComponent
|
|
|
|
[Fact]
|
|
public async Task UpdateFormComponent_UpdatesFields()
|
|
{
|
|
await using var db = CreateDbContext();
|
|
var entity = SeedComponent(db, "input", "basic");
|
|
await db.SaveChangesAsync();
|
|
|
|
var handler = new UpdateFormComponentCommandHandler(db);
|
|
var command = new UpdateFormComponentCommand(
|
|
Id: entity.Id,
|
|
DisplayName: "Renamed",
|
|
Category: "advanced",
|
|
Icon: "x",
|
|
DefaultSchema: "{\"a\":1}",
|
|
SupportedProps: "{\"b\":2}",
|
|
IsActive: false);
|
|
|
|
var result = await handler.Handle(command, CancellationToken.None);
|
|
|
|
result.DisplayName.Should().Be("Renamed");
|
|
result.IsActive.Should().BeFalse();
|
|
var stored = await db.FormComponentRegistries.FindAsync(entity.Id);
|
|
stored!.Category.Should().Be("advanced");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateFormComponent_Missing_ThrowsNotFound()
|
|
{
|
|
await using var db = CreateDbContext();
|
|
var handler = new UpdateFormComponentCommandHandler(db);
|
|
var command = new UpdateFormComponentCommand(
|
|
Id: Guid.NewGuid(),
|
|
DisplayName: "Renamed",
|
|
Category: "advanced",
|
|
Icon: "x",
|
|
DefaultSchema: "{}",
|
|
SupportedProps: "{}",
|
|
IsActive: true);
|
|
|
|
var act = async () => await handler.Handle(command, CancellationToken.None);
|
|
|
|
var ex = await act.Should().ThrowAsync<NotFoundException>();
|
|
ex.Which.Code.Should().Be(ErrorCode.NotFound);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region DeleteFormComponent
|
|
|
|
[Fact]
|
|
public async Task DeleteFormComponent_SoftDeletes()
|
|
{
|
|
await using var db = CreateDbContext();
|
|
var entity = SeedComponent(db, "input", "basic");
|
|
await db.SaveChangesAsync();
|
|
|
|
var handler = new DeleteFormComponentCommandHandler(db);
|
|
await handler.Handle(new DeleteFormComponentCommand(entity.Id), CancellationToken.None);
|
|
|
|
var stored = await db.FormComponentRegistries.FindAsync(entity.Id);
|
|
stored!.IsDeleted.Should().BeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DeleteFormComponent_Missing_ThrowsNotFound()
|
|
{
|
|
await using var db = CreateDbContext();
|
|
var handler = new DeleteFormComponentCommandHandler(db);
|
|
|
|
var act = async () => await handler.Handle(new DeleteFormComponentCommand(Guid.NewGuid()), CancellationToken.None);
|
|
|
|
var ex = await act.Should().ThrowAsync<NotFoundException>();
|
|
ex.Which.Code.Should().Be(ErrorCode.NotFound);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetFormComponentById
|
|
|
|
[Fact]
|
|
public async Task GetFormComponentById_ReturnsDto()
|
|
{
|
|
await using var db = CreateDbContext();
|
|
var entity = SeedComponent(db, "input", "basic");
|
|
await db.SaveChangesAsync();
|
|
|
|
var handler = new GetFormComponentByIdQueryHandler(db);
|
|
var result = await handler.Handle(new GetFormComponentByIdQuery(entity.Id), CancellationToken.None);
|
|
|
|
result.Should().NotBeNull();
|
|
result.Id.Should().Be(entity.Id);
|
|
result.Name.Should().Be("input");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetFormComponentById_Missing_ThrowsNotFound()
|
|
{
|
|
await using var db = CreateDbContext();
|
|
var handler = new GetFormComponentByIdQueryHandler(db);
|
|
|
|
var act = async () => await handler.Handle(new GetFormComponentByIdQuery(Guid.NewGuid()), CancellationToken.None);
|
|
|
|
var ex = await act.Should().ThrowAsync<NotFoundException>();
|
|
ex.Which.Code.Should().Be(ErrorCode.NotFound);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetFormComponents
|
|
|
|
[Fact]
|
|
public async Task GetFormComponents_FiltersByCategory()
|
|
{
|
|
await using var db = CreateDbContext();
|
|
SeedComponent(db, "a", "basic");
|
|
SeedComponent(db, "b", "advanced");
|
|
await db.SaveChangesAsync();
|
|
|
|
var handler = new GetFormComponentsQueryHandler(db);
|
|
var result = await handler.Handle(new GetFormComponentsQuery(Category: "basic", IsActive: null), CancellationToken.None);
|
|
|
|
result.Should().HaveCount(1);
|
|
result[0].Name.Should().Be("a");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetFormComponents_Empty_WhenNoMatch()
|
|
{
|
|
await using var db = CreateDbContext();
|
|
SeedComponent(db, "a", "basic");
|
|
await db.SaveChangesAsync();
|
|
|
|
var handler = new GetFormComponentsQueryHandler(db);
|
|
var result = await handler.Handle(new GetFormComponentsQuery(Category: "missing", IsActive: null), CancellationToken.None);
|
|
|
|
result.Should().BeEmpty();
|
|
}
|
|
|
|
#endregion
|
|
}
|