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.
218 lines
6.1 KiB
Go
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)
|
|
}
|
|
}
|