Files
dingtalk-workspace-cli/internal/helpers/drive_review_fixes_test.go
玉澜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

264 lines
9.2 KiB
Go
Raw Permalink 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 || !strings.Contains(err.Error(), "用户取消了操作") {
t.Fatalf("declined apply error = %v, want 用户取消了操作", 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)
}
}
}