Files
dingtalk-workspace-cli/internal/helpers/oa_coverage_more_test.go
mingyue.gmy 20c901d7ae fix(oa): require processCode in list-by-admin --request payloads
- Reject --request payloads with a missing, empty, or non-string
  processCode; the backend answers a bad processCode with success:true
  and an empty list, so validate client-side like startTime
- Add regression cases to keep changed-code coverage at 100%
2026-08-19 18:06:27 +08:00

292 lines
12 KiB
Go

package helpers
import (
"io"
"os"
"strings"
"testing"
)
func executeOACommand(t *testing.T, caller *scriptedToolCaller, args ...string) error {
t.Helper()
previous := deps
previousArgs := os.Args
os.Args = []string{"dws", "oa"}
InitDeps(caller)
deps.Out.w = io.Discard
deps.Out.errW = io.Discard
t.Cleanup(func() {
deps = previous
os.Args = previousArgs
})
cmd := newOaCommand()
cmd.PersistentFlags().Bool("yes", false, "跳过确认")
cmd.SilenceErrors = true
cmd.SilenceUsage = true
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
cmd.SetArgs(args)
return cmd.Execute()
}
func TestCrossPlatformCoverageOARemainingTimeAndRevertBranches(t *testing.T) {
installScriptedCaller(t, &scriptedToolCaller{dry: true})
for _, args := range [][]string{
{"approval", "list-pending", "--start", "bad", "--end", "2030-01-01T10:00:00+08:00"},
{"approval", "list-pending", "--start", "2030-01-01T09:00:00+08:00", "--end", "bad"},
{"approval", "list-pending", "--start", "2030-01-01T10:00:00+08:00", "--end", "2030-01-01T09:00:00+08:00"},
{"approval", "list-initiated", "--process-code", "code", "--start", "bad", "--end", "2030-01-01T10:00:00+08:00"},
{"approval", "list-initiated", "--process-code", "code", "--start", "2030-01-01T09:00:00+08:00", "--end", "bad"},
{"approval", "list-initiated", "--process-code", "code", "--start", "2030-01-01T10:00:00+08:00", "--end", "2030-01-01T09:00:00+08:00"},
} {
if err := executeFilterCoverage(t, newOaCommand(), args...); err == nil {
t.Fatalf("args=%v returned nil", args)
}
}
if err := executeFilterCoverage(t, newOaCommand(),
"approval", "list-pending",
"--start", "2030-01-01T09:00:00+08:00", "--end", "2030-01-01T10:00:00+08:00",
"--page", "2", "--size", "20", "--query", "travel",
); err != nil {
t.Fatalf("pending options: %v", err)
}
if err := executeFilterCoverage(t, newOaCommand(),
"approval", "revert-task", "--instance-id", "instance", "--task-id", "12",
"--target-activity-id", "activity", "--action", "REVERT_FOR_APPROVAL", "--remark", "retry",
); err != nil {
t.Fatalf("revert task: %v", err)
}
}
func TestCrossPlatformCoverageOAApprovalCreateInstanceMapsInternalSimpleOptions(t *testing.T) {
caller := &scriptedToolCaller{}
err := executeOACommand(t, caller,
"approval", "create-instance",
"--process-code", "PROC",
"--form-values", `{"事由":"测试"}`,
"--originator-user-id", "originator",
"--approvers", "approver-1,approver-2",
"--approvers-action-type", "AND",
"--cc-list", "cc-1,cc-2",
"--cc-position", "FINISH",
"--yes",
)
if err != nil {
t.Fatalf("create instance: %v", err)
}
if caller.server != "oa" || caller.tool != "start_process_instance" {
t.Fatalf("called %s/%s, want oa/start_process_instance", caller.server, caller.tool)
}
request, ok := caller.args["ProcessInstanceCreationPopRequest"].(map[string]any)
if !ok {
t.Fatalf("request payload = %#v", caller.args)
}
if got := request["originatorUserId"]; got != "originator" {
t.Fatalf("originatorUserId = %#v", got)
}
approvers, ok := request["approvers"].([]map[string]any)
if !ok || len(approvers) != 1 || approvers[0]["actionType"] != "AND" {
t.Fatalf("approvers = %#v", request["approvers"])
}
if got := approvers[0]["userIds"]; len(got.([]string)) != 2 || got.([]string)[0] != "approver-1" || got.([]string)[1] != "approver-2" {
t.Fatalf("approver userIds = %#v", got)
}
if got := request["ccList"]; len(got.([]string)) != 2 || got.([]string)[0] != "cc-1" || got.([]string)[1] != "cc-2" {
t.Fatalf("ccList = %#v", got)
}
if got := request["ccPosition"]; got != "FINISH" {
t.Fatalf("ccPosition = %#v", got)
}
}
func TestCrossPlatformCoverageOAApprovalCreateInstanceRejectsMixedRequestModes(t *testing.T) {
caller := &scriptedToolCaller{}
err := executeOACommand(t, caller,
"approval", "create-instance",
"--request", `{"processCode":"PROC"}`,
"--process-code", "PROC",
"--yes",
)
if err == nil {
t.Fatal("mixed request modes returned nil")
}
if caller.calls != 0 {
t.Fatalf("unexpected MCP call count: %d", caller.calls)
}
}
func TestCrossPlatformCoverageOAApprovalCreateInstanceRequiresExplicitYes(t *testing.T) {
caller := &scriptedToolCaller{}
err := executeOACommand(t, caller,
"approval", "create-instance",
"--request", `{"processCode":"PROC"}`,
)
if err == nil || !strings.Contains(err.Error(), "--yes") {
t.Fatalf("create instance without --yes error = %v, want explicit --yes requirement", err)
}
if caller.calls != 0 {
t.Fatalf("create instance without --yes made %d MCP calls", caller.calls)
}
}
func TestCrossPlatformCoverageOAApprovalListByAdminMapsSimpleOptions(t *testing.T) {
caller := &scriptedToolCaller{}
err := executeOACommand(t, caller,
"approval", "list-by-admin",
"--process-code", "PROC",
"--start", "2030-01-01T09:00:00+08:00",
"--end", "2030-01-01T10:00:00+08:00",
"--cursor", "5",
"--limit", "20",
"--user-ids", "user-1,user-2",
"--statuses", "RUNNING,COMPLETED",
)
if err != nil {
t.Fatalf("list by admin: %v", err)
}
if caller.server != "oa" || caller.tool != "get_process_instances_by_admin" {
t.Fatalf("called %s/%s, want oa/get_process_instances_by_admin", caller.server, caller.tool)
}
request, ok := caller.args["ProcessInstanceListQueryRequest"].(map[string]any)
if !ok {
t.Fatalf("request payload = %#v", caller.args)
}
if got := request["processCode"]; got != "PROC" {
t.Fatalf("processCode = %#v", got)
}
if got := request["cursor"]; got != float64(5) {
t.Fatalf("cursor = %#v", got)
}
if got := request["pageSize"]; got != float64(20) {
t.Fatalf("pageSize = %#v", got)
}
if got := request["startTime"]; got != "2030-01-01 09:00:00" {
t.Fatalf("startTime = %#v", got)
}
if got := request["endTime"]; got != "2030-01-01 10:00:00" {
t.Fatalf("endTime = %#v", got)
}
if got := request["userIds"]; len(got.([]string)) != 2 || got.([]string)[0] != "user-1" || got.([]string)[1] != "user-2" {
t.Fatalf("userIds = %#v", got)
}
if got := request["statuses"]; len(got.([]string)) != 2 || got.([]string)[0] != "RUNNING" || got.([]string)[1] != "COMPLETED" {
t.Fatalf("statuses = %#v", got)
}
}
func TestCrossPlatformCoverageOAApprovalNewCommandValidationAndRequestModes(t *testing.T) {
validCases := []struct {
name string
args []string
tool string
}{
{
name: "form schema",
args: []string{"approval", "form-schema", "--process-code", "PROC"},
tool: "get_process_schema",
},
{
name: "forecast simple mode",
args: []string{"approval", "forecast-process", "--process-code", "PROC", "--dept-id", "-1", "--form-values", `{"金额":"100"}`},
tool: "forecast_process",
},
{
name: "forecast request mode",
args: []string{"approval", "forecast-process", "--request", `{"processCode":"PROC"}`},
tool: "forecast_process",
},
{
name: "create request mode",
args: []string{"approval", "create-instance", "--request", `{"processCode":"PROC"}`, "--yes"},
tool: "start_process_instance",
},
{
name: "list-by-admin simple mode",
args: []string{"approval", "list-by-admin", "--process-code", "PROC", "--start", "2030-01-01T09:00:00+08:00"},
tool: "get_process_instances_by_admin",
},
{
name: "list-by-admin request mode",
args: []string{"approval", "list-by-admin", "--request", `{"processCode":"PROC","startTime":"2030-01-01 09:00:00","cursor":0,"pageSize":20}`},
tool: "get_process_instances_by_admin",
},
{
name: "list-by-admin request mode without pageSize",
args: []string{"approval", "list-by-admin", "--request", `{"processCode":"PROC","startTime":"2030-01-01 09:00:00","cursor":0}`},
tool: "get_process_instances_by_admin",
},
{
name: "list-by-admin request mode with endTime",
args: []string{"approval", "list-by-admin", "--request", `{"processCode":"PROC","startTime":"2030-01-01 09:00:00","endTime":"2030-01-01 23:59:59","cursor":0,"pageSize":20}`},
tool: "get_process_instances_by_admin",
},
}
for _, tc := range validCases {
t.Run(tc.name, func(t *testing.T) {
caller := &scriptedToolCaller{}
if err := executeOACommand(t, caller, tc.args...); err != nil {
t.Fatalf("execute %v: %v", tc.args, err)
}
if caller.tool != tc.tool || caller.calls != 1 {
t.Fatalf("called tool=%q calls=%d, want %q once", caller.tool, caller.calls, tc.tool)
}
})
}
invalidCases := [][]string{
{"approval", "form-schema"},
{"approval", "forecast-process"},
{"approval", "forecast-process", "--request", `{"processCode":"PROC"}`, "--process-code", "PROC"},
{"approval", "forecast-process", "--request", "{"},
{"approval", "forecast-process", "--request", "null"},
{"approval", "forecast-process", "--request", "{} {}"},
{"approval", "forecast-process", "--process-code", "PROC", "--dept-id", "bad", "--form-values", `{"金额":"100"}`},
{"approval", "forecast-process", "--process-code", "PROC", "--dept-id", "-1", "--form-values", "["},
{"approval", "create-instance", "--process-code", "PROC", "--form-values", `{}`},
{"approval", "create-instance", "--yes"},
{"approval", "create-instance", "--request", "{", "--yes"},
{"approval", "create-instance", "--request", "null", "--yes"},
{"approval", "create-instance", "--request", "{} {}", "--yes"},
{"approval", "create-instance", "--process-code", "PROC", "--form-values", "[", "--yes"},
{"approval", "create-instance", "--process-code", "PROC", "--form-values", `{}`, "--dept-id", "bad", "--yes"},
{"approval", "create-instance", "--process-code", "PROC", "--form-values", `{}`, "--approvers", "u", "--approvers-action-type", "bad", "--yes"},
{"approval", "create-instance", "--process-code", "PROC", "--form-values", `{}`, "--cc-list", "u", "--cc-position", "bad", "--yes"},
{"approval", "list-by-admin"},
{"approval", "list-by-admin", "--process-code", "PROC"},
{"approval", "list-by-admin", "--process-code", "PROC", "--start", "bad"},
{"approval", "list-by-admin", "--process-code", "PROC", "--start", "2030-01-01T10:00:00+08:00", "--end", "bad"},
{"approval", "list-by-admin", "--process-code", "PROC", "--start", "2030-01-01T10:00:00+08:00", "--end", "2030-01-01T09:00:00+08:00"},
{"approval", "list-by-admin", "--process-code", "PROC", "--start", "2030-01-01T09:00:00+08:00", "--cursor", "bad"},
{"approval", "list-by-admin", "--process-code", "PROC", "--start", "2030-01-01T09:00:00+08:00", "--limit", "bad"},
{"approval", "list-by-admin", "--process-code", "PROC", "--start", "2030-01-01T09:00:00+08:00", "--limit", "21"},
{"approval", "list-by-admin", "--process-code", "PROC", "--start", "2030-01-01T09:00:00+08:00", "--limit", "0"},
{"approval", "list-by-admin", "--process-code", "PROC", "--start", ""},
{"approval", "list-by-admin", "--request", "{"},
{"approval", "list-by-admin", "--request", "null"},
{"approval", "list-by-admin", "--request", `{"processCode":"PROC"}`, "--process-code", "PROC"},
{"approval", "list-by-admin", "--request", `{"processCode":"PROC","startTime":"2030-01-01 09:00:00","cursor":0,"pageSize":21}`},
{"approval", "list-by-admin", "--request", `{"processCode":"PROC","startTime":"2030-01-01 09:00:00","cursor":0,"pageSize":"20"}`},
{"approval", "list-by-admin", "--request", `{"processCode":"PROC","startTime":1893459600000,"cursor":0,"pageSize":20}`},
{"approval", "list-by-admin", "--request", `{"processCode":"PROC","startTime":"2030-01-01","cursor":0,"pageSize":20}`},
{"approval", "list-by-admin", "--request", `{"processCode":"PROC","startTime":"2030-01-01 10:00:00","endTime":"2030-01-01 09:00:00","cursor":0,"pageSize":20}`},
{"approval", "list-by-admin", "--request", `{"processCode":"PROC","startTime":"2030-01-01 09:00:00","endTime":1893463200000,"cursor":0,"pageSize":20}`},
{"approval", "list-by-admin", "--request", `{"processCode":"PROC","endTime":"NOT-A-TIME","cursor":0,"pageSize":20}`},
{"approval", "list-by-admin", "--request", `{"processCode":"PROC","startTime":"2030-01-01 09:00:00","endTime":"2030-01-01","cursor":0,"pageSize":20}`},
{"approval", "list-by-admin", "--request", `{"processCode":"PROC","startTime":"2030-01-01 09:00:00","endTime":"2030-01-01 09:00:00","cursor":0,"pageSize":20}`},
{"approval", "list-by-admin", "--request", `{"startTime":"2030-01-01 09:00:00","cursor":0,"pageSize":20}`},
{"approval", "list-by-admin", "--request", `{"processCode":"","startTime":"2030-01-01 09:00:00","cursor":0,"pageSize":20}`},
{"approval", "list-by-admin", "--request", `{"processCode":123,"startTime":"2030-01-01 09:00:00","cursor":0,"pageSize":20}`},
}
for _, args := range invalidCases {
caller := &scriptedToolCaller{}
if err := executeOACommand(t, caller, args...); err == nil {
t.Fatalf("invalid args %v returned nil", args)
}
if caller.calls != 0 {
t.Fatalf("invalid args %v made %d MCP calls", args, caller.calls)
}
}
}