xiangning baaff8db63 feat(workflow): 添加自定义函数表单绑定及权限控制功能
- 新增自定义函数与表单定义绑定关系字段 FormDefinitionId
- 实现按钮调用函数时的表单作用域权限校验机制
- 实现按钮提交表单时的作用域权限检查功能
- 添加自定义函数列表按表单定义筛选查询能力
- 新增 RelationPicker 关联选择表单组件支持
- 完善菜单权限体系支持受限但可见菜单配置
- 添加文件消息大小限制验证(最大200MB)
- 优化表单聚合变量同步异常处理机制
- 增加BuildFlow管理后台入口菜单种子数据
- 补充相关单元测试和架构约束测试用例
2026-07-05 09:30:45 +08:00

222 lines
8.9 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,
"[]",
10),
CancellationToken.None);
await buttonHandler.Handle(
new CreateBuildFlowButtonDefinitionCommand(
null,
null,
Guid.NewGuid(),
null,
"approve",
"审批",
"lucide:check",
BuildFlowButtonPlacement.RowAction,
"buildflow:button:approve",
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, "1", null, null, 1000),
CancellationToken.None);
var published = await createHandler.Handle(
new CreateCustomFunctionCommand("publishedRule", "已发布规则", null, "2", 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,
"return input.amount;",
null,
null,
1000,
expenseFormId),
CancellationToken.None);
await createHandler.Handle(
new CreateCustomFunctionCommand(
"purchaseAmountCalc",
"采购金额计算",
null,
"return input.amount;",
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");
}
}