// Copyright 2026 Alibaba Group // Licensed under the Apache License, Version 2.0 package doc import ( "context" "errors" "os" "strings" "testing" apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/localio" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/testseam" ) func TestCrossPlatformCoverageDocFinalCommonAndCanonicalBranches(t *testing.T) { t.Chdir(t.TempDir()) if err := os.WriteFile("staged.md", []byte("body"), 0o600); err != nil { t.Fatal(err) } if err := validateWorkspaceInputPath("file", "staged.md"); err != nil { t.Fatal(err) } for _, tc := range []struct { name string stub func(*testing.T) }{ {"getwd", func(t *testing.T) { testseam.Swap(t, &docGetwd, func() (string, error) { return "", errors.New("getwd") }) }}, {"base symlink", func(t *testing.T) { testseam.Swap(t, &docEvalSymlinks, func(string) (string, error) { return "", errors.New("base") }) }}, {"file symlink", func(t *testing.T) { calls := 0 testseam.Swap(t, &docEvalSymlinks, func(path string) (string, error) { calls++ if calls == 2 { return "", errors.New("file") } return path, nil }) }}, {"relative escape", func(t *testing.T) { testseam.Swap(t, &docRel, func(string, string) (string, error) { return "../escape", nil }) }}, {"relative error", func(t *testing.T) { testseam.Swap(t, &docRel, func(string, string) (string, error) { return "", errors.New("rel") }) }}, } { t.Run(tc.name, func(t *testing.T) { tc.stub(t) if err := validateWorkspaceInputPath("file", "staged.md"); err == nil { t.Fatal("expected validation failure") } }) } if _, err := validateJSONML(`["",{}]`); err == nil { t.Fatal("empty JSONML tag succeeded") } validation := classifyDocWriteFailure(apperrors.NewValidation("bad")) if validation.reason != "invalid_input" || validation.executionStarted { t.Fatalf("validation transition = %#v", validation) } permission := classifyDocWriteFailure(apperrors.NewAPI("forbidden")) if permission.reason != "permission_denied" { t.Fatalf("permission transition = %#v", permission) } auth := classifyDocWriteFailure(apperrors.NewAuth("login")) if auth.reason != "authentication_required" { t.Fatalf("auth transition = %#v", auth) } notStarted := classifyDocWriteFailure(apperrors.NewAPI("later", apperrors.WithExecutionStarted(false), apperrors.WithRetryable(true))) if notStarted.state != docFailureRetryable || notStarted.reason != "doc_write_not_started" { t.Fatalf("retryable transition = %#v", notStarted) } for _, cause := range []error{apperrors.NewAPI("permission denied"), apperrors.NewAuth("login")} { var typed *apperrors.Error if err := docUnknownWriteError("doc.test", "write", "n", cause); !errors.As(err, &typed) || typed.Category != apperrors.CategoryAuth { t.Fatalf("auth-classified error = %#v", err) } } for _, existing := range []error{ apperrors.NewValidation("existing"), apperrors.NewAPI("existing", apperrors.WithDetails(map[string]any{"status": "partial_success"})), } { if got := docUnknownWriteError("doc.test", "write", "n", existing); got != existing { t.Fatalf("existing classified error was wrapped: %p != %p", got, existing) } } if !containsText(nil, "") || !containsText("alpha", "ph") || containsText("alpha", "z") || !containsText(map[string]any{"nested": "alpha"}, "alpha") || containsText(map[string]any{"nested": "beta"}, "alpha") || !containsText([]any{"alpha"}, "alpha") || containsText([]any{"beta"}, "alpha") || containsText(3, "alpha") { t.Fatal("containsText branch contract failed") } jsonElement := []any{"p", map[string]any{"uuid": "b"}, "text"} jsonPayload := map[string]any{"jsonml": `["p",{"uuid":"b"},"text"]`} for _, value := range []any{jsonPayload, `["p",{"uuid":"b"},"text"]`, jsonElement} { if canonicalBlockContent(value, "jsonml") == "" { t.Fatalf("empty canonical JSONML for %#v", value) } } for _, value := range []any{ map[string]any{"text": "alpha"}, map[string]any{"id": "ignored", "nested": map[string]any{"text": "beta"}}, []any{"p", map[string]any{"text": "gamma"}}, []any{map[string]any{"text": "delta"}}, "epsilon", } { if canonicalBlockContent(value, "markdown") == "" { t.Fatalf("empty canonical markdown for %#v", value) } } jsonTree := map[string]any{ "jsonml": `["root",{},["p",{"uuid":"ref"},"before"],["p",{"uuid":"new"},"after"]]`, "content": "not-json", "nested": []any{"p", map[string]any{"uuid": "direct"}, "direct"}, } if len(orderedCanonicalBlocks(jsonTree, "jsonml")) < 3 || findJSONMLBlock(jsonTree, "new") == nil { t.Fatalf("JSONML traversal failed: %#v", orderedJSONMLBlocks(jsonTree)) } if findJSONMLBlock(jsonTree, "missing") != nil || jsonMLBlockIdentity([]any{"p"}) != "" || jsonMLBlockIdentity([]any{"p", "attrs"}) != "" { t.Fatal("JSONML identity negative contract failed") } if canonicalBlockIdentity(jsonElement, "jsonml") != "b" || canonicalBlockIdentity("bad", "jsonml") != "" { t.Fatal("JSONML canonical identity failed") } elementTree := map[string]any{"wrapper": []any{map[string]any{"id": "ref", "text": "before"}, map[string]any{"id": "new", "text": "after"}}} if len(orderedCanonicalBlocks(elementTree, "element")) != 2 || canonicalBlockIdentity(elementTree, "element") != "" || canonicalBlockIdentity("bad", "element") != "" { t.Fatal("element canonical traversal failed") } if !verifyInsertedCanonicalBlock(map[string]any{"blockId": "new"}, elementTree, "ref", "after", "element") || !verifyInsertedCanonicalBlock(map[string]any{}, elementTree, "ref", "after", "element") || verifyInsertedCanonicalBlock(map[string]any{}, elementTree, "new", "after", "element") { t.Fatal("inserted block verification branch contract failed") } if blockContentEquals(map[string]any{}, "missing", "after", "element") { t.Fatal("missing block matched") } if blockContentEquals(nil, "missing", "after", "element") { t.Fatal("nil block tree matched") } if blockContentEquals(map[string]any{}, "missing", "after", "jsonml") { t.Fatal("missing JSONML block matched") } if got := documentContentCandidates("root", "markdown"); len(got) != 1 || got[0] != "root" { t.Fatalf("root content candidates = %#v", got) } if got := documentContentCandidates([]any{map[string]any{"content": "nested"}}, "markdown"); len(got) != 1 || got[0] != "nested" { t.Fatalf("nested content candidates = %#v", got) } if got := splitDocMarkdown("a\nb", 0); len(got) != 1 { t.Fatalf("disabled split = %#v", got) } if got := splitDocMarkdown("ab\ncd", 4); len(got) != 2 || got[0] != "ab\n" { t.Fatalf("newline split = %#v", got) } } func TestCrossPlatformCoverageDocFinalExecutionFailureBranches(t *testing.T) { longContent := strings.Repeat("x", 10001) if err := runDocCoverage(t, Create, &docCoverageCaller{failAt: 2, responses: map[string][]map[string]any{}}, "--name", "n", "--content", longContent); err == nil { t.Fatal("partial chunk create succeeded") } for _, failAt := range []int{1, 2} { err := runDocCoverage(t, Update, &docCoverageCaller{failAt: failAt, responses: map[string][]map[string]any{}}, "--node", "n", "--command", "block_insert_after", "--after-block-id", "block-1", "--content", "x", "--yes") if err == nil { t.Fatalf("block mutation failAt=%d succeeded", failAt) } } mismatch := &docCoverageCaller{responses: map[string][]map[string]any{ "insert_document_block": {{"blockId": "new"}}, "list_document_blocks": {{"blocks": []any{map[string]any{"id": "new", "text": "wrong"}}}}, }} if err := runDocCoverage(t, Update, mismatch, "--node", "n", "--command", "block_insert_after", "--after-block-id", "block-1", "--content", "x", "--yes"); err == nil { t.Fatal("mismatched block verification succeeded") } for _, tc := range []struct { name string responses map[string][]map[string]any failAt int args []string }{ {"query error", map[string][]map[string]any{}, 1, []string{"--job-id", "j"}}, {"unknown", map[string][]map[string]any{"query_export_job": {{}}}, 0, []string{"--job-id", "j"}}, {"failed", map[string][]map[string]any{"query_export_job": {{"status": "CANCELLED"}}}, 0, []string{"--job-id", "j"}}, {"missing url", map[string][]map[string]any{"query_export_job": {{"status": "SUCCESS"}}}, 0, []string{"--job-id", "j", "--output", "out"}}, } { t.Run(tc.name, func(t *testing.T) { _ = runDocCoverage(t, ExportGet, &docCoverageCaller{failAt: tc.failAt, responses: tc.responses}, tc.args...) }) } t.Run("export getwd", func(t *testing.T) { testseam.Swap(t, &docGetwd, func() (string, error) { return "", errors.New("getwd") }) _ = runDocCoverage(t, ExportGet, &docCoverageCaller{responses: map[string][]map[string]any{"query_export_job": {{"status": "SUCCESS", "downloadUrl": "https://example.com/x"}}}}, "--job-id", "j", "--output", "out") }) t.Run("export download", func(t *testing.T) { testseam.Swap(t, &docDownload, func(context.Context, string, localio.DownloadOptions) (localio.DownloadResult, error) { return localio.DownloadResult{}, errors.New("download") }) _ = runDocCoverage(t, ExportGet, &docCoverageCaller{responses: map[string][]map[string]any{"query_export_job": {{"status": "SUCCESS", "downloadUrl": "https://example.com/x"}}}}, "--job-id", "j", "--output", "out") }) searchCaller := &docCoverageCaller{responses: map[string][]map[string]any{"search_documents": {{"documents": []any{}, "hasMore": false}}}} if err := runDocCoverage(t, Search, searchCaller, "--query", "q", "--creator-uids", "u", "--editor-uids", "e", "--mentioned-uids", "m", "--workspace-ids", "w"); err != nil { t.Fatal(err) } if got := searchCaller.history[0].params["pageSize"]; got != 10 { t.Fatalf("default search page size = %#v", got) } listCaller := &docCoverageCaller{responses: map[string][]map[string]any{"list_nodes": {{"nodes": []any{}, "hasMore": false}}}} if err := runDocCoverage(t, List, listCaller, "--folder", "f", "--workspace", "w"); err != nil { t.Fatal(err) } if got := listCaller.history[0].params["pageSize"]; got != 50 { t.Fatalf("default list page size = %#v", got) } _ = runDocCoverage(t, CommentReply, &docCoverageCaller{responses: map[string][]map[string]any{}}, "--node", "n", "--comment-key", "c", "--content", "x", "--emoji", "--mention", "[bad", "--yes") if err := runDocCoverage(t, CommentReply, &docCoverageCaller{responses: map[string][]map[string]any{}}, "--node", "n", "--comment-key", "c", "--content", "x", "--mention", "u", "--yes"); err != nil { t.Fatal(err) } _ = runDocCoverage(t, CommentUpdate, &docCoverageCaller{responses: map[string][]map[string]any{}}, "--node", "n", "--comment-key", "c", "--content", "x", "--mention", "[bad") _ = runDocCoverage(t, ExportSubmit, &docCoverageCaller{failAt: 1, responses: map[string][]map[string]any{}}, "--node", "n", "--export-format", "docx") if err := runDocCoverage(t, ExportSubmit, &docCoverageCaller{responses: map[string][]map[string]any{}}, "--node", "n", "--export-format", "docx"); err != nil { t.Fatal(err) } for _, tc := range []struct { name string responses map[string][]map[string]any failAt int args []string }{ {"template search error", map[string][]map[string]any{}, 1, []string{"--query", "q"}}, {"template not found", map[string][]map[string]any{"search_doc_templates": {{"templates": []any{}, "hasMore": false}}}, 0, []string{"--query", "q"}}, {"template resolved", map[string][]map[string]any{"search_doc_templates": {{"templates": []any{map[string]any{"templateId": "t"}}, "hasMore": false}}}, 0, []string{"--query", "q", "--source", "PUBLIC"}}, {"template continue", map[string][]map[string]any{"search_doc_templates": {{"templates": []any{map[string]any{"templateId": "t"}}, "nextCursor": "p2"}}}, 0, []string{"--query", "q", "--cursor", "p1"}}, } { t.Run(tc.name, func(t *testing.T) { _ = runDocCoverage(t, TemplateSearch, &docCoverageCaller{responses: tc.responses, failAt: tc.failAt}, tc.args...) }) } for _, raw := range [][]string{{"[bad"}, {"", " "}} { if _, err := normalizeMentionUserIDs(raw); err == nil { t.Fatalf("invalid mentions %#v succeeded", raw) } } if _, ok := sliceUTF16Range("A😀B", 1, 2); ok { t.Fatal("selection ending inside surrogate pair succeeded") } if _, ok := sliceUTF16Range("abc", -1, 1); ok { t.Fatal("negative selection range succeeded") } commentCases := []struct { name string caller *docCoverageCaller args []string wantError bool }{ {"block read", &docCoverageCaller{failAt: 1, responses: map[string][]map[string]any{}}, []string{"--node", "n", "--content", "c", "--block-id", "b", "--start", "0", "--end", "1", "--yes"}, true}, {"range", &docCoverageCaller{responses: map[string][]map[string]any{"list_document_blocks": {{"id": "b", "text": ""}}}}, []string{"--node", "n", "--content", "c", "--block-id", "b", "--start", "0", "--end", "1", "--yes"}, true}, {"selected mismatch", &docCoverageCaller{responses: map[string][]map[string]any{"list_document_blocks": {{"id": "b", "text": "alpha"}}}}, []string{"--node", "n", "--content", "c", "--block-id", "b", "--start", "0", "--end", "1", "--selected-text", "z", "--yes"}, true}, {"write error", &docCoverageCaller{failAt: 1, responses: map[string][]map[string]any{}}, []string{"--node", "n", "--content", "c", "--yes"}, true}, {"mention", &docCoverageCaller{responses: map[string][]map[string]any{}}, []string{"--node", "n", "--content", "c", "--mention", "[bad", "--yes"}, true}, } for _, tc := range commentCases { t.Run(tc.name, func(t *testing.T) { err := runDocCoverage(t, CommentCreate, tc.caller, tc.args...) if (err != nil) != tc.wantError { t.Fatalf("err=%v wantError=%v", err, tc.wantError) } }) } if !versionEvidenceMatches([]any{map[string]any{"target_version": 3}}, 3, map[string]bool{"targetversion": true}) { t.Fatal("array version evidence did not match") } if !versionEvidenceMatches(map[string]any{"wrapper": map[string]any{"target_version": 3}}, 3, map[string]bool{"targetversion": true}) { t.Fatal("nested version evidence did not match") } withDescription := collectTemplateCandidates(map[string]any{"templateId": "t", "description": "d"}) if len(withDescription) != 1 || withDescription[0]["description"] != "d" { t.Fatalf("template description = %#v", withDescription) } duplicateTemplates := &docCoverageCaller{responses: map[string][]map[string]any{"search_doc_templates": { {"templates": []any{map[string]any{"templateId": "t"}}, "hasMore": true, "nextCursor": "p2"}, {"templates": []any{map[string]any{"templateId": "t"}}, "hasMore": false}, }}} _ = runDocCoverage(t, CreateFromTemplate, duplicateTemplates, "--query", "q") stalledTemplates := &docCoverageCaller{responses: map[string][]map[string]any{"search_doc_templates": { {"templates": []any{map[string]any{"templateId": "t"}}, "hasMore": true, "nextCursor": "p2"}, {"templates": []any{map[string]any{"templateId": "t"}}, "hasMore": true, "nextCursor": "p2"}, }}} if err := runDocCoverage(t, CreateFromTemplate, stalledTemplates, "--query", "q"); err == nil { t.Fatal("stalled template cursor succeeded") } maxPages := make([]map[string]any, 20) for index := range maxPages { maxPages[index] = map[string]any{"templates": []any{map[string]any{"templateId": "t"}}, "hasMore": true, "nextCursor": "p" + strings.Repeat("x", index+1)} } if err := runDocCoverage(t, CreateFromTemplate, &docCoverageCaller{responses: map[string][]map[string]any{"search_doc_templates": maxPages}}, "--query", "q"); err == nil { t.Fatal("template max-pages succeeded") } t.Chdir(t.TempDir()) if err := os.WriteFile("media.bin", []byte("media"), 0o600); err != nil { t.Fatal(err) } _ = runDocCoverage(t, ResourceUpdate, &docCoverageCaller{responses: map[string][]map[string]any{}}, "--node", "n", "--file", "media.bin", "--dry-run", "--yes") if err := runDocCoverage(t, Import, &docCoverageCaller{dryRun: true, responses: map[string][]map[string]any{}}, "--file", "media.bin", "--folder", "f", "--dry-run"); err != nil { t.Fatal(err) } t.Run("empty preview", func(t *testing.T) { testseam.Swap(t, &docDownload, func(context.Context, string, localio.DownloadOptions) (localio.DownloadResult, error) { return localio.DownloadResult{}, nil }) _ = runDocCoverage(t, MediaPreview, &docCoverageCaller{responses: map[string][]map[string]any{}}, "--node", "n", "--resource-id", "r") }) t.Run("empty download", func(t *testing.T) { testseam.Swap(t, &docDownload, func(context.Context, string, localio.DownloadOptions) (localio.DownloadResult, error) { return localio.DownloadResult{}, nil }) _ = runDocCoverage(t, MediaDownload, &docCoverageCaller{responses: map[string][]map[string]any{}}, "--node", "n", "--resource-id", "r", "--output", "out") }) }