Files
dingtalk-workspace-cli/internal/helpers/todo_command_edges_more_test.go
T
玉澜andCursor 7773eb27f1 refactor(corecmd): rename package and finish ContractFinal leaf migration
Move cmdcore to corecmd, declare attendance ContractFinal in helpers, keep
Sheet destructive commands --yes-only, and align catalog/policy provenance.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-31 14:13:46 +08:00

284 lines
10 KiB
Go

package helpers
import (
"context"
"errors"
"io"
"os"
"path/filepath"
"strings"
"testing"
)
func executeTodoEdge(t *testing.T, caller *scriptedToolCaller, args ...string) error {
t.Helper()
previous := deps
previousArgs := os.Args
os.Args = append([]string{"dws", "todo"}, os.Args[1:]...)
InitDeps(caller)
deps.Out.w = io.Discard
deps.Out.errW = io.Discard
defer func() {
deps = previous
os.Args = previousArgs
}()
cmd := newTodoCommand()
if cmd.PersistentFlags().Lookup("yes") == nil {
cmd.PersistentFlags().Bool("yes", false, "confirm execution")
}
if cmd.PersistentFlags().Lookup("dry-run") == nil {
cmd.PersistentFlags().Bool("dry-run", false, "preview without executing")
}
cmd.SilenceErrors = true
cmd.SilenceUsage = true
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
cmd.SetIn(os.Stdin)
cmd.SetArgs(args)
return cmd.Execute()
}
func TestCrossPlatformCoverageTodoCreateAndListCommandEdges(t *testing.T) {
validDate := "2026-03-10T18:00:00+08:00"
errorCases := [][]string{
{"task", "create", "--title", "x", "--executors", "u", "--remind-at", validDate},
{"task", "create", "--title", "x", "--executors", "u", "--due", "bad"},
{"task", "create-sub", "--title", "x", "--executors", "u", "--parent-id", "abc"},
{"task", "create-sub", "--title", "x", "--executors", "u", "--parent-id", "1", "--remind-at", validDate},
{"task", "create-sub", "--title", "x", "--executors", "u", "--parent-id", "1", "--due", "bad"},
{"task", "list", "--role-types", "invalid"},
{"task", "list", "--plan-finish-date-start", "bad"},
{"task", "list", "--plan-finish-date-end", "bad"},
}
for _, args := range errorCases {
if err := executeTodoEdge(t, &scriptedToolCaller{}, args...); err == nil {
t.Fatalf("expected error for %v", args)
}
}
validCases := [][]string{
{"task", "create", "--title", "x", "--executors", "u1,u2", "--due", validDate, "--priority", "40", "--recurrence", "daily"},
{"task", "create", "--subject", "x", "--executors", "u", "--priority", "bad"},
{"task", "create-sub", "--content", "x", "--executors", "u", "--parent-id", "1", "--due", validDate, "--priority", "40", "--recurrence", "daily"},
{"task", "create-sub", "--title", "x", "--executors", "u", "--parent-id", "1", "--priority", "bad"},
{"task", "list", "--size", "bad"},
{"task", "list", "--status", "true", "--priority", "40,bad", "--role-types", "creator,executor", "--plan-finish-date-start", validDate, "--plan-finish-date-end", validDate},
}
for _, args := range validCases {
if err := executeTodoEdge(t, &scriptedToolCaller{}, args...); err != nil {
t.Fatalf("execute %v: %v", args, err)
}
}
if err := executeTodoEdge(t, &scriptedToolCaller{dry: true}, "task", "list", "--size", "21"); err != nil {
t.Fatalf("dry auto-page: %v", err)
}
}
func TestCrossPlatformCoverageTodoSimpleCommandValidationAndSuccessEdges(t *testing.T) {
errorCases := [][]string{
{"task", "create"},
{"task", "create", "--title", "x"},
{"task", "create-sub"},
{"task", "create-sub", "--title", "x"},
{"task", "list-sub"},
{"task", "update"},
{"task", "done"},
{"task", "get"},
{"task", "delete"},
{"task", "add-executor"},
{"task", "remove-executor"},
{"task", "add-participant"},
{"task", "remove-participant"},
{"task", "add-reminder"},
{"task", "reset-reminder"},
{"task", "add-attachment"},
{"task", "list-attachment"},
{"task", "remove-attachment"},
{"comment", "add"},
{"comment", "list"},
{"comment", "delete"},
}
for _, args := range errorCases {
if err := executeTodoEdge(t, &scriptedToolCaller{}, args...); err == nil {
t.Fatalf("expected validation error for %v", args)
}
}
validCases := []struct {
args []string
steps []scriptedToolStep
}{
{args: []string{"task", "list-sub", "--task-id", "1"}},
{
args: []string{"task", "done", "--task-id", "1", "--status", "true"},
steps: []scriptedToolStep{
{text: `{"result":{"todoDetailModel":{"taskId":"1"}}}`},
{text: `{}`},
},
},
{args: []string{"task", "add-executor", "--task-id", "1", "--executors", "u1,u2"}},
{args: []string{"task", "remove-executor", "--task-id", "1", "--executors", "u1,u2"}},
{args: []string{"task", "add-participant", "--task-id", "1", "--participants", "u1,u2"}},
{args: []string{"task", "remove-participant", "--task-id", "1", "--participants", "u1,u2"}},
{
args: []string{"task", "list-attachment", "--task-id", "1"},
steps: []scriptedToolStep{
{text: `{"result":{"todoDetailModel":{"taskId":"1"}}}`},
{text: `{}`},
},
},
{args: []string{"comment", "add", "--task-id", "1", "--content", "hello"}},
{args: []string{"comment", "list", "--task-id", "1"}},
}
for _, tc := range validCases {
if err := executeTodoEdge(t, &scriptedToolCaller{steps: tc.steps}, tc.args...); err != nil {
t.Fatalf("execute %v: %v", tc.args, err)
}
}
}
func TestCrossPlatformCoverageTodoUpdateReminderAndDetailEdges(t *testing.T) {
validDate := "2026-03-10T18:00:00+08:00"
errorCases := [][]string{
{"task", "update", "--task-id", "1", "--remind-at", validDate},
{"task", "update", "--task-id", "1", "--due", "bad"},
{"task", "add-reminder", "--task-id", "1", "--base-time", "customTime", "--reminder-time-stamp", "bad"},
{"task", "reset-reminder", "--task-id", "1", "--reminder-rules", `[{"baseTime":"customTime","reminderTimeStamp":"bad"}]`},
{"task", "reset-reminder", "--task-id", "1", "--reminder-rules", `not-json`},
{"task", "reset-reminder", "--task-id", "1", "--reminder-rules", `[1,{"baseTime":"dueTime"},{"baseTime":"customTime","reminderTimeStamp":"` + validDate + `"}]`},
}
for _, args := range errorCases {
if err := executeTodoEdge(t, &scriptedToolCaller{}, args...); err == nil {
t.Fatalf("expected error for %v", args)
}
}
validCases := [][]string{
{"task", "update", "--task-id", "1", "--title", "new", "--due", validDate, "--priority", "40", "--done", "true"},
{"task", "update", "--task-id", "1", "--priority", "bad"},
{"task", "add-reminder", "--task-id", "1", "--base-time", "dueTime", "--due-date-offset", "-10"},
{"task", "add-reminder", "--task-id", "1", "--base-time", "customTime", "--reminder-time-stamp", validDate},
}
for _, args := range validCases {
if err := executeTodoEdge(t, &scriptedToolCaller{}, args...); err != nil {
t.Fatalf("execute %v: %v", args, err)
}
}
details := []scriptedToolStep{
{text: ""},
{text: "not-json"},
{text: `{"result":{"todoDetailModel":{"detailUrl":{"appUrl":"https://app.example/a b","pcUrl":"https://pc.example/a b"}}}}`},
}
for _, step := range details {
if err := executeTodoEdge(t, &scriptedToolCaller{steps: []scriptedToolStep{step}}, "task", "get", "--task-id", "1"); err != nil {
t.Fatalf("detail %q: %v", step.text, err)
}
}
boom := errors.New("boom")
if err := executeTodoEdge(t, &scriptedToolCaller{steps: []scriptedToolStep{{err: boom}}}, "task", "get", "--task-id", "1"); err == nil {
t.Fatal("detail tool error returned nil")
}
}
func TestCrossPlatformCoverageTodoAttachmentCommandEdges(t *testing.T) {
file := filepath.Join(t.TempDir(), "attachment.txt")
if err := os.WriteFile(file, []byte("data"), 0o600); err != nil {
t.Fatal(err)
}
if err := executeTodoEdge(t, &scriptedToolCaller{dry: true}, "task", "add-attachment", "--task-id", "1", "--file-path", file); err != nil {
t.Fatalf("dry attachment: %v", err)
}
if err := executeTodoEdge(t, &scriptedToolCaller{}, "task", "add-attachment", "--task-id", "1", "--file-path", file+".missing"); err == nil {
t.Fatal("missing attachment returned nil")
}
originalPut := httpPutFile
defer func() { httpPutFile = originalPut }()
httpPutFile = func(context.Context, string, map[string]string, string, int64) error { return nil }
credential := `{"resourceUrl":"https://upload.invalid","uploadKey":"key"}`
for _, tc := range []struct {
name string
steps []scriptedToolStep
ok bool
}{
{"upload-error", []scriptedToolStep{{err: errors.New("upload")}}, false},
{"commit-parse-error", []scriptedToolStep{{text: credential}, {text: `{}`}}, false},
{"success", []scriptedToolStep{{text: credential}, {text: `{"dentryId":1,"spaceId":2}`}, {text: `{}`}}, true},
} {
err := executeTodoEdge(t, &scriptedToolCaller{steps: tc.steps}, "task", "add-attachment", "--task-id", "1", "--file-path", file)
if tc.ok && err != nil {
t.Fatalf("%s: %v", tc.name, err)
}
if !tc.ok && err == nil {
t.Fatalf("%s returned nil", tc.name)
}
}
}
func TestCrossPlatformCoverageTodoDeleteCancellationAndConfirmationEdges(t *testing.T) {
originalArgs, originalStdin := os.Args, os.Stdin
defer func() {
os.Args = originalArgs
os.Stdin = originalStdin
}()
os.Args = []string{"dws"}
for _, args := range [][]string{
{"task", "delete", "--task-id", "1"},
// remove-attachment is schema-excluded and still uses confirmDelete (nil cancel).
{"task", "remove-attachment", "--task-id", "1", "--attachment-id", "2"},
{"comment", "delete", "--task-id", "1", "--comment-id", "2"},
} {
readEnd, writeEnd, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
if _, err := writeEnd.WriteString("no\n"); err != nil {
t.Fatal(err)
}
_ = writeEnd.Close()
os.Stdin = readEnd
err = executeTodoEdge(t, &scriptedToolCaller{}, args...)
_ = readEnd.Close()
switch args[1] {
case "remove-attachment":
if err != nil {
t.Fatalf("cancel %v: %v", args, err)
}
default:
// Contract ConfirmSafety returns a typed cancel error (not silent nil).
if err == nil || !strings.Contains(err.Error(), "用户取消了操作") {
t.Fatalf("cancel %v: error = %v, want 用户取消了操作", args, err)
}
}
}
os.Args = []string{"dws", "--yes"}
for _, args := range [][]string{
{"task", "delete", "--task-id", "1", "--yes"},
{"task", "remove-attachment", "--task-id", "1", "--attachment-id", "2", "--yes"},
{"comment", "delete", "--task-id", "1", "--comment-id", "2", "--yes"},
} {
if err := executeTodoEdge(t, &scriptedToolCaller{}, args...); err != nil {
t.Fatalf("confirm %v: %v", args, err)
}
}
}
func TestCrossPlatformCoverageTodoMD5AndEmptyListEdges(t *testing.T) {
file := filepath.Join(t.TempDir(), "file.txt")
if err := os.WriteFile(file, []byte("data"), 0o600); err != nil {
t.Fatal(err)
}
original := todoFileMD5Hex
todoFileMD5Hex = func(string) (string, error) { return "", errors.New("md5") }
defer func() { todoFileMD5Hex = original }()
if _, err := buildTodoLocalFileMeta(file, "", ""); err == nil {
t.Fatal("md5 error returned nil")
}
if got := parseStringList(", ,"); got != nil {
t.Fatalf("empty list=%v", got)
}
}