- 从文件模块的多部分上传处理器中移除未使用的实体引用 - 从即时通讯模块的群组成员处理器中移除未使用的实体引用 - 从RAG模块的AI模型配置中移除未使用的实体引用 - 从监控模块的告警规则配置中移除未使用的实体引用 - 从平台模块的角色权限处理器中移除未使用的实体引用 - 从脚本模块的执行日志实体引用中进行清理 - 从多个基础设施配置类中移除未使用的依赖项 - 优化测试文件中的导入声明,移除不必要的引用
140 lines
5.6 KiB
C#
140 lines
5.6 KiB
C#
using System.Text.Json;
|
||
using FluentAssertions;
|
||
using Microsoft.EntityFrameworkCore;
|
||
using Microsoft.Extensions.DependencyInjection;
|
||
using global::BuildFlow.Shared.Domain.Common;
|
||
using Workflow.Application.Engine;
|
||
using Workflow.Domain.Engine;
|
||
using Workflow.Domain.Entities;
|
||
using Workflow.Domain.Enums;
|
||
using Workflow.Domain.Expressions;
|
||
using TaskStatus = global::Workflow.Domain.Enums.TaskStatus;
|
||
using InstanceStatus = global::Workflow.Domain.Enums.InstanceStatus;
|
||
using Xunit;
|
||
|
||
namespace BuildFlow.Workflow.Tests.Integration;
|
||
|
||
/// <summary>
|
||
/// 钉钉式审批流全链路端到端测试:树 → 编译 → 图落库读回 → ProcessEngine 启动实例 → 审批完成。
|
||
/// 这条链路证明:前端树编辑器产出的树,经 TreeToGraphCompiler 编译后,能被真实 ProcessEngine
|
||
/// 加载运行并完成审批。是"钉钉式设计器重做"最权威的运行时验证。
|
||
/// </summary>
|
||
public class TreeEndToEndTests
|
||
{
|
||
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
|
||
|
||
/// <summary>
|
||
/// 构造一棵"发起人 → 依次审批(A,B) → 结束"的树,编译成图,启动实例,A 通过后建 B,
|
||
/// B 通过后实例完成。
|
||
/// </summary>
|
||
[Fact]
|
||
public async Task Tree_ApprovalChain_RunsThroughEngine_ToCompletion()
|
||
{
|
||
// 1. 构造树
|
||
var userA = Guid.NewGuid();
|
||
var userB = Guid.NewGuid();
|
||
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 = "sequential",
|
||
assigneeRules = new[] { $"user:{userA}", $"user:{userB}" },
|
||
},
|
||
},
|
||
},
|
||
};
|
||
var treeJson = JsonSerializer.Serialize(tree, JsonOptions);
|
||
|
||
// 2. 编译成图
|
||
var parsedTree = JsonSerializer.Deserialize<WorkflowTree>(treeJson, JsonOptions)!;
|
||
var compiledGraph = TreeToGraphCompiler.Compile(parsedTree);
|
||
var graphJson = JsonSerializer.Serialize(compiledGraph, JsonOptions);
|
||
|
||
// 编译产物必须通过发布校验
|
||
var act = () => WorkflowGraphValidator.Validate(compiledGraph);
|
||
act.Should().NotThrow();
|
||
|
||
// 3. 落库(DefinitionJson = 编译产物)并读回,模拟引擎从快照加载
|
||
var options = new DbContextOptionsBuilder<WorkflowDbContext>()
|
||
.UseInMemoryDatabase($"tree-e2e-{Guid.NewGuid()}").Options;
|
||
var db = new WorkflowDbContext(options);
|
||
db.SavingChanges += (sender, _) =>
|
||
{
|
||
if (sender is not DbContext ctx) return;
|
||
foreach (var entry in ctx.ChangeTracker.Entries())
|
||
{
|
||
if (entry.Entity is IAuditable a) { a.CreatedBy ??= "test"; a.UpdatedBy ??= "test"; }
|
||
if (entry.Entity is IHasOperatorIP o) { o.OperatorIP ??= "127.0.0.1"; }
|
||
}
|
||
};
|
||
|
||
// 引擎 ProcessEngine.LoadGraphAsync 优先读 DefinitionJson(整图 JSON)。
|
||
// 把编译产物作为 WorkflowDefinition.DefinitionJson 存入。
|
||
var definition = new WorkflowDefinition
|
||
{
|
||
Id = Guid.NewGuid(),
|
||
Name = "依次审批流程",
|
||
Code = $"seq-e2e-{Guid.NewGuid():N}",
|
||
Status = DefinitionStatus.Published,
|
||
Version = 1,
|
||
IsEnabled = true,
|
||
DefinitionJson = graphJson,
|
||
DefinitionTree = treeJson,
|
||
CreatedBy = "test",
|
||
UpdatedBy = "test",
|
||
};
|
||
db.WorkflowDefinitions.Add(definition);
|
||
await db.SaveChangesAsync();
|
||
|
||
// 4. 启动实例
|
||
var engine = new ProcessEngine(db, new ServiceCollection().BuildServiceProvider(), new ConditionEvaluator());
|
||
var instance = new WorkflowInstance
|
||
{
|
||
Id = Guid.NewGuid(),
|
||
DefinitionId = definition.Id,
|
||
DefinitionCode = definition.Code,
|
||
DefinitionVersion = 1,
|
||
Title = "e2e-instance",
|
||
Status = InstanceStatus.Pending,
|
||
Variables = "{}",
|
||
InitiatorId = Guid.NewGuid(),
|
||
CreatedBy = "test",
|
||
UpdatedBy = "test",
|
||
};
|
||
db.WorkflowInstances.Add(instance);
|
||
await db.SaveChangesAsync();
|
||
|
||
await engine.StartAsync(instance);
|
||
|
||
// 启动后:依次审批只建 A 的任务(SequentialOrder=0)
|
||
var tasks = await db.WorkflowTasks
|
||
.Where(t => t.Status == TaskStatus.Pending)
|
||
.ToListAsync();
|
||
tasks.Should().HaveCount(1);
|
||
tasks[0].AssigneeId.Should().Be(userA, "依次审批首个任务应分配给 A");
|
||
tasks[0].SequentialOrder.Should().Be(0);
|
||
|
||
// 5. A 通过 → 建 B 的任务,实例未完成
|
||
await engine.CompleteTaskAsync(tasks[0], TaskResult.Approved);
|
||
var taskB = await db.WorkflowTasks
|
||
.SingleOrDefaultAsync(t => t.SequentialOrder == 1 && t.Status == TaskStatus.Pending);
|
||
taskB.Should().NotBeNull("A 通过后应建 B 的任务");
|
||
taskB!.AssigneeId.Should().Be(userB);
|
||
instance.Status.Should().NotBe(InstanceStatus.Completed, "B 未审完,实例不应完成");
|
||
|
||
// 6. B 通过 → 实例完成(token 到达 End)
|
||
await engine.CompleteTaskAsync(taskB, TaskResult.Approved);
|
||
instance.Status.Should().Be(InstanceStatus.Completed, "依次审批全部通过后实例应完成");
|
||
}
|
||
}
|