using FluentAssertions; using Microsoft.EntityFrameworkCore; using Workflow.Application.BuildFlow.Buttons.Commands; using Workflow.Application.BuildFlow.Buttons.Handlers; using Workflow.Application.BuildFlow.Buttons.Services; using Workflow.Application.BuildFlow.CustomFunctions.Commands; using Workflow.Application.BuildFlow.CustomFunctions.Handlers; using Workflow.Application.BuildFlow.CustomFunctions.Services; using Workflow.Domain.Entities; using Workflow.Domain.Enums; using Xunit; namespace BuildFlow.Workflow.Tests.BuildFlow; /// /// 按钮动作链测试,约束按钮配置驱动后端动作执行,而不是前端写死行为。 /// public class BuildFlowButtonActionTests { [Fact] public async Task CreateButtonDefinition_PersistsActionMetadata() { await using var db = TestDbFactory.Create(); var handler = new CreateBuildFlowButtonDefinitionCommandHandler(db); var button = await handler.Handle( new CreateBuildFlowButtonDefinitionCommand( null, null, null, null, "submit", "提交", "send", BuildFlowButtonPlacement.FormToolbar, "form:submit", null, null, """{"title":"确认提交?"}""", """[]""", 10), CancellationToken.None); button.Code.Should().Be("submit"); button.PermissionCode.Should().Be("form:submit"); button.Status.Should().Be(BuildFlowMetadataStatus.Draft); } [Fact] public async Task ExecuteAsync_RunFunctionAction_ReturnsFunctionResultAndWritesLog() { await using var db = TestDbFactory.Create(); var function = await CreatePublishedFunctionAsync(db); var button = CreateButton(function.Id); db.BuildFlowButtons.Add(button); await db.SaveChangesAsync(); var executor = new BuildFlowButtonActionExecutor( db, new CustomFunctionExecutionService(db)); var result = await executor.ExecuteAsync( button.Id, """{"amount":1200}""", CancellationToken.None); result.Success.Should().BeTrue(); result.Results.Should().ContainSingle().Which.ResultJson.Should().Be("\"high\""); (await db.BuildFlowActionExecutionLogs.CountAsync()).Should().Be(1); } [Fact] public async Task ExecuteAsync_WhenActionFails_DoesNotPersistActionLog() { await using var db = TestDbFactory.Create(); var button = CreateButton(Guid.NewGuid()); db.BuildFlowButtons.Add(button); await db.SaveChangesAsync(); var executor = new BuildFlowButtonActionExecutor( db, new CustomFunctionExecutionService(db)); var act = () => executor.ExecuteAsync(button.Id, "{}", CancellationToken.None); await act.Should().ThrowAsync(); (await db.BuildFlowActionExecutionLogs.CountAsync()).Should().Be(0); } private static async Task CreatePublishedFunctionAsync(WorkflowDbContext db) { var createHandler = new CreateCustomFunctionCommandHandler(db); var publishHandler = new PublishCustomFunctionCommandHandler(db); var function = await createHandler.Handle( new CreateCustomFunctionCommand( "button-amount-level", "按钮金额分级", null, "((System.Text.Json.JsonElement)Variables[\"amount\"]).GetDecimal() > 1000 ? \"high\" : \"normal\"", null, null, 5000), CancellationToken.None); await publishHandler.Handle(new PublishCustomFunctionCommand(function.Id), CancellationToken.None); return await db.CustomFunctions.FindAsync(function.Id) ?? throw new InvalidOperationException(); } private static BuildFlowButtonDefinition CreateButton(Guid functionId) => new() { Id = Guid.NewGuid(), Code = "submit-and-run", Label = "提交并计算", Placement = BuildFlowButtonPlacement.FormToolbar, Status = BuildFlowMetadataStatus.Published, ActionsJson = $$""" [ { "type": "runFunction", "target": "{{functionId}}", "params": { "amount": "$input.amount" }, "runPolicy": "stopOnFailure" } ] """, }; }