From a7c59f177e4360c5fe35ebaf20e93180fdab0f61 Mon Sep 17 00:00:00 2001 From: jiahongnan Date: Thu, 23 Jul 2026 19:59:34 +0800 Subject: [PATCH 1/3] fix: omit zero total from chat search sa: none doc: none cfg: none test: unit test --- shortcuts/im/im_chat_search.go | 14 +- shortcuts/im/im_chat_search_total_test.go | 128 ++++++++++++++++++ .../cli_e2e/im/im_chat_search_dryrun_test.go | 47 +++++++ 3 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 shortcuts/im/im_chat_search_total_test.go create mode 100644 tests/cli_e2e/im/im_chat_search_dryrun_test.go diff --git a/shortcuts/im/im_chat_search.go b/shortcuts/im/im_chat_search.go index ef46946ecc..3d08be5fcf 100644 --- a/shortcuts/im/im_chat_search.go +++ b/shortcuts/im/im_chat_search.go @@ -107,8 +107,8 @@ var ImChatSearch = common.Shortcut{ } rawItems, _ := resData["items"].([]interface{}) - totalF, _ := util.ToFloat64(resData["total"]) - total := totalF + total, hasPositiveTotal := util.ToFloat64(resData["total"]) + hasPositiveTotal = hasPositiveTotal && total > 0 hasMore, pageToken := common.PaginationMeta(resData) // Extract MetaData from each item @@ -144,10 +144,12 @@ var ImChatSearch = common.Shortcut{ outData := map[string]interface{}{ "chats": items, - "total": int(total), "has_more": hasMore, "page_token": pageToken, } + if hasPositiveTotal { + outData["total"] = int(total) + } if notice, _ := resData["notice"].(string); notice != "" { outData["notice"] = notice } @@ -198,7 +200,11 @@ var ImChatSearch = common.Shortcut{ } moreHint += ")" } - fmt.Fprintf(w, "\n%d chat(s) found%s\n", int(total), moreHint) + displayedTotal := len(items) + if hasPositiveTotal { + displayedTotal = int(total) + } + fmt.Fprintf(w, "\n%d chat(s) found%s\n", displayedTotal, moreHint) if mfOut.Meta.Hint != "" { fmt.Fprintln(w, mfOut.Meta.Hint) } diff --git a/shortcuts/im/im_chat_search_total_test.go b/shortcuts/im/im_chat_search_total_test.go new file mode 100644 index 0000000000..a2ddcea6dc --- /dev/null +++ b/shortcuts/im/im_chat_search_total_test.go @@ -0,0 +1,128 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package im + +import ( + "bytes" + "context" + "fmt" + "net/http" + "strings" + "testing" +) + +func TestImChatSearchExecuteTotalContract(t *testing.T) { + tests := []struct { + name string + total interface{} + wantTotal bool + wantValue int + }{ + {name: "missing"}, + {name: "invalid", total: "unknown"}, + {name: "zero", total: 0}, + {name: "positive", total: 7, wantTotal: true, wantValue: 7}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data := map[string]interface{}{ + "items": []interface{}{ + map[string]interface{}{ + "meta_data": map[string]interface{}{ + "chat_id": "oc_example", + "name": "Example chat", + }, + }, + }, + "has_more": false, + "page_token": "", + } + if tt.total != nil { + data["total"] = tt.total + } + + runtime := newBotShortcutRuntime(t, shortcutRoundTripFunc(func(req *http.Request) (*http.Response, error) { + if req.URL.Path != "/open-apis/im/v2/chats/search" { + return nil, fmt.Errorf("unexpected request: %s", req.URL.String()) + } + return shortcutJSONResponse(http.StatusOK, map[string]interface{}{ + "code": 0, + "data": data, + }), nil + })) + runtime.Cmd = newChatSearchNoticeTestCommand(t, "example") + runtime.Format = "json" + + if err := ImChatSearch.Execute(context.Background(), runtime); err != nil { + t.Fatalf("ImChatSearch.Execute() error = %v", err) + } + + got := decodeShortcutData(t, runtime) + total, exists := got["total"] + if exists != tt.wantTotal { + t.Fatalf("data total presence = %v, want %v; data=%#v", exists, tt.wantTotal, got) + } + if tt.wantTotal && int(total.(float64)) != tt.wantValue { + t.Fatalf("data.total = %v, want %d", total, tt.wantValue) + } + }) + } +} + +func TestImChatSearchExecutePrettyTotal(t *testing.T) { + tests := []struct { + name string + total interface{} + wantFooter string + }{ + {name: "missing uses displayed count", wantFooter: "1 chat(s) found"}, + {name: "zero uses displayed count", total: 0, wantFooter: "1 chat(s) found"}, + {name: "positive uses server total", total: 7, wantFooter: "7 chat(s) found"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data := map[string]interface{}{ + "items": []interface{}{ + map[string]interface{}{ + "meta_data": map[string]interface{}{ + "chat_id": "oc_example", + "name": "Example chat", + }, + }, + }, + "has_more": false, + "page_token": "", + } + if tt.total != nil { + data["total"] = tt.total + } + + runtime := newBotShortcutRuntime(t, shortcutRoundTripFunc(func(req *http.Request) (*http.Response, error) { + return shortcutJSONResponse(http.StatusOK, map[string]interface{}{ + "code": 0, + "data": data, + }), nil + })) + runtime.Cmd = newChatSearchNoticeTestCommand(t, "example") + runtime.Format = "pretty" + + if err := ImChatSearch.Execute(context.Background(), runtime); err != nil { + t.Fatalf("ImChatSearch.Execute() error = %v", err) + } + + out, ok := runtime.Factory.IOStreams.Out.(*bytes.Buffer) + if !ok { + t.Fatalf("stdout buffer has type %T", runtime.Factory.IOStreams.Out) + } + if !strings.Contains(out.String(), tt.wantFooter) { + t.Fatalf("pretty output missing %q:\n%s", tt.wantFooter, out.String()) + } + if strings.Contains(out.String(), "0 chat(s) found") { + t.Fatalf("pretty output reported a zero count despite displaying a row:\n%s", out.String()) + } + }) + } +} diff --git a/tests/cli_e2e/im/im_chat_search_dryrun_test.go b/tests/cli_e2e/im/im_chat_search_dryrun_test.go new file mode 100644 index 0000000000..bc9432258b --- /dev/null +++ b/tests/cli_e2e/im/im_chat_search_dryrun_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package im + +import ( + "context" + "testing" + "time" + + clie2e "github.com/larksuite/cli/tests/cli_e2e" + "github.com/stretchr/testify/require" +) + +func TestIM_ChatSearchDryRun(t *testing.T) { + t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) + t.Setenv("LARKSUITE_CLI_APP_ID", "app") + t.Setenv("LARKSUITE_CLI_APP_SECRET", "secret") + t.Setenv("LARKSUITE_CLI_BRAND", "feishu") + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + t.Cleanup(cancel) + + result, err := clie2e.RunCmd(ctx, clie2e.Request{ + Args: []string{ + "im", "+chat-search", + "--query", "example-chat", + "--search-types", "private,public_joined", + "--chat-modes", "group", + "--page-size", "25", + "--page-token", "next_page", + "--dry-run", + }, + DefaultAs: "bot", + }) + require.NoError(t, err) + result.AssertExitCode(t, 0) + + require.Equal(t, "POST", clie2e.DryRunGet(result.Stdout, "api.0.method").String(), "stdout:\n%s", result.Stdout) + require.Equal(t, "/open-apis/im/v2/chats/search", clie2e.DryRunGet(result.Stdout, "api.0.url").String(), "stdout:\n%s", result.Stdout) + require.Equal(t, `"example-chat"`, clie2e.DryRunGet(result.Stdout, "api.0.body.query").String(), "stdout:\n%s", result.Stdout) + require.Equal(t, "private", clie2e.DryRunGet(result.Stdout, "api.0.body.filter.search_types.0").String(), "stdout:\n%s", result.Stdout) + require.Equal(t, "public_joined", clie2e.DryRunGet(result.Stdout, "api.0.body.filter.search_types.1").String(), "stdout:\n%s", result.Stdout) + require.Equal(t, "default", clie2e.DryRunGet(result.Stdout, "api.0.body.filter.chat_modes.0").String(), "stdout:\n%s", result.Stdout) + require.Equal(t, int64(25), clie2e.DryRunGet(result.Stdout, "api.0.params.page_size").Int(), "stdout:\n%s", result.Stdout) + require.Equal(t, "next_page", clie2e.DryRunGet(result.Stdout, "api.0.params.page_token").String(), "stdout:\n%s", result.Stdout) +} From c2176ed2fb947f32f80a141c9fbbb97b7ab337b0 Mon Sep 17 00:00:00 2001 From: jiahongnan Date: Thu, 23 Jul 2026 20:11:27 +0800 Subject: [PATCH 2/3] fix: handle fractional chat search totals --- shortcuts/im/im_chat_search.go | 2 +- shortcuts/im/im_chat_search_total_test.go | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/shortcuts/im/im_chat_search.go b/shortcuts/im/im_chat_search.go index 3d08be5fcf..b2cf271500 100644 --- a/shortcuts/im/im_chat_search.go +++ b/shortcuts/im/im_chat_search.go @@ -108,7 +108,7 @@ var ImChatSearch = common.Shortcut{ rawItems, _ := resData["items"].([]interface{}) total, hasPositiveTotal := util.ToFloat64(resData["total"]) - hasPositiveTotal = hasPositiveTotal && total > 0 + hasPositiveTotal = hasPositiveTotal && int(total) > 0 hasMore, pageToken := common.PaginationMeta(resData) // Extract MetaData from each item diff --git a/shortcuts/im/im_chat_search_total_test.go b/shortcuts/im/im_chat_search_total_test.go index a2ddcea6dc..4d8855b507 100644 --- a/shortcuts/im/im_chat_search_total_test.go +++ b/shortcuts/im/im_chat_search_total_test.go @@ -22,6 +22,7 @@ func TestImChatSearchExecuteTotalContract(t *testing.T) { {name: "missing"}, {name: "invalid", total: "unknown"}, {name: "zero", total: 0}, + {name: "fractional truncates to zero", total: 0.5}, {name: "positive", total: 7, wantTotal: true, wantValue: 7}, } @@ -79,6 +80,7 @@ func TestImChatSearchExecutePrettyTotal(t *testing.T) { }{ {name: "missing uses displayed count", wantFooter: "1 chat(s) found"}, {name: "zero uses displayed count", total: 0, wantFooter: "1 chat(s) found"}, + {name: "fractional uses displayed count", total: 0.5, wantFooter: "1 chat(s) found"}, {name: "positive uses server total", total: 7, wantFooter: "7 chat(s) found"}, } From 59b8851f1ea5c94c7bb79ebc932e37e1004270d8 Mon Sep 17 00:00:00 2001 From: jiahongnan Date: Thu, 23 Jul 2026 20:16:25 +0800 Subject: [PATCH 3/3] fix: preserve chat search pretty total --- shortcuts/im/im_chat_search.go | 6 +-- shortcuts/im/im_chat_search_total_test.go | 59 ----------------------- 2 files changed, 1 insertion(+), 64 deletions(-) diff --git a/shortcuts/im/im_chat_search.go b/shortcuts/im/im_chat_search.go index b2cf271500..c8a94d3964 100644 --- a/shortcuts/im/im_chat_search.go +++ b/shortcuts/im/im_chat_search.go @@ -200,11 +200,7 @@ var ImChatSearch = common.Shortcut{ } moreHint += ")" } - displayedTotal := len(items) - if hasPositiveTotal { - displayedTotal = int(total) - } - fmt.Fprintf(w, "\n%d chat(s) found%s\n", displayedTotal, moreHint) + fmt.Fprintf(w, "\n%d chat(s) found%s\n", int(total), moreHint) if mfOut.Meta.Hint != "" { fmt.Fprintln(w, mfOut.Meta.Hint) } diff --git a/shortcuts/im/im_chat_search_total_test.go b/shortcuts/im/im_chat_search_total_test.go index 4d8855b507..2c822f66dc 100644 --- a/shortcuts/im/im_chat_search_total_test.go +++ b/shortcuts/im/im_chat_search_total_test.go @@ -4,11 +4,9 @@ package im import ( - "bytes" "context" "fmt" "net/http" - "strings" "testing" ) @@ -71,60 +69,3 @@ func TestImChatSearchExecuteTotalContract(t *testing.T) { }) } } - -func TestImChatSearchExecutePrettyTotal(t *testing.T) { - tests := []struct { - name string - total interface{} - wantFooter string - }{ - {name: "missing uses displayed count", wantFooter: "1 chat(s) found"}, - {name: "zero uses displayed count", total: 0, wantFooter: "1 chat(s) found"}, - {name: "fractional uses displayed count", total: 0.5, wantFooter: "1 chat(s) found"}, - {name: "positive uses server total", total: 7, wantFooter: "7 chat(s) found"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - data := map[string]interface{}{ - "items": []interface{}{ - map[string]interface{}{ - "meta_data": map[string]interface{}{ - "chat_id": "oc_example", - "name": "Example chat", - }, - }, - }, - "has_more": false, - "page_token": "", - } - if tt.total != nil { - data["total"] = tt.total - } - - runtime := newBotShortcutRuntime(t, shortcutRoundTripFunc(func(req *http.Request) (*http.Response, error) { - return shortcutJSONResponse(http.StatusOK, map[string]interface{}{ - "code": 0, - "data": data, - }), nil - })) - runtime.Cmd = newChatSearchNoticeTestCommand(t, "example") - runtime.Format = "pretty" - - if err := ImChatSearch.Execute(context.Background(), runtime); err != nil { - t.Fatalf("ImChatSearch.Execute() error = %v", err) - } - - out, ok := runtime.Factory.IOStreams.Out.(*bytes.Buffer) - if !ok { - t.Fatalf("stdout buffer has type %T", runtime.Factory.IOStreams.Out) - } - if !strings.Contains(out.String(), tt.wantFooter) { - t.Fatalf("pretty output missing %q:\n%s", tt.wantFooter, out.String()) - } - if strings.Contains(out.String(), "0 chat(s) found") { - t.Fatalf("pretty output reported a zero count despite displaying a row:\n%s", out.String()) - } - }) - } -}