From 5bc6519cb4185aa9b1b7bec47f87853e72985a3c Mon Sep 17 00:00:00 2001 From: search_zhuhao Date: Wed, 2 Sep 2026 21:55:20 +0800 Subject: [PATCH] =?UTF-8?q?Revert=20"feat:=20=E6=94=AF=E6=8C=81=E4=BC=9A?= =?UTF-8?q?=E8=AE=AE=E6=90=9C=E7=B4=A2=E4=BD=BF=E7=94=A8=E6=9C=BA=E5=99=A8?= =?UTF-8?q?=E4=BA=BA=E8=BA=AB=E4=BB=BD=20(#2445)"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 515f9f5a4ad914adeb0cace527fe86e2ad2d5211. --- cmd/root_integration_test.go | 39 ----------- shortcuts/vc/bot_identity_test.go | 67 +------------------ shortcuts/vc/skill_docs_test.go | 22 +++--- shortcuts/vc/vc_search.go | 4 +- .../lark-meeting/references/lark-vc-search.md | 11 ++- .../scenes/query-meeting-and-artifacts.md | 6 +- tests/cli_e2e/vc/vc_search_dryrun_test.go | 51 -------------- 7 files changed, 18 insertions(+), 182 deletions(-) delete mode 100644 tests/cli_e2e/vc/vc_search_dryrun_test.go diff --git a/cmd/root_integration_test.go b/cmd/root_integration_test.go index a288059e8e..1a96094a28 100644 --- a/cmd/root_integration_test.go +++ b/cmd/root_integration_test.go @@ -235,19 +235,6 @@ func TestIntegration_StrictModeBot_ProfileOverride_HidesCommandsInHelp(t *testin if !strings.Contains(stdout.String(), "+chat-create") { t.Fatalf("im --help should keep +chat-create in bot mode, got:\n%s", stdout.String()) } - - resetBuffers(stdout, stderr) - rootCmd = buildStrictModeIntegrationRootCmd(t, f) - code = executeRootIntegration(t, f, rootCmd, []string{"vc", "--help"}) - if code != 0 { - t.Fatalf("vc --help exit code = %d, want 0", code) - } - if stderr.Len() != 0 { - t.Fatalf("expected empty stderr, got: %s", stderr.String()) - } - if !strings.Contains(stdout.String(), "+search") { - t.Fatalf("vc --help should keep +search in bot mode, got:\n%s", stdout.String()) - } } func TestIntegration_StrictModeBot_ProfileOverride_DirectAuthLoginReturnsEnvelope(t *testing.T) { @@ -350,32 +337,6 @@ func TestIntegration_StrictModeBot_ProfileOverride_MessagesSearchDryRunSucceeds( } } -func TestIntegration_StrictModeBot_ProfileOverride_VCSearchDryRunSucceeds(t *testing.T) { - f, stdout, stderr := newStrictModeDefaultFactory(t, "target", core.StrictModeBot) - rootCmd := buildStrictModeIntegrationRootCmd(t, f) - - code := executeRootIntegration(t, f, rootCmd, []string{ - "vc", "+search", "--query", "roadmap", "--page-size", "5", "--page-token", "next", "--dry-run", - }) - - if code != 0 { - t.Fatalf("exit code = %d, want 0; stderr: %s", code, stderr.String()) - } - if stderr.Len() != 0 { - t.Fatalf("expected empty stderr, got: %s", stderr.String()) - } - out := stdout.String() - if !strings.Contains(out, `"/open-apis/vc/v1/meetings/search"`) { - t.Fatalf("vc +search dry-run did not include search API; stdout:\n%s", out) - } - if !strings.Contains(out, `"page_token":"next"`) && !strings.Contains(out, `"page_token": "next"`) { - t.Fatalf("vc +search dry-run did not preserve pagination; stdout:\n%s", out) - } - if !strings.Contains(out, `"identity":"bot"`) && !strings.Contains(out, `"identity": "bot"`) { - t.Fatalf("vc +search dry-run did not run as bot; stdout:\n%s", out) - } -} - func TestIntegration_StrictModeUser_ProfileOverride_ChatCreateDryRunSucceeds(t *testing.T) { // +chat-create supports both user and bot identities, so strict mode user // should allow it and force user identity. diff --git a/shortcuts/vc/bot_identity_test.go b/shortcuts/vc/bot_identity_test.go index 6e3e04e50c..571e1b2fd0 100644 --- a/shortcuts/vc/bot_identity_test.go +++ b/shortcuts/vc/bot_identity_test.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: MIT // // Tests pinning bot-identity support for the vc read shortcuts -// (+search / +detail / +notes / +recording). +// (+detail / +notes / +recording). package vc @@ -10,14 +10,12 @@ import ( "context" "errors" "reflect" - "slices" "strings" "testing" "github.com/larksuite/cli/errs" "github.com/larksuite/cli/internal/cmdutil" "github.com/larksuite/cli/internal/credential" - "github.com/larksuite/cli/internal/httpmock" ) // --------------------------------------------------------------------------- @@ -27,7 +25,6 @@ import ( func TestVCReadShortcutsSupportUserAndBotIdentity(t *testing.T) { want := []string{"user", "bot"} cases := map[string][]string{ - "+search": VCSearch.AuthTypes, "+detail": VCDetail.AuthTypes, "+notes": VCNotes.AuthTypes, "+recording": VCRecording.AuthTypes, @@ -125,68 +122,6 @@ func TestNotes_DryRun_BotIdentity_CalendarEventIDs(t *testing.T) { // below is the test that actually fails if that shortcut-local check regresses. // --------------------------------------------------------------------------- -func TestSearch_BotIdentityResolvesTenantToken(t *testing.T) { - cfg := defaultConfig() - f, stdout, _, _ := cmdutil.TestFactory(t, cfg) - resolver := &recordingIdentityTokenResolver{tatScopes: ""} - f.Credential = credential.NewCredentialProvider(nil, nil, resolver, nil) - - err := mountAndRun(t, VCSearch, []string{ - "+search", "--query", "weekly", "--page-size", "5", - "--page-token", "next", "--dry-run", "--as", "bot", - }, f, stdout) - if err != nil { - t.Fatalf("unexpected bot dry-run error: %v", err) - } - if len(resolver.requestsOfType(credential.TokenTypeTAT)) == 0 { - t.Fatalf("expected bot search to resolve TAT, requests: %v", resolver.requests) - } - if got := resolver.requestsOfType(credential.TokenTypeUAT); len(got) != 0 { - t.Fatalf("bot search must not resolve UAT, requests: %v", got) - } -} - -func TestSearch_BotPermissionErrorKeepsIdentityAndScope(t *testing.T) { - cfg := defaultConfig() - f, _, _, reg := cmdutil.TestFactory(t, cfg) - resolver := &recordingIdentityTokenResolver{tatScopes: ""} - f.Credential = credential.NewCredentialProvider(nil, nil, resolver, nil) - reg.Register(&httpmock.Stub{ - Method: "POST", - URL: "/open-apis/vc/v1/meetings/search", - Body: map[string]interface{}{ - "code": 99991672, - "msg": "app scope not enabled", - "error": map[string]interface{}{ - "permission_violations": []interface{}{ - map[string]interface{}{"subject": "vc:meeting.search:read"}, - }, - }, - }, - }) - - err := mountAndRun(t, VCSearch, []string{ - "+search", "--query", "weekly", "--as", "bot", - }, f, nil) - if err == nil { - t.Fatal("expected bot permission error") - } - var permissionErr *errs.PermissionError - if !errors.As(err, &permissionErr) { - t.Fatalf("expected *errs.PermissionError, got %T: %v", err, err) - } - if permissionErr.Code != 99991672 || permissionErr.Identity != "bot" { - t.Fatalf("permission error = %+v, want code 99991672 and bot identity", permissionErr) - } - if !slices.Contains(permissionErr.MissingScopes, "vc:meeting.search:read") { - t.Fatalf("missing scopes = %v, want vc:meeting.search:read", permissionErr.MissingScopes) - } - if strings.Contains(permissionErr.Hint, "auth login") { - t.Fatalf("bot permission hint must not suggest user login: %q", permissionErr.Hint) - } - reg.Verify(t) -} - func TestRecording_BotIdentityAwareScopePreflight(t *testing.T) { cfg := defaultConfig() f, stdout, _, _ := cmdutil.TestFactory(t, cfg) diff --git a/shortcuts/vc/skill_docs_test.go b/shortcuts/vc/skill_docs_test.go index 55776dc0e8..e06c92a0cf 100644 --- a/shortcuts/vc/skill_docs_test.go +++ b/shortcuts/vc/skill_docs_test.go @@ -34,28 +34,22 @@ func readSkillDoc(t *testing.T, relPath string) string { return string(data) } -// TestVCSearchIdentityDocsMatchAuthTypes pins the user/bot identity contract in -// code, the command reference, and the cross-command meeting workflow. +// TestVCSearchIdentityDocsMatchAuthTypes pins that `+search` stays user-only +// in both code and the reference owned by lark-meeting. If AuthTypes ever +// gains "bot", this test forces a deliberate documentation update instead of +// letting the docs silently fall out of sync. func TestVCSearchIdentityDocsMatchAuthTypes(t *testing.T) { skill := readSkillDoc(t, "skills/lark-meeting/SKILL.md") reference := readSkillDoc(t, "skills/lark-meeting/references/lark-vc-search.md") - scene := readSkillDoc(t, "skills/lark-meeting/scenes/query-meeting-and-artifacts.md") - for _, identity := range []string{"user", "bot"} { - if !hasAuthType(VCSearch.AuthTypes, identity) { - t.Errorf("VCSearch.AuthTypes = %v, want %s included", VCSearch.AuthTypes, identity) - } + if hasAuthType(VCSearch.AuthTypes, "bot") { + t.Fatalf("VCSearch.AuthTypes = %v now includes bot; update skills/lark-meeting/references/lark-vc-search.md wording (and this test) to reflect the new support instead of leaving the user-only claim below", VCSearch.AuthTypes) } if !strings.Contains(skill, "references/lark-vc-search.md") { t.Error("skills/lark-meeting/SKILL.md must link to the vc +search reference") } - for _, identity := range []string{"--as user", "--as bot"} { - if !strings.Contains(reference, identity) { - t.Errorf("lark-vc-search.md must document %s", identity) - } - } - if strings.Contains(scene, "`vc +search` 仅支持用户身份") { - t.Error("meeting artifact scene must not claim vc +search is user-only") + if !strings.Contains(reference, "仅支持 `user` 身份") && !strings.Contains(reference, "仅 `--as user`") { + t.Error("lark-vc-search.md must state that +search only supports user identity (matches VCSearch.AuthTypes)") } } diff --git a/shortcuts/vc/vc_search.go b/shortcuts/vc/vc_search.go index dc61194861..f0bac1abac 100644 --- a/shortcuts/vc/vc_search.go +++ b/shortcuts/vc/vc_search.go @@ -172,10 +172,10 @@ func meetingSearchDescription(item map[string]interface{}) string { var VCSearch = common.Shortcut{ Service: "vc", Command: "+search", - Description: "Search meeting records by keyword, time range, participant, organizer, or meeting room with user or bot identity (requires at least one filter)", + Description: "Search meeting records by keyword, time range, participant, organizer, or meeting room (requires at least one filter)", Risk: "read", Scopes: []string{"vc:meeting.search:read"}, - AuthTypes: []string{"user", "bot"}, + AuthTypes: []string{"user"}, HasFormat: true, Flags: []common.Flag{ {Name: "query", Desc: "search keyword"}, diff --git a/skills/lark-meeting/references/lark-vc-search.md b/skills/lark-meeting/references/lark-vc-search.md index b795ec9cdf..bd77c24f65 100644 --- a/skills/lark-meeting/references/lark-vc-search.md +++ b/skills/lark-meeting/references/lark-vc-search.md @@ -1,7 +1,7 @@ # vc +search -搜索已结束的历史会议记录,支持关键词、时间范围、组织者、参与者、会议室多条件过滤。只读,支持 `--as user` / `--as bot`。 +搜索已结束的历史会议记录,支持关键词、时间范围、组织者、参与者、会议室多条件过滤。只读,仅 `--as user`。 ## 关键词使用边界 @@ -28,7 +28,6 @@ lark-cli vc +search --query "周会" # 通过 9 位会议号查询会议 ID lark-cli vc +search --query "123456789" --format json --as user -lark-cli vc +search --query "123456789" --format json --as bot # 查询某一天开过的会(单日查询时,start 和 end 必须填写同一天) lark-cli vc +search --start 2026-03-10 --end 2026-03-10 @@ -76,11 +75,9 @@ lark-cli vc +search --query "周会" --page-token "" `vc +search` 只能搜索已结束的历史会议记录,不用于查询未来日程。查询未来会议安排请使用 [lark-calendar](../../lark-calendar/SKILL.md)。 -### 3. 支持 user 和 bot 身份 +### 3. 仅支持 user 身份 -该接口支持 `--as user` 和 `--as bot`。user 身份需要完成 `lark-cli auth login` 并具备 `vc:meeting.search:read` 权限;bot 身份使用应用的 tenant access token,需要确认当前应用已开通 `vc:meeting.search:read` scope,且运行环境能获取有效的 TAT。 - -搜索得到 `meeting_id` 后,后续 `vc +detail`、`vc +recording`、`vc meeting get` 和 `note +detail` 必须显式沿用本次搜索使用的身份。不要为了绕过权限错误自动切换身份。 +该接口仅支持 `user` 身份,使用前需完成 `lark-cli auth login` 并具备 `vc:meeting.search:read` 权限。 ### 4. 支持分页 @@ -134,7 +131,7 @@ lark-cli vc +search --query "周会" --page-size 15 --page-token "" | 命令直接报错,要求提供过滤条件 | 没有传入 `--query`、时间范围或任何过滤 ID | 至少补充一个过滤条件后重试 | | 时间参数校验失败 | `--start` 或 `--end` 格式不合法 | 改用 ISO 8601 或 `YYYY-MM-DD` | | 搜不到未来会议 | `vc +search` 只查历史会议 | 改用 [lark-calendar](../../lark-calendar/SKILL.md) 查询未来日程 | -| 权限不足 | 未授权 `vc:meeting.search:read` | `--as user`:按提示完成用户授权;`--as bot`:检查 tenant access token 和应用 scope,不要执行 `auth login` | +| 权限不足 | 未授权 `vc:meeting.search:read` | 使用 `auth login` 完成授权 | ## 提示 - 必须使用 `--format json` 输出,便于稳定解析。 diff --git a/skills/lark-meeting/scenes/query-meeting-and-artifacts.md b/skills/lark-meeting/scenes/query-meeting-and-artifacts.md index e893b06cc0..fe969165f7 100644 --- a/skills/lark-meeting/scenes/query-meeting-and-artifacts.md +++ b/skills/lark-meeting/scenes/query-meeting-and-artifacts.md @@ -15,7 +15,7 @@ | 已有信息 | 操作 | |---|---| | `meeting_id` | 直接查询会议或关联产物 | -| `meeting_no` / 9 位会议号 | 用 `vc +search --query "" --format json --as ` 搜索会议,从结果的 `id` 取得 `meeting_id` | +| `meeting_no` / 9 位会议号 | 用 `vc +search --query "" --format json --as user` 搜索会议,从结果的 `id` 取得 `meeting_id` | | Calendar `event_id` | 用 `calendar +meeting` 获取 `meeting_id` 和用户绑定的 `meeting_note` | | `note_id` | 直接进入 [智能纪要场景](query-note-and-artifacts.md) | | `minute_token` / 妙记 URL | 直接进入 [妙记场景](query-minutes-and-artifacts.md);URL 取路径最后一段并去掉 query 参数 | @@ -23,7 +23,7 @@ 没有标识时,用 `vc +search` 搜索已经结束的会议: ```bash -lark-cli vc +search --query --start --end --format json --as +lark-cli vc +search --query --start --end --format json ``` - 至少提供关键词、时间范围、组织者、参与者或会议室中的一个条件;不要把“总结”“回顾”“所有会议”等动作词当作 `--query`。 @@ -38,7 +38,7 @@ lark-cli vc +search --query --start --end --format json -- ## 选择查询身份 -- `vc +search`、`vc +detail`、`vc +recording`、`vc meeting get` 和 `note +detail` 均支持用户或应用身份。没有既有身份上下文时默认使用用户身份;用户明确要求应用视角或当前链路已经使用应用身份时,使用 `--as bot`。 +- `vc +search` 仅支持用户身份。`vc +detail`、`vc +recording`、`vc meeting get` 和 `note +detail` 支持用户或应用身份。 - 已有 `meeting_id`、`note_id` 或 `minute_token` 时,沿用其来源身份;后续 Minutes、Note、Doc 和 Drive 命令都显式传入同一个 `--as`。不要为查询参会人或绕过权限错误擅自切换身份。 - `note +transcript` 仅支持用户身份。应用身份查到 unified Note 时,先说明限制,只有用户明确同意后才切换身份。 diff --git a/tests/cli_e2e/vc/vc_search_dryrun_test.go b/tests/cli_e2e/vc/vc_search_dryrun_test.go deleted file mode 100644 index 5a84e4012b..0000000000 --- a/tests/cli_e2e/vc/vc_search_dryrun_test.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) 2026 Lark Technologies Pte. Ltd. -// SPDX-License-Identifier: MIT - -package vc - -import ( - "context" - "net/http" - "testing" - "time" - - clie2e "github.com/larksuite/cli/tests/cli_e2e" - "github.com/stretchr/testify/require" -) - -func TestVCSearchDryRunSupportsUserAndBotIdentity(t *testing.T) { - setVCDryRunEnv(t) - - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - t.Cleanup(cancel) - - for _, identity := range []string{"user", "bot"} { - t.Run(identity, func(t *testing.T) { - result, err := clie2e.RunCmd(ctx, clie2e.Request{ - Args: []string{ - "vc", "+search", "--query", "roadmap", "--page-size", "5", - "--page-token", "next", "--dry-run", - }, - DefaultAs: identity, - }) - require.NoError(t, err) - result.AssertExitCode(t, 0) - - out := result.Stdout - require.Equal(t, identity, clie2e.DryRunGet(out, "identity").String(), "stdout:\n%s", out) - require.Equal(t, http.MethodPost, clie2e.DryRunGet(out, "api.0.method").String(), "stdout:\n%s", out) - require.Equal(t, "/open-apis/vc/v1/meetings/search", clie2e.DryRunGet(out, "api.0.url").String(), "stdout:\n%s", out) - require.Equal(t, "roadmap", clie2e.DryRunGet(out, "api.0.body.query").String(), "stdout:\n%s", out) - require.Equal(t, "5", clie2e.DryRunGet(out, "api.0.params.page_size").String(), "stdout:\n%s", out) - require.Equal(t, "next", clie2e.DryRunGet(out, "api.0.params.page_token").String(), "stdout:\n%s", out) - }) - } - - help, err := clie2e.RunCmd(ctx, clie2e.Request{ - Args: []string{"vc", "+search", "--help"}, - DefaultAs: "bot", - }) - require.NoError(t, err) - help.AssertExitCode(t, 0) - require.Contains(t, help.Stdout, "identity type: user | bot") -}