- 移除IWorkflowTriggerService接口及其实现,改用Mediator模式直接发送命令 - 更新AiBotHandler移除workflowTrigger依赖,使用新命令模式触发工作流 - 修改ApproveTaskEndpoint和RejectTaskEndpoint返回bool类型并使用Send.OkAsync - 更新appsettings.json中AI助手的默认指令配置 - 在BuildFlowHostModule.cs中移除旧的工作流触发服务注册 - 添加SSE流式响应路径前缀配置以支持RAG聊天流 - 重构ChatAgentService使用HTTP客户端工厂和运行时配置解析 - 实现AiChatOutputSanitizer用于清洗AI输出中的模板内容 - 更新EmbeddingService使用运行时配置解析器动态创建客户端 - 调整RAG查询处理器中的系统提示指令内容 - 添加构建流按钮操作的单元测试验证发布和禁用功能 - 扩展即时消息集成测试覆盖文件消息元数据处理场景 - 更新平台权限种子数据添加按钮发布和禁用权限 - 实现AI模型运行时配置解析器支持动态模型切换
114 lines
4.4 KiB
C#
114 lines
4.4 KiB
C#
using FluentAssertions;
|
|
using MediatR;
|
|
using Workflow.Application.Integration.Commands;
|
|
using Workflow.Application.Integration.Handlers;
|
|
using Workflow.Application.Integration.Queries;
|
|
using Workflow.Application.Features.WorkflowInstances.Commands;
|
|
using Workflow.Domain.Entities;
|
|
using Workflow.Domain.Enums;
|
|
using Xunit;
|
|
|
|
namespace BuildFlow.Workflow.Tests.Integration;
|
|
|
|
/// <summary>
|
|
/// AI/RAG 触发工作流的 CQRS 契约测试,禁止回退到 shared service bridge。
|
|
/// </summary>
|
|
public class WorkflowTriggerCqrsTests
|
|
{
|
|
[Fact]
|
|
public async Task StartWorkflowByNameCommand_ShouldDispatchStartInstanceCommand()
|
|
{
|
|
await using var db = TestDbFactory.Create();
|
|
var definition = new WorkflowDefinition
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Name = "报销审批",
|
|
Code = "expense-approval",
|
|
Status = DefinitionStatus.Published,
|
|
Version = 1,
|
|
IsEnabled = true,
|
|
};
|
|
db.WorkflowDefinitions.Add(definition);
|
|
await db.SaveChangesAsync();
|
|
var instanceId = Guid.NewGuid();
|
|
var sender = new TestSender(instanceId);
|
|
var handler = new StartWorkflowByNameCommandHandler(db, sender);
|
|
|
|
var result = await handler.Handle(
|
|
new StartWorkflowByNameCommand("报销", Guid.NewGuid(), """{"amount":1200}"""),
|
|
CancellationToken.None);
|
|
|
|
result.Success.Should().BeTrue();
|
|
result.InstanceId.Should().Be(instanceId);
|
|
result.Title.Should().Be("报销");
|
|
sender.StartCommand.Should().NotBeNull();
|
|
sender.StartCommand!.DefinitionCode.Should().Be("expense-approval");
|
|
sender.StartCommand.Title.Should().StartWith("报销-");
|
|
sender.StartCommand.Variables.Should().Be("""{"amount":1200}""");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetTriggerableWorkflowDefinitionsQuery_ShouldReturnPublishedDefinitions()
|
|
{
|
|
await using var db = TestDbFactory.Create();
|
|
db.WorkflowDefinitions.AddRange(
|
|
new WorkflowDefinition
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Name = "报销审批",
|
|
Code = "expense-approval",
|
|
Status = DefinitionStatus.Published,
|
|
Version = 1,
|
|
IsEnabled = true,
|
|
},
|
|
new WorkflowDefinition
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Name = "草稿流程",
|
|
Code = "draft-flow",
|
|
Status = DefinitionStatus.Draft,
|
|
Version = 1,
|
|
IsEnabled = true,
|
|
});
|
|
await db.SaveChangesAsync();
|
|
var handler = new GetTriggerableWorkflowDefinitionsQueryHandler(db);
|
|
|
|
var result = await handler.Handle(new GetTriggerableWorkflowDefinitionsQuery(), CancellationToken.None);
|
|
|
|
result.Should().ContainSingle();
|
|
result[0].Key.Should().Be("报销审批");
|
|
result[0].Name.Should().Be("报销审批");
|
|
}
|
|
|
|
private sealed class TestSender(Guid instanceId) : ISender
|
|
{
|
|
public StartWorkflowInstanceCommand? StartCommand { get; private set; }
|
|
|
|
public Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default)
|
|
{
|
|
if (request is StartWorkflowInstanceCommand command && typeof(TResponse) == typeof(Guid))
|
|
{
|
|
StartCommand = command;
|
|
return Task.FromResult((TResponse)(object)instanceId);
|
|
}
|
|
|
|
throw new NotSupportedException($"Unexpected request {request.GetType().Name}");
|
|
}
|
|
|
|
public Task Send<TRequest>(TRequest request, CancellationToken cancellationToken = default)
|
|
where TRequest : IRequest
|
|
=> throw new NotSupportedException($"Unexpected request {typeof(TRequest).Name}");
|
|
|
|
public Task<object?> Send(object request, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException($"Unexpected request {request.GetType().Name}");
|
|
|
|
public IAsyncEnumerable<TResponse> CreateStream<TResponse>(
|
|
IStreamRequest<TResponse> request,
|
|
CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException($"Unexpected stream request {request.GetType().Name}");
|
|
|
|
public IAsyncEnumerable<object?> CreateStream(object request, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException($"Unexpected stream request {request.GetType().Name}");
|
|
}
|
|
}
|