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>
264 lines
9.2 KiB
Go
264 lines
9.2 KiB
Go
// 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)
|
||
}
|
||
}
|
||
}
|