Files
修雨 4c5f1faeb1 feat: align open CLI with dws-wukong via cedar discovery version code (#509)
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.
2026-06-26 21:49:38 +08:00

750 lines
22 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"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/executor"
"github.com/spf13/cobra"
)
type docCommandRunner struct {
calls int
last executor.Invocation
all []executor.Invocation
responses []map[string]any
}
func (r *docCommandRunner) Run(_ context.Context, invocation executor.Invocation) (executor.Result, error) {
r.calls++
r.last = invocation
r.all = append(r.all, invocation)
result := executor.Result{Invocation: invocation}
if idx := r.calls - 1; idx >= 0 && idx < len(r.responses) {
result.Response = r.responses[idx]
}
return result, nil
}
func executeDocCommand(t *testing.T, cmd *cobra.Command, args ...string) (string, string, error) {
t.Helper()
var out, errOut bytes.Buffer
cmd.SetOut(&out)
cmd.SetErr(&errOut)
cmd.SetArgs(args)
err := cmd.Execute()
return out.String(), errOut.String(), err
}
func newDocTestRoot(runner executor.Runner) *cobra.Command {
cmd := docHandler{}.Command(runner)
cmd.PersistentFlags().Bool("dry-run", false, "dry run")
cmd.PersistentFlags().Bool("yes", false, "skip confirmation")
return cmd
}
func TestDocPermissionListLimitAliases(t *testing.T) {
t.Parallel()
cases := []struct {
name string
args []string
want int
}{
{name: "limit", args: []string{"--node", "NODE_001", "--limit", "50"}, want: 50},
{name: "max results", args: []string{"--node", "NODE_001", "--max-results", "40"}, want: 40},
{name: "page size", args: []string{"--node", "NODE_001", "--page-size", "10"}, want: 10},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
runner := &docCommandRunner{}
cmd := newDocPermissionListCommand(runner)
_, errOut, err := executeDocCommand(t, cmd, tc.args...)
if err != nil {
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut)
}
if runner.last.Tool != "list_permission" {
t.Fatalf("tool = %q, want list_permission", runner.last.Tool)
}
if got := runner.last.Params["maxResults"]; got != tc.want {
t.Fatalf("maxResults = %#v, want %d", got, tc.want)
}
})
}
}
func TestDocPermissionRemoveRoutesToRemovePermission(t *testing.T) {
t.Parallel()
runner := &docCommandRunner{}
cmd := newDocTestRoot(runner)
_, errOut, err := executeDocCommand(t, cmd,
"permission", "rm",
"--node", "NODE_001",
"--users", "uid1,uid2",
"--workspace", "WS_001",
)
if err != nil {
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut)
}
if runner.last.Tool != "remove_permission" {
t.Fatalf("tool = %q, want remove_permission", runner.last.Tool)
}
if got := runner.last.Params["nodeId"]; got != "NODE_001" {
t.Fatalf("nodeId = %#v, want NODE_001", got)
}
users, ok := runner.last.Params["userIds"].([]string)
if !ok || strings.Join(users, ",") != "uid1,uid2" {
t.Fatalf("userIds = %#v, want uid1,uid2", runner.last.Params["userIds"])
}
if got := runner.last.Params["workspaceId"]; got != "WS_001" {
t.Fatalf("workspaceId = %#v, want WS_001", got)
}
}
func TestDocPermissionListMaxresultsRejected(t *testing.T) {
t.Parallel()
runner := &docCommandRunner{}
cmd := newDocPermissionListCommand(runner)
_, _, err := executeDocCommand(t, cmd, "--node", "NODE_001", "--maxresults", "10")
if err == nil {
t.Fatal("Execute() error = nil, want unknown flag")
}
if !strings.Contains(err.Error(), "unknown flag: --maxresults") {
t.Fatalf("error = %q, want unknown flag for --maxresults", err.Error())
}
if runner.calls != 0 {
t.Fatalf("runner calls = %d, want 0", runner.calls)
}
}
func TestDocUpdateOverwriteRequiresYes(t *testing.T) {
t.Parallel()
runner := &docCommandRunner{}
cmd := newDocTestRoot(runner)
_, _, err := executeDocCommand(t, cmd,
"update",
"--node", "NODE_001",
"--content", "# overwrite probe",
"--mode", "overwrite",
)
if err == nil {
t.Fatal("Execute() error = nil, want --yes validation failure")
}
if !strings.Contains(err.Error(), "--yes") {
t.Fatalf("error = %q, want --yes hint", err.Error())
}
if runner.calls != 0 {
t.Fatalf("runner calls = %d, want 0", runner.calls)
}
}
func TestDocUpdateOverwriteAllowsYesAndDryRun(t *testing.T) {
t.Parallel()
cases := []struct {
name string
extraArgs []string
wantDryRun bool
}{
{name: "yes", extraArgs: []string{"--yes"}},
{name: "dry run", extraArgs: []string{"--dry-run"}, wantDryRun: true},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
runner := &docCommandRunner{}
cmd := newDocTestRoot(runner)
args := []string{
"update",
"--node", "NODE_001",
"--content", "# overwrite probe",
"--mode", "overwrite",
}
args = append(args, tc.extraArgs...)
_, errOut, err := executeDocCommand(t, cmd, args...)
if err != nil {
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut)
}
if runner.calls != 1 {
t.Fatalf("runner calls = %d, want 1", runner.calls)
}
if runner.last.DryRun != tc.wantDryRun {
t.Fatalf("DryRun = %v, want %v", runner.last.DryRun, tc.wantDryRun)
}
})
}
}
func TestDocListAcceptsFolderCompatibilityAliases(t *testing.T) {
t.Parallel()
cases := []struct {
name string
args []string
}{
{name: "node", args: []string{"--node", "FOLDER_001"}},
{name: "file id", args: []string{"--file-id", "FOLDER_001"}},
{name: "nodee typo", args: []string{"--nodee", "FOLDER_001"}},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
runner := &docCommandRunner{}
cmd := newDocListCommand(runner)
_, errOut, err := executeDocCommand(t, cmd, tc.args...)
if err != nil {
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut)
}
if runner.last.Tool != "list_nodes" {
t.Fatalf("tool = %q, want list_nodes", runner.last.Tool)
}
if got := runner.last.Params["folderId"]; got != "FOLDER_001" {
t.Fatalf("folderId = %#v, want FOLDER_001", got)
}
})
}
}
func TestDocListAcceptsWukongPaginationAliases(t *testing.T) {
t.Parallel()
runner := &docCommandRunner{}
cmd := newDocListCommand(runner)
_, errOut, err := executeDocCommand(t, cmd, "--workspace", "WS_001", "--limit", "20", "--cursor", "TOKEN_001")
if err != nil {
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut)
}
if runner.last.Tool != "list_nodes" {
t.Fatalf("tool = %q, want list_nodes", runner.last.Tool)
}
if got := runner.last.Params["workspaceId"]; got != "WS_001" {
t.Fatalf("workspaceId = %#v, want WS_001", got)
}
if got := runner.last.Params["pageSize"]; got != 20 {
t.Fatalf("pageSize = %#v, want 20", got)
}
if got := runner.last.Params["pageToken"]; got != "TOKEN_001" {
t.Fatalf("pageToken = %#v, want TOKEN_001", got)
}
}
func TestDocSearchAcceptsWukongPaginationAliases(t *testing.T) {
t.Parallel()
runner := &docCommandRunner{}
cmd := newDocSearchCommand(runner)
_, errOut, err := executeDocCommand(t, cmd, "--query", "方案", "--limit", "20", "--cursor", "TOKEN_001")
if err != nil {
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut)
}
if runner.last.Tool != "search_documents" {
t.Fatalf("tool = %q, want search_documents", runner.last.Tool)
}
if got := runner.last.Params["pageSize"]; got != 20 {
t.Fatalf("pageSize = %#v, want 20", got)
}
if got := runner.last.Params["pageToken"]; got != "TOKEN_001" {
t.Fatalf("pageToken = %#v, want TOKEN_001", got)
}
}
func TestDocExportDryRunPassesWukongExportFormat(t *testing.T) {
t.Parallel()
runner := &docCommandRunner{}
cmd := newDocTestRoot(runner)
out, errOut, err := executeDocCommand(t, cmd,
"--dry-run",
"export",
"--node", "NODE_001",
"--output", "out.docx",
"--export-format", "docx",
)
if err != nil {
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut)
}
if runner.calls != 0 {
t.Fatalf("runner calls = %d, want 0 for dry-run", runner.calls)
}
var inv executor.Invocation
if err := json.Unmarshal([]byte(out), &inv); err != nil {
t.Fatalf("Unmarshal(%q) error = %v", out, err)
}
if inv.Tool != "submit_export_job" {
t.Fatalf("tool = %q, want submit_export_job", inv.Tool)
}
if got := inv.Params["exportFormat"]; got != "docx" {
t.Fatalf("exportFormat = %#v, want docx", got)
}
}
func TestDocUploadDryRunUsesWukongFileUploadWorkflow(t *testing.T) {
t.Parallel()
contentPath := filepath.Join(t.TempDir(), "report.pdf")
if err := os.WriteFile(contentPath, []byte("pdf"), 0600); err != nil {
t.Fatal(err)
}
runner := &docCommandRunner{}
cmd := newDocTestRoot(runner)
out, errOut, err := executeDocCommand(t, cmd,
"--dry-run",
"upload",
"--file", contentPath,
"--name", "Q1汇报",
"--folder", "FOLDER_001",
"--convert",
)
if err != nil {
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut)
}
if runner.calls != 0 {
t.Fatalf("runner calls = %d, want 0 for dry-run", runner.calls)
}
var payload map[string]any
if err := json.Unmarshal([]byte(out), &payload); err != nil {
t.Fatalf("Unmarshal(%q) error = %v", out, err)
}
step1, ok := payload["step_1_get_file_upload_info"].(map[string]any)
if !ok {
t.Fatalf("missing step_1_get_file_upload_info in %#v", payload)
}
if got := step1["tool"]; got != "get_file_upload_info" {
t.Fatalf("step1 tool = %#v, want get_file_upload_info", got)
}
step3, ok := payload["step_3_commit_uploaded_file"].(map[string]any)
if !ok {
t.Fatalf("missing step_3_commit_uploaded_file in %#v", payload)
}
params, ok := step3["params"].(map[string]any)
if !ok {
t.Fatalf("step3 params type = %T", step3["params"])
}
if got := params["name"]; got != "Q1汇报.pdf" {
t.Fatalf("name = %#v, want Q1汇报.pdf", got)
}
if got := params["folderId"]; got != "FOLDER_001" {
t.Fatalf("folderId = %#v, want FOLDER_001", got)
}
if got := params["convertToOnlineDoc"]; got != true {
t.Fatalf("convertToOnlineDoc = %#v, want true", got)
}
}
func TestDocCommentListAcceptsWukongPaginationAliases(t *testing.T) {
t.Parallel()
runner := &docCommandRunner{}
cmd := newDocCommentListCommand(runner)
_, errOut, err := executeDocCommand(t, cmd, "--node", "NODE_001", "--limit", "20", "--cursor", "TOKEN_001")
if err != nil {
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut)
}
if runner.last.Tool != "list_comments" {
t.Fatalf("tool = %q, want list_comments", runner.last.Tool)
}
if got := runner.last.Params["pageSize"]; got != 20 {
t.Fatalf("pageSize = %#v, want 20", got)
}
if got := runner.last.Params["nextToken"]; got != "TOKEN_001" {
t.Fatalf("nextToken = %#v, want TOKEN_001", got)
}
}
func TestDocCommentReplyEmojiIsBoolFlag(t *testing.T) {
t.Parallel()
runner := &docCommandRunner{}
cmd := newDocCommentReplyCommand(runner)
_, errOut, err := executeDocCommand(t, cmd,
"--node", "NODE_001",
"--comment-key", "COMMENT_KEY",
"--content", "比心",
"--emoji",
)
if err != nil {
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut)
}
if runner.last.Tool != "reply_comment" {
t.Fatalf("tool = %q, want reply_comment", runner.last.Tool)
}
if got := runner.last.Params["emoji"]; got != true {
t.Fatalf("emoji = %#v, want true", got)
}
}
func TestDocCreateAcceptsParentFolderAliases(t *testing.T) {
t.Parallel()
cases := []struct {
name string
args []string
}{
{
name: "parent folder id",
args: []string{"--name", "doc", "--parent-folder-id", "FOLDER_001", "--content", "hello"},
},
{
name: "parent folder",
args: []string{"--name", "doc", "--parent-folder", "FOLDER_001", "--content", "hello"},
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
runner := &docCommandRunner{}
cmd := newDocCreateCommand(runner)
_, errOut, err := executeDocCommand(t, cmd, tc.args...)
if err != nil {
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut)
}
if runner.last.Tool != "create_document" {
t.Fatalf("tool = %q, want create_document", runner.last.Tool)
}
if got := runner.last.Params["folderId"]; got != "FOLDER_001" {
t.Fatalf("folderId = %#v, want FOLDER_001", got)
}
})
}
}
func TestDocCreateAcceptsContentPathAlias(t *testing.T) {
t.Parallel()
contentPath := filepath.Join(t.TempDir(), "doc.md")
if err := os.WriteFile(contentPath, []byte("# from file"), 0600); err != nil {
t.Fatal(err)
}
runner := &docCommandRunner{}
cmd := newDocCreateCommand(runner)
_, errOut, err := executeDocCommand(t, cmd, "--name", "doc", "--content-path", contentPath)
if err != nil {
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut)
}
if runner.last.Tool != "create_document" {
t.Fatalf("tool = %q, want create_document", runner.last.Tool)
}
if got := runner.last.Params["markdown"]; got != "# from file" {
t.Fatalf("markdown = %#v, want # from file", got)
}
}
func TestDocReadPassesJsonMLFormatAndOutput(t *testing.T) {
t.Parallel()
runner := &docCommandRunner{}
cmd := newDocReadCommand(runner)
_, errOut, err := executeDocCommand(t, cmd,
"--node", "NODE_001",
"--content-format", "jsonml",
"--output", "body.json",
)
if err != nil {
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut)
}
if runner.last.Tool != "get_document_content" {
t.Fatalf("tool = %q, want get_document_content", runner.last.Tool)
}
if got := runner.last.Params["format"]; got != "jsonml" {
t.Fatalf("format = %#v, want jsonml", got)
}
if got := runner.last.Params["__output__"]; got != "body.json" {
t.Fatalf("__output__ = %#v, want body.json", got)
}
}
func TestDocCreatePassesJsonMLContentAndFixFlag(t *testing.T) {
t.Parallel()
runner := &docCommandRunner{responses: []map[string]any{{"nodeId": "NODE_NEW"}}}
cmd := newDocCreateCommand(runner)
_, errOut, err := executeDocCommand(t, cmd,
"--name", "doc",
"--content", `{"jsonml":[["p",{},"hello"]]}`,
"--content-format", "jsonml",
"--fix-jsonml",
)
if err != nil {
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut)
}
if runner.calls != 2 {
t.Fatalf("calls = %d, want 2", runner.calls)
}
if runner.all[0].Tool != "create_document" {
t.Fatalf("first tool = %q, want create_document", runner.all[0].Tool)
}
if runner.last.Tool != "update_document" {
t.Fatalf("last tool = %q, want update_document", runner.last.Tool)
}
if got := runner.last.Params["format"]; got != "jsonml" {
t.Fatalf("format = %#v, want jsonml", got)
}
jsonml, ok := runner.last.Params["jsonml"].(string)
if !ok || !strings.Contains(jsonml, `"root"`) || !strings.Contains(jsonml, `"hello"`) {
t.Fatalf("jsonml = %#v, want normalized root JSONML", runner.last.Params["jsonml"])
}
if _, ok := runner.last.Params["markdown"]; ok {
t.Fatalf("markdown = %#v, want omitted", runner.last.Params["markdown"])
}
if _, ok := runner.last.Params["fixJsonml"]; ok {
t.Fatalf("fixJsonml = %#v, want omitted", runner.last.Params["fixJsonml"])
}
}
func TestDocUpdatePassesJsonMLRevisionAndNoFixFlag(t *testing.T) {
t.Parallel()
runner := &docCommandRunner{}
cmd := newDocTestRoot(runner)
_, errOut, err := executeDocCommand(t, cmd,
"update",
"--node", "NODE_001",
"--content", `["root",{},["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"updated"]]]]`,
"--content-format", "jsonml",
"--revision", "42",
"--no-fix-jsonml",
"--mode", "overwrite",
"--yes",
)
if err != nil {
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut)
}
if runner.last.Tool != "update_document" {
t.Fatalf("tool = %q, want update_document", runner.last.Tool)
}
if got := runner.last.Params["format"]; got != "jsonml" {
t.Fatalf("format = %#v, want jsonml", got)
}
if got := runner.last.Params["revision"]; got != 42 {
t.Fatalf("revision = %#v, want 42", got)
}
if _, ok := runner.last.Params["noFixJsonml"]; ok {
t.Fatalf("noFixJsonml = %#v, want omitted", runner.last.Params["noFixJsonml"])
}
if _, ok := runner.last.Params["index"]; ok {
t.Fatalf("index = %#v, want omitted for JSONML update", runner.last.Params["index"])
}
}
func TestDocBlockListPassesJsonMLFormatAndBlockID(t *testing.T) {
t.Parallel()
runner := &docCommandRunner{}
cmd := newDocBlockListCommand(runner)
_, errOut, err := executeDocCommand(t, cmd,
"--node", "DOC_001",
"--content-format", "jsonml",
"--block-id", "BLOCK_001",
)
if err != nil {
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut)
}
if runner.last.Tool != "list_document_blocks" {
t.Fatalf("tool = %q, want list_document_blocks", runner.last.Tool)
}
if got := runner.last.Params["format"]; got != "jsonml" {
t.Fatalf("format = %#v, want jsonml", got)
}
if got := runner.last.Params["blockId"]; got != "BLOCK_001" {
t.Fatalf("blockId = %#v, want BLOCK_001", got)
}
}
func TestDocBlockInsertPassesJsonMLAndParentBlock(t *testing.T) {
t.Parallel()
runner := &docCommandRunner{}
cmd := newDocBlockInsertCommand(runner)
_, errOut, err := executeDocCommand(t, cmd,
"--node", "DOC_001",
"--content-format", "jsonml",
"--element", `["p",{},"hello"]`,
"--parent-block", "PARENT_001",
"--index", "1",
)
if err != nil {
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut)
}
if runner.last.Tool != "insert_document_block" {
t.Fatalf("tool = %q, want insert_document_block", runner.last.Tool)
}
if got := runner.last.Params["format"]; got != "jsonml" {
t.Fatalf("format = %#v, want jsonml", got)
}
jsonml, ok := runner.last.Params["jsonml"].(string)
if !ok || !strings.Contains(jsonml, `"span"`) || !strings.Contains(jsonml, `"hello"`) {
t.Fatalf("jsonml = %#v, want normalized JSONML node", runner.last.Params["jsonml"])
}
if got := runner.last.Params["referenceBlockId"]; got != "PARENT_001" {
t.Fatalf("referenceBlockId = %#v, want PARENT_001", got)
}
if got := runner.last.Params["index"]; got != 1 {
t.Fatalf("index = %#v, want 1", got)
}
if _, ok := runner.last.Params["element"]; ok {
t.Fatalf("element = %#v, want omitted", runner.last.Params["element"])
}
}
func TestDocBlockUpdatePassesJsonMLAndFixFlags(t *testing.T) {
t.Parallel()
runner := &docCommandRunner{}
cmd := newDocBlockUpdateCommand(runner)
_, errOut, err := executeDocCommand(t, cmd,
"--node", "DOC_001",
"--block-id", "BLOCK_001",
"--content-format", "jsonml",
"--element", `["p",{},"new"]`,
"--fix-jsonml",
"--no-fix-jsonml",
)
if err != nil {
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut)
}
if runner.last.Tool != "update_document_block" {
t.Fatalf("tool = %q, want update_document_block", runner.last.Tool)
}
if got := runner.last.Params["format"]; got != "jsonml" {
t.Fatalf("format = %#v, want jsonml", got)
}
if got := runner.last.Params["jsonml"]; got != `["p",{},"new"]` {
t.Fatalf("jsonml = %#v, want original node when --no-fix-jsonml wins", got)
}
if _, ok := runner.last.Params["fixJsonml"]; ok {
t.Fatalf("fixJsonml = %#v, want omitted", runner.last.Params["fixJsonml"])
}
if _, ok := runner.last.Params["noFixJsonml"]; ok {
t.Fatalf("noFixJsonml = %#v, want omitted", runner.last.Params["noFixJsonml"])
}
}
func TestDocBlockInsertTypeCallout(t *testing.T) {
t.Parallel()
runner := &docCommandRunner{}
cmd := newDocBlockInsertCommand(runner)
_, errOut, err := executeDocCommand(t, cmd,
"--node", "DOC_001",
"--type", "callout",
"--text", "接口变更通知;DBA 审核;告警规则;安全评审",
"--where", "end",
)
if err != nil {
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut)
}
if runner.last.Tool != "insert_document_block" {
t.Fatalf("tool = %q, want insert_document_block", runner.last.Tool)
}
if _, ok := runner.last.Params["where"]; ok {
t.Fatalf("where = %#v, want omitted for --where end", runner.last.Params["where"])
}
element, ok := runner.last.Params["element"].(map[string]any)
if !ok {
t.Fatalf("element = %#v, want map", runner.last.Params["element"])
}
if got := element["blockType"]; got != "callout" {
t.Fatalf("blockType = %#v, want callout", got)
}
callout, ok := element["callout"].(map[string]any)
if !ok {
t.Fatalf("callout = %#v, want map", element["callout"])
}
if got := callout["text"]; got != "接口变更通知;DBA 审核;告警规则;安全评审" {
t.Fatalf("callout.text = %#v", got)
}
}
func TestDocBlockInsertTypeListAndColumns(t *testing.T) {
t.Parallel()
t.Run("ordered list", func(t *testing.T) {
t.Parallel()
runner := &docCommandRunner{}
cmd := newDocBlockInsertCommand(runner)
_, errOut, err := executeDocCommand(t, cmd,
"--node", "DOC_001",
"--type", "ordered-list",
"--list-id", "schedule",
"--text", "需求评审",
)
if err != nil {
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut)
}
element := runner.last.Params["element"].(map[string]any)
if got := element["blockType"]; got != "orderedList" {
t.Fatalf("blockType = %#v, want orderedList", got)
}
list := element["orderedList"].(map[string]any)["list"].(map[string]any)
if got := list["listId"]; got != "schedule" {
t.Fatalf("listId = %#v, want schedule", got)
}
})
t.Run("columns", func(t *testing.T) {
t.Parallel()
runner := &docCommandRunner{}
cmd := newDocBlockInsertCommand(runner)
_, errOut, err := executeDocCommand(t, cmd,
"--node", "DOC_001",
"--type", "columns",
"--columns", "2",
"--text", "方案A||方案B",
)
if err != nil {
t.Fatalf("Execute() error = %v\nstderr:\n%s", err, errOut)
}
element := runner.last.Params["element"].(map[string]any)
if got := element["blockType"]; got != "columns" {
t.Fatalf("blockType = %#v, want columns", got)
}
columns := element["columns"].(map[string]any)
if got := columns["size"]; got != 2 {
t.Fatalf("columns.size = %#v, want 2", got)
}
children := element["children"].([]any)
if len(children) != 2 {
t.Fatalf("children len = %d, want 2", len(children))
}
})
}