fix: update sheets filter view condition validation

This commit is contained in:
/
2026-05-27 17:31:19 +08:00
parent e98471ce26
commit 823a55a1ef
3 changed files with 173 additions and 8 deletions

View File

@@ -8,6 +8,7 @@ import (
"encoding/json"
"fmt"
"strings"
"unicode/utf8"
"github.com/larksuite/cli/internal/output"
"github.com/larksuite/cli/internal/validate"
@@ -264,7 +265,7 @@ var SheetCreateFilterViewCondition = common.Shortcut{
{Name: "sheet-id", Desc: "sheet ID", Required: true},
{Name: "filter-view-id", Desc: "filter view ID", Required: true},
{Name: "condition-id", Desc: "column letter (e.g. E)", Required: true},
{Name: "filter-type", Desc: "filter type: hiddenValue, number, text, color", Required: true},
{Name: "filter-type", Desc: "filter type: multiValue, number, text, color", Required: true},
{Name: "compare-type", Desc: "comparison operator (e.g. less, beginsWith, between)"},
{Name: "expected", Desc: "filter values JSON array (e.g. [\"6\"])", Required: true},
},
@@ -272,7 +273,7 @@ var SheetCreateFilterViewCondition = common.Shortcut{
if _, err := validateFilterViewToken(runtime); err != nil {
return err
}
return validateExpectedFlag(runtime.Str("expected"))
return validateFilterViewConditionFlags(runtime)
},
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
token, _ := validateFilterViewToken(runtime)
@@ -306,7 +307,7 @@ var SheetUpdateFilterViewCondition = common.Shortcut{
{Name: "sheet-id", Desc: "sheet ID", Required: true},
{Name: "filter-view-id", Desc: "filter view ID", Required: true},
{Name: "condition-id", Desc: "column letter (e.g. E)", Required: true},
{Name: "filter-type", Desc: "filter type: hiddenValue, number, text, color"},
{Name: "filter-type", Desc: "filter type: multiValue, number, text, color"},
{Name: "compare-type", Desc: "comparison operator"},
{Name: "expected", Desc: "filter values JSON array"},
},
@@ -319,10 +320,7 @@ var SheetUpdateFilterViewCondition = common.Shortcut{
!hasNonEmptyStringFlag(runtime, "expected") {
return common.FlagErrorf("specify at least one of --filter-type, --compare-type, or --expected")
}
if s := runtime.Str("expected"); s != "" {
return validateExpectedFlag(s)
}
return nil
return validateFilterViewConditionFlags(runtime)
},
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
token, _ := validateFilterViewToken(runtime)
@@ -469,6 +467,55 @@ func validateExpectedFlag(s string) error {
return nil
}
func validateFilterViewConditionFlags(runtime *common.RuntimeContext) error {
filterType := strings.TrimSpace(runtime.Str("filter-type"))
if filterType != "" {
switch filterType {
case "multiValue", "number", "text", "color":
case "hiddenValue":
return output.ErrValidation("--filter-type hiddenValue is no longer supported by Lark Sheets filter view conditions; use --filter-type multiValue with --expected values to show, and omit --compare-type")
default:
return output.ErrValidation("--filter-type must be one of multiValue, number, text, color; got %q", filterType)
}
}
expected := runtime.Str("expected")
if filterType == "multiValue" {
if strings.TrimSpace(runtime.Str("compare-type")) != "" {
return output.ErrValidation("--compare-type must be omitted when --filter-type multiValue")
}
values, err := parseExpectedStringArray(expected)
if err != nil {
return err
}
if len(values) == 0 {
return output.ErrValidation("--expected must contain at least one value when --filter-type multiValue")
}
for i, value := range values {
if utf8.RuneCountInString(value) > 50000 {
return output.ErrValidation("--expected[%d] must be 50000 characters or fewer when --filter-type multiValue", i)
}
}
return nil
}
if expected != "" {
return validateExpectedFlag(expected)
}
return nil
}
func parseExpectedStringArray(s string) ([]string, error) {
if strings.TrimSpace(s) == "" {
return nil, output.ErrValidation("--expected is required when --filter-type multiValue")
}
var values []string
if err := json.Unmarshal([]byte(s), &values); err != nil {
return nil, output.ErrValidation("--expected must be a JSON string array when --filter-type multiValue (e.g. [\"A\",\"B\"]), got: %s", s)
}
return values, nil
}
func buildConditionBody(runtime *common.RuntimeContext, includeConditionID bool) map[string]interface{} {
body := map[string]interface{}{}
if includeConditionID {

View File

@@ -334,6 +334,104 @@ func TestCreateFilterViewConditionExecuteSuccess(t *testing.T) {
}
}
func TestCreateFilterViewConditionMultiValueExecuteSuccess(t *testing.T) {
f, stdout, _, reg := cmdutil.TestFactory(t, sheetsTestConfig())
stub := &httpmock.Stub{
Method: "POST", URL: "/open-apis/sheets/v3/spreadsheets/shtTOKEN/sheets/sheet1/filter_views/fv1/conditions",
Body: map[string]interface{}{"code": 0, "msg": "success", "data": map[string]interface{}{
"condition": map[string]interface{}{"condition_id": "C", "filter_type": "multiValue"},
}},
}
reg.Register(stub)
err := mountAndRunSheets(t, SheetCreateFilterViewCondition, []string{
"+create-filter-view-condition", "--spreadsheet-token", "shtTOKEN",
"--sheet-id", "sheet1", "--filter-view-id", "fv1",
"--condition-id", "C", "--filter-type", "multiValue",
"--expected", `["A","B"]`, "--as", "user",
}, f, stdout)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
var body map[string]interface{}
if err := json.Unmarshal(stub.CapturedBody, &body); err != nil {
t.Fatalf("parse body: %v", err)
}
if body["filter_type"] != "multiValue" {
t.Fatalf("unexpected filter_type: %v", body["filter_type"])
}
if _, ok := body["compare_type"]; ok {
t.Fatalf("multiValue body must omit compare_type: %v", body)
}
expected, ok := body["expected"].([]interface{})
if !ok || len(expected) != 2 || expected[0] != "A" || expected[1] != "B" {
t.Fatalf("unexpected expected values: %#v", body["expected"])
}
}
func TestCreateFilterViewConditionRejectsHiddenValue(t *testing.T) {
f, stdout, _, _ := cmdutil.TestFactory(t, sheetsTestConfig())
err := mountAndRunSheets(t, SheetCreateFilterViewCondition, []string{
"+create-filter-view-condition", "--spreadsheet-token", "shtTOKEN",
"--sheet-id", "sheet1", "--filter-view-id", "fv1",
"--condition-id", "C", "--filter-type", "hiddenValue",
"--expected", `["A"]`, "--as", "user",
}, f, stdout)
if err == nil {
t.Fatal("expected hiddenValue validation error, got nil")
}
if !strings.Contains(err.Error(), "--filter-type hiddenValue is no longer supported") {
t.Fatalf("unexpected error message: %v", err)
}
}
func TestCreateFilterViewConditionRejectsInvalidMultiValueFlags(t *testing.T) {
cases := []struct {
name string
args []string
wantText string
}{
{
name: "compare type",
args: []string{"--compare-type", "less", "--expected", `["A"]`},
wantText: "--compare-type must be omitted",
},
{
name: "empty expected",
args: []string{"--expected", `[]`},
wantText: "at least one value",
},
{
name: "non-string expected",
args: []string{"--expected", `[1]`},
wantText: "JSON string array",
},
{
name: "too long expected",
args: []string{"--expected", `["` + strings.Repeat("x", 50001) + `"]`},
wantText: "50000 characters",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
f, stdout, _, _ := cmdutil.TestFactory(t, sheetsTestConfig())
args := []string{
"+create-filter-view-condition", "--spreadsheet-token", "shtTOKEN",
"--sheet-id", "sheet1", "--filter-view-id", "fv1",
"--condition-id", "C", "--filter-type", "multiValue",
}
args = append(args, tc.args...)
args = append(args, "--as", "user")
err := mountAndRunSheets(t, SheetCreateFilterViewCondition, args, f, stdout)
if err == nil {
t.Fatalf("expected validation error for %s, got nil", tc.name)
}
if !strings.Contains(err.Error(), tc.wantText) {
t.Fatalf("unexpected error message: %v", err)
}
})
}
}
// ── UpdateFilterViewCondition ────────────────────────────────────────────────
func TestUpdateFilterViewConditionDryRun(t *testing.T) {
@@ -384,6 +482,21 @@ func TestUpdateFilterViewConditionRejectsNoFields(t *testing.T) {
}
}
func TestUpdateFilterViewConditionRejectsHiddenValue(t *testing.T) {
f, stdout, _, _ := cmdutil.TestFactory(t, sheetsTestConfig())
err := mountAndRunSheets(t, SheetUpdateFilterViewCondition, []string{
"+update-filter-view-condition", "--spreadsheet-token", "shtTOKEN",
"--sheet-id", "sheet1", "--filter-view-id", "fv1", "--condition-id", "C",
"--filter-type", "hiddenValue", "--expected", `["A"]`, "--as", "user",
}, f, stdout)
if err == nil {
t.Fatal("expected hiddenValue validation error, got nil")
}
if !strings.Contains(err.Error(), "--filter-type hiddenValue is no longer supported") {
t.Fatalf("unexpected error message: %v", err)
}
}
// ── ListFilterViewConditions ─────────────────────────────────────────────────
func TestListFilterViewConditionsDryRun(t *testing.T) {

View File

@@ -123,6 +123,11 @@ lark-cli sheets +create-filter-view-condition --spreadsheet-token "shtxxxxxxxx"
lark-cli sheets +create-filter-view-condition --spreadsheet-token "shtxxxxxxxx" \
--sheet-id "<sheetId>" --filter-view-id "<fvId>" \
--condition-id "G" --filter-type "text" --compare-type "beginsWith" --expected '["a"]'
# 多值筛选:只展示 Grade 为 A 或 B 的行multiValue 不传 compare-type
lark-cli sheets +create-filter-view-condition --spreadsheet-token "shtxxxxxxxx" \
--sheet-id "<sheetId>" --filter-view-id "<fvId>" \
--condition-id "C" --filter-type "multiValue" --expected '["A","B"]'
```
参数:
@@ -134,7 +139,7 @@ lark-cli sheets +create-filter-view-condition --spreadsheet-token "shtxxxxxxxx"
| `--sheet-id` | 是 | 工作表 ID |
| `--filter-view-id` | 是 | 筛选视图 ID |
| `--condition-id` | 是 | 列字母,如 `E` |
| `--filter-type` | 是 | `hiddenValue` / `number` / `text` / `color` |
| `--filter-type` | 是 | `multiValue` / `number` / `text` / `color` |
| `--compare-type` | 否 | 比较运算符 |
| `--expected` | 是 | 筛选值 JSON 数组 |