- 引入 CustomFunctionType 枚举,支持 Script 和 Http 两种函数类型 - 在自定义函数中添加 Http 相关字段(HttpMethod、HttpUrl、HttpHeaders、HttpBodyTemplate) - 实现 HTTP 函数执行器,支持变量替换和 SSRF 防护 - 将按钮动作链拆分为 before、main、after 三个阶段 - 新增前置和后置动作 JSON 字段存储 - 实现批量执行多个函数的 executeFunctions 动作类型 - 更新数据库迁移以支持新的字段结构 - 调整相关 DTO、命令和处理器以兼容新功能
236 lines
9.4 KiB
C#
236 lines
9.4 KiB
C#
using FluentAssertions;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Workflow.Application.BuildFlow.Applications.Commands;
|
|
using Workflow.Application.BuildFlow.Applications.Handlers;
|
|
using Workflow.Application.BuildFlow.Applications.Queries;
|
|
using Workflow.Application.BuildFlow.Buttons.Commands;
|
|
using Workflow.Application.BuildFlow.Buttons.Handlers;
|
|
using Workflow.Application.BuildFlow.Buttons.Queries;
|
|
using Workflow.Application.BuildFlow.CustomFunctions.Commands;
|
|
using Workflow.Application.BuildFlow.CustomFunctions.Handlers;
|
|
using Workflow.Application.BuildFlow.CustomFunctions.Queries;
|
|
using Workflow.Application.BuildFlow.Pages.Commands;
|
|
using Workflow.Application.BuildFlow.Pages.Handlers;
|
|
using Workflow.Application.BuildFlow.Pages.Queries;
|
|
using Workflow.Domain.Enums;
|
|
using Xunit;
|
|
|
|
namespace BuildFlow.Workflow.Tests.BuildFlow;
|
|
|
|
/// <summary>
|
|
/// BuildFlow 应用元数据测试,约束应用和页面定义作为低代码平台顶层归属存在。
|
|
/// </summary>
|
|
public class BuildFlowApplicationTests
|
|
{
|
|
[Fact]
|
|
public async Task CreateApplicationAndPage_PersistsMetadata()
|
|
{
|
|
await using var db = TestDbFactory.Create();
|
|
var appHandler = new CreateBuildFlowApplicationCommandHandler(db);
|
|
var pageHandler = new CreateBuildFlowPageDefinitionCommandHandler(db);
|
|
|
|
var app = await appHandler.Handle(
|
|
new CreateBuildFlowApplicationCommand("leave", "请假应用", "员工请假流程"),
|
|
CancellationToken.None);
|
|
var page = await pageHandler.Handle(
|
|
new CreateBuildFlowPageDefinitionCommand(
|
|
app.Id,
|
|
"leave-form",
|
|
"请假申请页",
|
|
BuildFlowPageType.Form,
|
|
"""{"type":"form","formCode":"leave-request"}"""),
|
|
CancellationToken.None);
|
|
|
|
app.Code.Should().Be("leave");
|
|
app.Status.Should().Be(BuildFlowMetadataStatus.Draft);
|
|
page.ApplicationId.Should().Be(app.Id);
|
|
page.SchemaJson.Should().Contain("leave-request");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetApplicationList_FiltersByStatus()
|
|
{
|
|
await using var db = TestDbFactory.Create();
|
|
var appHandler = new CreateBuildFlowApplicationCommandHandler(db);
|
|
var draft = await appHandler.Handle(
|
|
new CreateBuildFlowApplicationCommand("draft-app", "草稿应用", null),
|
|
CancellationToken.None);
|
|
await appHandler.Handle(
|
|
new CreateBuildFlowApplicationCommand("published-app", "已发布应用", null),
|
|
CancellationToken.None);
|
|
var published = await db.BuildFlowApplications.SingleAsync(x => x.Code == "published-app");
|
|
published.Status = BuildFlowMetadataStatus.Published;
|
|
await db.SaveChangesAsync();
|
|
|
|
var handler = new GetBuildFlowApplicationListQueryHandler(db);
|
|
var result = await handler.Handle(
|
|
new GetBuildFlowApplicationListQuery(1, 20, BuildFlowMetadataStatus.Draft, null),
|
|
CancellationToken.None);
|
|
|
|
result.Total.Should().Be(1);
|
|
result.Items.Should().ContainSingle(x => x.Id == draft.Id && x.Code == "draft-app");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetPageList_FiltersByApplication()
|
|
{
|
|
await using var db = TestDbFactory.Create();
|
|
var appHandler = new CreateBuildFlowApplicationCommandHandler(db);
|
|
var pageHandler = new CreateBuildFlowPageDefinitionCommandHandler(db);
|
|
var leaveApp = await appHandler.Handle(new CreateBuildFlowApplicationCommand("leave", "请假应用", null), CancellationToken.None);
|
|
var purchaseApp = await appHandler.Handle(new CreateBuildFlowApplicationCommand("purchase", "采购应用", null), CancellationToken.None);
|
|
await pageHandler.Handle(
|
|
new CreateBuildFlowPageDefinitionCommand(leaveApp.Id, "leave-list", "请假列表", BuildFlowPageType.List, "{}"),
|
|
CancellationToken.None);
|
|
await pageHandler.Handle(
|
|
new CreateBuildFlowPageDefinitionCommand(purchaseApp.Id, "purchase-list", "采购列表", BuildFlowPageType.List, "{}"),
|
|
CancellationToken.None);
|
|
|
|
var handler = new GetBuildFlowPageDefinitionListQueryHandler(db);
|
|
var result = await handler.Handle(
|
|
new GetBuildFlowPageDefinitionListQuery(1, 20, leaveApp.Id, null, null),
|
|
CancellationToken.None);
|
|
|
|
result.Total.Should().Be(1);
|
|
result.Items.Should().ContainSingle(x => x.ApplicationId == leaveApp.Id && x.Code == "leave-list");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetButtonList_FiltersByFormAndPlacement()
|
|
{
|
|
await using var db = TestDbFactory.Create();
|
|
var formDefinitionId = Guid.NewGuid();
|
|
var buttonHandler = new CreateBuildFlowButtonDefinitionCommandHandler(db);
|
|
await buttonHandler.Handle(
|
|
new CreateBuildFlowButtonDefinitionCommand(
|
|
null,
|
|
null,
|
|
formDefinitionId,
|
|
null,
|
|
"submit",
|
|
"提交",
|
|
"lucide:send",
|
|
BuildFlowButtonPlacement.FormToolbar,
|
|
"buildflow:button:submit",
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
"[]",
|
|
null,
|
|
10),
|
|
CancellationToken.None);
|
|
await buttonHandler.Handle(
|
|
new CreateBuildFlowButtonDefinitionCommand(
|
|
null,
|
|
null,
|
|
Guid.NewGuid(),
|
|
null,
|
|
"approve",
|
|
"审批",
|
|
"lucide:check",
|
|
BuildFlowButtonPlacement.RowAction,
|
|
"buildflow:button:approve",
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
"[]",
|
|
null,
|
|
20),
|
|
CancellationToken.None);
|
|
|
|
var handler = new GetBuildFlowButtonDefinitionListQueryHandler(db);
|
|
var result = await handler.Handle(
|
|
new GetBuildFlowButtonDefinitionListQuery(
|
|
1,
|
|
20,
|
|
null,
|
|
null,
|
|
formDefinitionId,
|
|
null,
|
|
BuildFlowButtonPlacement.FormToolbar,
|
|
null),
|
|
CancellationToken.None);
|
|
|
|
result.Total.Should().Be(1);
|
|
result.Items.Should().ContainSingle(x => x.Code == "submit" && x.FormDefinitionId == formDefinitionId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetCustomFunctionList_FiltersPublishedFunctions()
|
|
{
|
|
await using var db = TestDbFactory.Create();
|
|
var createHandler = new CreateCustomFunctionCommandHandler(db);
|
|
var publishHandler = new PublishCustomFunctionCommandHandler(db);
|
|
var draft = await createHandler.Handle(
|
|
new CreateCustomFunctionCommand("draftRule", "草稿规则", null, CustomFunctionType.Script, "1", null, null, null, null, null, null, 1000),
|
|
CancellationToken.None);
|
|
var published = await createHandler.Handle(
|
|
new CreateCustomFunctionCommand("publishedRule", "已发布规则", null, CustomFunctionType.Script, "2", null, null, null, null, null, null, 1000),
|
|
CancellationToken.None);
|
|
await publishHandler.Handle(new PublishCustomFunctionCommand(published.Id), CancellationToken.None);
|
|
|
|
var handler = new GetCustomFunctionListQueryHandler(db);
|
|
var result = await handler.Handle(
|
|
new GetCustomFunctionListQuery(1, 20, BuildFlowMetadataStatus.Published, null),
|
|
CancellationToken.None);
|
|
|
|
result.Total.Should().Be(1);
|
|
result.Items.Should().ContainSingle(x => x.Id == published.Id && x.Name == "publishedRule");
|
|
result.Items.Should().NotContain(x => x.Id == draft.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetCustomFunctionList_FiltersByFormDefinition()
|
|
{
|
|
await using var db = TestDbFactory.Create();
|
|
var expenseFormId = Guid.NewGuid();
|
|
var purchaseFormId = Guid.NewGuid();
|
|
var createHandler = new CreateCustomFunctionCommandHandler(db);
|
|
var expenseFunction = await createHandler.Handle(
|
|
new CreateCustomFunctionCommand(
|
|
"expenseAmountCalc",
|
|
"费用金额计算",
|
|
null,
|
|
CustomFunctionType.Script,
|
|
"return input.amount;",
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
1000,
|
|
expenseFormId),
|
|
CancellationToken.None);
|
|
await createHandler.Handle(
|
|
new CreateCustomFunctionCommand(
|
|
"purchaseAmountCalc",
|
|
"采购金额计算",
|
|
null,
|
|
CustomFunctionType.Script,
|
|
"return input.amount;",
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
1000,
|
|
purchaseFormId),
|
|
CancellationToken.None);
|
|
|
|
var handler = new GetCustomFunctionListQueryHandler(db);
|
|
var result = await handler.Handle(
|
|
new GetCustomFunctionListQuery(1, 20, null, null, expenseFormId),
|
|
CancellationToken.None);
|
|
|
|
result.Total.Should().Be(1);
|
|
result.Items.Should().ContainSingle(x =>
|
|
x.Id == expenseFunction.Id &&
|
|
x.FormDefinitionId == expenseFormId &&
|
|
x.Name == "expenseAmountCalc");
|
|
}
|
|
}
|