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

140 lines
5.6 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 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, "依次审批全部通过后实例应完成");
}
}