Files
修雨 4c5f1faeb1 feat: align open CLI with dws-wukong via cedar discovery version code (#509)
Switches the discovery version code bamboo -> cedar and aligns the open edition CLI with dws-wukong across communication (calendar book/acl/attendee, minutes tag, mail folder/template/contact, chat file upload, todo add-attachment, attendance transforms) and structured-office (aitable advperm/view/section/workflow/record, sheet/drive/wiki/doc) domains. Includes output-envelope parity, parse_bool/attendance_class_check_time transforms, --calendar-id support, CHANGELOG 1.0.43 and README command-index refresh. cedar config validated on pre and prod endpoints.
2026-06-26 21:49:38 +08:00

218 lines
6.1 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.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package helpers
import (
"bytes"
"context"
"testing"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/executor"
)
type sheetCommandRunner struct {
last executor.Invocation
calls []executor.Invocation
}
func (r *sheetCommandRunner) Run(_ context.Context, invocation executor.Invocation) (executor.Result, error) {
r.last = invocation
r.calls = append(r.calls, invocation)
return executor.Result{Invocation: invocation}, nil
}
func executeSheetCommand(t *testing.T, runner *sheetCommandRunner, args ...string) {
t.Helper()
cmd := sheetHandler{}.Command(runner)
var out, errOut bytes.Buffer
cmd.SetOut(&out)
cmd.SetErr(&errOut)
cmd.SetArgs(args)
if err := cmd.Execute(); err != nil {
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut.String())
}
}
func TestSheetCreateCallsCreateWorkspaceSheet(t *testing.T) {
t.Parallel()
runner := &sheetCommandRunner{}
executeSheetCommand(t, runner, "create", "--name", "销售数据", "--folder", "FOLDER_001", "--workspace", "WS_001")
if runner.last.Tool != "create_workspace_sheet" {
t.Fatalf("tool = %q, want create_workspace_sheet", runner.last.Tool)
}
if got := runner.last.Params["name"]; got != "销售数据" {
t.Fatalf("name = %#v", got)
}
if got := runner.last.Params["folderId"]; got != "FOLDER_001" {
t.Fatalf("folderId = %#v", got)
}
if got := runner.last.Params["workspaceId"]; got != "WS_001" {
t.Fatalf("workspaceId = %#v", got)
}
}
func TestSheetRangeUpdateCallsSetCellRange(t *testing.T) {
t.Parallel()
runner := &sheetCommandRunner{}
executeSheetCommand(t, runner,
"range", "update",
"--node", "NODE_001",
"--sheet-id", "SHEET_001",
"--range", "A1:B1",
"--values", `[[{"type":"text","text":"姓名"},{"type":"text","text":"分数"}]]`,
)
if runner.last.Tool != "set_cell_range" {
t.Fatalf("tool = %q, want set_cell_range", runner.last.Tool)
}
if got := runner.last.Params["rangeAddress"]; got != "A1:B1" {
t.Fatalf("rangeAddress = %#v", got)
}
cells, ok := runner.last.Params["cells"].([][]any)
if !ok || len(cells) != 1 || len(cells[0]) != 2 {
t.Fatalf("cells = %#v", runner.last.Params["cells"])
}
}
func TestSheetRangeSetStyleExpandsScalarStyle(t *testing.T) {
t.Parallel()
runner := &sheetCommandRunner{}
executeSheetCommand(t, runner,
"range", "set-style",
"--node", "NODE_001",
"--sheet-id", "SHEET_001",
"--range", "A1:B2",
"--bg-color", "#FFF2CC",
"--font-weight", "bold",
)
if runner.last.Tool != "update_range" {
t.Fatalf("tool = %q, want update_range", runner.last.Tool)
}
bg, ok := runner.last.Params["backgroundColors"].([][]string)
if !ok || len(bg) != 2 || len(bg[0]) != 2 || bg[1][1] != "#FFF2CC" {
t.Fatalf("backgroundColors = %#v", runner.last.Params["backgroundColors"])
}
weights, ok := runner.last.Params["fontWeights"].([][]string)
if !ok || weights[0][0] != "bold" {
t.Fatalf("fontWeights = %#v", runner.last.Params["fontWeights"])
}
}
func TestSheetFilterViewUpdateCriteriaCallsSetCriteria(t *testing.T) {
t.Parallel()
runner := &sheetCommandRunner{}
executeSheetCommand(t, runner,
"filter-view", "update-criteria",
"--node", "NODE_001",
"--sheet-id", "SHEET_001",
"--filter-view-id", "FV_001",
"--column", "2",
"--filter-criteria", `{"filterType":"values","visibleValues":["销售部"]}`,
)
if runner.last.Tool != "set_filter_view_criteria" {
t.Fatalf("tool = %q, want set_filter_view_criteria", runner.last.Tool)
}
if got := runner.last.Params["filterViewId"]; got != "FV_001" {
t.Fatalf("filterViewId = %#v", got)
}
if got := runner.last.Params["column"]; got != 2 {
t.Fatalf("column = %#v", got)
}
}
func TestSheetCondFormatCreateExpandsCondition(t *testing.T) {
t.Parallel()
runner := &sheetCommandRunner{}
executeSheetCommand(t, runner,
"cond-format", "create",
"--node", "NODE_001",
"--sheet-id", "SHEET_001",
"--ranges", `["A1:A10"]`,
"--condition", `{"numberCondition":{"operator":"greater","value1":"80"}}`,
"--cell-style", `{"backgroundColor":"#FFCDD2"}`,
)
if runner.last.Tool != "create_cond_format" {
t.Fatalf("tool = %q, want create_cond_format", runner.last.Tool)
}
if _, ok := runner.last.Params["numberCondition"]; !ok {
t.Fatalf("numberCondition missing from %#v", runner.last.Params)
}
if _, ok := runner.last.Params["cellStyle"]; !ok {
t.Fatalf("cellStyle missing from %#v", runner.last.Params)
}
}
func TestSheetCSVCommandsAcceptFileIDAlias(t *testing.T) {
t.Parallel()
runner := &sheetCommandRunner{}
executeSheetCommand(t, runner,
"csv-get",
"--file-id", "NODE_001",
"--sheet-id", "SHEET_001",
"--range", "A1:B2",
)
if runner.last.Tool != "get_range_as_csv" {
t.Fatalf("tool = %q, want get_range_as_csv", runner.last.Tool)
}
if got := runner.last.Params["nodeId"]; got != "NODE_001" {
t.Fatalf("csv-get nodeId = %#v", got)
}
executeSheetCommand(t, runner,
"csv-put",
"--file-id", "NODE_002",
"--sheet-id", "SHEET_002",
"--csv", "a,b\n1,2",
"--start-cell", "A1",
)
if runner.last.Tool != "set_range_from_csv" {
t.Fatalf("tool = %q, want set_range_from_csv", runner.last.Tool)
}
if got := runner.last.Params["nodeId"]; got != "NODE_002" {
t.Fatalf("csv-put nodeId = %#v", got)
}
}
func TestSheetReplaceAllowsEmptyReplacement(t *testing.T) {
t.Parallel()
runner := &sheetCommandRunner{}
executeSheetCommand(t, runner,
"replace",
"--node", "NODE_001",
"--sheet-id", "SHEET_001",
"--find", "临时",
"--replacement", "",
)
if runner.last.Tool != "replace_all" {
t.Fatalf("tool = %q, want replace_all", runner.last.Tool)
}
if got := runner.last.Params["replaceText"]; got != "" {
t.Fatalf("replaceText = %#v, want empty string", got)
}
}