// Copyright 2026 Alibaba Group // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. package helpers import ( "bytes" "context" "encoding/json" "errors" "io" "os" "path/filepath" "reflect" "strings" "testing" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" "github.com/spf13/cobra" ) type markdownDriveCall struct { server string tool string args map[string]any } type markdownDriveStep struct { text string err error } type markdownDriveCaller struct { steps []markdownDriveStep calls []markdownDriveCall format string dryRun bool } func (c *markdownDriveCaller) CallTool(_ context.Context, server, tool string, args map[string]any) (*edition.ToolResult, error) { copied := make(map[string]any, len(args)) for key, value := range args { copied[key] = value } c.calls = append(c.calls, markdownDriveCall{server: server, tool: tool, args: copied}) index := len(c.calls) - 1 if index >= len(c.steps) { return &edition.ToolResult{}, nil } step := c.steps[index] if step.err != nil { return nil, step.err } return &edition.ToolResult{Content: []edition.ContentBlock{{Type: "text", Text: step.text}}}, nil } func (c *markdownDriveCaller) Format() string { return c.format } func (c *markdownDriveCaller) DryRun() bool { return c.dryRun } func (*markdownDriveCaller) Fields() string { return "" } func (*markdownDriveCaller) JQ() string { return "" } func installMarkdownDriveDeps(t *testing.T, caller *markdownDriveCaller) (*bytes.Buffer, *bytes.Buffer) { t.Helper() previousDeps := deps previousPut := httpPutFile previousGet := httpGetFile InitDeps(caller) stdout := &bytes.Buffer{} stderr := &bytes.Buffer{} deps.Out = &Formatter{w: stdout, errW: stderr} t.Cleanup(func() { deps = previousDeps httpPutFile = previousPut httpGetFile = previousGet }) return stdout, stderr } func executeMarkdownDriveCommand(t *testing.T, product *cobra.Command, input io.Reader, args ...string) error { t.Helper() root := &cobra.Command{Use: "dws", SilenceErrors: true, SilenceUsage: true} root.PersistentFlags().Bool("dry-run", false, "preview only") root.PersistentFlags().BoolP("yes", "y", false, "skip confirmation") root.AddCommand(product) root.SetArgs(args) if input == nil { input = strings.NewReader("") } root.SetIn(input) root.SetOut(io.Discard) if deps != nil && deps.Out != nil { root.SetErr(deps.Out.errW) } else { root.SetErr(io.Discard) } return root.Execute() } func executeMarkdownGlobalDryRun(t *testing.T, product *cobra.Command, args ...string) error { t.Helper() root := &cobra.Command{Use: "dws", SilenceErrors: true, SilenceUsage: true} root.PersistentFlags().Bool("dry-run", false, "preview only") root.PersistentFlags().BoolP("yes", "y", false, "skip confirmation") if err := root.PersistentFlags().Set("dry-run", "true"); err != nil { t.Fatal(err) } root.AddCommand(product) root.SetArgs(args) root.SetIn(strings.NewReader("")) root.SetOut(io.Discard) if deps != nil && deps.Out != nil { root.SetErr(deps.Out.errW) } return root.Execute() } func writeMarkdownDriveFixture(t *testing.T, name, content string) string { t.Helper() path := filepath.Join(t.TempDir(), name) if err := os.WriteFile(path, []byte(content), 0o600); err != nil { t.Fatal(err) } return path } func installMarkdownHTTPGet(t *testing.T, content string) { t.Helper() httpGetFile = func(_ context.Context, _ string, _ map[string]string, destPath string) error { return os.WriteFile(destPath, []byte(content), 0o600) } } func TestDriveUploadOverwriteRoutesAndConfirms(t *testing.T) { t.Run("node and folder are mutually exclusive", func(t *testing.T) { caller := &markdownDriveCaller{format: "json"} installMarkdownDriveDeps(t, caller) path := writeMarkdownDriveFixture(t, "payload.md", "body") err := executeMarkdownDriveCommand(t, newDriveCommand(), nil, "drive", "upload", "--file", path, "--node", "file-1", "--folder", "folder-1", "--yes") if err == nil || !strings.Contains(err.Error(), "--node 与 --folder 互斥") { t.Fatalf("expected mutual-exclusion error, got %v", err) } if len(caller.calls) != 0 { t.Fatalf("mutual-exclusion failure made MCP calls: %#v", caller.calls) } }) t.Run("space and workspace are mutually exclusive", func(t *testing.T) { caller := &markdownDriveCaller{format: "json"} installMarkdownDriveDeps(t, caller) path := writeMarkdownDriveFixture(t, "payload.md", "body") err := executeMarkdownDriveCommand(t, newDriveCommand(), nil, "drive", "upload", "--file", path, "--space-id", "space-1", "--workspace", "workspace-1") if err == nil || !strings.Contains(err.Error(), "--space-id 与 --workspace 互斥") { t.Fatalf("expected target-domain conflict, got %v", err) } if len(caller.calls) != 0 { t.Fatalf("target-domain conflict made MCP calls: %#v", caller.calls) } }) t.Run("overwrite dry run is a no-network JSON plan", func(t *testing.T) { for _, route := range []struct { name string args []string }{ {name: "drive", args: []string{"--space-id", "space-1"}}, {name: "doc", args: []string{"--workspace", "workspace-1"}}, } { t.Run(route.name, func(t *testing.T) { caller := &markdownDriveCaller{format: "json", dryRun: true} stdout, _ := installMarkdownDriveDeps(t, caller) path := writeMarkdownDriveFixture(t, "payload.md", "body") args := []string{"drive", "upload", "--file", path, "--node", "file-1"} args = append(args, route.args...) if err := executeMarkdownDriveCommand(t, newDriveCommand(), nil, args...); err != nil { t.Fatal(err) } if len(caller.calls) != 0 { t.Fatalf("dry run made MCP calls: %#v", caller.calls) } var payload map[string]any if err := json.Unmarshal(stdout.Bytes(), &payload); err != nil { t.Fatalf("dry-run output is not JSON: %q: %v", stdout.String(), err) } if payload["dry_run"] != true || payload["executed"] != false || payload["preview_kind"] != "plan" || payload["node_id"] != "file-1" || payload["source"] != route.name { t.Fatalf("dry-run payload = %#v", payload) } }) } }) t.Run("overwrite dry run is human readable", func(t *testing.T) { for _, route := range []struct { name string routeArgs []string wantTarget string }{ {name: "drive", routeArgs: []string{"--space-id", "space-1"}, wantTarget: "覆盖目标"}, {name: "doc", routeArgs: []string{"--workspace", "workspace-1"}, wantTarget: "覆盖目标"}, } { t.Run(route.name, func(t *testing.T) { caller := &markdownDriveCaller{format: "raw", dryRun: true} stdout, _ := installMarkdownDriveDeps(t, caller) path := writeMarkdownDriveFixture(t, "payload.md", "body") args := []string{"drive", "upload", "--file", path, "--node", "file-1"} args = append(args, route.routeArgs...) if err := executeMarkdownDriveCommand(t, newDriveCommand(), nil, args...); err != nil { t.Fatal(err) } if len(caller.calls) != 0 { t.Fatalf("human dry run made MCP calls: %#v", caller.calls) } if text := stdout.String(); !strings.Contains(text, route.wantTarget) || !strings.Contains(text, "file-1") { t.Fatalf("human dry-run output = %q", text) } }) } }) t.Run("drive overwrite uses explicit route and overwrite id", func(t *testing.T) { caller := &markdownDriveCaller{ format: "json", steps: []markdownDriveStep{ {text: `{"uploadId":"upload-1","resourceUrls":[{"url":"https://upload.test/drive","headers":{"x-test":"yes"}}]}`}, {text: `{"ok":true}`}, }, } installMarkdownDriveDeps(t, caller) path := writeMarkdownDriveFixture(t, "payload.md", "body") var uploaded string httpPutFile = func(_ context.Context, resourceURL string, headers map[string]string, filePath string, size int64) error { data, err := os.ReadFile(filePath) uploaded = string(data) if err != nil { return err } if resourceURL != "https://upload.test/drive" || headers["x-test"] != "yes" || size != 4 { t.Fatalf("unexpected PUT request: url=%q headers=%v size=%d", resourceURL, headers, size) } return nil } err := executeMarkdownDriveCommand(t, newDriveCommand(), nil, "drive", "upload", "--file", path, "--node", "file-1", "--space-id", "space-1", "--mime-type", "text/markdown", "--yes") if err != nil { t.Fatal(err) } if uploaded != "body" { t.Fatalf("uploaded content = %q", uploaded) } if len(caller.calls) != 2 { t.Fatalf("calls = %#v", caller.calls) } for _, call := range caller.calls { if call.server != "drive" { t.Fatalf("server = %q, want drive", call.server) } if call.args["overwriteFileId"] != "file-1" { t.Fatalf("%s overwriteFileId = %#v", call.tool, call.args["overwriteFileId"]) } if _, exists := call.args["parentId"]; exists { t.Fatalf("%s unexpectedly received parentId: %#v", call.tool, call.args) } } if caller.calls[0].tool != "get_upload_info" || caller.calls[1].tool != "commit_upload" { t.Fatalf("unexpected upload sequence: %#v", caller.calls) } }) t.Run("document overwrite uses standard dangerous confirmation", func(t *testing.T) { caller := &markdownDriveCaller{ format: "json", steps: []markdownDriveStep{ {text: `{"resourceUrl":"https://upload.test/doc","uploadKey":"key-1"}`}, {text: `{"ok":true}`}, }, } installMarkdownDriveDeps(t, caller) path := writeMarkdownDriveFixture(t, "payload.md", "body") httpPutFile = func(context.Context, string, map[string]string, string, int64) error { return nil } err := executeMarkdownDriveCommand(t, newDriveCommand(), nil, "drive", "upload", "--file", path, "--file-name", "renamed", "--node", "node-1", "--workspace", "workspace-1", "--convert", "--yes") if err != nil { t.Fatal(err) } if len(caller.calls) != 2 { t.Fatalf("calls = %#v", caller.calls) } wantStep1 := map[string]any{"workspaceId": "workspace-1", "overwriteNodeId": "node-1", "name": "renamed.md"} if !reflect.DeepEqual(caller.calls[0].args, wantStep1) { t.Fatalf("step1 args = %#v, want %#v", caller.calls[0].args, wantStep1) } if caller.calls[1].args["overwriteNodeId"] != "node-1" || caller.calls[1].args["convertToOnlineDoc"] != true { t.Fatalf("commit args = %#v", caller.calls[1].args) } }) t.Run("negative confirmation prevents all writes", func(t *testing.T) { caller := &markdownDriveCaller{format: "json"} _, stderr := installMarkdownDriveDeps(t, caller) path := writeMarkdownDriveFixture(t, "payload.md", "body") err := executeMarkdownDriveCommand(t, newDriveCommand(), strings.NewReader("no\n"), "drive", "upload", "--file", path, "--node", "file-1") if err != nil { t.Fatal(err) } if len(caller.calls) != 0 { t.Fatalf("cancelled overwrite made calls: %#v", caller.calls) } if text := stderr.String(); !strings.Contains(text, "overwrite drive file") || strings.Contains(strings.ToLower(text), "delete") { t.Fatalf("unexpected confirmation text: %q", text) } }) t.Run("negative document confirmation prevents all writes", func(t *testing.T) { caller := &markdownDriveCaller{format: "json"} _, stderr := installMarkdownDriveDeps(t, caller) path := writeMarkdownDriveFixture(t, "payload.md", "body") err := executeMarkdownDriveCommand(t, newDriveCommand(), strings.NewReader("no\n"), "drive", "upload", "--file", path, "--node", "node-1", "--workspace", "workspace-1") if err != nil { t.Fatal(err) } if len(caller.calls) != 0 { t.Fatalf("cancelled document overwrite made calls: %#v", caller.calls) } if text := stderr.String(); !strings.Contains(text, "overwrite document-space file") { t.Fatalf("unexpected confirmation text: %q", text) } }) } func TestMarkdownGlobalAndLocalDryRunAreDistinct(t *testing.T) { t.Run("global dry run reads root persistent flag without os args", func(t *testing.T) { caller := &markdownDriveCaller{format: "json"} stdout, _ := installMarkdownDriveDeps(t, caller) err := executeMarkdownGlobalDryRun(t, newMarkdownCommand(), "markdown", "overwrite") if err != nil { t.Fatal(err) } if len(caller.calls) != 0 { t.Fatalf("global preview made calls: %#v", caller.calls) } var payload map[string]any if err := json.Unmarshal(stdout.Bytes(), &payload); err != nil { t.Fatalf("global preview is not pure JSON: %v\n%s", err, stdout.String()) } if payload["dry_run"] != true || payload["executed"] != false { t.Fatalf("global preview payload = %#v", payload) } }) t.Run("real argv global dry run survives leaf flag shadowing", func(t *testing.T) { caller := &markdownDriveCaller{format: "json"} stdout, _ := installMarkdownDriveDeps(t, caller) previousArgs := os.Args os.Args = []string{ "dws", "--dry-run", "markdown", "overwrite", "--node", "node-1", "--content", "new", "--name", "README.md", } t.Cleanup(func() { os.Args = previousArgs }) if err := executeMarkdownDriveCommand(t, newMarkdownCommand(), nil, "--dry-run", "markdown", "overwrite", "--node", "node-1", "--content", "new", "--name", "README.md", ); err != nil { t.Fatal(err) } if len(caller.calls) != 0 { t.Fatalf("global dry run made MCP calls: %#v", caller.calls) } var payload map[string]any if err := json.Unmarshal(stdout.Bytes(), &payload); err != nil { t.Fatalf("global dry-run output is not JSON: %q: %v", stdout.String(), err) } if payload["dry_run"] != true || payload["preview_kind"] != "plan" { t.Fatalf("global dry-run payload = %#v", payload) } }) t.Run("local dry run downloads and renders diff", func(t *testing.T) { caller := &markdownDriveCaller{ format: "json", steps: []markdownDriveStep{ {text: `{"downloadUrl":"https://download.test/current.md","fileName":"current.md"}`}, }, } stdout, _ := installMarkdownDriveDeps(t, caller) installMarkdownHTTPGet(t, "old\n") path := writeMarkdownDriveFixture(t, "incoming.md", "new\n") err := executeMarkdownDriveCommand(t, newMarkdownCommand(), nil, "markdown", "overwrite", "--node", "file-1", "--file", path, "--name", "current.md", "--space-id", "space-1", "--dry-run") if err != nil { t.Fatal(err) } if len(caller.calls) != 1 || caller.calls[0].tool != "download_file" { t.Fatalf("local preview calls = %#v", caller.calls) } var payload map[string]any if err := json.Unmarshal(stdout.Bytes(), &payload); err != nil { t.Fatalf("local preview is not JSON: %v\n%s", err, stdout.String()) } if payload["before"] != "old\n" || payload["after"] != "new\n" || payload["operation"] != "overwrite" { t.Fatalf("diff payload = %#v", payload) } }) } func TestMarkdownCreateDriveAndDocRouting(t *testing.T) { tests := []struct { name string args []string steps []markdownDriveStep wantServer string wantTool string wantBody string }{ { name: "content defaults to doc", args: []string{"markdown", "create", "--name", "README.md", "--content", "# hello"}, steps: []markdownDriveStep{ {text: `{"resourceUrl":"https://upload.test/doc","uploadKey":"doc-key"}`}, {text: `{"created":true}`}, }, wantServer: "doc", wantTool: "get_file_upload_info", wantBody: "# hello", }, { name: "file with space id uses drive", args: []string{"markdown", "create", "--space-id", "space-1"}, steps: []markdownDriveStep{ {text: `{"uploadId":"drive-key","resourceUrls":[{"url":"https://upload.test/drive"}]}`}, {text: `{"created":true}`}, }, wantServer: "drive", wantTool: "get_upload_info", wantBody: "from file", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { caller := &markdownDriveCaller{format: "json", steps: test.steps} installMarkdownDriveDeps(t, caller) if test.wantServer == "drive" { path := writeMarkdownDriveFixture(t, "source.md", test.wantBody) test.args = append(test.args, "--file", path) } var uploaded string httpPutFile = func(_ context.Context, _ string, _ map[string]string, path string, _ int64) error { data, err := os.ReadFile(path) uploaded = string(data) return err } if err := executeMarkdownDriveCommand(t, newMarkdownCommand(), nil, test.args...); err != nil { t.Fatal(err) } if uploaded != test.wantBody { t.Fatalf("uploaded body = %q, want %q", uploaded, test.wantBody) } if len(caller.calls) != 2 || caller.calls[0].server != test.wantServer || caller.calls[0].tool != test.wantTool { t.Fatalf("calls = %#v", caller.calls) } }) } } func TestMarkdownFetchAutoRoutesAndKeepsJSONPure(t *testing.T) { caller := &markdownDriveCaller{ format: "json", steps: []markdownDriveStep{ {err: errors.New("not found in drive")}, {text: `{"name":"doc","extension":"md"}`}, {text: `{"resourceUrl":"https://download.test/internal.file","fileName":"../../evil.md"}`}, }, } stdout, stderr := installMarkdownDriveDeps(t, caller) installMarkdownHTTPGet(t, "external markdown") outputDir := t.TempDir() err := executeMarkdownDriveCommand(t, newMarkdownCommand(), nil, "markdown", "fetch", "--node", "node-1", "--output", outputDir) if err != nil { t.Fatal(err) } if len(caller.calls) != 3 || caller.calls[0].server != "drive" || caller.calls[1].server != "doc" || caller.calls[2].server != "doc" { t.Fatalf("auto-route calls = %#v", caller.calls) } savedPath := filepath.Join(outputDir, "evil.md") if data, err := os.ReadFile(savedPath); err != nil || string(data) != "external markdown" { t.Fatalf("safe output file: data=%q err=%v", data, err) } var payload map[string]any if err := json.Unmarshal(stdout.Bytes(), &payload); err != nil { t.Fatalf("fetch stdout is not pure JSON: %v\n%s", err, stdout.String()) } if payload["content"] != "external markdown" || payload["saved_to"] != savedPath || payload["source"] != "doc" { t.Fatalf("fetch payload = %#v", payload) } if stderr.Len() != 0 { t.Fatalf("JSON fetch unexpectedly wrote warnings: %q", stderr.String()) } } func TestMarkdownFetchDryRunAndRawOutput(t *testing.T) { t.Run("dry run avoids network", func(t *testing.T) { caller := &markdownDriveCaller{format: "json", dryRun: true} stdout, _ := installMarkdownDriveDeps(t, caller) err := executeMarkdownDriveCommand(t, newMarkdownCommand(), nil, "markdown", "fetch", "--node", "node-1", "--output", "out.md") if err != nil { t.Fatal(err) } if len(caller.calls) != 0 { t.Fatalf("dry-run fetch calls = %#v", caller.calls) } var payload map[string]any if err := json.Unmarshal(stdout.Bytes(), &payload); err != nil || payload["operation"] != "fetch" { t.Fatalf("dry-run payload: err=%v output=%q", err, stdout.String()) } }) t.Run("raw output keeps warning on stderr", func(t *testing.T) { caller := &markdownDriveCaller{ format: "raw", steps: []markdownDriveStep{{text: `{"downloadUrl":"https://download.test/current.md","fileName":"current.md"}`}}, } stdout, stderr := installMarkdownDriveDeps(t, caller) installMarkdownHTTPGet(t, "untrusted body") err := executeMarkdownDriveCommand(t, newMarkdownCommand(), nil, "markdown", "fetch", "--node", "node-1", "--space-id", "space-1") if err != nil { t.Fatal(err) } if stdout.String() != "untrusted body\n" { t.Fatalf("raw stdout = %q", stdout.String()) } if !strings.Contains(stderr.String(), "不可信数据") { t.Fatalf("missing out-of-band warning: %q", stderr.String()) } }) t.Run("route flags remain exclusive", func(t *testing.T) { caller := &markdownDriveCaller{format: "json"} installMarkdownDriveDeps(t, caller) err := executeMarkdownDriveCommand(t, newMarkdownCommand(), nil, "markdown", "fetch", "--node", "node-1", "--space-id", "space-1", "--workspace", "workspace-1") if err == nil || !strings.Contains(err.Error(), "互斥") { t.Fatalf("error = %v", err) } }) } func TestMarkdownOutputPathRejectsRemoteSymlink(t *testing.T) { dir := t.TempDir() target := writeMarkdownDriveFixture(t, "target.md", "keep") link := filepath.Join(dir, "remote.md") if err := os.Symlink(target, link); err != nil { t.Skipf("symlink unsupported: %v", err) } if _, err := resolveMarkdownOutputPath(dir, "../../remote.md"); err == nil || !strings.Contains(err.Error(), "符号链接") { t.Fatalf("expected symlink rejection, got %v", err) } if data, err := os.ReadFile(target); err != nil || string(data) != "keep" { t.Fatalf("symlink target changed: data=%q err=%v", data, err) } } func TestMarkdownOverwriteAndPatchWrites(t *testing.T) { t.Run("overwrite uses drive overwrite upload", func(t *testing.T) { caller := &markdownDriveCaller{ format: "json", steps: []markdownDriveStep{ {text: `{"uploadId":"upload-1","resourceUrls":[{"url":"https://upload.test/drive"}]}`}, {text: `{"updated":true}`}, }, } stdout, _ := installMarkdownDriveDeps(t, caller) var uploaded string httpPutFile = func(_ context.Context, _ string, _ map[string]string, path string, _ int64) error { data, err := os.ReadFile(path) uploaded = string(data) return err } err := executeMarkdownDriveCommand(t, newMarkdownCommand(), nil, "markdown", "overwrite", "--node", "file-1", "--content", "# changed", "--name", "README.md", "--space-id", "space-1", "--yes") if err != nil { t.Fatal(err) } if uploaded != "# changed" { t.Fatalf("uploaded content = %q", uploaded) } if len(caller.calls) != 2 { t.Fatalf("calls = %#v", caller.calls) } for _, call := range caller.calls { if call.server != "drive" || call.args["overwriteFileId"] != "file-1" { t.Fatalf("overwrite call = %#v", call) } } var payload map[string]any if err := json.Unmarshal(stdout.Bytes(), &payload); err != nil || payload["updated"] != true { t.Fatalf("overwrite output is not pure server JSON: err=%v output=%q", err, stdout.String()) } }) t.Run("regex patch uses doc route and literal replacement", func(t *testing.T) { caller := &markdownDriveCaller{ format: "json", steps: []markdownDriveStep{ {text: `{"resourceUrl":"https://download.test/current.md","fileName":"internal.file"}`}, {text: `{"name":"remote","extension":"md"}`}, {text: `{"resourceUrl":"https://upload.test/doc","uploadKey":"key-1"}`}, {text: `{"patched":true}`}, }, } stdout, _ := installMarkdownDriveDeps(t, caller) installMarkdownHTTPGet(t, "v1 v2") var uploaded string httpPutFile = func(_ context.Context, _ string, _ map[string]string, path string, _ int64) error { data, err := os.ReadFile(path) uploaded = string(data) return err } err := executeMarkdownDriveCommand(t, newMarkdownCommand(), nil, "markdown", "patch", "--node", "node-1", "--pattern", `v\d`, "--content", "$1", "--regex", "--workspace", "workspace-1", "--yes") if err != nil { t.Fatal(err) } if uploaded != "$1 $1" { t.Fatalf("regex replacement expanded capture syntax: %q", uploaded) } if len(caller.calls) != 4 { t.Fatalf("calls = %#v", caller.calls) } if caller.calls[0].tool != "download_file" || caller.calls[1].tool != "get_document_info" || caller.calls[2].tool != "get_file_upload_info" || caller.calls[3].tool != "commit_uploaded_file" { t.Fatalf("patch sequence = %#v", caller.calls) } if caller.calls[2].args["overwriteNodeId"] != "node-1" || caller.calls[2].args["name"] != "remote.md" { t.Fatalf("patch upload args = %#v", caller.calls[2].args) } var payload map[string]any if err := json.Unmarshal(stdout.Bytes(), &payload); err != nil || payload["patched"] != true { t.Fatalf("patch output is not pure server JSON: err=%v output=%q", err, stdout.String()) } }) } func TestMarkdownPatchZeroMatchNeverUploads(t *testing.T) { caller := &markdownDriveCaller{ format: "json", steps: []markdownDriveStep{ {text: `{"downloadUrl":"https://download.test/current.md","fileName":"current.md"}`}, }, } stdout, _ := installMarkdownDriveDeps(t, caller) installMarkdownHTTPGet(t, "alpha beta") httpPutFile = func(context.Context, string, map[string]string, string, int64) error { t.Fatal("zero-match patch attempted an upload") return nil } err := executeMarkdownDriveCommand(t, newMarkdownCommand(), nil, "markdown", "patch", "--node", "file-1", "--pattern", "missing", "--content", "new", "--space-id", "space-1", "--yes") if err != nil { t.Fatal(err) } if len(caller.calls) != 1 || caller.calls[0].tool != "download_file" { t.Fatalf("zero-match calls = %#v", caller.calls) } var payload map[string]any if err := json.Unmarshal(stdout.Bytes(), &payload); err != nil { t.Fatalf("zero-match output is not JSON: %v\n%s", err, stdout.String()) } if payload["changed"] != false || payload["match_count"] != float64(0) { t.Fatalf("zero-match payload = %#v", payload) } } func TestMarkdownPatchCancellationStopsBeforeUploadMetadata(t *testing.T) { caller := &markdownDriveCaller{ format: "raw", steps: []markdownDriveStep{{text: `{"downloadUrl":"https://download.test/current.md","fileName":"current.md"}`}}, } _, stderr := installMarkdownDriveDeps(t, caller) installMarkdownHTTPGet(t, "old value") httpPutFile = func(context.Context, string, map[string]string, string, int64) error { t.Fatal("cancelled patch attempted upload") return nil } err := executeMarkdownDriveCommand(t, newMarkdownCommand(), strings.NewReader("no\n"), "markdown", "patch", "--node", "file-1", "--pattern", "old", "--content", "new", "--space-id", "space-1") if err == nil || !strings.Contains(err.Error(), "用户取消了操作") { t.Fatalf("error = %v, want 用户取消了操作", err) } if len(caller.calls) != 0 { t.Fatalf("cancelled patch calls = %#v, want none before ConfirmSafety decline", caller.calls) } _ = stderr } func TestMarkdownHelpersCoverSafeNamesAndErrorRouting(t *testing.T) { names := map[string]string{ `../../escape.md`: "escape.md", `..\..\windows.md`: "windows.md", ".": "unnamed", "\x00": "unnamed", "safe\x00-name.md": "safe-name.md", "/absolute/nested/a.md": "a.md", } for input, want := range names { if got := sanitizeFileName(input); got != want { t.Errorf("sanitizeFileName(%q) = %q, want %q", input, got, want) } } if got := parseRemoteFileName(`{"result":{"fileName":"../../drive.md"}}`); got != "drive.md" { t.Fatalf("drive remote name = %q", got) } if got := parseRemoteFileName(`{"name":"doc","extension":"md"}`); got != "doc.md" { t.Fatalf("doc remote name = %q", got) } if got := parseRemoteFileName(`{"name":"doc.md","extension":"md"}`); got != "doc.md" { t.Fatalf("doc remote name with extension = %q", got) } if got := parseRemoteFileName(`{`); got != "" { t.Fatalf("invalid remote metadata name = %q, want empty", got) } if got := parseRemoteFileName(`{"extension":"md"}`); got != "" { t.Fatalf("remote metadata without name = %q, want empty", got) } if !isTimeoutCLIError(&CLIError{Code: CodeNetworkTimeout}) || !isTimeoutCLIError(errors.New("request timeout")) || !isPermissionCLIError(&CLIError{Code: CodeAuthPermission}) || !isPermissionCLIError(&PATError{RawJSON: `{}`}) { t.Fatal("typed routing errors were not classified") } if isTimeoutCLIError(nil) || isPermissionCLIError(nil) || isPermissionCLIError(errors.New("ordinary failure")) { t.Fatal("ordinary errors were misclassified") } } func TestMarkdownDownloadHelpersCoverFailuresAndFallbackNames(t *testing.T) { downloadPayload := `{"downloadUrl":"https://download.test/content","fileName":"\u0000"}` boom := errors.New("download failed") t.Run("drive MCP error", func(t *testing.T) { caller := &markdownDriveCaller{steps: []markdownDriveStep{{err: boom}}} installMarkdownDriveDeps(t, caller) if _, _, err := downloadFromDrive(context.Background(), "file-1", "space-1"); !errors.Is(err, boom) { t.Fatalf("error = %v, want %v", err, boom) } }) t.Run("doc MCP error", func(t *testing.T) { caller := &markdownDriveCaller{steps: []markdownDriveStep{{err: boom}}} installMarkdownDriveDeps(t, caller) if _, _, err := downloadFromDoc(context.Background(), "node-1"); !errors.Is(err, boom) { t.Fatalf("error = %v, want %v", err, boom) } }) t.Run("drive fallback name and successful read", func(t *testing.T) { caller := &markdownDriveCaller{steps: []markdownDriveStep{{text: downloadPayload}}} installMarkdownDriveDeps(t, caller) installMarkdownHTTPGet(t, "drive body") content, name, err := downloadFromDrive(context.Background(), "file-1", "space-1") if err != nil { t.Fatal(err) } if content != "drive body" || name != "download.md" { t.Fatalf("download = (%q, %q), want (%q, %q)", content, name, "drive body", "download.md") } }) t.Run("doc fallback name and successful read", func(t *testing.T) { caller := &markdownDriveCaller{steps: []markdownDriveStep{{text: downloadPayload}}} installMarkdownDriveDeps(t, caller) installMarkdownHTTPGet(t, "doc body") content, name, err := downloadFromDoc(context.Background(), "node-1") if err != nil { t.Fatal(err) } if content != "doc body" || name != "download.md" { t.Fatalf("download = (%q, %q), want (%q, %q)", content, name, "doc body", "download.md") } }) for _, domain := range []struct { name string run func() error }{ { name: "drive", run: func() error { _, _, err := downloadFromDrive(context.Background(), "file-1", "") return err }, }, { name: "doc", run: func() error { _, _, err := downloadFromDoc(context.Background(), "node-1") return err }, }, } { t.Run(domain.name+" temp directory error", func(t *testing.T) { caller := &markdownDriveCaller{steps: []markdownDriveStep{{text: downloadPayload}}} installMarkdownDriveDeps(t, caller) t.Setenv("TMPDIR", filepath.Join(t.TempDir(), "missing")) err := domain.run() if err == nil || !strings.Contains(err.Error(), "创建临时目录失败") { t.Fatalf("error = %v, want temporary-directory failure", err) } }) t.Run(domain.name+" HTTP error", func(t *testing.T) { caller := &markdownDriveCaller{steps: []markdownDriveStep{{text: downloadPayload}}} installMarkdownDriveDeps(t, caller) httpGetFile = func(context.Context, string, map[string]string, string) error { return boom } if err := domain.run(); !errors.Is(err, boom) { t.Fatalf("error = %v, want %v", err, boom) } }) t.Run(domain.name+" read error", func(t *testing.T) { caller := &markdownDriveCaller{steps: []markdownDriveStep{{text: downloadPayload}}} installMarkdownDriveDeps(t, caller) httpGetFile = func(context.Context, string, map[string]string, string) error { return nil } err := domain.run() if err == nil || !strings.Contains(err.Error(), "读取下载内容失败") { t.Fatalf("error = %v, want read failure", err) } }) } } func TestMarkdownFetchRemoteFileNamePropagatesLookupError(t *testing.T) { boom := errors.New("metadata failed") caller := &markdownDriveCaller{steps: []markdownDriveStep{{err: boom}}} installMarkdownDriveDeps(t, caller) if _, err := fetchRemoteFileName(context.Background(), "node-1", true); !errors.Is(err, boom) { t.Fatalf("error = %v, want %v", err, boom) } } func TestMarkdownPublishesTypedConstraints(t *testing.T) { findLeaf := func(t *testing.T, root *cobra.Command, path ...string) *cobra.Command { t.Helper() leaf, remaining, err := root.Find(path) if err != nil || leaf == nil || len(remaining) != 0 { t.Fatalf("find %v: leaf=%v remaining=%v err=%v", path, leaf, remaining, err) } return leaf } hasGroup := func(groups [][]string, names ...string) bool { want := append([]string(nil), names...) for _, group := range groups { if reflect.DeepEqual(group, want) { return true } } return false } constraints := func(t *testing.T, command *cobra.Command) cli.RuntimeSchemaConstraints { t.Helper() var parsed cli.RuntimeSchemaConstraints raw := "" if command.Annotations != nil { raw = command.Annotations["dws.schema.constraints"] } if raw == "" { t.Fatalf("%s has no typed constraints annotation", command.CommandPath()) } if err := json.Unmarshal([]byte(raw), &parsed); err != nil { t.Fatalf("%s constraints: %v", command.CommandPath(), err) } return parsed } markdown := newMarkdownCommand() fetch := constraints(t, findLeaf(t, markdown, "fetch")) if !hasGroup(fetch.MutuallyExclusive, "space-id", "workspace") { t.Fatalf("markdown fetch constraints = %#v", fetch) } create := constraints(t, findLeaf(t, markdown, "create")) if !hasGroup(create.MutuallyExclusive, "content", "file") || !hasGroup(create.MutuallyExclusive, "space-id", "workspace") || !hasGroup(create.RequireOneOf, "content", "file") { t.Fatalf("markdown create constraints = %#v", create) } overwrite := constraints(t, findLeaf(t, markdown, "overwrite")) if !hasGroup(overwrite.MutuallyExclusive, "content", "file") || !hasGroup(overwrite.MutuallyExclusive, "space-id", "workspace") || !hasGroup(overwrite.RequireOneOf, "content", "file") { t.Fatalf("markdown overwrite constraints = %#v", overwrite) } patch := constraints(t, findLeaf(t, markdown, "patch")) if !hasGroup(patch.MutuallyExclusive, "space-id", "workspace") { t.Fatalf("markdown patch constraints = %#v", patch) } } func TestMarkdownContentSourcesAndHumanDiffs(t *testing.T) { caller := &markdownDriveCaller{format: "raw"} stdout, _ := installMarkdownDriveDeps(t, caller) cmd := &cobra.Command{Use: "source"} cmd.SetIn(strings.NewReader("from stdin")) if got, err := resolveMarkdownContentSource(cmd, "-"); err != nil || got != "from stdin" { t.Fatalf("stdin source = %q, %v", got, err) } path := writeMarkdownDriveFixture(t, "source.md", "from file") if got, err := resolveMarkdownContentSource(cmd, "@"+path); err != nil || got != "from file" { t.Fatalf("file source = %q, %v", got, err) } if got, err := resolveMarkdownContentSource(cmd, "literal"); err != nil || got != "literal" { t.Fatalf("literal source = %q, %v", got, err) } if _, err := resolveMarkdownContentSource(cmd, "@"); err == nil { t.Fatal("empty @file source unexpectedly succeeded") } if _, err := resolveMarkdownContentSource(cmd, "@"+filepath.Join(t.TempDir(), "missing.md")); err == nil { t.Fatal("missing @file source unexpectedly succeeded") } longBefore := strings.Repeat("old\n", 25) longAfter := strings.Repeat("new\n", 25) overwriteDiff := renderMarkdownOverwriteDiff("node-1", longBefore, longAfter) if !strings.Contains(overwriteDiff, "... (") || !strings.Contains(overwriteDiff, "No write performed") { t.Fatalf("overwrite diff did not truncate safely:\n%s", overwriteDiff) } if err := printMarkdownPatchDiff("node-1", "old", "new", 1); err != nil { t.Fatal(err) } if text := stdout.String(); !strings.Contains(text, "markdown patch") || !strings.Contains(text, "- old") || !strings.Contains(text, "+ new") { t.Fatalf("human patch diff = %q", text) } stdout.Reset() if err := printMarkdownDryRun(map[string]any{"operation": "fetch"}, "获取 Markdown 内容", "node-1"); err != nil { t.Fatal(err) } if text := stdout.String(); !strings.Contains(text, "获取 Markdown 内容") || !strings.Contains(text, "node-1") { t.Fatalf("human dry-run output = %q", text) } if markdownRouteName(false) != "drive" || markdownRouteName(true) != "doc" { t.Fatal("route names are incorrect") } } func TestMarkdownValidationRejectsAmbiguousOrUnsafeInputs(t *testing.T) { tests := []struct { name string args []string want string }{ { name: "create requires source", args: []string{"markdown", "create", "--name", "a.md"}, want: "必须指定其一", }, { name: "create rejects both sources", args: []string{"markdown", "create", "--name", "a.md", "--content", "x", "--file", "x.md"}, want: "互斥", }, { name: "create content requires name", args: []string{"markdown", "create", "--content", "x"}, want: "必须指定 --name", }, { name: "create route flags are exclusive", args: []string{"markdown", "create", "--name", "a.md", "--content", "x", "--space-id", "s", "--workspace", "w"}, want: "--space-id 与 --workspace 互斥", }, { name: "overwrite requires node", args: []string{"markdown", "overwrite", "--content", "x", "--name", "a.md", "--yes"}, want: "flag --node is required", }, { name: "overwrite requires source", args: []string{"markdown", "overwrite", "--node", "n", "--space-id", "s", "--yes"}, want: "必须指定其一", }, { name: "overwrite rejects both sources", args: []string{"markdown", "overwrite", "--node", "n", "--content", "x", "--file", "x.md", "--space-id", "s", "--yes"}, want: "互斥", }, { name: "overwrite route flags are exclusive", args: []string{"markdown", "overwrite", "--node", "n", "--content", "x", "--name", "a.md", "--space-id", "s", "--workspace", "w", "--yes"}, want: "--space-id 与 --workspace 互斥", }, { name: "patch requires all values", args: []string{"markdown", "patch", "--node", "n", "--pattern", "x", "--yes"}, want: "均为必填", }, { name: "patch route flags are exclusive", args: []string{"markdown", "patch", "--node", "n", "--pattern", "x", "--content", "y", "--space-id", "s", "--workspace", "w", "--yes"}, want: "--space-id 与 --workspace 互斥", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { caller := &markdownDriveCaller{format: "json"} installMarkdownDriveDeps(t, caller) err := executeMarkdownDriveCommand(t, newMarkdownCommand(), nil, test.args...) if err == nil || !strings.Contains(err.Error(), test.want) { t.Fatalf("error = %v, want substring %q", err, test.want) } if len(caller.calls) != 0 { t.Fatalf("validation failure made calls: %#v", caller.calls) } }) } t.Run("create rejects non-markdown file", func(t *testing.T) { caller := &markdownDriveCaller{format: "json"} installMarkdownDriveDeps(t, caller) path := writeMarkdownDriveFixture(t, "source.txt", "text") err := executeMarkdownDriveCommand(t, newMarkdownCommand(), nil, "markdown", "create", "--file", path) if err == nil || !strings.Contains(err.Error(), "必须以 .md 结尾") { t.Fatalf("error = %v", err) } }) t.Run("overwrite rejects directory source", func(t *testing.T) { caller := &markdownDriveCaller{format: "json"} installMarkdownDriveDeps(t, caller) err := executeMarkdownDriveCommand(t, newMarkdownCommand(), nil, "markdown", "overwrite", "--node", "n", "--file", t.TempDir(), "--space-id", "s", "--yes") if err == nil || !strings.Contains(err.Error(), "是目录而非文件") { t.Fatalf("error = %v", err) } }) } func TestMarkdownPatchPreviewAndRegexErrors(t *testing.T) { t.Run("human local preview does not upload", func(t *testing.T) { caller := &markdownDriveCaller{ format: "raw", steps: []markdownDriveStep{{text: `{"downloadUrl":"https://download.test/current.md","fileName":"current.md"}`}}, } stdout, _ := installMarkdownDriveDeps(t, caller) installMarkdownHTTPGet(t, "old value") httpPutFile = func(context.Context, string, map[string]string, string, int64) error { t.Fatal("local patch preview attempted upload") return nil } err := executeMarkdownDriveCommand(t, newMarkdownCommand(), nil, "markdown", "patch", "--node", "n", "--pattern", "old", "--content", "new", "--space-id", "s", "--dry-run") if err != nil { t.Fatal(err) } if len(caller.calls) != 1 { t.Fatalf("calls = %#v", caller.calls) } if text := stdout.String(); !strings.Contains(text, "- old value") || !strings.Contains(text, "+ new value") { t.Fatalf("preview output = %q", text) } }) t.Run("invalid regex stops after fetch", func(t *testing.T) { caller := &markdownDriveCaller{ format: "json", steps: []markdownDriveStep{{text: `{"downloadUrl":"https://download.test/current.md","fileName":"current.md"}`}}, } installMarkdownDriveDeps(t, caller) installMarkdownHTTPGet(t, "old value") err := executeMarkdownDriveCommand(t, newMarkdownCommand(), nil, "markdown", "patch", "--node", "n", "--pattern", "[", "--content", "new", "--space-id", "s", "--regex", "--yes") if err == nil || !strings.Contains(err.Error(), "正则表达式编译失败") { t.Fatalf("error = %v", err) } if len(caller.calls) != 1 { t.Fatalf("invalid regex made extra calls: %#v", caller.calls) } }) } func TestMarkdownOverwritePreservesRemoteName(t *testing.T) { caller := &markdownDriveCaller{ format: "json", steps: []markdownDriveStep{ {text: `{"name":"remote","extension":"md"}`}, {text: `{"resourceUrl":"https://upload.test/doc","uploadKey":"key-1"}`}, {text: `{"updated":true}`}, }, } installMarkdownDriveDeps(t, caller) path := writeMarkdownDriveFixture(t, "local-name.md", "updated") httpPutFile = func(context.Context, string, map[string]string, string, int64) error { return nil } err := executeMarkdownDriveCommand(t, newMarkdownCommand(), nil, "markdown", "overwrite", "--node", "node-1", "--file", path, "--workspace", "workspace-1", "--yes") if err != nil { t.Fatal(err) } if len(caller.calls) != 3 || caller.calls[0].tool != "get_document_info" { t.Fatalf("calls = %#v", caller.calls) } if caller.calls[1].args["name"] != "remote.md" || caller.calls[2].args["name"] != "remote.md" { t.Fatalf("remote name was not preserved: %#v %#v", caller.calls[1].args, caller.calls[2].args) } } func TestMarkdownDomainResolutionErrorsAreActionable(t *testing.T) { tests := []struct { name string err error want string }{ {name: "timeout", err: context.DeadlineExceeded, want: "超时"}, {name: "permission", err: &CLIError{Code: CodeAuthPermission, Message: "denied"}, want: "无权限"}, {name: "not found", err: errors.New("missing"), want: "均未找到"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { caller := &markdownDriveCaller{ format: "json", steps: []markdownDriveStep{{err: test.err}, {err: test.err}}, } installMarkdownDriveDeps(t, caller) _, err := resolveFileDomain(context.Background(), "node-1") if err == nil || !strings.Contains(err.Error(), test.want) { t.Fatalf("error = %v, want substring %q", err, test.want) } if len(caller.calls) != 2 { t.Fatalf("route probes = %#v", caller.calls) } }) } t.Run("drive success wins without doc probe", func(t *testing.T) { caller := &markdownDriveCaller{ format: "json", steps: []markdownDriveStep{{text: `{"result":{"fileName":"a.md"}}`}}, } installMarkdownDriveDeps(t, caller) domain, err := resolveFileDomain(context.Background(), "node-1") if err != nil || domain != "drive" || len(caller.calls) != 1 { t.Fatalf("domain=%q err=%v calls=%#v", domain, err, caller.calls) } }) }