Files
larksuite__cli/shortcuts/base/table_create_test.go
CarolSum d289566ad0 feat(base): require --fields on +table-create (#2221)
* feat(base): require --fields on +table-create

A table created without --fields gets the platform default schema. Those
default fields then sit in the table alongside every field the caller adds
afterwards, and no field command removes them all, so the only clean recovery
is to drop the table and start over.

Make --fields required so the schema is declared up front, the way
+base-create already recommends via --table-name + --fields.

- Mark --fields Required on +table-create, and reject blank / non-array /
  empty-array values in Validate: cobra's MarkFlagRequired only checks that the
  flag was set, so --fields "" and --fields "[]" would still reach the API with
  no fields body and fall back to the default schema.
- Validate runs ahead of the dry-run branch, so --dry-run can no longer preview
  an invocation the real call would reject.
- Update the lark-base skill, e2e coverage notes and the live e2e helper.

BREAKING CHANGE: `lark-cli base +table-create --base-token <t> --name <n>`
without --fields now fails with a validation error instead of creating a
default-schema table. Callers that relied on create-empty-then-add-fields must
pass the schema to --fields.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(base): pin typed metadata on the invalid-schema rejection

Address review feedback on the +table-create schema validation.

- The invalid-fields-JSON rejection now asserts category, subtype and param
  through assertInvalidArgumentValidation, plus the preserved *json.SyntaxError
  cause, instead of only asserting that some error came back.
- Document why the missing-flag test asserts cobra's text rather than errs
  metadata, and pin that layer boundary: cobra's ValidateRequiredFlags emits a
  plain error and the dispatcher types it later (cmd/root_test.go). The test now
  fails if that boundary moves, so the weaker assertion cannot silently outlive
  its reason.
- Reword the --fields tip and the lark-base skill note: both described the
  fieldless path as if it were still reachable through +table-create. They now
  say the command rejects omitted / blank / empty schemas up front, while
  keeping why the schema must be declared here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(base): enrich +table-create field example and drop redundant tips

The cobra Required declaration and flag Desc already advertise the
--fields requirement, so the tips paragraph restating it (and its
SKILL.md / coverage.md echoes) is dropped. The select-field example
now carries multiple/hue/lightness so agents copy a complete option
shape.

* chore: remove useless example

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 10:56:41 +08:00

100 lines
3.9 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package base
import (
"strings"
"testing"
"github.com/larksuite/cli/errs"
"github.com/larksuite/cli/shortcuts/common"
)
func tableCreateFlag(t *testing.T, name string) common.Flag {
t.Helper()
for _, flag := range BaseTableCreate.Flags {
if flag.Name == name {
return flag
}
}
t.Fatalf("+table-create has no --%s flag", name)
return common.Flag{}
}
// The declaration itself is the contract: --fields must stay cobra-required so
// `--help` and the machine-readable schema both advertise it, not just Validate.
func TestBaseTableCreateDeclaresFieldsRequired(t *testing.T) {
if !tableCreateFlag(t, "fields").Required {
t.Fatal("--fields must be declared Required on +table-create")
}
}
// A missing flag fails inside cobra's ValidateRequiredFlags, which emits a plain
// error — the typed envelope is applied later by the dispatcher, whose
// classification of "required flag(s)" is pinned in cmd/root_test.go. So this
// test asserts the text contract the dispatcher keys on, and pins the layer
// boundary itself; asserting errs metadata here would test a promise this layer
// does not make.
func TestBaseTableCreateRejectsMissingFields(t *testing.T) {
factory, stdout, _ := newExecuteFactory(t)
err := runShortcut(t, BaseTableCreate, []string{"+table-create", "--base-token", "app_x", "--name", "Orders"}, factory, stdout)
if err == nil {
t.Fatal("expected +table-create without --fields to fail")
}
if !strings.Contains(err.Error(), `required flag(s) "fields" not set`) {
t.Fatalf("err=%v, want cobra required-flag error for fields", err)
}
if _, typed := errs.ProblemOf(err); typed {
t.Fatal("cobra required-flag errors reach the dispatcher untyped; if that changed, assert the typed metadata here instead")
}
}
// cobra's MarkFlagRequired only checks that the flag was set, so blank and
// empty-array values still reach Validate. Both would otherwise create a table
// with the platform default schema instead of the caller's.
func TestBaseTableCreateRejectsBlankFields(t *testing.T) {
factory, stdout, _ := newExecuteFactory(t)
err := runShortcut(t, BaseTableCreate, []string{"+table-create", "--base-token", "app_x", "--name", "Orders", "--fields", " "}, factory, stdout)
assertInvalidArgumentValidation(t, err, "--fields", nil, "cannot be blank")
}
func TestBaseTableCreateRejectsEmptyFieldsArray(t *testing.T) {
factory, stdout, _ := newExecuteFactory(t)
err := runShortcut(t, BaseTableCreate, []string{"+table-create", "--base-token", "app_x", "--name", "Orders", "--fields", "[]"}, factory, stdout)
assertInvalidArgumentValidation(t, err, "--fields", nil, "at least one field")
}
func TestBaseTableCreateRejectsNonObjectFieldItem(t *testing.T) {
factory, stdout, _ := newExecuteFactory(t)
err := runShortcut(t, BaseTableCreate, []string{"+table-create", "--base-token", "app_x", "--name", "Orders", "--fields", `["Title"]`}, factory, stdout)
assertInvalidArgumentValidation(t, err, "--fields", nil, "must be an object")
}
// Validate runs ahead of the dry-run branch, so --dry-run cannot be used to
// preview an invocation the real call would reject.
func TestBaseTableCreateDryRunRejectsEmptyFieldsArray(t *testing.T) {
factory, stdout, _ := newExecuteFactory(t)
err := runShortcut(t, BaseTableCreate, []string{"+table-create", "--base-token", "app_x", "--name", "Orders", "--fields", "[]", "--dry-run"}, factory, stdout)
assertInvalidArgumentValidation(t, err, "--fields", nil, "at least one field")
if stdout.Len() != 0 {
t.Fatalf("rejected dry-run must not print a request preview, stdout=%s", stdout.String())
}
}
func TestBaseTableCreateValidateAcceptsFieldSchema(t *testing.T) {
runtime := newBaseTestRuntime(map[string]string{
"base-token": "app_x",
"name": "Orders",
"fields": `[{"name":"OrderNo","type":"text"}]`,
}, nil, nil)
if err := validateTableCreate(runtime); err != nil {
t.Fatalf("valid field schema rejected: %v", err)
}
}