-
Notifications
You must be signed in to change notification settings - Fork 948
docs: clarify mail message shortcut guidance #1306
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| // Copyright (c) 2026 Lark Technologies Pte. Ltd. | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| package mail | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "context" | ||
| "encoding/json" | ||
| "strings" | ||
| "testing" | ||
|
|
||
| "github.com/spf13/cobra" | ||
|
|
||
| "github.com/larksuite/cli/shortcuts/common" | ||
| ) | ||
|
|
||
| func TestMailMessageHelpMentionsSingleMessageOnly(t *testing.T) { | ||
| help := strings.ToLower(mountedShortcutHelp(t, MailMessage)) | ||
| for _, want := range []string{ | ||
| "single email", | ||
| "mail +messages", | ||
| "do not loop mail +message", | ||
| "single email message id", | ||
| } { | ||
| if !strings.Contains(help, want) { | ||
| t.Errorf("mail +message help should mention %q; got:\n%s", want, help) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestMailMessagesHelpMentionsBatchLimitAndAutoChunk(t *testing.T) { | ||
| help := strings.ToLower(mountedShortcutHelp(t, MailMessages)) | ||
| for _, want := range []string{ | ||
| "multiple emails", | ||
| "at most 20 ids per batch_get request", | ||
| "merges output", | ||
| "current backend raw request validation rejects more than 50 ids", | ||
| } { | ||
| if !strings.Contains(help, want) { | ||
| t.Errorf("mail +messages help should mention %q; got:\n%s", want, help) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestMailMessageDryRunMentionsSingleMessageOnly(t *testing.T) { | ||
| runtime := runtimeForShortcutDryRun(t, MailMessage, map[string]string{ | ||
| "message-id": "msg_1", | ||
| }) | ||
| got := strings.ToLower(marshalDryRun(t, MailMessage.DryRun(context.Background(), runtime))) | ||
| for _, want := range []string{ | ||
| "one email only", | ||
| "for multiple ids use mail +messages", | ||
| } { | ||
| if !strings.Contains(got, want) { | ||
| t.Errorf("mail +message dry-run should mention %q; got:\n%s", want, got) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestMailMessagesDryRunMentionsChunkAndBackendLimit(t *testing.T) { | ||
| runtime := runtimeForShortcutDryRun(t, MailMessages, map[string]string{ | ||
| "message-ids": "msg_1,msg_2", | ||
| }) | ||
| got := strings.ToLower(marshalDryRun(t, MailMessages.DryRun(context.Background(), runtime))) | ||
| for _, want := range []string{ | ||
| "auto-chunks at most 20 ids per request", | ||
| "merges output", | ||
| "backend raw request validation rejects more than 50 ids", | ||
| } { | ||
| if !strings.Contains(got, want) { | ||
| t.Errorf("mail +messages dry-run should mention %q; got:\n%s", want, got) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func mountedShortcutHelp(t *testing.T, shortcut common.Shortcut) string { | ||
| t.Helper() | ||
| f, _, _, _ := mailShortcutTestFactory(t) | ||
| parent := &cobra.Command{Use: "mail"} | ||
| shortcut.Mount(parent, f) | ||
| cmd, _, err := parent.Find([]string{shortcut.Command}) | ||
| if err != nil { | ||
| t.Fatalf("find mounted shortcut %s: %v", shortcut.Command, err) | ||
| } | ||
| if cmd == parent { | ||
| t.Fatalf("shortcut %s was not mounted", shortcut.Command) | ||
| } | ||
| var out bytes.Buffer | ||
| cmd.SetOut(&out) | ||
| cmd.SetErr(&out) | ||
| if err := cmd.Help(); err != nil { | ||
| t.Fatalf("help for %s: %v", shortcut.Command, err) | ||
| } | ||
| return cmd.Short + "\n" + out.String() | ||
| } | ||
|
|
||
| func runtimeForShortcutDryRun(t *testing.T, shortcut common.Shortcut, values map[string]string) *common.RuntimeContext { | ||
| t.Helper() | ||
| cmd := &cobra.Command{Use: shortcut.Command} | ||
| for _, fl := range shortcut.Flags { | ||
| switch fl.Type { | ||
| case "bool": | ||
| cmd.Flags().Bool(fl.Name, fl.Default == "true", "") | ||
| default: | ||
| cmd.Flags().String(fl.Name, fl.Default, "") | ||
| } | ||
| } | ||
| if err := cmd.ParseFlags(nil); err != nil { | ||
| t.Fatalf("parse flags failed: %v", err) | ||
| } | ||
| for k, v := range values { | ||
| if err := cmd.Flags().Set(k, v); err != nil { | ||
| t.Fatalf("set flag --%s failed: %v", k, err) | ||
| } | ||
| } | ||
| return &common.RuntimeContext{Cmd: cmd} | ||
| } | ||
|
|
||
| func marshalDryRun(t *testing.T, dry *common.DryRunAPI) string { | ||
| t.Helper() | ||
| raw, err := json.Marshal(dry) | ||
| if err != nil { | ||
| t.Fatalf("marshal dry-run: %v", err) | ||
| } | ||
| return string(raw) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,14 +23,14 @@ type mailMessagesOutput struct { | |
| var MailMessages = common.Shortcut{ | ||
| Service: "mail", | ||
| Command: "+messages", | ||
| Description: "Use when reading full content for multiple emails by message ID. Prefer this shortcut over calling raw mail user_mailbox.messages batch_get directly, because it base64url-decodes body fields and returns normalized per-message output that is easier to consume.", | ||
| Description: "Use when reading full content for multiple emails by message ID. This shortcut sends at most 20 IDs per batch_get request and merges output; current backend raw request validation rejects more than 50 IDs.", | ||
| Risk: "read", | ||
| Scopes: []string{"mail:user_mailbox.message:readonly", "mail:user_mailbox.message.address:read", "mail:user_mailbox.message.subject:read", "mail:user_mailbox.message.body:read"}, | ||
| AuthTypes: []string{"user", "bot"}, | ||
| HasFormat: true, | ||
| Flags: []common.Flag{ | ||
| {Name: "mailbox", Default: "me", Desc: "email address (default: me)"}, | ||
| {Name: "message-ids", Desc: `Required. Comma-separated email message IDs. Example: "id1,id2,id3"`, Required: true}, | ||
| {Name: "message-ids", Desc: `Required. Comma-separated email message IDs. This shortcut sends at most 20 IDs per batch_get request and merges output; current backend raw request validation rejects more than 50 IDs. Example: "id1,id2,id3"`, Required: true}, | ||
| {Name: "html", Type: "bool", Default: "true", Desc: "Whether to return HTML body (false returns plain text only to save bandwidth)"}, | ||
| {Name: "print-output-schema", Type: "bool", Desc: "Print output field reference (run this first to learn field names before parsing output)"}, | ||
| }, | ||
|
|
@@ -52,7 +52,7 @@ var MailMessages = common.Shortcut{ | |
| body["message_ids"] = messageIDs | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤖 AI Review | [P2 正确性] dry-run 展示的 batch_get 请求与真实分批执行不一致 这里会把用户传入的全部 修复建议: 让 dry-run 也表达真实执行计划:对 >20 IDs 输出分批计划/多段 body,或只展示首批并明确 “dry-run body is illustrative; execution splits N IDs into M requests”,避免生成 raw-invalid 的单次请求。 如有疑问或认为判断不准确,欢迎直接回复讨论。 |
||
| } | ||
| return common.NewDryRunAPI(). | ||
| Desc("Fetch multiple emails via messages.batch_get (auto-chunked in batches of 20 IDs during execution)"). | ||
| Desc("Fetch multiple emails via messages.batch_get; execution auto-chunks at most 20 IDs per request and merges output. Current backend raw request validation rejects more than 50 IDs per raw request."). | ||
| POST(mailboxPath(mailboxID, "messages", "batch_get")). | ||
| Body(body) | ||
| }, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤖 AI Review | [P3 可维护性] IM feed group 错误契约迁移缺少回归覆盖
这里把 feed group 调用从
DoAPIJSON切到DoAPIJSONTyped,成功数据结构不变,但错误分类/输出契约会从 legacy API error 路径变为 typed error 路径;同类改动还出现在im_feed_group_list_item.go和im_feed_group_query_item.go。如果脚本或 AI 依赖旧的错误 envelope/message,非零 API code 场景会发生可见行为变化,但当前新增测试主要覆盖 mail 文案和成功分批,没有锁住 feed group 的错误输出。修复建议: 补一组 feed group 非零 API code / HTTP error 的单测,断言返回的 code、message、log_id/subtype 等关键字段;或者在 PR 描述中明确这是有意的 IM 错误契约迁移。
如有疑问或认为判断不准确,欢迎直接回复讨论。