Files
dingtalk-workspace-cli/internal/helpers/drive_review_fixes_test.go
T
玉澜 9567cfd3d8 fix(review): align wukong port with upstream behavior and PR #621 review findings
Must-fix: drive permission apply now gates on confirmDangerousAction and
declares confirmation=user_required, matching its help-text promise.

Wukong parity restored: formula-verify --exit-on-error (payload-parsing
exit path) and --targets conflict error, sheet info --include, chat
location/profile message types, search-advanced wukong flag aliases,
and a dedicated drive download-version leaf replacing the removed
polymorphic download --version.

Consistency fixes: transfer-owner --node/--workspace XOR and JSON-aware
dry-run after --yes validation; drive list --versions rejects
--depth/--pattern instead of misleading depth errors; depth BFS resumes
rate-limited folders from the failed page cursor to avoid duplicates;
doc style cover upload honors cmd.Context() and a 20 MiB size cap; chat
user-settings set validates per-item openConversationId and is
risk=medium.

Hardened the skill static audit to scan fenced code blocks and reject
unknown subcommands on group commands, fixing the stale aitable/drive
doc examples it exposed. Added CHANGELOG entry and coverage tests for
all changed statements plus previously untested ported commands.
2026-07-29 15:34:59 +08:00

264 lines
9.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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"
"io"
"reflect"
"strings"
"testing"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition"
"github.com/spf13/cobra"
)
// executeDriveCommandCapture 执行 drive 命令并捕获 deps.Out 的 stdout 输出,
// 供需要断言 JSON 输出形态的测试使用(等价于 installDepthCaller + Execute)。
func executeDriveCommandCapture(t *testing.T, caller edition.ToolCaller, args ...string) (*bytes.Buffer, error) {
t.Helper()
previousDeps := deps
t.Cleanup(func() { deps = previousDeps })
InitDeps(caller)
buf := &bytes.Buffer{}
deps.Out.w = buf
deps.Out.errW = io.Discard
root := newDriveCommand()
if root.PersistentFlags().Lookup("yes") == nil {
root.PersistentFlags().Bool("yes", false, "confirm high-risk operation")
}
if root.PersistentFlags().Lookup("dry-run") == nil {
root.PersistentFlags().Bool("dry-run", false, "preview without executing")
}
root.SilenceErrors = true
root.SilenceUsage = true
root.SetArgs(args)
err := root.Execute()
return buf, err
}
// ── drive permission apply:确认门禁与帮助文案契约一致 ──
func TestCrossPlatformCoverageDrivePermissionApplyDeclined(t *testing.T) {
caller := &guardedMutationCaller{}
root := newDriveCommand()
root.SetIn(strings.NewReader("no\n"))
err := executeGuardedMutationCommand(t, caller, func() *cobra.Command { return root },
"permission", "apply", "--node", "node-1", "--role", "READER", "--users", "u1")
if err != nil {
t.Fatal(err)
}
if len(caller.calls) != 0 {
t.Fatalf("declined apply calls = %#v, want none", caller.calls)
}
}
func TestCrossPlatformCoverageDrivePermissionApplyYesProceeds(t *testing.T) {
caller := &guardedMutationCaller{}
err := executeGuardedMutationCommand(t, caller, newDriveCommand,
"permission", "apply", "--node", "node-1", "--role", "reader", "--users", "u1,u2", "--yes")
if err != nil {
t.Fatal(err)
}
if len(caller.calls) != 1 {
t.Fatalf("calls = %#v, want exactly one", caller.calls)
}
call := caller.calls[0]
if call.productID != "drive" || call.toolName != "apply_permission" {
t.Fatalf("call = %#v", call)
}
if call.args["nodeId"] != "node-1" || call.args["roleId"] != "READER" {
t.Fatalf("args = %#v", call.args)
}
if !reflect.DeepEqual(call.args["receivers"], []string{"u1", "u2"}) {
t.Fatalf("receivers = %#v", call.args["receivers"])
}
}
func TestCrossPlatformCoverageDrivePermissionApplyDryRunNoPromptNoCall(t *testing.T) {
caller := &guardedMutationCaller{dryRun: true}
root := newDriveCommand()
promptOut := &bytes.Buffer{}
root.SetErr(promptOut)
root.SetIn(strings.NewReader("no\n"))
err := executeGuardedMutationCommand(t, caller, func() *cobra.Command { return root },
"permission", "apply", "--node", "node-1", "--role", "READER", "--users", "u1", "--dry-run")
if err != nil {
t.Fatal(err)
}
if len(caller.calls) != 0 {
t.Fatalf("dry-run apply calls = %#v, want none", caller.calls)
}
if strings.Contains(promptOut.String(), "Confirm action") {
t.Fatalf("dry-run prompted for confirmation: %q", promptOut.String())
}
}
// ── drive permission transfer-owner:dry-run JSON 输出且校验先于 dry-run ──
func TestCrossPlatformCoverageDriveTransferOwnerDryRunJSONNode(t *testing.T) {
caller := &scriptedToolCaller{format: "json", dry: true}
out, err := executeDriveCommandCapture(t, caller,
"permission", "transfer-owner", "--node", "node-1", "--new-owner", "user-1", "--dry-run")
if err != nil {
t.Fatal(err)
}
if caller.calls != 0 {
t.Fatalf("dry-run tool calls = %d, want none", caller.calls)
}
var payload map[string]any
if err := json.Unmarshal(out.Bytes(), &payload); err != nil {
t.Fatalf("dry-run output is not JSON: %v\n%s", err, out.String())
}
if payload["dry_run"] != true || payload["executed"] != false {
t.Fatalf("payload = %#v", payload)
}
if payload["operation"] != "转交所有者" || payload["newOwnerId"] != "user-1" || payload["nodeId"] != "node-1" {
t.Fatalf("payload = %#v", payload)
}
if _, ok := payload["workspaceId"]; ok {
t.Fatalf("unexpected workspaceId in %#v", payload)
}
}
func TestCrossPlatformCoverageDriveTransferOwnerDryRunJSONWorkspace(t *testing.T) {
caller := &scriptedToolCaller{format: "json", dry: true}
out, err := executeDriveCommandCapture(t, caller,
"permission", "transfer-owner", "--workspace", "ws-1", "--new-owner", "user-1", "--dry-run")
if err != nil {
t.Fatal(err)
}
var payload map[string]any
if err := json.Unmarshal(out.Bytes(), &payload); err != nil {
t.Fatalf("dry-run output is not JSON: %v\n%s", err, out.String())
}
if payload["workspaceId"] != "ws-1" || payload["newOwnerId"] != "user-1" {
t.Fatalf("payload = %#v", payload)
}
if _, ok := payload["nodeId"]; ok {
t.Fatalf("unexpected nodeId in %#v", payload)
}
}
func TestCrossPlatformCoverageDriveTransferOwnerDryRunYesValidatesFirst(t *testing.T) {
caller := &guardedMutationCaller{dryRun: true}
err := executeGuardedMutationCommand(t, caller, newDriveCommand,
"permission", "transfer-owner", "--node", "node-1", "--new-owner", "user-1", "--yes", "--dry-run")
if err == nil || !strings.Contains(err.Error(), "--reserve-role is required") {
t.Fatalf("err = %v, want --reserve-role required even under --dry-run", err)
}
if len(caller.calls) != 0 {
t.Fatalf("calls = %#v, want none", caller.calls)
}
}
func TestCrossPlatformCoverageDriveTransferOwnerDryRunNonJSON(t *testing.T) {
caller := &scriptedToolCaller{format: "table", dry: true}
_, err := executeDriveCommandCapture(t, caller,
"permission", "transfer-owner", "--node", "node-1", "--new-owner", "user-1", "--dry-run")
if err != nil {
t.Fatal(err)
}
if caller.calls != 0 {
t.Fatalf("dry-run tool calls = %d, want none", caller.calls)
}
}
// ── drive list:--versions 与 --depth/--pattern 的交互 ──
func TestCrossPlatformCoverageDriveListVersionsRejectsDepth(t *testing.T) {
caller := &guardedMutationCaller{}
err := executeGuardedMutationCommand(t, caller, newDriveCommand,
"list", "--versions", "--node", "node-1", "--depth", "2", "--limit", "10")
if err == nil || !strings.Contains(err.Error(), "--depth") {
t.Fatalf("err = %v, want explicit --versions/--depth conflict", err)
}
if len(caller.calls) != 0 {
t.Fatalf("calls = %#v, want none", caller.calls)
}
}
func TestCrossPlatformCoverageDriveListVersionsRejectsPattern(t *testing.T) {
caller := &guardedMutationCaller{}
err := executeGuardedMutationCommand(t, caller, newDriveCommand,
"list", "--versions", "--node", "node-1", "--pattern", "x")
if err == nil || !strings.Contains(err.Error(), "--pattern") {
t.Fatalf("err = %v, want explicit --versions/--pattern conflict", err)
}
if len(caller.calls) != 0 {
t.Fatalf("calls = %#v, want none", caller.calls)
}
}
func TestCrossPlatformCoverageDriveListVersionsAllowsDepthOne(t *testing.T) {
caller := &guardedMutationCaller{}
err := executeGuardedMutationCommand(t, caller, newDriveCommand,
"list", "--versions", "--node", "node-1", "--depth", "1")
if err != nil {
t.Fatal(err)
}
if len(caller.calls) != 1 || caller.calls[0].toolName != "list_file_versions" {
t.Fatalf("calls = %#v", caller.calls)
}
}
func TestCrossPlatformCoverageDriveListVersionsWithLimit(t *testing.T) {
caller := &guardedMutationCaller{}
err := executeGuardedMutationCommand(t, caller, newDriveCommand,
"list", "--versions", "--node", "node-1", "--limit", "10")
if err != nil {
t.Fatal(err)
}
if len(caller.calls) != 1 {
t.Fatalf("calls = %#v, want exactly one", caller.calls)
}
call := caller.calls[0]
if call.productID != "drive" || call.toolName != "list_file_versions" {
t.Fatalf("call = %#v", call)
}
if call.args["nodeId"] != "node-1" || call.args["maxResults"] != 10 {
t.Fatalf("args = %#v", call.args)
}
}
func TestCrossPlatformCoverageDriveDownloadVersionDryRun(t *testing.T) {
caller := &guardedMutationCaller{dryRun: true}
err := executeGuardedMutationCommand(t, caller, newDriveCommand,
"download-version", "--node", "node-1", "--version", "3", "--output", "./x.pdf", "--dry-run")
if err != nil {
t.Fatal(err)
}
if len(caller.calls) != 0 {
t.Fatalf("dry-run calls = %#v, want none", caller.calls)
}
}
func TestCrossPlatformCoverageDriveDownloadVersionDirectoryOutput(t *testing.T) {
SetHTTPGetFile(func(context.Context, string, map[string]string, string) error { return nil })
t.Cleanup(func() { SetHTTPGetFile(nil) })
dir := t.TempDir()
for _, resp := range []string{
`{"downloadUrl":"https://oss.test/get/report_v3.pdf","fileName":"报告v3.pdf"}`,
`{"downloadUrl":"https://oss.test/get/inferred_v3.pdf"}`,
} {
caller := &scriptedToolCaller{steps: []scriptedToolStep{{text: resp}}}
installScriptedCaller(t, caller)
root := newDriveCommand()
root.PersistentFlags().Bool("dry-run", false, "")
root.SilenceErrors = true
root.SilenceUsage = true
root.SetArgs([]string{"download-version", "--node", "node-1", "--version", "3", "--output", dir})
if err := root.Execute(); err != nil {
t.Fatal(err)
}
if caller.calls != 1 {
t.Fatalf("calls = %d, want 1", caller.calls)
}
}
}