init: 条件流引擎初始提交

This commit is contained in:
2026-08-21 09:31:45 +08:00
commit ced18b781a
16 changed files with 2992 additions and 0 deletions
+309
View File
@@ -0,0 +1,309 @@
package tests
import (
"fmt"
"testing"
"git.fsdpf.net/go/condflow"
"git.fsdpf.net/go/condflow/tests/fixtures"
"git.fsdpf.net/go/condition"
"git.fsdpf.net/go/contracts/base"
"git.fsdpf.net/go/reflux"
"git.fsdpf.net/go/reflux/fieldx"
"git.fsdpf.net/go/req"
"git.fsdpf.net/go/req/resx"
"github.com/samber/do/v2"
"github.com/stretchr/testify/suite"
)
// CondFlowTestSuite 条件流测试套件
type CondFlowTestSuite struct {
suite.Suite
app do.Injector
flow *condflow.CondFlow
executor condflow.ActionInvoker
}
// SetupTest 在每个测试前运行
func (s *CondFlowTestSuite) SetupTest() {
// 初始化 DI 容器
s.app = do.New()
// 创建 executor
approveRes := resx.New(s.app, "Approval", "approvals",
resx.WithUuid("550e8400-e29b-41d4-a716-446655440001"),
resx.WithName("审批"),
resx.WithFields(
resx.NewResField("id", "", resx.FieldWithName("ID"), resx.FieldWithDataType(req.ResInteger)),
resx.NewResField("created_user", "", resx.FieldWithName("创建者"), resx.FieldWithDefault("00000000-0000-0000-0000-000000000000"), resx.FieldWithDataType(req.ResString)),
resx.NewResField("owned_user", "", resx.FieldWithName("拥有者"), resx.FieldWithDefault("00000000-0000-0000-0000-000000000000"), resx.FieldWithDataType(req.ResString)),
resx.NewResField("created_at", "", resx.FieldWithName("创建时间"), resx.FieldWithDefault("sql:CURRENT_TIMESTAMP"), resx.FieldWithDataType(req.ResTimestamp)),
resx.NewResField("updated_at", "", resx.FieldWithName("更新时间"), resx.FieldWithDefault("sql:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"), resx.FieldWithDataType(req.ResTimestamp)),
resx.NewResField("uuid", "", resx.FieldWithName("UUID"), resx.FieldWithDefault("sql:uuid()"), resx.FieldWithDataType(req.ResString)),
resx.NewResField("status", "", resx.FieldWithName("状态"), resx.FieldWithDefault("pending"), resx.FieldWithDataType(req.ResString)),
resx.NewResField("amount", "", resx.FieldWithName("审批金额"), resx.FieldWithDefault("0"), resx.FieldWithDataType(req.ResDecimal)),
resx.NewResField("approver_role", "", resx.FieldWithName("审批人角色"), resx.FieldWithDataType(req.ResString)),
resx.NewResField("approval_method", "", resx.FieldWithName("审批方式"), resx.FieldWithDataType(req.ResString)),
),
)
s.executor = condflow.NewActionInvoker(fixtures.NewApproveExecutor(approveRes), nil)
do.ProvideNamedValue(s.app, fmt.Sprintf("%s/ApproveExecutor", s.executor.Res().GetCode()), s.executor)
// 通过字符串验证预期的 action 是否注册成功
expectedActions := []string{
"AutoApprove",
"Manager",
"Director",
"Reject",
}
for _, actionName := range expectedActions {
_, err := s.executor.GetAction(actionName)
s.Require().NoError(err, "获取 action '%s' 失败", actionName)
}
// 创建条件流
s.flow = condflow.New("test-flow")
}
// TearDownTest 在每个测试后运行
func (s *CondFlowTestSuite) TearDownTest() {
if s.app != nil {
s.app.Shutdown()
}
}
// TestSmallAmountAutoApproval 测试小额自动审批
func (s *CondFlowTestSuite) TestSmallAmountAutoApproval() {
// 创建条件: 金额 <= 1000
cond := condition.New(condition.Describe("小额自动审批"))
cond.SetExpr(condition.NewExpr("@", "amount", condition.Operator(condition.LE), condition.Token("1000", condition.STRING)))
config, _ := fieldx.SchemaFromMap(map[string]any{
"notify": map[string]any{
"type": "string",
"value": "true",
},
"message": map[string]any{
"type": "string",
"value": "申请已自动通过",
},
})
// 创建条件分支,使用 action 名称字符串
condCase := condflow.NewCondCase("auto-approve-small", cond, "Approval/ApproveExecutor@AutoApprove").
WithPriority(100).
WithActionConfig(config)
// 添加到流程
s.flow.AddCase(condCase)
// 准备输入数据
input := reflux.New(map[string]any{
"amount": 500.0,
"user": map[string]any{
"id": 1,
"credit_score": 80,
},
})
// 执行流程
err := s.flow.Run(s.app, input, base.GetSystemUser())
s.NoError(err)
}
// TestManagerApproval 测试经理审批
func (s *CondFlowTestSuite) TestManagerApproval() {
// 创建条件: 1000 < 金额 <= 10000
cond := condition.New(condition.Describe("经理审批"))
cond.SetExpr(condition.NewExpr("@", "amount", condition.Operator(condition.GT), condition.Token("1000", condition.STRING))).
SetExpr(condition.NewExpr("@", "amount", condition.Operator(condition.LE), condition.Token("10000", condition.STRING)))
config, _ := fieldx.SchemaFromMap(map[string]any{
"approver_role": map[string]any{
"type": "string",
"value": "manager",
},
"timeout_hours": map[string]any{
"type": "string",
"value": "24",
},
})
// 创建条件分支,使用 action 名称字符串
condCase := condflow.NewCondCase("manager-approval", cond, "Approval/ApproveExecutor@Manager").
WithPriority(90).
WithActionConfig(config)
// 添加到流程
s.flow.AddCase(condCase)
// 准备输入数据
input := reflux.New(map[string]any{
"amount": 5000.0,
"user": map[string]any{
"id": 1,
"credit_score": 80,
},
})
// 执行流程
err := s.flow.Run(s.app, input, base.GetSystemUser())
s.NoError(err)
}
// TestLowCreditReject 测试低信用评分拒绝
func (s *CondFlowTestSuite) TestLowCreditReject() {
// 创建条件: 用户信用评分 < 60
cond := condition.New(condition.Describe("信用不足拒绝"))
cond.SetExpr(condition.NewExpr("@", "user->>'credit_score'", condition.Operator(condition.LT), condition.Token("60", condition.STRING)))
config, _ := fieldx.SchemaFromMap(map[string]any{
"reason": map[string]any{
"type": "string",
"value": "信用评分不足",
},
"notify": map[string]any{
"type": "string",
"value": "true",
},
"suggest_retry": map[string]any{
"type": "string",
"value": "false",
},
})
// 创建条件分支,使用 action 名称字符串
condCase := condflow.NewCondCase("reject-low-credit", cond, "Approval/ApproveExecutor@Reject").
WithPriority(200).
WithActionConfig(config)
// 添加到流程
s.flow.AddCase(condCase)
// 准备输入数据
input := reflux.New(map[string]any{
"amount": 500.0,
"user": map[string]any{
"id": 1,
"credit_score": 50,
},
})
// 执行流程
err := s.flow.Run(s.app, input, base.GetSystemUser())
s.NoError(err)
}
// TestMultipleCases 测试多个条件分支
func (s *CondFlowTestSuite) TestMultipleCases() {
// Case 1: 小额自动审批
case1Cond := condition.New(condition.Describe("小额自动审批"))
case1Cond.SetExpr(condition.NewExpr("@", "amount", condition.Operator(condition.LE), condition.Token("1000", condition.STRING)))
case1 := condflow.NewCondCase("auto-approve-small", case1Cond, "Approval/ApproveExecutor@AutoApprove").
WithPriority(100)
// Case 2: 经理审批
case2Cond := condition.New(condition.Describe("经理审批"))
case2Cond.SetExpr(condition.NewExpr("@", "amount", condition.Operator(condition.GT), condition.Token("1000", condition.STRING))).
SetExpr(condition.NewExpr("@", "amount", condition.Operator(condition.LE), condition.Token("10000", condition.STRING)))
case2 := condflow.NewCondCase("manager-approval", case2Cond, "Approval/ApproveExecutor@Manager").
WithPriority(90)
// Case 3: 总监审批
case3Cond := condition.New(condition.Describe("总监审批"))
case3Cond.SetExpr(condition.NewExpr("@", "amount", condition.Operator(condition.GT), condition.Token("10000", condition.STRING)))
case3 := condflow.NewCondCase("director-approval", case3Cond, "Approval/ApproveExecutor@Director").
WithPriority(80)
// Case 4: 低信用拒绝(最高优先级)
case4Cond := condition.New(condition.Describe("信用不足拒绝"))
case4Cond.SetExpr(condition.NewExpr("@", "user->>'credit_score'", condition.Operator(condition.LT), condition.Token("60", condition.STRING)))
case4Config, _ := fieldx.SchemaFromMap(map[string]any{
"reason": map[string]any{
"type": "string",
"value": "信用评分不足",
},
})
case4 := condflow.NewCondCase("reject-low-credit", case4Cond, "Approval/ApproveExecutor@Reject").
WithPriority(200).
WithActionConfig(case4Config)
// 添加所有条件分支
s.flow.AddCase(case4).
AddCase(case1).
AddCase(case2).
AddCase(case3)
// 测试不同的输入数据
testCases := []struct {
name string
input map[string]any
expect string
}{
{
name: "小额正常用户",
input: map[string]any{
"amount": 500.0,
"user": map[string]any{
"id": 1,
"credit_score": 80,
},
},
expect: "auto-approve",
},
{
name: "中额正常用户",
input: map[string]any{
"amount": 5000.0,
"user": map[string]any{
"id": 1,
"credit_score": 80,
},
},
expect: "manager-approve",
},
{
name: "大额正常用户",
input: map[string]any{
"amount": 15000.0,
"user": map[string]any{
"id": 1,
"credit_score": 80,
},
},
expect: "director-approve",
},
{
name: "低信用用户",
input: map[string]any{
"amount": 500.0,
"user": map[string]any{
"id": 1,
"credit_score": 50,
},
},
expect: "reject",
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
input := reflux.New(tc.input)
err := s.flow.Run(s.app, input, base.GetSystemUser())
s.NoError(err)
})
}
}
// TestSuite 运行测试套件
func TestCondFlow(t *testing.T) {
suite.Run(t, new(CondFlowTestSuite))
}