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; /// /// 钉钉式审批流全链路端到端测试:树 → 编译 → 图落库读回 → ProcessEngine 启动实例 → 审批完成。 /// 这条链路证明:前端树编辑器产出的树,经 TreeToGraphCompiler 编译后,能被真实 ProcessEngine /// 加载运行并完成审批。是"钉钉式设计器重做"最权威的运行时验证。 /// public class TreeEndToEndTests { private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web); /// /// 构造一棵"发起人 → 依次审批(A,B) → 结束"的树,编译成图,启动实例,A 通过后建 B, /// B 通过后实例完成。 /// [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(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() .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, "依次审批全部通过后实例应完成"); } }