xiangning 6c110b579a refactor(dependencies): 移除未使用的域实体引用
- 从文件模块的多部分上传处理器中移除未使用的实体引用
- 从即时通讯模块的群组成员处理器中移除未使用的实体引用
- 从RAG模块的AI模型配置中移除未使用的实体引用
- 从监控模块的告警规则配置中移除未使用的实体引用
- 从平台模块的角色权限处理器中移除未使用的实体引用
- 从脚本模块的执行日志实体引用中进行清理
- 从多个基础设施配置类中移除未使用的依赖项
- 优化测试文件中的导入声明,移除不必要的引用
2026-07-19 14:09:47 +08:00

203 lines
8.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Text.Json;
using FluentAssertions;
using Workflow.Application.Engine;
using Workflow.Application.Features.WorkflowDefinitions.Commands;
using Workflow.Application.Features.WorkflowDefinitions.Handlers;
using Workflow.Domain.Entities;
using Workflow.Domain.Enums;
using Xunit;
using ShapeMap = global::Workflow.Domain.Engine.WorkflowShapeMap;
namespace BuildFlow.Workflow.Tests.Integration;
/// <summary>
/// 钉钉式树定义的端到端发布测试:构造带 DefinitionTree 的草稿定义 →
/// 发布 → 验证编译产出 DefinitionJson、通过图校验、生成不可变快照、状态变 Published。
/// 这条链路是「树编辑器保存 → 发布 → 引擎可运行」的核心保障。
/// </summary>
public class TreeDefinitionPublishTests
{
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
/// <summary>构造一棵典型的钉钉式树:发起人 → 审批 → 条件分支(大额/默认) → 抄送。</summary>
private static string BuildTreeJson()
{
var tree = new
{
root = new
{
id = Guid.NewGuid().ToString(),
type = "Initiator",
config = new { title = "发起人" },
child = new
{
id = Guid.NewGuid().ToString(),
type = "Approval",
config = new
{
title = "主管审批",
approvalMode = "countersign",
assigneeRules = new[] { "user:11111111-1111-1111-1111-111111111111" },
},
child = new
{
id = Guid.NewGuid().ToString(),
type = "ConditionBranch",
config = new { title = "金额分支" },
branches = new object[]
{
new
{
id = Guid.NewGuid().ToString(),
label = "大额",
condition = "amount > 5000",
child = new
{
id = Guid.NewGuid().ToString(),
type = "Approval",
config = new
{
title = "总监审批",
approvalMode = "countersign",
assigneeRules = new[] { "user:22222222-2222-2222-2222-222222222222" },
},
},
},
new
{
id = Guid.NewGuid().ToString(),
label = "默认",
condition = (string?)null,
child = (object?)null,
},
},
child = new
{
id = Guid.NewGuid().ToString(),
type = "Cc",
config = new
{
title = "抄送财务",
recipients = new[] { "user:33333333-3333-3333-3333-333333333333" },
},
},
},
},
},
};
return JsonSerializer.Serialize(tree, JsonOptions);
}
[Fact]
public async Task Publish_TreeDefinition_CompilesToValidGraph_AndCreatesSnapshot()
{
// Arrange:构造一个 Draft 草稿,只填 DefinitionTree(不填 DefinitionJson)
await using var db = TestDbFactory.Create();
var definitionId = Guid.NewGuid();
db.WorkflowDefinitions.Add(new WorkflowDefinition
{
Id = definitionId,
Name = "报销审批",
Code = $"expense-{definitionId:N}",
Status = DefinitionStatus.Draft,
Version = 1,
IsEnabled = true,
DefinitionTree = BuildTreeJson(),
});
await db.SaveChangesAsync();
// Act:发布
var handler = new PublishWorkflowDefinitionCommandHandler(db);
await handler.Handle(new PublishWorkflowDefinitionCommand(definitionId), CancellationToken.None);
// Assert:定义状态变 Published、版本自增、DefinitionJson 被编译出来
var updated = await db.WorkflowDefinitions.FindAsync(definitionId);
updated.Should().NotBeNull();
updated!.Status.Should().Be(DefinitionStatus.Published);
updated.Version.Should().Be(2);
updated.DefinitionJson.Should().NotBeNullOrEmpty("树必须被编译成图");
// 编译出的图必须能被引擎解析且通过校验
var graph = WorkflowGraphParser.Parse(updated.DefinitionJson);
graph.Nodes.Should().NotBeEmpty();
var act = () => WorkflowGraphValidator.Validate(graph);
act.Should().NotThrow("编译产物必须通过发布校验");
// 图结构断言:含 Start/End/Approval/Cc/Condition
graph.Nodes.Should().Contain(n => n.Shape == ShapeMap.Shapes.Start);
graph.Nodes.Should().Contain(n => n.Shape == ShapeMap.Shapes.End);
graph.Nodes.Should().Contain(n => n.Shape == ShapeMap.Shapes.Approval);
graph.Nodes.Should().Contain(n => n.Shape == ShapeMap.Shapes.Cc);
graph.Nodes.Should().Contain(n => n.Shape == ShapeMap.Shapes.Condition);
// 快照已生成,且 DefinitionTree/DefinitionJson 都写进快照
var snapshot = db.WorkflowDefinitionSnapshots
.FirstOrDefault(s => s.DefinitionId == definitionId && s.Version == 2);
snapshot.Should().NotBeNull();
snapshot!.DefinitionJson.Should().NotBeNullOrEmpty();
snapshot.DefinitionTree.Should().NotBeNullOrEmpty("快照应同时保存树供审计回显");
}
[Fact]
public async Task Publish_LegacyDefinitionWithoutTree_StillWorks()
{
// 兼容老的自由画布定义(无 DefinitionTree,只有 DefinitionJson)也能发布
await using var db = TestDbFactory.Create();
var definitionId = Guid.NewGuid();
// 手工构造一个最小合法图:Start → End
var minGraph = new
{
nodes = new[]
{
new { id = Guid.NewGuid().ToString(), shape = "wf-start", data = new { title = "开始" } },
new { id = Guid.NewGuid().ToString(), shape = "wf-end", data = new { title = "结束" } },
},
edges = new[]
{
new
{
id = Guid.NewGuid().ToString(),
shape = "wf-edge",
source = new { cell = "" },
target = new { cell = "" },
data = new { edgeType = 0 },
},
},
};
// 填好 source/target 指向上面两个节点
var nodes = minGraph.nodes;
var edges = new[]
{
new
{
id = Guid.NewGuid().ToString(),
shape = "wf-edge",
source = new { cell = nodes[0].id },
target = new { cell = nodes[1].id },
data = new { edgeType = 0 },
},
};
var graphJson = JsonSerializer.Serialize(new { nodes, edges }, JsonOptions);
db.WorkflowDefinitions.Add(new WorkflowDefinition
{
Id = definitionId,
Name = "老流程",
Code = $"legacy-{definitionId:N}",
Status = DefinitionStatus.Draft,
Version = 1,
IsEnabled = true,
DefinitionJson = graphJson,
});
await db.SaveChangesAsync();
var handler = new PublishWorkflowDefinitionCommandHandler(db);
var act = () => handler.Handle(new PublishWorkflowDefinitionCommand(definitionId), CancellationToken.None);
await act.Should().NotThrowAsync("无树的老定义走原 DefinitionJson 路径");
var updated = await db.WorkflowDefinitions.FindAsync(definitionId);
updated!.Status.Should().Be(DefinitionStatus.Published);
}
}