mirror of
https://github.com/larksuite/cli.git
synced 2026-07-09 18:34:03 +08:00
Compare commits
3 Commits
feat/plugi
...
feat/mail-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc56cc5665 | ||
|
|
c02fa92667 | ||
|
|
49f60d9239 |
@@ -94,7 +94,7 @@ func TestMailTriageTableHintRoutesSingleAndMultipleReads(t *testing.T) {
|
||||
registerTriageReadHintStubs(reg)
|
||||
|
||||
err := runMountedMailShortcut(t, MailTriage, []string{
|
||||
"+triage", "--max", "1",
|
||||
"+triage", "--format", "table", "--max", "1",
|
||||
}, f, stdout)
|
||||
if err != nil {
|
||||
t.Fatalf("triage returned error: %v", err)
|
||||
|
||||
@@ -55,7 +55,7 @@ var MailTriage = common.Shortcut{
|
||||
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"},
|
||||
Flags: []common.Flag{
|
||||
{Name: "format", Default: "table", Enum: []string{"table", "json", "data"}, Desc: "output format: table | json | data (json/data output object with pagination fields)"},
|
||||
{Name: "format", Default: "json", Enum: []string{"table", "json", "data"}, Desc: "output format: table | json | data (json/data output object with pagination fields)"},
|
||||
{Name: "max", Type: "int", Default: "20", Desc: "maximum number of messages to fetch (1-400; auto-paginates internally)"},
|
||||
{Name: "page-size", Type: "int", Desc: "alias for --max"},
|
||||
{Name: "page-token", Desc: "pagination token from a previous response to fetch the next page"},
|
||||
|
||||
@@ -1655,6 +1655,39 @@ func TestMailTriageMissingMessageMetadataStillGetsMailboxID(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestMailTriageDefaultFormatIsJSON verifies that with no --format flag the
|
||||
// command defaults to json and prints the paginated object to stdout, not the
|
||||
// human table.
|
||||
func TestMailTriageDefaultFormatIsJSON(t *testing.T) {
|
||||
f, stdout, _, reg := mailShortcutTestFactory(t)
|
||||
defer reg.Verify(t)
|
||||
|
||||
registerMailTriageListStub(reg, "me", []string{"msg_ok"}, false, "")
|
||||
registerMailTriageBatchStub(reg, "me", []map[string]interface{}{
|
||||
mailTriageBatchMessage("msg_ok", "Present"),
|
||||
})
|
||||
|
||||
err := runMountedMailShortcut(t, MailTriage, []string{
|
||||
"+triage",
|
||||
"--filter", `{"folder_id":"INBOX"}`,
|
||||
}, f, stdout)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
data := decodeMailTriageJSONOutput(t, stdout) // fails to unmarshal if default were table
|
||||
messages := mailTriageMessagesFromOutput(t, data)
|
||||
if len(messages) != 1 {
|
||||
t.Fatalf("expected 1 message, got %d", len(messages))
|
||||
}
|
||||
if _, ok := data["count"]; !ok {
|
||||
t.Fatalf("default json output missing count field: %#v", data)
|
||||
}
|
||||
if _, ok := data["has_more"]; !ok {
|
||||
t.Fatalf("default json output missing has_more field: %#v", data)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMailTriageTableOutputPreservesMailboxContext verifies public mailbox table hints.
|
||||
func TestMailTriageTableOutputPreservesMailboxContext(t *testing.T) {
|
||||
tests := []struct {
|
||||
@@ -1678,7 +1711,7 @@ func TestMailTriageTableOutputPreservesMailboxContext(t *testing.T) {
|
||||
mailTriageBatchMessage("msg_001", "Table message"),
|
||||
})
|
||||
|
||||
args := []string{"+triage", "--max", "1", "--filter", `{"folder_id":"INBOX"}`}
|
||||
args := []string{"+triage", "--format", "table", "--max", "1", "--filter", `{"folder_id":"INBOX"}`}
|
||||
if tt.mailbox != "me" {
|
||||
args = append(args, "--mailbox", tt.mailbox)
|
||||
}
|
||||
@@ -1719,6 +1752,7 @@ func TestMailTriageDefaultTableOutputPrintsSearchNoticeToStderr(t *testing.T) {
|
||||
|
||||
if err := runMountedMailShortcut(t, MailTriage, []string{
|
||||
"+triage",
|
||||
"--format", "table",
|
||||
"--query", strings.Repeat("q", 81),
|
||||
}, f, stdout); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
|
||||
@@ -99,7 +99,7 @@ var MailWatch = common.Shortcut{
|
||||
Scopes: []string{"mail:event", "mail:user_mailbox.event.mail_address:read", "mail:user_mailbox:readonly", "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"},
|
||||
Flags: []common.Flag{
|
||||
{Name: "format", Default: "data", Enum: []string{"json", "data"}, Desc: "json: NDJSON stream with ok/data envelope; data: bare NDJSON stream"},
|
||||
{Name: "format", Default: "json", Enum: []string{"json", "data"}, Desc: "json: NDJSON stream with ok/data envelope; data: bare NDJSON stream"},
|
||||
{Name: "msg-format", Default: "metadata", Desc: "message payload mode: metadata(headers + meta, for triage/notification) | minimal(IDs and state only, no headers, for tracking read/folder changes) | plain_text_full(all metadata fields + full plain-text body) | event(raw WebSocket event, no API call, for debug) | full(full message including HTML body and attachments)"},
|
||||
{Name: "output-dir", Desc: "Write each message as a JSON file (always full payload, regardless of --msg-format)"},
|
||||
{Name: "mailbox", Default: "me", Desc: "email address (default: me)"},
|
||||
@@ -385,12 +385,7 @@ var MailWatch = common.Shortcut{
|
||||
}
|
||||
}
|
||||
|
||||
switch outFormat {
|
||||
case "json", "":
|
||||
output.PrintNdjson(out, output.Envelope{OK: true, Identity: string(runtime.As()), Data: outputData})
|
||||
case "data":
|
||||
output.PrintNdjson(out, outputData)
|
||||
}
|
||||
output.PrintNdjson(out, watchOutputValue(outFormat, string(runtime.As()), outputData))
|
||||
}
|
||||
|
||||
rawHandler := func(ctx context.Context, event *larkevent.EventReq) error {
|
||||
@@ -687,6 +682,17 @@ func minimalWatchMessage(message map[string]interface{}) map[string]interface{}
|
||||
return out
|
||||
}
|
||||
|
||||
// watchOutputValue selects the per-event value that +watch prints as NDJSON:
|
||||
// "data" emits the bare payload; every other format (json — the default) wraps
|
||||
// it in an ok/identity/data envelope. Extracted from Execute so the default
|
||||
// envelope behavior is unit-testable without a live WebSocket.
|
||||
func watchOutputValue(outFormat, identity string, outputData interface{}) interface{} {
|
||||
if outFormat == "data" {
|
||||
return outputData
|
||||
}
|
||||
return output.Envelope{OK: true, Identity: identity, Data: outputData}
|
||||
}
|
||||
|
||||
func watchFetchFailureValue(messageID, fetchFormat string, err error, eventBody map[string]interface{}) map[string]interface{} {
|
||||
payload := map[string]interface{}{
|
||||
"ok": false,
|
||||
|
||||
@@ -885,3 +885,59 @@ func dryRunAPIsForMailWatchTest(t *testing.T, dry *common.DryRunAPI) []struct {
|
||||
}
|
||||
return payload.API
|
||||
}
|
||||
|
||||
// TestWatchOutputValueDefaultIsJSONEnvelope verifies +watch's default format
|
||||
// (json) wraps each event in an ok/identity/data envelope, while --format data
|
||||
// emits the bare payload. Covers the default-format behavior without a live
|
||||
// WebSocket (addresses the coderabbitai review ask).
|
||||
func TestWatchOutputValueDefaultIsJSONEnvelope(t *testing.T) {
|
||||
payload := map[string]interface{}{"message": map[string]interface{}{"message_id": "m1"}}
|
||||
|
||||
// default (json) → ok/identity/data envelope
|
||||
b, err := json.Marshal(watchOutputValue("json", "user", payload))
|
||||
if err != nil {
|
||||
t.Fatalf("marshal json value: %v", err)
|
||||
}
|
||||
var env map[string]interface{}
|
||||
if err := json.Unmarshal(b, &env); err != nil {
|
||||
t.Fatalf("unmarshal json value: %v", err)
|
||||
}
|
||||
if env["ok"] != true {
|
||||
t.Fatalf("default json must have ok:true, got %s", b)
|
||||
}
|
||||
if env["identity"] != "user" {
|
||||
t.Fatalf("default json must carry identity, got %s", b)
|
||||
}
|
||||
if _, ok := env["data"]; !ok {
|
||||
t.Fatalf("default json must have data field, got %s", b)
|
||||
}
|
||||
|
||||
// empty format (safety) also defaults to the json envelope
|
||||
b3, err := json.Marshal(watchOutputValue("", "bot", payload))
|
||||
if err != nil {
|
||||
t.Fatalf("marshal empty-format value: %v", err)
|
||||
}
|
||||
var env3 map[string]interface{}
|
||||
if err := json.Unmarshal(b3, &env3); err != nil {
|
||||
t.Fatalf("unmarshal empty-format value: %v", err)
|
||||
}
|
||||
if env3["ok"] != true {
|
||||
t.Fatalf("empty format should default to json envelope, got %s", b3)
|
||||
}
|
||||
|
||||
// --format data → bare payload, no envelope
|
||||
b2, err := json.Marshal(watchOutputValue("data", "user", payload))
|
||||
if err != nil {
|
||||
t.Fatalf("marshal data value: %v", err)
|
||||
}
|
||||
var bare map[string]interface{}
|
||||
if err := json.Unmarshal(b2, &bare); err != nil {
|
||||
t.Fatalf("unmarshal data value: %v", err)
|
||||
}
|
||||
if _, hasOK := bare["ok"]; hasOK {
|
||||
t.Fatalf("--format data must be bare (no envelope), got %s", b2)
|
||||
}
|
||||
if _, hasMsg := bare["message"]; !hasMsg {
|
||||
t.Fatalf("--format data payload should carry message, got %s", b2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ lark-cli mail +triage --page-size 10
|
||||
|------|------|------|
|
||||
| `--filter <json>` | — | 筛选条件(见下方字段说明) |
|
||||
| `--query <text>` | — | 全文搜索关键词 |
|
||||
| `--format <mode>` | `table` | `table` / `json` / `data`(`json` 和 `data` 均输出含分页信息的对象) |
|
||||
| `--format <mode>` | `json` | `json`(默认,输出含分页信息的对象)/ `data`(同 `json`)/ `table`(人类可读表格) |
|
||||
| `--max <n>` | `20` | 最大返回条数(1-400),内部自动分页拉取 |
|
||||
| `--page-size <n>` | — | `--max` 的别名,两者含义相同;同时指定时 `--page-size` 优先 |
|
||||
| `--page-token <token>` | — | 上一次响应返回的分页令牌,传入后从该位置继续拉取。令牌带 `search:` 或 `list:` 前缀,标识来源路径,不可混用 |
|
||||
|
||||
@@ -47,7 +47,7 @@ lark-cli mail +watch --print-output-schema
|
||||
|------|------|------|
|
||||
| `--mailbox <id>` | `me` | 订阅目标邮箱 |
|
||||
| `--msg-format <mode>` | `metadata` | 输出模式:`metadata` / `minimal` / `plain_text_full` / `full` / `event` |
|
||||
| `--format <mode>` | `data` | 输出样式:`json`(带 ok/data 信封的 NDJSON 流)/ `data`(裸 NDJSON 流) |
|
||||
| `--format <mode>` | `json` | 输出样式:`json`(默认,带 ok/data 信封的 NDJSON 流)/ `data`(裸 NDJSON 流) |
|
||||
| `--folder-ids <json-array>` | — | 文件夹 ID 过滤,如 `["INBOX","SENT"]` |
|
||||
| `--folders <json-array>` | — | 文件夹名称过滤(与 `--folder-ids` 取并集) |
|
||||
| `--label-ids <json-array>` | — | 标签 ID 过滤,如 `["FLAGGED","IMPORTANT"]` |
|
||||
|
||||
Reference in New Issue
Block a user