Skip to content

Commit 6f3e956

Browse files
committed
feat: surface search API notices
sa: safe doc: none cfg: none test: unit test
1 parent ae35b35 commit 6f3e956

20 files changed

Lines changed: 335 additions & 17 deletions

shortcuts/contact/contact_search_user.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ type searchUserAPIData struct {
8585
Items []searchUserAPIItem `json:"items"`
8686
HasMore bool `json:"has_more"`
8787
PageToken string `json:"page_token"`
88+
Notice string `json:"notice"`
8889
}
8990

9091
type searchUserAPIItem struct {
@@ -126,6 +127,7 @@ type searchUser struct {
126127
type searchUserResponse struct {
127128
Users []searchUser `json:"users"`
128129
HasMore bool `json:"has_more"`
130+
Notice string `json:"notice,omitempty"`
129131
}
130132

131133
var ContactSearchUser = common.Shortcut{
@@ -222,7 +224,7 @@ func executeSearchUserSingle(ctx context.Context, runtime *common.RuntimeContext
222224
}
223225

224226
users, hasMore := projectUsers(respData, runtime.Str("lang"), runtime.Config.Brand)
225-
out := searchUserResponse{Users: users, HasMore: hasMore}
227+
out := searchUserResponse{Users: users, HasMore: hasMore, Notice: respData.Notice}
226228

227229
runtime.OutFormat(out, &output.Meta{Count: len(users)}, func(w io.Writer) {
228230
if len(users) == 0 {

shortcuts/contact/contact_search_user_fanout.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ type fanoutResult struct {
4545
Query string
4646
Users []searchUser
4747
HasMore bool
48+
Notice string
4849
ErrMsg string // empty = success
4950
Err error // original failure, kept for typed all-failed propagation
5051
}
@@ -94,7 +95,7 @@ func runOneQuery(ctx context.Context, runtime *common.RuntimeContext, index int,
9495
}
9596

9697
users, hasMore := projectUsers(respData, runtime.Str("lang"), runtime.Config.Brand)
97-
return fanoutResult{Index: index, Query: query, Users: users, HasMore: hasMore}
98+
return fanoutResult{Index: index, Query: query, Users: users, HasMore: hasMore, Notice: respData.Notice}
9899
}
99100

100101
func fanoutErrorResult(index int, query string, err error) fanoutResult {
@@ -113,11 +114,13 @@ type querySummary struct {
113114
Query string `json:"query"`
114115
Error string `json:"error,omitempty"`
115116
HasMore bool `json:"has_more"`
117+
Notice string `json:"notice,omitempty"`
116118
}
117119

118120
type fanoutResponse struct {
119121
Users []fanoutUser `json:"users"`
120122
Queries []querySummary `json:"queries"`
123+
Notice string `json:"notice,omitempty"`
121124
}
122125

123126
// buildFanoutResponse walks results by Index (input order), flattens users[]
@@ -142,6 +145,7 @@ func buildFanoutResponse(queries []string, results []fanoutResult) (*fanoutRespo
142145
Query: queries[i],
143146
Error: r.ErrMsg,
144147
HasMore: r.HasMore,
148+
Notice: r.Notice,
145149
})
146150
if r.ErrMsg != "" {
147151
failed++
@@ -152,6 +156,9 @@ func buildFanoutResponse(queries []string, results []fanoutResult) (*fanoutRespo
152156
}
153157
continue
154158
}
159+
if out.Notice == "" {
160+
out.Notice = r.Notice
161+
}
155162
for _, u := range r.Users {
156163
out.Users = append(out.Users, fanoutUser{searchUser: u, MatchedQuery: queries[i]})
157164
}

shortcuts/contact/contact_search_user_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,7 @@ func searchUserStub() *httpmock.Stub {
569569
Body: map[string]interface{}{
570570
"code": 0, "msg": "ok",
571571
"data": map[string]interface{}{
572+
"notice": "The query is too long and has been truncated to the first 50 characters for search.",
572573
"items": []interface{}{
573574
map[string]interface{}{
574575
"id": "ou_a",
@@ -631,6 +632,9 @@ func TestSearchUser_Integration_JSONStructuredFields(t *testing.T) {
631632
if !ok {
632633
t.Fatalf("envelope.data: expected object, got %v\nraw=%s", got["data"], stdout.String())
633634
}
635+
if data["notice"] != "The query is too long and has been truncated to the first 50 characters for search." {
636+
t.Fatalf("data.notice = %v", data["notice"])
637+
}
634638
users, _ := data["users"].([]interface{})
635639
if len(users) != 1 {
636640
t.Fatalf("users: expected 1, got %d (output=%s)", len(users), stdout.String())
@@ -1406,6 +1410,7 @@ func TestFanout_PartialFailure_ExitZero(t *testing.T) {
14061410
BodyFilter: func(b []byte) bool { return strings.Contains(string(b), `"alice"`) },
14071411
Body: map[string]interface{}{"code": 0, "msg": "ok",
14081412
"data": map[string]interface{}{
1413+
"notice": "The query is too long and has been truncated to the first 50 characters for search.",
14091414
"items": []interface{}{map[string]interface{}{"id": "ou_a"}},
14101415
"has_more": false,
14111416
}},
@@ -1432,10 +1437,17 @@ func TestFanout_PartialFailure_ExitZero(t *testing.T) {
14321437
if len(users) != 1 {
14331438
t.Errorf("users: expected 1 (alice), got %d; stdout=%s", len(users), stdout.String())
14341439
}
1440+
if data["notice"] != "The query is too long and has been truncated to the first 50 characters for search." {
1441+
t.Fatalf("data.notice = %v", data["notice"])
1442+
}
14351443
queries := data["queries"].([]interface{})
14361444
if len(queries) != 2 {
14371445
t.Fatalf("queries: expected 2, got %d", len(queries))
14381446
}
1447+
q0 := queries[0].(map[string]interface{})
1448+
if q0["notice"] != "The query is too long and has been truncated to the first 50 characters for search." {
1449+
t.Fatalf("queries[0].notice = %v", q0["notice"])
1450+
}
14391451
q1 := queries[1].(map[string]interface{})
14401452
if !strings.HasPrefix(q1["error"].(string), "HTTP 500") {
14411453
t.Errorf("queries[1].error: got %q", q1["error"])

shortcuts/doc/docs_search.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ var DocsSearch = common.Shortcut{
7474
"page_token": data["page_token"],
7575
"results": normalizedItems,
7676
}
77+
if notice, _ := data["notice"].(string); notice != "" {
78+
resultData["notice"] = notice
79+
}
7780

7881
runtime.OutFormat(resultData, &output.Meta{Count: len(normalizedItems)}, func(w io.Writer) {
7982
if len(normalizedItems) == 0 {

shortcuts/doc/docs_search_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,46 @@ import (
77
"encoding/json"
88
"strings"
99
"testing"
10+
11+
"github.com/larksuite/cli/internal/cmdutil"
12+
"github.com/larksuite/cli/internal/httpmock"
1013
)
1114

15+
func TestDocsSearchExecutePassesThroughNotice(t *testing.T) {
16+
const notice = "The query is too long and has been truncated to the first 50 characters for search."
17+
18+
f, stdout, _, reg := cmdutil.TestFactory(t, docsTestConfigWithAppID("docs-search-notice"))
19+
reg.Register(&httpmock.Stub{
20+
Method: "POST",
21+
URL: "/open-apis/search/v2/doc_wiki/search",
22+
Body: map[string]interface{}{
23+
"code": 0,
24+
"msg": "ok",
25+
"data": map[string]interface{}{
26+
"notice": notice,
27+
"res_units": []interface{}{},
28+
"total": 0,
29+
"has_more": false,
30+
"page_token": "",
31+
},
32+
},
33+
})
34+
35+
if err := mountAndRunDocs(t, DocsSearch, []string{"+search", "--query", "incident", "--format", "json", "--as", "user"}, f, stdout); err != nil {
36+
t.Fatalf("DocsSearch.Execute() error = %v", err)
37+
}
38+
reg.Verify(t)
39+
40+
var env map[string]interface{}
41+
if err := json.Unmarshal(stdout.Bytes(), &env); err != nil {
42+
t.Fatalf("json.Unmarshal(stdout) error = %v\nstdout=%s", err, stdout.String())
43+
}
44+
data, _ := env["data"].(map[string]interface{})
45+
if got, _ := data["notice"].(string); got != notice {
46+
t.Fatalf("data.notice = %q, want %q; data=%#v", got, notice, data)
47+
}
48+
}
49+
1250
func TestAddIsoTimeFieldsSupportsJSONNumber(t *testing.T) {
1351
t.Parallel()
1452

shortcuts/drive/drive_search.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ var DriveSearch = common.Shortcut{
147147
"page_token": data["page_token"],
148148
"results": normalizedItems,
149149
}
150+
if notice, _ := data["notice"].(string); notice != "" {
151+
resultData["notice"] = notice
152+
}
150153

151154
runtime.OutFormat(resultData, &output.Meta{Count: len(normalizedItems)}, func(w io.Writer) {
152155
renderDriveSearchTable(w, data, normalizedItems)

shortcuts/drive/drive_search_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,47 @@ import (
1414
"time"
1515

1616
"github.com/larksuite/cli/errs"
17+
"github.com/larksuite/cli/internal/cmdutil"
1718
"github.com/larksuite/cli/internal/errclass"
19+
"github.com/larksuite/cli/internal/httpmock"
1820
"github.com/larksuite/cli/internal/output"
1921
)
2022

23+
func TestDriveSearchExecutePassesThroughNotice(t *testing.T) {
24+
const notice = "The query is too long and has been truncated to the first 50 characters for search."
25+
26+
f, stdout, _, reg := cmdutil.TestFactory(t, driveTestConfig())
27+
reg.Register(&httpmock.Stub{
28+
Method: "POST",
29+
URL: "/open-apis/search/v2/doc_wiki/search",
30+
Body: map[string]interface{}{
31+
"code": 0,
32+
"msg": "ok",
33+
"data": map[string]interface{}{
34+
"notice": notice,
35+
"res_units": []interface{}{},
36+
"total": 0,
37+
"has_more": false,
38+
"page_token": "",
39+
},
40+
},
41+
})
42+
43+
if err := mountAndRunDrive(t, DriveSearch, []string{"+search", "--query", "incident", "--format", "json", "--as", "user"}, f, stdout); err != nil {
44+
t.Fatalf("DriveSearch.Execute() error = %v", err)
45+
}
46+
reg.Verify(t)
47+
48+
var env map[string]interface{}
49+
if err := json.Unmarshal(stdout.Bytes(), &env); err != nil {
50+
t.Fatalf("json.Unmarshal(stdout) error = %v\nstdout=%s", err, stdout.String())
51+
}
52+
data, _ := env["data"].(map[string]interface{})
53+
if got, _ := data["notice"].(string); got != notice {
54+
t.Fatalf("data.notice = %q, want %q; data=%#v", got, notice, data)
55+
}
56+
}
57+
2158
// TestClampOpenedTimeWindow covers the 3-month / 1-year boundary logic that
2259
// narrows --opened-since / --opened-until and generates the multi-slice notice.
2360
func TestClampOpenedTimeWindow(t *testing.T) {

shortcuts/im/im_chat_search.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ var ImChatSearch = common.Shortcut{
142142
"has_more": hasMore,
143143
"page_token": pageToken,
144144
}
145+
if notice, _ := resData["notice"].(string); notice != "" {
146+
outData["notice"] = notice
147+
}
145148
if mfOut.Meta.Applied != "" {
146149
outData["filter"] = MuteFilterMetaToMap(mfOut.Meta)
147150
}

shortcuts/im/im_messages_search.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ var ImMessagesSearch = common.Shortcut{
9191
return err
9292
}
9393

94-
rawItems, hasMore, nextPageToken, truncatedByLimit, pageLimit, err := searchMessages(runtime, req)
94+
rawItems, hasMore, nextPageToken, truncatedByLimit, pageLimit, notice, err := searchMessages(runtime, req)
9595
if err != nil {
9696
return err
9797
}
@@ -103,6 +103,9 @@ var ImMessagesSearch = common.Shortcut{
103103
"has_more": hasMore,
104104
"page_token": nextPageToken,
105105
}
106+
if notice != "" {
107+
outData["notice"] = notice
108+
}
106109
runtime.OutFormat(outData, nil, func(w io.Writer) {
107110
fmt.Fprintln(w, "No matching messages found.")
108111
})
@@ -131,6 +134,9 @@ var ImMessagesSearch = common.Shortcut{
131134
"page_token": nextPageToken,
132135
"note": "failed to fetch message details, returning ID list only",
133136
}
137+
if notice != "" {
138+
outData["notice"] = notice
139+
}
134140
runtime.OutFormat(outData, nil, func(w io.Writer) {
135141
fmt.Fprintf(w, "Found %d messages (failed to fetch details):\n", len(messageIds))
136142
for _, id := range messageIds {
@@ -206,6 +212,9 @@ var ImMessagesSearch = common.Shortcut{
206212
"has_more": hasMore,
207213
"page_token": nextPageToken,
208214
}
215+
if notice != "" {
216+
outData["notice"] = notice
217+
}
209218
runtime.OutFormat(outData, nil, func(w io.Writer) {
210219
if len(enriched) == 0 {
211220
fmt.Fprintln(w, "No matching messages found.")
@@ -392,7 +401,7 @@ func messagesSearchPaginationConfig(runtime *common.RuntimeContext) (autoPaginat
392401
return autoPaginate, pageLimit
393402
}
394403

395-
func searchMessages(runtime *common.RuntimeContext, req *messagesSearchRequest) ([]interface{}, bool, string, bool, int, error) {
404+
func searchMessages(runtime *common.RuntimeContext, req *messagesSearchRequest) ([]interface{}, bool, string, bool, int, string, error) {
396405
autoPaginate, pageLimit := messagesSearchPaginationConfig(runtime)
397406
pageToken := ""
398407
if tokens := req.params["page_token"]; len(tokens) > 0 {
@@ -410,6 +419,7 @@ func searchMessages(runtime *common.RuntimeContext, req *messagesSearchRequest)
410419
lastPageToken string
411420
truncatedByLimit bool
412421
pageCount int
422+
notice string
413423
)
414424

415425
for {
@@ -423,9 +433,12 @@ func searchMessages(runtime *common.RuntimeContext, req *messagesSearchRequest)
423433

424434
searchData, err := runtime.DoAPIJSONTyped(http.MethodPost, "/open-apis/im/v1/messages/search", params, req.body)
425435
if err != nil {
426-
return nil, false, "", false, pageLimit, err
436+
return nil, false, "", false, pageLimit, "", err
427437
}
428438

439+
if notice == "" {
440+
notice, _ = searchData["notice"].(string)
441+
}
429442
items, _ := searchData["items"].([]interface{})
430443
allItems = append(allItems, items...)
431444
lastHasMore, lastPageToken = common.PaginationMeta(searchData)
@@ -441,7 +454,7 @@ func searchMessages(runtime *common.RuntimeContext, req *messagesSearchRequest)
441454
pageToken = lastPageToken
442455
}
443456

444-
return allItems, lastHasMore, lastPageToken, truncatedByLimit, pageLimit, nil
457+
return allItems, lastHasMore, lastPageToken, truncatedByLimit, pageLimit, notice, nil
445458
}
446459

447460
func batchMGetMessages(runtime *common.RuntimeContext, messageIds []string) ([]interface{}, error) {

0 commit comments

Comments
 (0)