249 lines
10 KiB
Go
249 lines
10 KiB
Go
package helpers
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/runtimeannotate"
|
|
)
|
|
|
|
func TestCrossPlatformCoverageSetDropdownSourceRangeCommand(t *testing.T) {
|
|
caller := &scriptedToolCaller{steps: []scriptedToolStep{{text: `{"success":true}`}}}
|
|
installScriptedCaller(t, caller)
|
|
err := executeDimensionCoverage(t, "set-dropdown",
|
|
"--node", "node-1",
|
|
"--sheet-id", "target-sheet",
|
|
"--range", "B2:B100",
|
|
"--source-sheet-id", "source-sheet",
|
|
"--source-range", "$t$1:$t$3",
|
|
"--multi-select",
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("set SourceRange dropdown: %v", err)
|
|
}
|
|
if caller.tool != "set_dropdown_lists" {
|
|
t.Fatalf("tool = %q, want set_dropdown_lists", caller.tool)
|
|
}
|
|
if _, exists := caller.args["options"]; exists {
|
|
t.Fatalf("SourceRange request must omit options: %#v", caller.args)
|
|
}
|
|
source, ok := caller.args["sourceRange"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("sourceRange = %#v", caller.args["sourceRange"])
|
|
}
|
|
if source["sheetId"] != "source-sheet" || source["a1Notation"] != "$t$1:$t$3" {
|
|
t.Fatalf("sourceRange = %#v", source)
|
|
}
|
|
if caller.args["enableMultiSelect"] != true {
|
|
t.Fatalf("enableMultiSelect = %#v", caller.args["enableMultiSelect"])
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageSetDropdownInlineCommandStillOmitsSourceRange(t *testing.T) {
|
|
caller := &scriptedToolCaller{steps: []scriptedToolStep{{text: `{"success":true}`}}}
|
|
installScriptedCaller(t, caller)
|
|
err := executeDimensionCoverage(t, "set-dropdown",
|
|
"--node", "node-1",
|
|
"--sheet-id", "sheet-1",
|
|
"--range", "A1:A3",
|
|
"--options", `[{"value":"one","color":"#ff0000"}]`,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("set inline dropdown: %v", err)
|
|
}
|
|
if _, exists := caller.args["sourceRange"]; exists {
|
|
t.Fatalf("inline request must omit sourceRange: %#v", caller.args)
|
|
}
|
|
options, ok := caller.args["options"].([]map[string]any)
|
|
if !ok || len(options) != 1 || options[0]["value"] != "one" {
|
|
t.Fatalf("options = %#v", caller.args["options"])
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageSetDropdownModeConstraints(t *testing.T) {
|
|
base := []string{"--node", "node", "--sheet-id", "sheet", "--range", "A1"}
|
|
tests := []struct {
|
|
name string
|
|
args []string
|
|
want string
|
|
}{
|
|
{name: "neither", want: "one of the flags"},
|
|
{name: "both", args: []string{"--options", `[{"value":"one"}]`, "--source-sheet-id", "source", "--source-range", "A1:A3"}, want: "none of the others"},
|
|
{name: "source sheet without range", args: []string{"--options", `[{"value":"one"}]`, "--source-sheet-id", "source"}, want: "must all be set"},
|
|
{name: "range without source sheet", args: []string{"--source-range", "A1:A3"}, want: "must all be set"},
|
|
{name: "sheet prefix", args: []string{"--source-sheet-id", "source", "--source-range", "Sheet2!A1:A3"}, want: "不能包含工作表前缀"},
|
|
{name: "formula", args: []string{"--source-sheet-id", "source", "--source-range", "=A1:A3"}, want: "必须是单一连续区域"},
|
|
{name: "multi region", args: []string{"--source-sheet-id", "source", "--source-range", "A1:A3,C1:C3"}, want: "必须是单一连续区域"},
|
|
{name: "blank source range", args: []string{"--source-sheet-id", "source", "--source-range", " \t "}, want: "--source-range 不能为空"},
|
|
{name: "blank source sheet", args: []string{"--source-sheet-id", " \t ", "--source-range", "A1:A3"}, want: "--source-sheet-id 不能为空"},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
caller := &scriptedToolCaller{}
|
|
installScriptedCaller(t, caller)
|
|
args := append(append([]string{}, base...), tc.args...)
|
|
err := executeDimensionCoverage(t, "set-dropdown", args...)
|
|
if err == nil || !strings.Contains(err.Error(), tc.want) {
|
|
t.Fatalf("error = %v, want contains %q", err, tc.want)
|
|
}
|
|
if caller.calls != 0 {
|
|
t.Fatalf("calls = %d, validation must fail before request", caller.calls)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageSetDropdownRunEValidation(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
options string
|
|
source string
|
|
rangeID string
|
|
want string
|
|
}{
|
|
{name: "neither mode", want: "必须且只能指定一个"},
|
|
{name: "source range without sheet", rangeID: "A1:A3", want: "必须同时指定 --source-sheet-id"},
|
|
{name: "source sheet without range", options: `[{"value":"one"}]`, source: "source", want: "必须同时指定 --source-range"},
|
|
{name: "invalid options JSON", options: "{", want: "JSON 解析失败"},
|
|
{name: "missing option value", options: `[{}]`, want: "value 为空"},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
cmd := dimensionCoverageCommand(t, "set-dropdown")
|
|
for name, value := range map[string]string{
|
|
"node": "node", "sheet-id": "sheet", "range": "A1",
|
|
"options": tc.options, "source-sheet-id": tc.source, "source-range": tc.rangeID,
|
|
} {
|
|
if err := cmd.Flags().Set(name, value); err != nil {
|
|
t.Fatalf("set --%s: %v", name, err)
|
|
}
|
|
}
|
|
err := cmd.RunE(cmd, nil)
|
|
if err == nil || !strings.Contains(err.Error(), tc.want) {
|
|
t.Fatalf("error = %v, want contains %q", err, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageSetDropdownPreservesSchemaConstraints(t *testing.T) {
|
|
cmd := dimensionCoverageCommand(t, "set-dropdown")
|
|
if constraints := runtimeannotate.CommandConstraints(cmd); !runtimeannotate.ConstraintsEmpty(constraints) {
|
|
t.Fatalf("constraints = %#v, want no new schema-level constraints", constraints)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageBatchSetDropdownSourceRange(t *testing.T) {
|
|
got, err := translateBatchOp(map[string]any{
|
|
"toolName": "set-dropdown",
|
|
"input": map[string]any{
|
|
"sheet-id": "target-sheet",
|
|
"range": "B2:B100",
|
|
"source-sheet-id": "source-sheet",
|
|
"source-range": "T:T",
|
|
"multi-select": true,
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("translate SourceRange batch op: %v", err)
|
|
}
|
|
input := got["input"].(map[string]any)
|
|
if _, exists := input["options"]; exists {
|
|
t.Fatalf("SourceRange batch input must omit options: %#v", input)
|
|
}
|
|
source := input["sourceRange"].(map[string]any)
|
|
if source["sheetId"] != "source-sheet" || source["a1Notation"] != "T:T" {
|
|
t.Fatalf("sourceRange = %#v", source)
|
|
}
|
|
|
|
invalid := []map[string]any{
|
|
{},
|
|
{"options": []any{map[string]any{"value": "one"}}, "source-sheet-id": "source", "source-range": "A1:A3"},
|
|
{"source-range": "A1:A3"},
|
|
{"source-sheet-id": "source", "source-range": "A1:A3", "colors": []any{"#fff"}},
|
|
{"source-sheet-id": "source", "source-range": "A1:A3", "source-colors": []any{"#fff"}},
|
|
{"source-sheet-id": "source", "source-range": "Sheet2!A1:A3"},
|
|
}
|
|
for _, value := range invalid {
|
|
if _, err := translateBatchOp(map[string]any{"toolName": "set-dropdown", "input": value}); err == nil {
|
|
t.Errorf("invalid batch SourceRange input %#v returned nil", value)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageBatchSetDropdownValidationGuidance(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input map[string]any
|
|
want string
|
|
}{
|
|
{
|
|
name: "inline top-level colors",
|
|
input: map[string]any{"options": []any{map[string]any{"value": "one"}}, "colors": []any{"#fff"}},
|
|
want: "Inline 颜色请写入 options[].color",
|
|
},
|
|
{
|
|
name: "source top-level colors",
|
|
input: map[string]any{"source-sheet-id": "source", "source-range": "A1:A3", "source-colors": []any{"#fff"}},
|
|
want: "SourceRange 颜色写入暂不支持",
|
|
},
|
|
{
|
|
name: "blank source range",
|
|
input: map[string]any{"source-sheet-id": "source", "source-range": " \t "},
|
|
want: "--source-range 不能为空",
|
|
},
|
|
{
|
|
name: "blank source sheet",
|
|
input: map[string]any{"source-sheet-id": " \t ", "source-range": "A1:A3"},
|
|
want: "--source-sheet-id 不能为空",
|
|
},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, err := translateBatchOp(map[string]any{"toolName": "set-dropdown", "input": tc.input})
|
|
if err == nil || !strings.Contains(err.Error(), tc.want) {
|
|
t.Fatalf("error = %v, want contains %q", err, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageDropdownHelpDescribesDynamicAndInvalidContracts(t *testing.T) {
|
|
setDropdown := dimensionCoverageCommand(t, "set-dropdown")
|
|
if !strings.Contains(setDropdown.Long, "会自动调整引用并保持 valid") ||
|
|
!strings.Contains(setDropdown.Long, "只有 invalid 时才重新选择来源并写入") {
|
|
t.Fatalf("set-dropdown help does not describe verified structural behavior:\n%s", setDropdown.Long)
|
|
}
|
|
getDropdown := dimensionCoverageCommand(t, "get-dropdown")
|
|
if !strings.Contains(getDropdown.Long, `仅在 sourceRangeStatus="valid" 时返回`) ||
|
|
!strings.Contains(getDropdown.Long, "invalid 结果仍保留配置组,但省略 sourceRange") {
|
|
t.Fatalf("get-dropdown help does not describe conditional sourceRange readback:\n%s", getDropdown.Long)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageValidateSourceRangeDataValidation(t *testing.T) {
|
|
valid := []any{
|
|
map[string]any{"type": "dropdown", "sourceRange": map[string]any{"sheetId": "source", "a1Notation": "T1:T3"}},
|
|
map[string]any{"type": "dropdown", "sourceRange": map[string]any{"sheetId": "missing-sheet", "a1Notation": "not-validated-locally"}, "enableMultiSelect": true},
|
|
}
|
|
for _, value := range valid {
|
|
if err := validateDataValidation(value, "dv"); err != nil {
|
|
t.Errorf("valid SourceRange data validation %#v: %v", value, err)
|
|
}
|
|
}
|
|
|
|
invalid := []any{
|
|
map[string]any{"type": "dropdown", "options": []any{map[string]any{"value": "one"}}, "sourceRange": map[string]any{"sheetId": "source", "a1Notation": "T1:T3"}},
|
|
map[string]any{"type": "dropdown", "sourceRange": "T1:T3"},
|
|
map[string]any{"type": "dropdown", "sourceRange": map[string]any{"a1Notation": "T1:T3"}},
|
|
map[string]any{"type": "dropdown", "sourceRange": map[string]any{"sheetId": "source"}},
|
|
map[string]any{"type": "dropdown", "sourceRange": map[string]any{"sheetId": "source", "a1Notation": "T1:T3", "colors": []any{"#fff"}}},
|
|
map[string]any{"type": "dropdown", "sourceRange": map[string]any{"sheetId": "source", "a1Notation": "T1:T3"}, "enableMultiSelect": "yes"},
|
|
}
|
|
for _, value := range invalid {
|
|
if err := validateDataValidation(value, "dv"); err == nil {
|
|
t.Errorf("invalid SourceRange data validation %#v returned nil", value)
|
|
}
|
|
}
|
|
}
|