mirror of
https://github.com/larksuite/cli.git
synced 2026-07-09 18:34:03 +08:00
Compare commits
1 Commits
codex/docs
...
sun/docs-u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba497de3ba |
@@ -151,12 +151,12 @@ func resolveFetchLang(runtime *common.RuntimeContext) string {
|
||||
|
||||
// buildReadOption 拼装 read_option JSON;full/空模式返回 nil,让服务端走默认全文路径。
|
||||
func buildReadOption(runtime *common.RuntimeContext) map[string]interface{} {
|
||||
mode := effectiveFetchReadMode(runtime)
|
||||
mode := strings.TrimSpace(runtime.Str("scope"))
|
||||
if mode == "" || mode == "full" {
|
||||
return nil
|
||||
}
|
||||
ro := map[string]interface{}{"read_mode": mode}
|
||||
if v := effectiveFetchStartBlockID(runtime, mode); v != "" {
|
||||
if v := strings.TrimSpace(runtime.Str("start-block-id")); v != "" {
|
||||
ro["start_block_id"] = v
|
||||
}
|
||||
if v := strings.TrimSpace(runtime.Str("end-block-id")); v != "" {
|
||||
@@ -177,77 +177,6 @@ func buildReadOption(runtime *common.RuntimeContext) map[string]interface{} {
|
||||
return ro
|
||||
}
|
||||
|
||||
func effectiveFetchReadMode(runtime *common.RuntimeContext) string {
|
||||
mode := rawFetchReadMode(runtime)
|
||||
if shouldUseDocSelectionAnchor(runtime, mode) {
|
||||
if anchor, _ := docSelectionAnchorStartBlockID(runtime); anchor != "" {
|
||||
return "range"
|
||||
}
|
||||
}
|
||||
return mode
|
||||
}
|
||||
|
||||
func rawFetchReadMode(runtime *common.RuntimeContext) string {
|
||||
mode := strings.TrimSpace(runtime.Str("scope"))
|
||||
if mode == "" {
|
||||
return "full"
|
||||
}
|
||||
return mode
|
||||
}
|
||||
|
||||
func effectiveFetchStartBlockID(runtime *common.RuntimeContext, mode string) string {
|
||||
if v := strings.TrimSpace(runtime.Str("start-block-id")); v != "" {
|
||||
if anchor, ok, _ := parseFetchSelectionAnchor(v, "--start-block-id"); ok {
|
||||
return anchor
|
||||
}
|
||||
return v
|
||||
}
|
||||
if mode == "range" && shouldUseDocSelectionAnchor(runtime, rawFetchReadMode(runtime)) {
|
||||
if anchor, _ := docSelectionAnchorStartBlockID(runtime); anchor != "" {
|
||||
return anchor
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func shouldUseDocSelectionAnchor(runtime *common.RuntimeContext, mode string) bool {
|
||||
if runtime.Changed("start-block-id") || runtime.Changed("end-block-id") {
|
||||
return false
|
||||
}
|
||||
if runtime.Changed("scope") {
|
||||
return mode == "range"
|
||||
}
|
||||
return mode == "" || mode == "full"
|
||||
}
|
||||
|
||||
func docSelectionAnchorStartBlockID(runtime *common.RuntimeContext) (string, error) {
|
||||
ref, err := parseDocumentRef(runtime.Str("doc"))
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
anchor, ok, err := parseFetchSelectionAnchor(ref.Fragment, "--doc")
|
||||
if err != nil || !ok {
|
||||
return "", err
|
||||
}
|
||||
return anchor, nil
|
||||
}
|
||||
|
||||
func parseFetchSelectionAnchor(raw, param string) (string, bool, error) {
|
||||
value := strings.TrimSpace(raw)
|
||||
value = strings.TrimPrefix(value, "#")
|
||||
for _, prefix := range []string{"share-", "part-"} {
|
||||
if !strings.HasPrefix(value, prefix) {
|
||||
continue
|
||||
}
|
||||
anchorID := strings.TrimSpace(strings.TrimPrefix(value, prefix))
|
||||
if anchorID == "" {
|
||||
return "", false, errs.NewValidationError(errs.SubtypeInvalidArgument, "selection anchor id is required after %s", prefix).WithParam(param)
|
||||
}
|
||||
return prefix + anchorID, true, nil
|
||||
}
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
// effectiveFetchDetail degrades detail options that cannot be represented by
|
||||
// non-XML exports. The original flag value is left intact so callers can still
|
||||
// surface an explicit warning in execute output.
|
||||
@@ -279,10 +208,7 @@ func addFetchDetailDowngradeWarning(runtime *common.RuntimeContext, data map[str
|
||||
|
||||
// validateReadModeFlags 客户端前置校验,服务端也会再校验一次。
|
||||
func validateReadModeFlags(runtime *common.RuntimeContext) error {
|
||||
mode := effectiveFetchReadMode(runtime)
|
||||
if err := validateFetchSelectionAnchorUsage(runtime, mode); err != nil {
|
||||
return err
|
||||
}
|
||||
mode := strings.TrimSpace(runtime.Str("scope"))
|
||||
if mode == "" || mode == "full" {
|
||||
return nil
|
||||
}
|
||||
@@ -301,7 +227,7 @@ func validateReadModeFlags(runtime *common.RuntimeContext) error {
|
||||
case "outline":
|
||||
return nil
|
||||
case "range":
|
||||
if effectiveFetchStartBlockID(runtime, mode) == "" &&
|
||||
if strings.TrimSpace(runtime.Str("start-block-id")) == "" &&
|
||||
strings.TrimSpace(runtime.Str("end-block-id")) == "" {
|
||||
return errs.NewValidationError(errs.SubtypeInvalidArgument, "range mode requires --start-block-id or --end-block-id").WithParams(
|
||||
errs.InvalidParam{Name: "--start-block-id", Reason: "provide --start-block-id or --end-block-id for range mode"},
|
||||
@@ -323,42 +249,3 @@ func validateReadModeFlags(runtime *common.RuntimeContext) error {
|
||||
return errs.NewValidationError(errs.SubtypeInvalidArgument, "invalid --scope %q", mode).WithParam("--scope")
|
||||
}
|
||||
}
|
||||
|
||||
func validateFetchSelectionAnchorUsage(runtime *common.RuntimeContext, mode string) error {
|
||||
startBlockID := strings.TrimSpace(runtime.Str("start-block-id"))
|
||||
endBlockID := strings.TrimSpace(runtime.Str("end-block-id"))
|
||||
|
||||
startAnchor, startIsAnchor, err := parseFetchSelectionAnchor(startBlockID, "--start-block-id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, endIsAnchor, err := parseFetchSelectionAnchor(endBlockID, "--end-block-id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if endIsAnchor {
|
||||
return errs.NewValidationError(errs.SubtypeInvalidArgument, "--end-block-id does not support selection anchors; pass the #share anchor in --doc URL").WithParam("--end-block-id")
|
||||
}
|
||||
if !startIsAnchor {
|
||||
_, _, err := parseFetchSelectionAnchorFromDoc(runtime)
|
||||
return err
|
||||
}
|
||||
if mode != "range" {
|
||||
return errs.NewValidationError(errs.SubtypeInvalidArgument, "--start-block-id selection anchor %q requires --scope range", startAnchor).WithParam("--start-block-id")
|
||||
}
|
||||
if endBlockID != "" {
|
||||
return errs.NewValidationError(errs.SubtypeInvalidArgument, "--start-block-id selection anchor %q cannot be combined with --end-block-id", startAnchor).WithParams(
|
||||
errs.InvalidParam{Name: "--start-block-id", Reason: "selection anchors define the complete selected range"},
|
||||
errs.InvalidParam{Name: "--end-block-id", Reason: "remove --end-block-id when --start-block-id is a selection anchor"},
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseFetchSelectionAnchorFromDoc(runtime *common.RuntimeContext) (string, bool, error) {
|
||||
ref, err := parseDocumentRef(runtime.Str("doc"))
|
||||
if err != nil {
|
||||
return "", false, nil
|
||||
}
|
||||
return parseFetchSelectionAnchor(ref.Fragment, "--doc")
|
||||
}
|
||||
|
||||
@@ -180,63 +180,6 @@ func TestBuildFetchBodyIncludesReadOption(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildFetchBodyUsesSelectionAnchorFragmentAsRangeStart(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
runtime := newFetchBodyTestRuntime(context.Background())
|
||||
mustSetFetchFlag(t, runtime, "doc", "https://example.larksuite.com/wiki/wikcnToken#share-CUE3d6Ykno2fkexEvt8cGF8Wnse")
|
||||
|
||||
body := buildFetchBody(runtime)
|
||||
want := map[string]interface{}{
|
||||
"read_mode": "range",
|
||||
"start_block_id": "share-CUE3d6Ykno2fkexEvt8cGF8Wnse",
|
||||
}
|
||||
if got := body["read_option"]; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("read_option = %#v, want %#v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildFetchBodyExplicitFullIgnoresSelectionAnchorFragment(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
runtime := newFetchBodyTestRuntime(context.Background())
|
||||
mustSetFetchFlag(t, runtime, "doc", "https://example.larksuite.com/wiki/wikcnToken#share-CUE3d6Ykno2fkexEvt8cGF8Wnse")
|
||||
mustSetFetchFlag(t, runtime, "scope", "full")
|
||||
|
||||
body := buildFetchBody(runtime)
|
||||
if _, ok := body["read_option"]; ok {
|
||||
t.Fatalf("did not expect read_option for explicit full scope: %#v", body["read_option"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildFetchBodyDoesNotAutoReadOrdinaryFragment(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
runtime := newFetchBodyTestRuntime(context.Background())
|
||||
mustSetFetchFlag(t, runtime, "doc", "https://example.larksuite.com/wiki/wikcnToken#blk_plain")
|
||||
|
||||
body := buildFetchBody(runtime)
|
||||
if _, ok := body["read_option"]; ok {
|
||||
t.Fatalf("did not expect read_option for ordinary URL fragment: %#v", body["read_option"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildReadOptionNormalizesExplicitSelectionAnchorStart(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
runtime := newFetchBodyTestRuntime(context.Background())
|
||||
mustSetFetchFlag(t, runtime, "scope", "range")
|
||||
mustSetFetchFlag(t, runtime, "start-block-id", "#part-CUE3d6Ykno2fkexEvt8cGF8Wnse")
|
||||
|
||||
want := map[string]interface{}{
|
||||
"read_mode": "range",
|
||||
"start_block_id": "part-CUE3d6Ykno2fkexEvt8cGF8Wnse",
|
||||
}
|
||||
if got := buildReadOption(runtime); !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("buildReadOption() = %#v, want %#v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildReadOptionModes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -378,31 +321,6 @@ func TestValidateReadModeFlagsRejectsInvalidScopeOptions(t *testing.T) {
|
||||
},
|
||||
wantParam: "--keyword",
|
||||
},
|
||||
{
|
||||
name: "selection anchor cannot be end block",
|
||||
setFlags: map[string]string{
|
||||
"scope": "range",
|
||||
"end-block-id": "#share-CUE3d6Ykno2fkexEvt8cGF8Wnse",
|
||||
},
|
||||
wantParam: "--end-block-id",
|
||||
},
|
||||
{
|
||||
name: "selection anchor start cannot combine with end block",
|
||||
setFlags: map[string]string{
|
||||
"scope": "range",
|
||||
"start-block-id": "#share-CUE3d6Ykno2fkexEvt8cGF8Wnse",
|
||||
"end-block-id": "blk_end",
|
||||
},
|
||||
wantParams: []string{"--start-block-id", "--end-block-id"},
|
||||
},
|
||||
{
|
||||
name: "selection anchor start requires range",
|
||||
setFlags: map[string]string{
|
||||
"scope": "section",
|
||||
"start-block-id": "#share-CUE3d6Ykno2fkexEvt8cGF8Wnse",
|
||||
},
|
||||
wantParam: "--start-block-id",
|
||||
},
|
||||
{
|
||||
name: "section needs start block",
|
||||
setFlags: map[string]string{
|
||||
@@ -457,19 +375,6 @@ func TestValidateReadModeFlagsAcceptsValidScopeOptions(t *testing.T) {
|
||||
"end-block-id": "blk_end",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "range with selection anchor start",
|
||||
setFlags: map[string]string{
|
||||
"scope": "range",
|
||||
"start-block-id": "#share-CUE3d6Ykno2fkexEvt8cGF8Wnse",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "default scope with selection anchor fragment",
|
||||
setFlags: map[string]string{
|
||||
"doc": "https://example.larksuite.com/wiki/wikcnToken#share-CUE3d6Ykno2fkexEvt8cGF8Wnse",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "keyword with keyword",
|
||||
setFlags: map[string]string{
|
||||
@@ -979,7 +884,6 @@ func TestDocsFetchRejectsLegacyFlags(t *testing.T) {
|
||||
|
||||
func newFetchBodyTestRuntime(ctx context.Context) *common.RuntimeContext {
|
||||
cmd := &cobra.Command{Use: "+fetch"}
|
||||
cmd.Flags().String("doc", "doxcnFetchDryRun", "")
|
||||
cmd.Flags().String("doc-format", fetchDefault("doc-format"), "")
|
||||
cmd.Flags().String("detail", fetchDefault("detail"), "")
|
||||
cmd.Flags().String("lang", fetchDefault("lang"), "")
|
||||
|
||||
@@ -17,9 +17,8 @@ import (
|
||||
const docsSceneContextKey = "lark_cli_docs_scene"
|
||||
|
||||
type documentRef struct {
|
||||
Kind string
|
||||
Token string
|
||||
Fragment string
|
||||
Kind string
|
||||
Token string
|
||||
}
|
||||
|
||||
func parseDocumentRef(input string) (documentRef, error) {
|
||||
@@ -29,13 +28,13 @@ func parseDocumentRef(input string) (documentRef, error) {
|
||||
}
|
||||
|
||||
if token, ok := extractDocumentToken(raw, "/wiki/"); ok {
|
||||
return documentRef{Kind: "wiki", Token: token, Fragment: extractDocumentFragment(raw)}, nil
|
||||
return documentRef{Kind: "wiki", Token: token}, nil
|
||||
}
|
||||
if token, ok := extractDocumentToken(raw, "/docx/"); ok {
|
||||
return documentRef{Kind: "docx", Token: token, Fragment: extractDocumentFragment(raw)}, nil
|
||||
return documentRef{Kind: "docx", Token: token}, nil
|
||||
}
|
||||
if token, ok := extractDocumentToken(raw, "/doc/"); ok {
|
||||
return documentRef{Kind: "doc", Token: token, Fragment: extractDocumentFragment(raw)}, nil
|
||||
return documentRef{Kind: "doc", Token: token}, nil
|
||||
}
|
||||
if strings.Contains(raw, "://") {
|
||||
return documentRef{}, errs.NewValidationError(errs.SubtypeInvalidArgument, "unsupported --doc input %q: use a docx URL/token or a wiki URL that resolves to docx", raw).WithParam("--doc")
|
||||
@@ -63,14 +62,6 @@ func extractDocumentToken(raw, marker string) (string, bool) {
|
||||
return token, true
|
||||
}
|
||||
|
||||
func extractDocumentFragment(raw string) string {
|
||||
idx := strings.Index(raw, "#")
|
||||
if idx < 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(raw[idx+1:])
|
||||
}
|
||||
|
||||
// doDocAPI executes an OpenAPI request against the docs_ai endpoints and returns
|
||||
// the parsed "data" field from the standard Lark response envelope {code, msg, data}.
|
||||
// CallAPITyped lifts the x-tt-logid response header onto the typed error so log_id
|
||||
|
||||
@@ -13,12 +13,11 @@ func TestParseDocumentRef(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
wantKind string
|
||||
wantToken string
|
||||
wantFragment string
|
||||
wantErr string
|
||||
name string
|
||||
input string
|
||||
wantKind string
|
||||
wantToken string
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "docx url",
|
||||
@@ -32,13 +31,6 @@ func TestParseDocumentRef(t *testing.T) {
|
||||
wantKind: "wiki",
|
||||
wantToken: "xxxxxx",
|
||||
},
|
||||
{
|
||||
name: "wiki url with selection anchor",
|
||||
input: "https://example.larksuite.com/wiki/xxxxxx#share-CUE3d6Ykno2fkexEvt8cGF8Wnse",
|
||||
wantKind: "wiki",
|
||||
wantToken: "xxxxxx",
|
||||
wantFragment: "share-CUE3d6Ykno2fkexEvt8cGF8Wnse",
|
||||
},
|
||||
{
|
||||
name: "doc url",
|
||||
input: "https://example.larksuite.com/doc/xxxxxx",
|
||||
@@ -81,9 +73,6 @@ func TestParseDocumentRef(t *testing.T) {
|
||||
if got.Token != tt.wantToken {
|
||||
t.Fatalf("parseDocumentRef(%q) token = %q, want %q", tt.input, got.Token, tt.wantToken)
|
||||
}
|
||||
if got.Fragment != tt.wantFragment {
|
||||
t.Fatalf("parseDocumentRef(%q) fragment = %q, want %q", tt.input, got.Fragment, tt.wantFragment)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
name: lark-doc
|
||||
version: 2.0.0
|
||||
description: "飞书云文档(Docx / Wiki 文档):读取和编辑飞书文档内容。当用户给出文档 URL 或 token,或需要查看、创建、编辑文档、插入或下载文档图片附件时使用。文档中嵌入的电子表格、多维表格、画板,先用本 skill 提取 token 再切到对应 skill。当用户给出 doubao.com 的 /docx/ 或 /wiki/ URL/token 时,也应直接使用本 skill;路由依据是 URL 路径模式和 token,而不是域名。不负责文档评论管理,也不负责表格或 Base 的数据操作。当用户明确要操作飞书思维笔记时,也使用本 skill。"
|
||||
metadata:
|
||||
requires:
|
||||
@@ -14,7 +13,7 @@ metadata:
|
||||
|
||||
```bash
|
||||
# 常用示例
|
||||
lark-cli docs +fetch --doc "文档URL或token,后缀有 #share-... 锚点时会局部读取"
|
||||
lark-cli docs +fetch --doc "文档URL或token"
|
||||
lark-cli docs +create --content '<title>标题</title><p>内容</p>'
|
||||
lark-cli docs +update --doc "文档URL或token" --command append --content '<p>内容</p>'
|
||||
```
|
||||
|
||||
@@ -17,10 +17,8 @@ lark-cli docs +fetch --doc Z1Fj...tnAc --detail with-ids
|
||||
lark-cli docs +fetch --doc Z1Fj...tnAc --scope outline --max-depth 3
|
||||
|
||||
# 按 block id 区间精读
|
||||
lark-cli docs +fetch --doc Z1Fj...tnAc --scope range --start-block-id blkA --end-block-id blkB --detail with-ids
|
||||
|
||||
# URL 带 #share 选区锚点时自动局部读取
|
||||
lark-cli docs +fetch --doc 'docURL#share-anchor'
|
||||
lark-cli docs +fetch --doc Z1Fj...tnAc \
|
||||
--scope range --start-block-id blkA --end-block-id blkB --detail with-ids
|
||||
|
||||
# 读整个章节(以标题 id 为锚点,自动展开到下一个同级/更高级标题前)
|
||||
lark-cli docs +fetch --doc Z1Fj...tnAc \
|
||||
|
||||
@@ -122,6 +122,8 @@ lark-cli docs +update --doc "<doc_id>" --command block_replace \
|
||||
--content '<p>替换后的段落内容</p>'
|
||||
```
|
||||
|
||||
投票 block 也使用 `block_replace` 做整块替换:replacement 中的新 `<poll>` 会创建新的投票 block,不继承旧投票的 block id、option id、票数、投票人、发布时间或当前用户选择;如需把投票改成普通内容,直接把 `--content` 写成目标 XML。
|
||||
|
||||
### block_delete — 删除指定 block
|
||||
|
||||
```bash
|
||||
|
||||
@@ -9,6 +9,7 @@ p, h1-h9, ul, ol, li, table, thead, tbody, tr, th, td, blockquote, pre, code, hr
|
||||
|-|-|-|
|
||||
| `<title>` | 文档标题(每篇唯一)| `align` |
|
||||
| `<checkbox>` | 待办项| `done="true"\|"false"` |
|
||||
| `<poll>` | 投票块,支持创建草稿、可选创建后发布 | `poll-type`, `is-anonymous`, `enable-due-time`, `due-time`, `publish-on-create` |
|
||||
|
||||
## 容器标签
|
||||
|标签|说明|关键属性|
|
||||
@@ -48,6 +49,40 @@ p, h1-h9, ul, ol, li, table, thead, tbody, tr, th, td, blockquote, pre, code, hr
|
||||
- `<sub-page-list>` — `<sub-page-list></sub-page-list>` 子页面列表块;仅 wiki 文档可插入
|
||||
- bitable、base_ref、synced_reference、synced_source、okr — 不可创建,仅支持移动
|
||||
|
||||
## 投票 block
|
||||
|
||||
投票使用结构化 XML 表达。默认创建未发布草稿:空标题、两个空选项、单选、实名、无截止时间、结果策略固定为投票后可见。
|
||||
|
||||
```xml
|
||||
<poll></poll>
|
||||
```
|
||||
|
||||
带内容创建:
|
||||
|
||||
```xml
|
||||
<poll poll-type="single" is-anonymous="false">
|
||||
<poll-title>午饭吃什么?</poll-title>
|
||||
<poll-option>米饭</poll-option>
|
||||
<poll-option>面条</poll-option>
|
||||
</poll>
|
||||
```
|
||||
|
||||
创建后尝试发布:
|
||||
|
||||
```xml
|
||||
<poll publish-on-create="true">
|
||||
<poll-title>午饭吃什么?</poll-title>
|
||||
<poll-option>米饭</poll-option>
|
||||
<poll-option>面条</poll-option>
|
||||
</poll>
|
||||
```
|
||||
|
||||
公开可写属性只有:`poll-type="single|multiple"`、`is-anonymous="true|false"`、`enable-due-time="true|false"`、`due-time="毫秒时间戳"`、`publish-on-create="true|false"`。不要写入 `when-result-visible`、`option-id`、票数、投票人或当前用户投票状态。
|
||||
|
||||
读取已发布投票时,XML 可能带只读结果字段,例如 `is-published`、`result-visible`、`user-count`、`poll-option count/percent/selected/voters-ref`。这些字段只用于展示,重新导入或 `block_replace` 时会被忽略;匿名投票不会通过 `reference_map` 暴露真实投票人。`voters-ref` 是读取详情的 opaque handle,不是投票操作入口。
|
||||
|
||||
修改投票配置、替换已发布投票、把投票替换成普通内容,都使用 `block_replace`,语义是删除旧 block 并插入 replacement。新 `<poll>` 会创建新的投票 block,不继承旧 block id、option id、票数、投票人、发布时间或当前用户选择。
|
||||
|
||||
# 四、块级复制与移动
|
||||
|
||||
## 移动(block_move_after)
|
||||
|
||||
@@ -43,35 +43,6 @@ func TestDocsFetchDryRunIgnoresAPIVersionCompatFlag(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocsFetchDryRunSelectionAnchorFragmentBecomesRangeStart(t *testing.T) {
|
||||
setDocsDryRunEnv(t)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
||||
Args: []string{
|
||||
"docs", "+fetch",
|
||||
"--doc", "https://example.larksuite.com/wiki/wikcnDryRun#share-CUE3d6Ykno2fkexEvt8cGF8Wnse",
|
||||
"--dry-run",
|
||||
},
|
||||
DefaultAs: "bot",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
result.AssertExitCode(t, 0)
|
||||
|
||||
out := result.Stdout
|
||||
if got := gjson.Get(out, "api.0.url").String(); got != "/open-apis/docs_ai/v1/documents/wikcnDryRun/fetch" {
|
||||
t.Fatalf("url=%q, want docs fetch endpoint\nstdout:\n%s", got, out)
|
||||
}
|
||||
if got := gjson.Get(out, "api.0.body.read_option.read_mode").String(); got != "range" {
|
||||
t.Fatalf("read_mode=%q, want range\nstdout:\n%s", got, out)
|
||||
}
|
||||
if got := gjson.Get(out, "api.0.body.read_option.start_block_id").String(); got != "share-CUE3d6Ykno2fkexEvt8cGF8Wnse" {
|
||||
t.Fatalf("start_block_id=%q, want selection anchor\nstdout:\n%s", got, out)
|
||||
}
|
||||
}
|
||||
|
||||
func setDocsDryRunEnv(t *testing.T) {
|
||||
t.Helper()
|
||||
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
||||
|
||||
Reference in New Issue
Block a user