From e19e09019ce026a5641f4de960352996f9c28583 Mon Sep 17 00:00:00 2001 From: wangweiming-01 Date: Thu, 21 May 2026 11:07:37 +0800 Subject: [PATCH] feat: return real tenant URLs for drive +upload and markdown +create (#992) Change-Id: I6b513eef57a3479c8971b3bb6cbf005cad3f8040 --- shortcuts/common/drive_meta.go | 58 ++++++++++--- shortcuts/common/drive_meta_test.go | 39 +++++++++ shortcuts/drive/drive_io_test.go | 69 +++++++++++---- shortcuts/drive/drive_upload.go | 28 +++--- shortcuts/markdown/markdown_create.go | 25 ++++-- shortcuts/markdown/markdown_test.go | 86 +++++++++++++++++-- .../cli_e2e/drive/drive_upload_dryrun_test.go | 4 + .../cli_e2e/markdown/markdown_dryrun_test.go | 6 ++ .../markdown/markdown_workflow_test.go | 2 +- 9 files changed, 263 insertions(+), 54 deletions(-) diff --git a/shortcuts/common/drive_meta.go b/shortcuts/common/drive_meta.go index ea4d91be..e917818a 100644 --- a/shortcuts/common/drive_meta.go +++ b/shortcuts/common/drive_meta.go @@ -3,29 +3,61 @@ package common -// FetchDriveMetaTitle looks up the document title via the drive metas batch_query API. -func FetchDriveMetaTitle(runtime *RuntimeContext, token, docType string) (string, error) { +// DriveMeta is the subset of drive metas/batch_query fields used by shortcuts. +type DriveMeta struct { + Title string + URL string +} + +// FetchDriveMeta looks up document metadata via the drive metas batch_query API. +func FetchDriveMeta(runtime *RuntimeContext, token, docType string, withURL bool) (DriveMeta, error) { + body := map[string]interface{}{ + "request_docs": []map[string]interface{}{ + { + "doc_token": token, + "doc_type": docType, + }, + }, + } + if withURL { + body["with_url"] = true + } + data, err := runtime.CallAPI( "POST", "/open-apis/drive/v1/metas/batch_query", nil, - map[string]interface{}{ - "request_docs": []map[string]interface{}{ - { - "doc_token": token, - "doc_type": docType, - }, - }, - }, + body, ) if err != nil { - return "", err + return DriveMeta{}, err } metas := GetSlice(data, "metas") if len(metas) == 0 { - return "", nil + return DriveMeta{}, nil } meta, _ := metas[0].(map[string]interface{}) - return GetString(meta, "title"), nil + return DriveMeta{ + Title: GetString(meta, "title"), + URL: GetString(meta, "url"), + }, nil +} + +// FetchDriveMetaTitle looks up the document title via the drive metas batch_query API. +func FetchDriveMetaTitle(runtime *RuntimeContext, token, docType string) (string, error) { + meta, err := FetchDriveMeta(runtime, token, docType, false) + if err != nil { + return "", err + } + return meta.Title, nil +} + +// FetchDriveMetaURL looks up the document access URL via the drive metas batch_query API. +func FetchDriveMetaURL(runtime *RuntimeContext, token, docType string) (string, error) { + meta, err := FetchDriveMeta(runtime, token, docType, true) + if err != nil { + return "", err + } + return meta.URL, nil } diff --git a/shortcuts/common/drive_meta_test.go b/shortcuts/common/drive_meta_test.go index aad18696..fdffe65b 100644 --- a/shortcuts/common/drive_meta_test.go +++ b/shortcuts/common/drive_meta_test.go @@ -5,6 +5,7 @@ package common import ( "context" + "encoding/json" "fmt" "sync/atomic" "testing" @@ -105,6 +106,44 @@ func TestFetchDriveMetaTitle(t *testing.T) { }) } +func TestFetchDriveMetaURL(t *testing.T) { + runtime, reg := newDriveMetaTestRuntime(t) + stub := &httpmock.Stub{ + Method: "POST", + URL: "/open-apis/drive/v1/metas/batch_query", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{ + "metas": []map[string]interface{}{ + { + "doc_token": "boxcnABC", + "doc_type": "file", + "title": "report.pdf", + "url": "https://tenant.example.com/file/boxcnABC", + }, + }, + }, + }, + } + reg.Register(stub) + + got, err := FetchDriveMetaURL(runtime, "boxcnABC", "file") + if err != nil { + t.Fatalf("FetchDriveMetaURL() error: %v", err) + } + if got != "https://tenant.example.com/file/boxcnABC" { + t.Fatalf("url = %q, want tenant URL", got) + } + + var body map[string]interface{} + if err := json.Unmarshal(stub.CapturedBody, &body); err != nil { + t.Fatalf("decode captured body: %v", err) + } + if body["with_url"] != true { + t.Fatalf("with_url = %#v, want true", body["with_url"]) + } +} + func newDriveMetaTestRuntime(t *testing.T) (*RuntimeContext, *httpmock.Registry) { t.Helper() t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) diff --git a/shortcuts/drive/drive_io_test.go b/shortcuts/drive/drive_io_test.go index 01ee3c46..a1b7807e 100644 --- a/shortcuts/drive/drive_io_test.go +++ b/shortcuts/drive/drive_io_test.go @@ -604,9 +604,9 @@ func TestDriveUploadSmallFileToWiki(t *testing.T) { } } -func TestDriveUploadFallbackURLForExplorerParent(t *testing.T) { +func TestDriveUploadUsesMetaURLForExplorerParent(t *testing.T) { uploadTestConfig := &core.CliConfig{ - AppID: "drive-upload-explorer-fallback-url", AppSecret: "test-secret", Brand: core.BrandFeishu, + AppID: "drive-upload-explorer-meta-url", AppSecret: "test-secret", Brand: core.BrandFeishu, } f, stdout, _, reg := cmdutil.TestFactory(t, uploadTestConfig) @@ -615,12 +615,21 @@ func TestDriveUploadFallbackURLForExplorerParent(t *testing.T) { URL: "/open-apis/drive/v1/files/upload_all", Body: map[string]interface{}{ "code": 0, "msg": "ok", - // upload_all only ever returns file_token; url is never present — - // this exercises the fallback path unconditionally for explorer - // parents. "data": map[string]interface{}{"file_token": "file_explorer_small"}, }, }) + reg.Register(&httpmock.Stub{ + Method: "POST", + URL: "/open-apis/drive/v1/metas/batch_query", + Body: map[string]interface{}{ + "code": 0, "msg": "ok", + "data": map[string]interface{}{ + "metas": []map[string]interface{}{ + {"doc_token": "file_explorer_small", "doc_type": "file", "url": "https://tenant.example.com/file/file_explorer_small"}, + }, + }, + }, + }) tmpDir := t.TempDir() origDir, _ := os.Getwd() @@ -641,14 +650,14 @@ func TestDriveUploadFallbackURLForExplorerParent(t *testing.T) { } data := decodeDriveEnvelope(t, stdout) - if got, want := data["url"], "https://www.feishu.cn/file/file_explorer_small"; got != want { - t.Fatalf("data.url = %#v, want %q (brand-standard fallback)", got, want) + if got, want := data["url"], "https://tenant.example.com/file/file_explorer_small"; got != want { + t.Fatalf("data.url = %#v, want %q (metadata URL)", got, want) } } -func TestDriveUploadOmitsURLForWikiParent(t *testing.T) { +func TestDriveUploadUsesMetaURLForWikiParent(t *testing.T) { uploadTestConfig := &core.CliConfig{ - AppID: "drive-upload-wiki-no-url", AppSecret: "test-secret", Brand: core.BrandFeishu, + AppID: "drive-upload-wiki-meta-url", AppSecret: "test-secret", Brand: core.BrandFeishu, } f, stdout, _, reg := cmdutil.TestFactory(t, uploadTestConfig) @@ -660,6 +669,18 @@ func TestDriveUploadOmitsURLForWikiParent(t *testing.T) { "data": map[string]interface{}{"file_token": "file_wiki_small"}, }, }) + reg.Register(&httpmock.Stub{ + Method: "POST", + URL: "/open-apis/drive/v1/metas/batch_query", + Body: map[string]interface{}{ + "code": 0, "msg": "ok", + "data": map[string]interface{}{ + "metas": []map[string]interface{}{ + {"doc_token": "file_wiki_small", "doc_type": "file", "url": "https://tenant.example.com/file/file_wiki_small"}, + }, + }, + }, + }) tmpDir := t.TempDir() withDriveWorkingDir(t, tmpDir) @@ -677,8 +698,8 @@ func TestDriveUploadOmitsURLForWikiParent(t *testing.T) { } data := decodeDriveEnvelope(t, stdout) - if _, ok := data["url"]; ok { - t.Fatalf("data.url should be omitted for wiki-hosted files (no standalone URL); got %#v", data["url"]) + if got, want := data["url"], "https://tenant.example.com/file/file_wiki_small"; got != want { + t.Fatalf("data.url = %#v, want %q (metadata URL)", got, want) } } @@ -1078,14 +1099,15 @@ func TestDriveUploadDryRunUsesWikiTarget(t *testing.T) { var got struct { API []struct { + URL string `json:"url"` Body map[string]interface{} `json:"body"` } `json:"api"` } if err := json.Unmarshal(data, &got); err != nil { t.Fatalf("unmarshal dry run json: %v", err) } - if len(got.API) != 1 { - t.Fatalf("expected 1 API call, got %d", len(got.API)) + if len(got.API) != 2 { + t.Fatalf("expected 2 API calls, got %d", len(got.API)) } if got.API[0].Body["parent_type"] != driveUploadParentTypeWiki { t.Fatalf("parent_type = %#v, want %q", got.API[0].Body["parent_type"], driveUploadParentTypeWiki) @@ -1093,6 +1115,12 @@ func TestDriveUploadDryRunUsesWikiTarget(t *testing.T) { if got.API[0].Body["parent_node"] != "wikcn_dryrun_upload_target" { t.Fatalf("parent_node = %#v, want %q", got.API[0].Body["parent_node"], "wikcn_dryrun_upload_target") } + if got.API[1].URL != "/open-apis/drive/v1/metas/batch_query" { + t.Fatalf("metadata URL = %q, want metas/batch_query", got.API[1].URL) + } + if got.API[1].Body["with_url"] != true { + t.Fatalf("metadata with_url = %#v, want true", got.API[1].Body["with_url"]) + } } func TestNewDriveUploadSpecPreservesPathAndName(t *testing.T) { @@ -1168,18 +1196,25 @@ func TestDriveUploadDryRunIncludesFileToken(t *testing.T) { var got struct { API []struct { + URL string `json:"url"` Body map[string]interface{} `json:"body"` } `json:"api"` } if err := json.Unmarshal(data, &got); err != nil { t.Fatalf("unmarshal dry run json: %v", err) } - if len(got.API) != 1 { - t.Fatalf("expected 1 API call, got %d", len(got.API)) + if len(got.API) != 2 { + t.Fatalf("expected 2 API calls, got %d", len(got.API)) } if got.API[0].Body["file_token"] != "boxcn_dryrun_overwrite" { t.Fatalf("file_token = %#v, want %q", got.API[0].Body["file_token"], "boxcn_dryrun_overwrite") } + if got.API[1].URL != "/open-apis/drive/v1/metas/batch_query" { + t.Fatalf("metadata URL = %q, want metas/batch_query", got.API[1].URL) + } + if got.API[1].Body["with_url"] != true { + t.Fatalf("metadata with_url = %#v, want true", got.API[1].Body["with_url"]) + } } func TestDriveUploadDryRunBotOverwriteSkipsPermissionGrantHint(t *testing.T) { @@ -1222,8 +1257,8 @@ func TestDriveUploadDryRunBotOverwriteSkipsPermissionGrantHint(t *testing.T) { if err := json.Unmarshal(data, &got); err != nil { t.Fatalf("unmarshal dry run json: %v", err) } - if len(got.API) != 1 { - t.Fatalf("expected 1 API call, got %d", len(got.API)) + if len(got.API) != 2 { + t.Fatalf("expected 2 API calls, got %d", len(got.API)) } if got.API[0].Body["file_token"] != "boxcn_dryrun_overwrite" { t.Fatalf("file_token = %#v, want %q", got.API[0].Body["file_token"], "boxcn_dryrun_overwrite") diff --git a/shortcuts/drive/drive_upload.go b/shortcuts/drive/drive_upload.go index 446ec400..c2688208 100644 --- a/shortcuts/drive/drive_upload.go +++ b/shortcuts/drive/drive_upload.go @@ -92,7 +92,7 @@ var DriveUpload = common.Shortcut{ Command: "+upload", Description: "Upload a local file to Drive", Risk: "write", - Scopes: []string{"drive:file:upload"}, + Scopes: []string{"drive:file:upload", "drive:drive.metadata:readonly"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ {Name: "file", Desc: "local file path (files > 20MB use multipart upload automatically)", Required: true}, @@ -124,11 +124,22 @@ var DriveUpload = common.Shortcut{ body["file_token"] = spec.FileToken } d := common.NewDryRunAPI(). - Desc("multipart/form-data upload (files > 20MB use chunked 3-step upload)"). + Desc("multipart/form-data upload (files > 20MB use chunked 3-step upload), then fetch the real Drive URL via metadata"). POST("/open-apis/drive/v1/files/upload_all"). Body(body) + d.POST("/open-apis/drive/v1/metas/batch_query"). + Desc("Fetch the uploaded file's real access URL"). + Body(map[string]interface{}{ + "request_docs": []map[string]interface{}{ + { + "doc_token": "", + "doc_type": "file", + }, + }, + "with_url": true, + }) if runtime.IsBot() && !isOverwrite { - d.Desc("After file upload succeeds in bot mode, the CLI will also try to grant the current CLI user full_access (可管理权限) on the new file.") + d.Set("post_upload_note", "After file upload succeeds in bot mode, the CLI will also try to grant the current CLI user full_access (可管理权限) on the new file.") } return d }, @@ -165,13 +176,10 @@ var DriveUpload = common.Shortcut{ if uploadResult.Version != "" { out["version"] = uploadResult.Version } - // wiki-hosted files have no standalone /file/ URL — only the - // wiki node URL, which the upload response doesn't carry. Skip the - // fallback for parent_type=wiki rather than emit a link that 404s. - if target.ParentType == driveUploadParentTypeExplorer { - if u := common.BuildResourceURL(runtime.Config.Brand, "file", uploadResult.FileToken); u != "" { - out["url"] = u - } + if u, metaErr := common.FetchDriveMetaURL(runtime, uploadResult.FileToken, "file"); metaErr == nil && strings.TrimSpace(u) != "" { + out["url"] = u + } else if metaErr != nil { + fmt.Fprintf(runtime.IO().ErrOut, "warning: uploaded file URL lookup failed: %v\n", metaErr) } if !isOverwrite { if grant := common.AutoGrantCurrentUserDrivePermission(runtime, uploadResult.FileToken, "file"); grant != nil { diff --git a/shortcuts/markdown/markdown_create.go b/shortcuts/markdown/markdown_create.go index 50dfaa0b..bd4f2281 100644 --- a/shortcuts/markdown/markdown_create.go +++ b/shortcuts/markdown/markdown_create.go @@ -5,6 +5,7 @@ package markdown import ( "context" + "fmt" "io" "strings" @@ -16,7 +17,7 @@ var MarkdownCreate = common.Shortcut{ Command: "+create", Description: "Create a Markdown file in Drive", Risk: "write", - Scopes: []string{"drive:file:upload"}, + Scopes: []string{"drive:file:upload", "drive:drive.metadata:readonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ @@ -55,7 +56,19 @@ var MarkdownCreate = common.Shortcut{ if err != nil { return common.NewDryRunAPI().Set("error", err.Error()) } - return markdownUploadDryRun(spec, fileSize, fileSize > markdownSinglePartSizeLimit) + dry := markdownUploadDryRun(spec, fileSize, fileSize > markdownSinglePartSizeLimit) + dry.POST("/open-apis/drive/v1/metas/batch_query"). + Desc("Fetch the created Markdown file's real access URL"). + Body(map[string]interface{}{ + "request_docs": []map[string]interface{}{ + { + "doc_token": "", + "doc_type": "file", + }, + }, + "with_url": true, + }) + return dry }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { spec := markdownUploadSpec{ @@ -87,10 +100,10 @@ var MarkdownCreate = common.Shortcut{ "file_name": finalMarkdownFileName(spec), "size_bytes": fileSize, } - if target := spec.Target(); target.ParentType == markdownUploadParentTypeExplorer { - if u := common.BuildResourceURL(runtime.Config.Brand, "file", result.FileToken); u != "" { - out["url"] = u - } + if u, metaErr := common.FetchDriveMetaURL(runtime, result.FileToken, "file"); metaErr == nil && strings.TrimSpace(u) != "" { + out["url"] = u + } else if metaErr != nil { + fmt.Fprintf(runtime.IO().ErrOut, "warning: created Markdown file URL lookup failed: %v\n", metaErr) } if grant := common.AutoGrantCurrentUserDrivePermission(runtime, result.FileToken, "file"); grant != nil { out["permission_grant"] = grant diff --git a/shortcuts/markdown/markdown_test.go b/shortcuts/markdown/markdown_test.go index 4ff57b19..32f312c8 100644 --- a/shortcuts/markdown/markdown_test.go +++ b/shortcuts/markdown/markdown_test.go @@ -403,6 +403,9 @@ func TestMarkdownCreateDryRunWithInlineContent(t *testing.T) { if !strings.Contains(out, "/open-apis/drive/v1/files/upload_all") { t.Fatalf("dry-run missing upload_all: %s", out) } + if !strings.Contains(out, "/open-apis/drive/v1/metas/batch_query") || !strings.Contains(out, `"with_url": true`) { + t.Fatalf("dry-run missing metadata URL lookup: %s", out) + } if !strings.Contains(out, "markdown content") { t.Fatalf("dry-run missing content marker: %s", out) } @@ -429,6 +432,9 @@ func TestMarkdownCreateDryRunWithWikiToken(t *testing.T) { if !strings.Contains(out, `"parent_node": "wikcn_markdown_dryrun_target"`) { t.Fatalf("dry-run missing wiki parent_node: %s", out) } + if !strings.Contains(out, "/open-apis/drive/v1/metas/batch_query") || !strings.Contains(out, `"with_url": true`) { + t.Fatalf("dry-run missing metadata URL lookup: %s", out) + } } func TestMarkdownCreateDryRunReportsSourceFileError(t *testing.T) { @@ -470,6 +476,9 @@ func TestMarkdownCreateDryRunWithFileUsesStatOnly(t *testing.T) { if !strings.Contains(out, "/open-apis/drive/v1/files/upload_prepare") { t.Fatalf("dry-run missing multipart prepare step: %s", out) } + if !strings.Contains(out, "/open-apis/drive/v1/metas/batch_query") || !strings.Contains(out, `"with_url": true`) { + t.Fatalf("dry-run missing metadata URL lookup: %s", out) + } if strings.Contains(out, "open should not be called in dry-run") { t.Fatalf("dry-run unexpectedly tried to open the source file: %s", out) } @@ -489,6 +498,18 @@ func TestMarkdownCreateSuccessUploadAll(t *testing.T) { }, } reg.Register(uploadStub) + reg.Register(&httpmock.Stub{ + Method: "POST", + URL: "/open-apis/drive/v1/metas/batch_query", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{ + "metas": []map[string]interface{}{ + {"doc_token": "box_md_create", "doc_type": "file", "url": "https://tenant.example.com/file/box_md_create"}, + }, + }, + }, + }) err := mountAndRunMarkdown(t, MarkdownCreate, []string{ "+create", @@ -521,12 +542,12 @@ func TestMarkdownCreateSuccessUploadAll(t *testing.T) { if !strings.Contains(stdout.String(), `"file_name": "README.md"`) { t.Fatalf("stdout missing file_name: %s", stdout.String()) } - if !strings.Contains(stdout.String(), `"url": "https://www.feishu.cn/file/box_md_create"`) { + if !strings.Contains(stdout.String(), `"url": "https://tenant.example.com/file/box_md_create"`) { t.Fatalf("stdout missing url: %s", stdout.String()) } } -func TestMarkdownCreateSuccessUploadAllToWikiOmitsURL(t *testing.T) { +func TestMarkdownCreateSuccessUploadAllToWikiReturnsMetaURL(t *testing.T) { f, stdout, _, reg := cmdutil.TestFactory(t, markdownTestConfig()) uploadStub := &httpmock.Stub{ Method: "POST", @@ -540,6 +561,18 @@ func TestMarkdownCreateSuccessUploadAllToWikiOmitsURL(t *testing.T) { }, } reg.Register(uploadStub) + reg.Register(&httpmock.Stub{ + Method: "POST", + URL: "/open-apis/drive/v1/metas/batch_query", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{ + "metas": []map[string]interface{}{ + {"doc_token": "box_md_create_wiki", "doc_type": "file", "url": "https://tenant.example.com/file/box_md_create_wiki"}, + }, + }, + }, + }) err := mountAndRunMarkdown(t, MarkdownCreate, []string{ "+create", @@ -558,8 +591,8 @@ func TestMarkdownCreateSuccessUploadAllToWikiOmitsURL(t *testing.T) { if got := body.Fields["parent_node"]; got != "wikcn_markdown_create_target" { t.Fatalf("parent_node = %q, want %q", got, "wikcn_markdown_create_target") } - if strings.Contains(stdout.String(), `"url":`) { - t.Fatalf("stdout should omit url for wiki-hosted markdown files: %s", stdout.String()) + if !strings.Contains(stdout.String(), `"url": "https://tenant.example.com/file/box_md_create_wiki"`) { + t.Fatalf("stdout missing metadata url for wiki-hosted markdown file: %s", stdout.String()) } } @@ -575,6 +608,18 @@ func TestMarkdownCreatePrettyOutputIncludesPermissionGrant(t *testing.T) { }, }, }) + reg.Register(&httpmock.Stub{ + Method: "POST", + URL: "/open-apis/drive/v1/metas/batch_query", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{ + "metas": []map[string]interface{}{ + {"doc_token": "box_md_create_pretty", "doc_type": "file", "url": "https://tenant.example.com/file/box_md_create_pretty"}, + }, + }, + }, + }) err := mountAndRunMarkdown(t, MarkdownCreate, []string{ "+create", @@ -591,7 +636,7 @@ func TestMarkdownCreatePrettyOutputIncludesPermissionGrant(t *testing.T) { if !strings.Contains(out, "file_token: box_md_create_pretty") { t.Fatalf("pretty output missing file_token: %s", out) } - if !strings.Contains(out, "url: https://www.feishu.cn/file/box_md_create_pretty") { + if !strings.Contains(out, "url: https://tenant.example.com/file/box_md_create_pretty") { t.Fatalf("pretty output missing url: %s", out) } if !strings.Contains(out, "permission_grant.status: skipped") { @@ -649,6 +694,18 @@ func TestMarkdownCreateMultipartUploadSuccess(t *testing.T) { }, }, }) + reg.Register(&httpmock.Stub{ + Method: "POST", + URL: "/open-apis/drive/v1/metas/batch_query", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{ + "metas": []map[string]interface{}{ + {"doc_token": "box_md_multipart", "doc_type": "file", "url": "https://tenant.example.com/file/box_md_multipart"}, + }, + }, + }, + }) tmpDir := t.TempDir() withMarkdownWorkingDir(t, tmpDir) @@ -677,6 +734,9 @@ func TestMarkdownCreateMultipartUploadSuccess(t *testing.T) { if !strings.Contains(stdout.String(), `"file_token": "box_md_multipart"`) { t.Fatalf("stdout missing multipart file_token: %s", stdout.String()) } + if !strings.Contains(stdout.String(), `"url": "https://tenant.example.com/file/box_md_multipart"`) { + t.Fatalf("stdout missing multipart metadata url: %s", stdout.String()) + } } func TestMarkdownCreateMultipartUploadToWikiUsesWikiParent(t *testing.T) { @@ -715,6 +775,18 @@ func TestMarkdownCreateMultipartUploadToWikiUsesWikiParent(t *testing.T) { }, }, }) + reg.Register(&httpmock.Stub{ + Method: "POST", + URL: "/open-apis/drive/v1/metas/batch_query", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{ + "metas": []map[string]interface{}{ + {"doc_token": "box_md_multipart_wiki", "doc_type": "file", "url": "https://tenant.example.com/file/box_md_multipart_wiki"}, + }, + }, + }, + }) tmpDir := t.TempDir() withMarkdownWorkingDir(t, tmpDir) @@ -749,8 +821,8 @@ func TestMarkdownCreateMultipartUploadToWikiUsesWikiParent(t *testing.T) { if got := body["parent_node"]; got != "wikcn_markdown_multipart_target" { t.Fatalf("parent_node = %#v, want %q", got, "wikcn_markdown_multipart_target") } - if strings.Contains(stdout.String(), `"url":`) { - t.Fatalf("stdout should omit url for wiki-hosted multipart markdown files: %s", stdout.String()) + if !strings.Contains(stdout.String(), `"url": "https://tenant.example.com/file/box_md_multipart_wiki"`) { + t.Fatalf("stdout missing metadata url for wiki-hosted multipart markdown file: %s", stdout.String()) } } diff --git a/tests/cli_e2e/drive/drive_upload_dryrun_test.go b/tests/cli_e2e/drive/drive_upload_dryrun_test.go index 66b339a4..b33d88f4 100644 --- a/tests/cli_e2e/drive/drive_upload_dryrun_test.go +++ b/tests/cli_e2e/drive/drive_upload_dryrun_test.go @@ -34,6 +34,8 @@ func TestDriveUploadDryRun_WikiTarget(t *testing.T) { output := strings.TrimSpace(result.Stdout) assert.Contains(t, output, "/open-apis/drive/v1/files/upload_all") + assert.Contains(t, output, "/open-apis/drive/v1/metas/batch_query") + assert.Contains(t, output, `"with_url": true`) assert.Contains(t, output, "parent_type") assert.Contains(t, output, "parent_node") assert.Contains(t, output, "wikcnDryRunUploadTarget") @@ -61,6 +63,8 @@ func TestDriveUploadDryRun_WithFileToken(t *testing.T) { output := strings.TrimSpace(result.Stdout) assert.Contains(t, output, "/open-apis/drive/v1/files/upload_all") + assert.Contains(t, output, "/open-apis/drive/v1/metas/batch_query") + assert.Contains(t, output, `"with_url": true`) assert.Contains(t, output, `"parent_node": "fldDryRunUploadTarget"`) assert.Contains(t, output, `"file_token": "boxcnDryRunOverwriteTarget"`) } diff --git a/tests/cli_e2e/markdown/markdown_dryrun_test.go b/tests/cli_e2e/markdown/markdown_dryrun_test.go index dcb16789..fa8afa33 100644 --- a/tests/cli_e2e/markdown/markdown_dryrun_test.go +++ b/tests/cli_e2e/markdown/markdown_dryrun_test.go @@ -37,6 +37,8 @@ func TestMarkdownCreateDryRun_Content(t *testing.T) { output := strings.TrimSpace(result.Stdout) assert.Contains(t, output, "/open-apis/drive/v1/files/upload_all") + assert.Contains(t, output, "/open-apis/drive/v1/metas/batch_query") + assert.Contains(t, output, `"with_url": true`) assert.Contains(t, output, `"file_name": "README.md"`) assert.Contains(t, output, `"parent_node": "fldcnMarkdownDryRun"`) assert.Contains(t, output, `"parent_type": "explorer"`) @@ -64,6 +66,8 @@ func TestMarkdownCreateDryRun_WikiTarget(t *testing.T) { output := strings.TrimSpace(result.Stdout) assert.Contains(t, output, "/open-apis/drive/v1/files/upload_all") + assert.Contains(t, output, "/open-apis/drive/v1/metas/batch_query") + assert.Contains(t, output, `"with_url": true`) assert.Contains(t, output, `"file_name": "README.md"`) assert.Contains(t, output, `"parent_node": "wikcnMarkdownDryRun"`) assert.Contains(t, output, `"parent_type": "wiki"`) @@ -94,6 +98,8 @@ func TestMarkdownCreateDryRun_FileShowsConcreteSize(t *testing.T) { output := strings.TrimSpace(result.Stdout) assert.Contains(t, output, "/open-apis/drive/v1/files/upload_all") + assert.Contains(t, output, "/open-apis/drive/v1/metas/batch_query") + assert.Contains(t, output, `"with_url": true`) assert.Contains(t, output, `"file": "@note.md"`) assert.Contains(t, output, `"size": 5`) } diff --git a/tests/cli_e2e/markdown/markdown_workflow_test.go b/tests/cli_e2e/markdown/markdown_workflow_test.go index e725b5f9..f43c05c0 100644 --- a/tests/cli_e2e/markdown/markdown_workflow_test.go +++ b/tests/cli_e2e/markdown/markdown_workflow_test.go @@ -217,7 +217,7 @@ func TestMarkdownCreateWorkflow_WikiParent(t *testing.T) { fileToken := gjson.Get(createResult.Stdout, "data.file_token").String() require.NotEmpty(t, fileToken, "stdout:\n%s", createResult.Stdout) - require.False(t, gjson.Get(createResult.Stdout, "data.url").Exists(), "stdout:\n%s", createResult.Stdout) + require.NotEmpty(t, gjson.Get(createResult.Stdout, "data.url").String(), "stdout:\n%s", createResult.Stdout) parentT.Cleanup(func() { requireDeleteWikiHostedMarkdownFile(parentT, fileToken)