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.
636 lines
23 KiB
Go
636 lines
23 KiB
Go
package cli_compat_test
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"testing"
|
|
)
|
|
|
|
// ── base list ───────────────────────────────────────────────
|
|
|
|
func TestAitableBaseList_should_call_list_bases(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
err := execCmd(t, root, []string{"aitable", "base", "list"}, nil)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
assertToolName(t, cap, "list_bases")
|
|
assertCallCount(t, cap, 1)
|
|
}
|
|
|
|
// ── base search ─────────────────────────────────────────────
|
|
|
|
func TestAitableBaseSearch_should_call_search_bases(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "base", "search"}, map[string]string{
|
|
"query": "项目管理",
|
|
})
|
|
assertToolName(t, cap, "search_bases")
|
|
assertToolArg(t, cap, "query", "项目管理")
|
|
}
|
|
|
|
func TestAitableBaseSearch_without_query_should_call_list_bases(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
if err := execCmd(t, root, []string{"aitable", "base", "search"}, map[string]string{
|
|
"cursor": "NEXT_CURSOR",
|
|
}); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
assertToolName(t, cap, "list_bases")
|
|
assertToolArg(t, cap, "cursor", "NEXT_CURSOR")
|
|
assertArgNotPresent(t, cap, "query")
|
|
assertCallCount(t, cap, 1)
|
|
}
|
|
|
|
// ── base get ────────────────────────────────────────────────
|
|
|
|
func TestAitableBaseGet_should_call_get_base(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "base", "get"}, map[string]string{
|
|
"base-id": "BASE_001",
|
|
})
|
|
assertToolName(t, cap, "get_base")
|
|
assertToolArg(t, cap, "baseId", "BASE_001")
|
|
}
|
|
|
|
// ── base create ─────────────────────────────────────────────
|
|
|
|
func TestAitableBaseCreate_should_call_create_base(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "base", "create"}, map[string]string{
|
|
"name": "新表格",
|
|
})
|
|
assertToolName(t, cap, "create_base")
|
|
assertToolArg(t, cap, "baseName", "新表格")
|
|
}
|
|
|
|
func TestAitableBaseCreate_should_pass_template_id(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "base", "create"}, map[string]string{
|
|
"name": "模板表格", "template-id": "TPL_001",
|
|
})
|
|
assertToolArg(t, cap, "templateId", "TPL_001")
|
|
}
|
|
|
|
// ── base update ─────────────────────────────────────────────
|
|
|
|
func TestAitableBaseUpdate_should_call_update_base(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "base", "update"}, map[string]string{
|
|
"base-id": "BASE_001", "name": "新名称",
|
|
})
|
|
assertToolName(t, cap, "update_base")
|
|
assertToolArg(t, cap, "baseId", "BASE_001")
|
|
assertToolArg(t, cap, "newBaseName", "新名称")
|
|
}
|
|
|
|
func TestAitableBaseUpdate_should_pass_description(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "base", "update"}, map[string]string{
|
|
"base-id": "B1", "name": "N", "desc": "备注",
|
|
})
|
|
assertToolArg(t, cap, "description", "备注")
|
|
}
|
|
|
|
// ── base delete ─────────────────────────────────────────────
|
|
|
|
func TestAitableBaseDelete_should_call_delete_base(t *testing.T) {
|
|
cap := setupTestDepsAutoConfirm(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "base", "delete"}, map[string]string{
|
|
"base-id": "BASE_DEL",
|
|
})
|
|
assertToolName(t, cap, "delete_base")
|
|
assertToolArg(t, cap, "baseId", "BASE_DEL")
|
|
}
|
|
|
|
func TestAitableBaseDelete_should_pass_reason(t *testing.T) {
|
|
cap := setupTestDepsAutoConfirm(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "base", "delete"}, map[string]string{
|
|
"base-id": "B1", "reason": "不再需要",
|
|
})
|
|
assertToolArg(t, cap, "reason", "不再需要")
|
|
}
|
|
|
|
func TestAitableBaseDelete_should_not_call_mcp_in_dry_run(t *testing.T) {
|
|
cap := setupTestDepsWithDryRun(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "base", "delete"}, map[string]string{
|
|
"base-id": "B1",
|
|
})
|
|
assertCallCount(t, cap, 0)
|
|
}
|
|
|
|
// ── table get ───────────────────────────────────────────────
|
|
|
|
func TestAitableTableGet_should_call_get_tables(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "table", "get"}, map[string]string{
|
|
"base-id": "BASE_001",
|
|
})
|
|
assertToolName(t, cap, "get_tables")
|
|
assertToolArg(t, cap, "baseId", "BASE_001")
|
|
}
|
|
|
|
func TestAitableTableGet_should_pass_table_ids(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "table", "get"}, map[string]string{
|
|
"base-id": "B1", "table-ids": "tbl1,tbl2",
|
|
})
|
|
assertToolArg(t, cap, "tableIds", []string{"tbl1", "tbl2"})
|
|
}
|
|
|
|
// ── table create ────────────────────────────────────────────
|
|
|
|
func TestAitableTableCreate_should_call_create_table(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "table", "create"}, map[string]string{
|
|
"base-id": "B1", "name": "任务表",
|
|
"fields": `[{"fieldName":"名称","type":"text"}]`,
|
|
})
|
|
assertToolName(t, cap, "create_table")
|
|
assertToolArg(t, cap, "baseId", "B1")
|
|
assertToolArg(t, cap, "tableName", "任务表")
|
|
}
|
|
|
|
func TestAitableTableCreate_without_fields_should_pass_empty_fields(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
if err := execCmd(t, root, []string{"aitable", "table", "create"}, map[string]string{
|
|
"base-id": "B1", "name": "空字段表",
|
|
}); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
assertToolName(t, cap, "create_table")
|
|
assertToolArg(t, cap, "fields", []any{})
|
|
}
|
|
|
|
// ── table update ────────────────────────────────────────────
|
|
|
|
func TestAitableTableUpdate_should_call_update_table(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "table", "update"}, map[string]string{
|
|
"base-id": "B1", "table-id": "TBL_001", "name": "新表名",
|
|
})
|
|
assertToolName(t, cap, "update_table")
|
|
assertToolArg(t, cap, "newTableName", "新表名")
|
|
}
|
|
|
|
// ── table delete ────────────────────────────────────────────
|
|
|
|
func TestAitableTableDelete_should_call_delete_table(t *testing.T) {
|
|
cap := setupTestDepsAutoConfirm(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "table", "delete"}, map[string]string{
|
|
"base-id": "B1", "table-id": "TBL_DEL",
|
|
})
|
|
assertToolName(t, cap, "delete_table")
|
|
assertToolArg(t, cap, "tableId", "TBL_DEL")
|
|
}
|
|
|
|
func TestAitableTableDelete_should_pass_reason(t *testing.T) {
|
|
cap := setupTestDepsAutoConfirm(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "table", "delete"}, map[string]string{
|
|
"base-id": "B1", "table-id": "T1", "reason": "测试清理",
|
|
})
|
|
assertToolArg(t, cap, "reason", "测试清理")
|
|
}
|
|
|
|
// ── field get ───────────────────────────────────────────────
|
|
|
|
func TestAitableFieldGet_should_call_get_fields(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "field", "get"}, map[string]string{
|
|
"base-id": "B1", "table-id": "T1",
|
|
})
|
|
assertToolName(t, cap, "get_fields")
|
|
assertToolArg(t, cap, "baseId", "B1")
|
|
assertToolArg(t, cap, "tableId", "T1")
|
|
}
|
|
|
|
func TestAitableFieldGet_should_pass_field_ids(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "field", "get"}, map[string]string{
|
|
"base-id": "B1", "table-id": "T1", "field-ids": "fld1,fld2",
|
|
})
|
|
assertToolArg(t, cap, "fieldIds", []string{"fld1", "fld2"})
|
|
}
|
|
|
|
// ── field create ────────────────────────────────────────────
|
|
|
|
func TestAitableFieldCreate_should_call_create_fields(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "field", "create"}, map[string]string{
|
|
"base-id": "B1", "table-id": "T1",
|
|
"fields": `[{"fieldName":"状态","type":"singleSelect"}]`,
|
|
})
|
|
assertToolName(t, cap, "create_fields")
|
|
assertToolArg(t, cap, "baseId", "B1")
|
|
assertToolArg(t, cap, "tableId", "T1")
|
|
}
|
|
|
|
// ── field update ────────────────────────────────────────────
|
|
|
|
func TestAitableFieldUpdate_should_call_update_field(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "field", "update"}, map[string]string{
|
|
"base-id": "B1", "table-id": "T1", "field-id": "FLD_001",
|
|
"name": "新字段名",
|
|
})
|
|
assertToolName(t, cap, "update_field")
|
|
assertToolArg(t, cap, "fieldId", "FLD_001")
|
|
assertToolArg(t, cap, "newFieldName", "新字段名")
|
|
}
|
|
|
|
func TestAitableFieldUpdate_should_pass_config_json(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "field", "update"}, map[string]string{
|
|
"base-id": "B1", "table-id": "T1", "field-id": "F1",
|
|
"config": `{"options":[{"name":"X"}]}`,
|
|
})
|
|
expected := map[string]any{
|
|
"options": []any{map[string]any{"name": "X"}},
|
|
}
|
|
assertToolArg(t, cap, "config", expected)
|
|
}
|
|
|
|
func TestAitableFieldUpdate_should_pass_ai_config_json(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
if err := execCmd(t, root, []string{"aitable", "field", "update"}, map[string]string{
|
|
"base-id": "B1",
|
|
"table-id": "T1",
|
|
"field-id": "F1",
|
|
"ai-config": `{"outputType":"text","prompt":[{"type":"text","value":"请总结"}]}`,
|
|
}); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
expected := map[string]any{
|
|
"outputType": "text",
|
|
"prompt": []any{map[string]any{"type": "text", "value": "请总结"}},
|
|
}
|
|
assertToolName(t, cap, "update_field")
|
|
assertToolArg(t, cap, "aiConfig", expected)
|
|
}
|
|
|
|
func TestAitableFieldUpdate_without_mutation_should_fail_before_call(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
err := execCmd(t, root, []string{"aitable", "field", "update"}, map[string]string{
|
|
"base-id": "B1", "table-id": "T1", "field-id": "F1",
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected validation error")
|
|
}
|
|
assertCallCount(t, cap, 0)
|
|
}
|
|
|
|
// ── field delete ────────────────────────────────────────────
|
|
|
|
func TestAitableFieldDelete_should_call_delete_field(t *testing.T) {
|
|
cap := setupTestDepsAutoConfirm(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "field", "delete"}, map[string]string{
|
|
"base-id": "B1", "table-id": "T1", "field-id": "FLD_DEL",
|
|
})
|
|
assertToolName(t, cap, "delete_field")
|
|
assertToolArg(t, cap, "fieldId", "FLD_DEL")
|
|
}
|
|
|
|
// ── record query ────────────────────────────────────────────
|
|
|
|
func TestAitableRecordQuery_should_call_query_records(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "record", "query"}, map[string]string{
|
|
"base-id": "B1", "table-id": "T1",
|
|
})
|
|
assertToolName(t, cap, "query_records")
|
|
assertToolArg(t, cap, "baseId", "B1")
|
|
assertToolArg(t, cap, "tableId", "T1")
|
|
}
|
|
|
|
func TestAitableRecordQuery_should_pass_record_ids(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "record", "query"}, map[string]string{
|
|
"base-id": "B1", "table-id": "T1", "record-ids": "rec1,rec2",
|
|
})
|
|
assertToolArg(t, cap, "recordIds", []string{"rec1", "rec2"})
|
|
}
|
|
|
|
func TestAitableRecordQuery_should_normalize_sort_order_and_query_keyword(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
if err := execCmd(t, root, []string{"aitable", "record", "query"}, map[string]string{
|
|
"base-id": "B1",
|
|
"table-id": "T1",
|
|
"query": "待办",
|
|
"sort": `[{"fieldId":"fld_due","order":"desc"}]`,
|
|
}); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
assertToolName(t, cap, "query_records")
|
|
assertToolArg(t, cap, "keyword", "待办")
|
|
assertArgNotPresent(t, cap, "query")
|
|
assertToolArg(t, cap, "sort", []any{map[string]any{
|
|
"fieldId": "fld_due",
|
|
"direction": "desc",
|
|
}})
|
|
}
|
|
|
|
// ── record create ───────────────────────────────────────────
|
|
|
|
func TestAitableRecordCreate_should_call_create_records(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "record", "create"}, map[string]string{
|
|
"base-id": "B1", "table-id": "T1",
|
|
"records": `[{"cells":{"fld1":"hello"}}]`,
|
|
})
|
|
assertToolName(t, cap, "create_records")
|
|
assertToolArg(t, cap, "baseId", "B1")
|
|
}
|
|
|
|
// ── record update ───────────────────────────────────────────
|
|
|
|
func TestAitableRecordUpdate_should_call_update_records(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "record", "update"}, map[string]string{
|
|
"base-id": "B1", "table-id": "T1",
|
|
"records": `[{"recordId":"rec1","cells":{"fld1":"updated"}}]`,
|
|
})
|
|
assertToolName(t, cap, "update_records")
|
|
}
|
|
|
|
// ── record delete ───────────────────────────────────────────
|
|
|
|
func TestAitableRecordDelete_should_call_delete_records(t *testing.T) {
|
|
cap := setupTestDepsAutoConfirm(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "record", "delete"}, map[string]string{
|
|
"base-id": "B1", "table-id": "T1", "record-ids": "rec1,rec2",
|
|
})
|
|
assertToolName(t, cap, "delete_records")
|
|
assertToolArg(t, cap, "recordIds", []string{"rec1", "rec2"})
|
|
assertCallCount(t, cap, 1)
|
|
}
|
|
|
|
// ── template search ─────────────────────────────────────────
|
|
|
|
func TestAitableTemplateSearch_should_call_search_templates(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
_ = execCmd(t, root, []string{"aitable", "template", "search"}, map[string]string{
|
|
"query": "项目管理",
|
|
})
|
|
assertToolName(t, cap, "search_templates")
|
|
assertToolArg(t, cap, "query", "项目管理")
|
|
}
|
|
|
|
// ── export data ─────────────────────────────────────────────
|
|
|
|
func TestAitableExportData_should_match_wukong_flag_surface(t *testing.T) {
|
|
root := buildRoot()
|
|
cmd, _, err := root.Find([]string{"aitable", "export", "data"})
|
|
if err != nil {
|
|
t.Fatalf("find export data command: %v", err)
|
|
}
|
|
for _, name := range []string{"base-id", "scope", "format", "task-id", "table-id", "view-id", "timeout-ms"} {
|
|
if cmd.Flags().Lookup(name) == nil {
|
|
t.Fatalf("expected --%s flag to exist", name)
|
|
}
|
|
}
|
|
for _, name := range []string{"export-format", "output", "timeout-sec"} {
|
|
if cmd.Flags().Lookup(name) != nil {
|
|
t.Fatalf("flag --%s should not be exposed by Wukong-compatible export data", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAitableExportData_should_call_export_data_with_wukong_payload(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
|
|
var out bytes.Buffer
|
|
root.SetOut(&out)
|
|
root.SetErr(&bytes.Buffer{})
|
|
root.SetArgs([]string{
|
|
"aitable", "export", "data",
|
|
"--base-id", "B1",
|
|
"--scope", "table",
|
|
"--table-id", "T1",
|
|
"--format", "excel",
|
|
"--timeout-ms", "1000",
|
|
"--dry-run",
|
|
})
|
|
if err := root.Execute(); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
var inv struct {
|
|
Invocation struct {
|
|
Tool string `json:"tool"`
|
|
Params map[string]any `json:"params"`
|
|
DryRun bool `json:"dry_run"`
|
|
} `json:"invocation"`
|
|
}
|
|
var flatInv struct {
|
|
Tool string `json:"tool"`
|
|
Params map[string]any `json:"params"`
|
|
DryRun bool `json:"dry_run"`
|
|
}
|
|
if err := json.Unmarshal(out.Bytes(), &inv); err != nil {
|
|
t.Fatalf("json.Unmarshal() error = %v\noutput:\n%s", err, out.String())
|
|
}
|
|
if inv.Invocation.Tool != "" {
|
|
cap.record(inv.Invocation.Tool, inv.Invocation.Params, "")
|
|
} else if err := json.Unmarshal(out.Bytes(), &flatInv); err == nil && flatInv.Tool != "" {
|
|
cap.record(flatInv.Tool, flatInv.Params, "")
|
|
}
|
|
|
|
assertToolName(t, cap, "export_data")
|
|
assertToolArg(t, cap, "baseId", "B1")
|
|
assertToolArg(t, cap, "scope", "table")
|
|
assertToolArg(t, cap, "tableId", "T1")
|
|
assertToolArg(t, cap, "format", "excel")
|
|
assertToolArg(t, cap, "timeoutMs", 1000)
|
|
assertArgNotPresent(t, cap, "__async__")
|
|
assertArgNotPresent(t, cap, "__output__")
|
|
assertCallCount(t, cap, 1)
|
|
}
|
|
|
|
// ── view ────────────────────────────────────────────────────
|
|
|
|
func TestAitableViewCreate_should_pass_sub_type_description_and_config(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
if err := execCmd(t, root, []string{"aitable", "view", "create"}, map[string]string{
|
|
"base-id": "B1",
|
|
"table-id": "T1",
|
|
"view-type": "Grid",
|
|
"view-sub-type": "Grid",
|
|
"name": "视图名",
|
|
"desc": `{"content":[]}`,
|
|
"config": `{"visibleFieldIds":["fld1","fld2"]}`,
|
|
}); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
assertToolName(t, cap, "create_view")
|
|
assertToolArg(t, cap, "viewSubType", "Grid")
|
|
assertToolArg(t, cap, "viewDescription", map[string]any{"content": []any{}})
|
|
assertToolArg(t, cap, "config", map[string]any{"visibleFieldIds": []any{"fld1", "fld2"}})
|
|
}
|
|
|
|
func TestAitableViewUpdate_should_pass_description(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
if err := execCmd(t, root, []string{"aitable", "view", "update"}, map[string]string{
|
|
"base-id": "B1",
|
|
"table-id": "T1",
|
|
"view-id": "V1",
|
|
"desc": `{"content":[]}`,
|
|
}); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
assertToolName(t, cap, "update_view")
|
|
assertToolArg(t, cap, "viewDescription", map[string]any{"content": []any{}})
|
|
}
|
|
|
|
func TestAitableRecordHistoryList_should_call_query_record_history(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
if err := execCmd(t, root, []string{"aitable", "record", "history-list"}, map[string]string{
|
|
"base-id": "B1",
|
|
"table-id": "T1",
|
|
"record-id": "R1",
|
|
"limit": "30",
|
|
"offset": "10",
|
|
}); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
assertToolName(t, cap, "query_record_history")
|
|
assertToolArg(t, cap, "baseId", "B1")
|
|
assertToolArg(t, cap, "tableId", "T1")
|
|
assertToolArg(t, cap, "recordId", "R1")
|
|
assertToolArg(t, cap, "limit", 30)
|
|
assertToolArg(t, cap, "offset", 10)
|
|
}
|
|
|
|
func TestAitableRecordUpsert_should_call_record_upsert(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
if err := execCmd(t, root, []string{"aitable", "record", "upsert"}, map[string]string{
|
|
"base-id": "B1",
|
|
"table-id": "T1",
|
|
"records": `[{"recordId":"R1","cells":{"fld":"updated"}},{"cells":{"fld":"new"}}]`,
|
|
}); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
assertToolName(t, cap, "record_upsert")
|
|
assertToolArg(t, cap, "baseId", "B1")
|
|
assertToolArg(t, cap, "tableId", "T1")
|
|
}
|
|
|
|
func TestAitableViewLock_should_call_lock_or_unlock_view(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
if err := execCmdWithArgs(t, root, []string{"aitable", "view", "lock"}, map[string]string{
|
|
"base-id": "B1",
|
|
"table-id": "T1",
|
|
"view-id": "V1",
|
|
}, []string{"--off"}); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
assertToolName(t, cap, "lock_or_unlock_view")
|
|
assertToolArg(t, cap, "action", "unlock")
|
|
}
|
|
|
|
func TestAitableWorkflowList_should_call_list_workflows(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
if err := execCmd(t, root, []string{"aitable", "workflow", "list"}, map[string]string{
|
|
"base-id": "B1",
|
|
"limit": "50",
|
|
"offset": "100",
|
|
}); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
assertToolName(t, cap, "list_workflows")
|
|
assertToolArg(t, cap, "baseId", "B1")
|
|
assertToolArg(t, cap, "limit", 50)
|
|
assertToolArg(t, cap, "offset", 100)
|
|
}
|
|
|
|
func TestAitableAdvpermRoleCreate_should_call_create_role(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
if err := execCmd(t, root, []string{"aitable", "advperm", "role-create"}, map[string]string{
|
|
"base-id": "B1",
|
|
"name": "市场可读",
|
|
"sub-roles": `[{"targetId":"T1","targetType":"sheet","authLevel":"read"}]`,
|
|
}); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
assertToolName(t, cap, "create_role")
|
|
assertToolArg(t, cap, "baseId", "B1")
|
|
assertToolArg(t, cap, "name", "市场可读")
|
|
assertToolArg(t, cap, "subRoles", []any{map[string]any{
|
|
"targetId": "T1",
|
|
"targetType": "sheet",
|
|
"authLevel": "read",
|
|
}})
|
|
}
|
|
|
|
func TestAitableSectionMoveNode_should_call_move_nsheet_node(t *testing.T) {
|
|
cap := setupTestDeps(t, "aitable")
|
|
root := buildRoot()
|
|
if err := execCmd(t, root, []string{"aitable", "section", "move-node"}, map[string]string{
|
|
"base-id": "B1",
|
|
"node-id": "NODE_001",
|
|
"new-parent-section-id": "SEC_001",
|
|
"target-index": "0",
|
|
}); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
assertToolName(t, cap, "move_nsheet_node")
|
|
assertToolArg(t, cap, "baseId", "B1")
|
|
assertToolArg(t, cap, "nodeId", "NODE_001")
|
|
assertToolArg(t, cap, "newParentSectionId", "SEC_001")
|
|
assertToolArg(t, cap, "targetIndex", 0)
|
|
}
|
|
|
|
func TestAitableSurface_should_not_expose_open_source_only_commands(t *testing.T) {
|
|
root := buildRoot()
|
|
for _, path := range [][]string{
|
|
{"aitable", "record", "batch-update"},
|
|
{"aitable", "attachment", "upload-file"},
|
|
{"aitable", "form"},
|
|
} {
|
|
cmd, _, err := root.Find(path)
|
|
if err == nil && cmd != nil && !cmd.Hidden {
|
|
t.Fatalf("command %v should not be visible in Wukong-compatible surface", path)
|
|
}
|
|
}
|
|
}
|