Skip to content

Commit bd71cd1

Browse files
stainless-app[bot]batuhan
authored andcommitted
feat: add --max-items flag for paginated/streaming endpoints
1 parent 94bf74c commit bd71cd1

8 files changed

Lines changed: 66 additions & 46 deletions

File tree

internal/mocktest/mocktest.go

Lines changed: 7 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"testing"
1616
"time"
1717

18+
"github.com/stretchr/testify/assert"
1819
"github.com/stretchr/testify/require"
1920
)
2021

@@ -84,46 +85,12 @@ func TestRunMockTestWithPipeAndFlags(t *testing.T, pipeData []byte, args ...stri
8485

8586
t.Logf("Testing command: go run ./cmd/beeper-desktop-cli %s", strings.Join(args[2:], " "))
8687

87-
cliCmd := exec.Command("go", args...)
88-
cliCmd.Stdin = bytes.NewReader(pipeData)
89-
90-
// Pipe the CLI tool's output into `head` so it doesn't hang when simulating
91-
// paginated or streamed endpoints. 100 lines of output should be enough to
92-
// test that the API endpoint worked, or report back a meaningful amount of
93-
// data if something went wrong.
94-
headCmd := exec.Command("head", "-n", "100")
95-
pipe, err := cliCmd.StdoutPipe()
96-
require.NoError(t, err, "Failed to create pipe for CLI command")
97-
headCmd.Stdin = pipe
98-
99-
// Capture `head` output and CLI command stderr outputs:
100-
var output strings.Builder
101-
headCmd.Stdout = &output
102-
headCmd.Stderr = &output
103-
cliCmd.Stderr = &output
104-
105-
// First start `head`, so it's ready for data to come in:
106-
err = headCmd.Start()
107-
require.NoError(t, err, "Failed to start `head` command")
108-
109-
// Next start the CLI command so it can pipe data to `head` without
110-
// buffering any data in advance:
111-
err = cliCmd.Start()
112-
require.NoError(t, err, "Failed to start CLI command")
113-
114-
// Ensure that the stdout pipe is closed as soon as `head` exits, to let the
115-
// CLI tool know that no more output is needed and it can stop streaming
116-
// test data for streaming/paginated endpoints. This needs to happen before
117-
// calling `cliCmd.Wait()`, otherwise there will be a deadlock.
118-
err = headCmd.Wait()
119-
pipe.Close()
120-
require.NoError(t, err, "`head` command finished with an error")
121-
122-
// Finally, wait for the CLI tool to finish up:
123-
err = cliCmd.Wait()
124-
require.NoError(t, err, "CLI command failed\n%s", output.String())
125-
126-
t.Logf("Test passed successfully\nOutput:\n%s", output.String())
88+
cmd := exec.Command("go", args...)
89+
cmd.Stdin = bytes.NewReader(pipeData)
90+
output, err := cmd.CombinedOutput()
91+
assert.NoError(t, err, "Test failed\nError: %v\nOutput: %s", err, output)
92+
93+
t.Logf("Test passed successfully\nOutput:\n%s", string(output))
12794
}
12895

12996
func TestFile(t *testing.T, contents string) string {

pkg/cmd/accountcontact.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ var accountsContactsList = cli.Command{
4646
Usage: "Optional search query for blended contact lookup.",
4747
QueryPath: "query",
4848
},
49+
&requestflag.Flag[int64]{
50+
Name: "max-items",
51+
Usage: "The maximum number of items to return (use -1 for unlimited).",
52+
},
4953
},
5054
Action: handleAccountsContactsList,
5155
HideHelpCommand: true,
@@ -119,7 +123,11 @@ func handleAccountsContactsList(ctx context.Context, cmd *cli.Command) error {
119123
params,
120124
options...,
121125
)
122-
return ShowJSONIterator(os.Stdout, "accounts:contacts list", iter, format, transform)
126+
maxItems := int64(-1)
127+
if cmd.IsSet("max-items") {
128+
maxItems = cmd.Value("max-items").(int64)
129+
}
130+
return ShowJSONIterator(os.Stdout, "accounts:contacts list", iter, format, transform, maxItems)
123131
}
124132
}
125133

pkg/cmd/accountcontact_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ func TestAccountsContactsList(t *testing.T) {
1313
mocktest.TestRunMockTestWithFlags(
1414
t, "accounts:contacts", "list",
1515
"--access-token", "string",
16+
"--max-items", "10",
1617
"--account-id", "accountID",
1718
"--cursor", "1725489123456|c29tZUltc2dQYWdl",
1819
"--direction", "before",

pkg/cmd/chat.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ var chatsList = cli.Command{
136136
Usage: "Pagination direction used with 'cursor': 'before' fetches older results, 'after' fetches newer results. Defaults to 'before' when only 'cursor' is provided.",
137137
QueryPath: "direction",
138138
},
139+
&requestflag.Flag[int64]{
140+
Name: "max-items",
141+
Usage: "The maximum number of items to return (use -1 for unlimited).",
142+
},
139143
},
140144
Action: handleChatsList,
141145
HideHelpCommand: true,
@@ -231,6 +235,10 @@ var chatsSearch = cli.Command{
231235
Usage: "Set to true to only retrieve chats that have unread messages",
232236
QueryPath: "unreadOnly",
233237
},
238+
&requestflag.Flag[int64]{
239+
Name: "max-items",
240+
Usage: "The maximum number of items to return (use -1 for unlimited).",
241+
},
234242
},
235243
Action: handleChatsSearch,
236244
HideHelpCommand: true,
@@ -346,7 +354,11 @@ func handleChatsList(ctx context.Context, cmd *cli.Command) error {
346354
return ShowJSON(os.Stdout, "chats list", obj, format, transform)
347355
} else {
348356
iter := client.Chats.ListAutoPaging(ctx, params, options...)
349-
return ShowJSONIterator(os.Stdout, "chats list", iter, format, transform)
357+
maxItems := int64(-1)
358+
if cmd.IsSet("max-items") {
359+
maxItems = cmd.Value("max-items").(int64)
360+
}
361+
return ShowJSONIterator(os.Stdout, "chats list", iter, format, transform, maxItems)
350362
}
351363
}
352364

@@ -416,6 +428,10 @@ func handleChatsSearch(ctx context.Context, cmd *cli.Command) error {
416428
return ShowJSON(os.Stdout, "chats search", obj, format, transform)
417429
} else {
418430
iter := client.Chats.SearchAutoPaging(ctx, params, options...)
419-
return ShowJSONIterator(os.Stdout, "chats search", iter, format, transform)
431+
maxItems := int64(-1)
432+
if cmd.IsSet("max-items") {
433+
maxItems = cmd.Value("max-items").(int64)
434+
}
435+
return ShowJSONIterator(os.Stdout, "chats search", iter, format, transform, maxItems)
420436
}
421437
}

pkg/cmd/chat_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ func TestChatsList(t *testing.T) {
8888
mocktest.TestRunMockTestWithFlags(
8989
t, "chats", "list",
9090
"--access-token", "string",
91+
"--max-items", "10",
9192
"--account-id", "local-whatsapp_ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc",
9293
"--account-id", "local-instagram_ba_eRfQMmnSNy_p7Ih7HL7RduRpKFU",
9394
"--cursor", "1725489123456|c29tZUltc2dQYWdl",
@@ -122,6 +123,7 @@ func TestChatsSearch(t *testing.T) {
122123
mocktest.TestRunMockTestWithFlags(
123124
t, "chats", "search",
124125
"--access-token", "string",
126+
"--max-items", "10",
125127
"--account-id", "local-whatsapp_ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc",
126128
"--account-id", "local-telegram_ba_QFrb5lrLPhO3OT5MFBeTWv0x4BI",
127129
"--cursor", "1725489123456|c29tZUltc2dQYWdl",

pkg/cmd/cmdutil.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ type HasRawJSON interface {
350350

351351
// For an iterator over different value types, display its values to the user in
352352
// different formats.
353-
func ShowJSONIterator[T any](stdout *os.File, title string, iter jsonview.Iterator[T], format string, transform string) error {
353+
func ShowJSONIterator[T any](stdout *os.File, title string, iter jsonview.Iterator[T], format string, transform string, itemsToDisplay int64) error {
354354
if format == "explore" {
355355
return jsonview.ExploreJSONStream(title, iter)
356356
}
@@ -366,6 +366,9 @@ func ShowJSONIterator[T any](stdout *os.File, title string, iter jsonview.Iterat
366366
output := []byte{}
367367
numberOfNewlines := 0
368368
for iter.Next() {
369+
if itemsToDisplay == 0 {
370+
break
371+
}
369372
item := iter.Current()
370373
var obj gjson.Result
371374
if hasRaw, ok := any(item).(HasRawJSON); ok {
@@ -383,6 +386,7 @@ func ShowJSONIterator[T any](stdout *os.File, title string, iter jsonview.Iterat
383386
}
384387

385388
output = append(output, json...)
389+
itemsToDisplay -= 1
386390
numberOfNewlines += countTerminalLines(json, terminalWidth)
387391

388392
// If the output won't fit in the terminal window, stream it to a pager
@@ -409,6 +413,9 @@ func ShowJSONIterator[T any](stdout *os.File, title string, iter jsonview.Iterat
409413
}
410414

411415
for iter.Next() {
416+
if itemsToDisplay == 0 {
417+
break
418+
}
412419
item := iter.Current()
413420
var obj gjson.Result
414421
if hasRaw, ok := any(item).(HasRawJSON); ok {
@@ -423,6 +430,7 @@ func ShowJSONIterator[T any](stdout *os.File, title string, iter jsonview.Iterat
423430
if err := ShowJSON(pager, title, obj, format, transform); err != nil {
424431
return err
425432
}
433+
itemsToDisplay -= 1
426434
}
427435
return iter.Err()
428436
})

pkg/cmd/message.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ var messagesList = cli.Command{
6060
Usage: "Pagination direction used with 'cursor': 'before' fetches older results, 'after' fetches newer results. Defaults to 'before' when only 'cursor' is provided.",
6161
QueryPath: "direction",
6262
},
63+
&requestflag.Flag[int64]{
64+
Name: "max-items",
65+
Usage: "The maximum number of items to return (use -1 for unlimited).",
66+
},
6367
},
6468
Action: handleMessagesList,
6569
HideHelpCommand: true,
@@ -138,6 +142,10 @@ var messagesSearch = cli.Command{
138142
Usage: "Filter by sender: 'me' (messages sent by the authenticated user), 'others' (messages sent by others), or a specific user ID string (user.id).",
139143
QueryPath: "sender",
140144
},
145+
&requestflag.Flag[int64]{
146+
Name: "max-items",
147+
Usage: "The maximum number of items to return (use -1 for unlimited).",
148+
},
141149
},
142150
Action: handleMessagesSearch,
143151
HideHelpCommand: true,
@@ -297,7 +305,11 @@ func handleMessagesList(ctx context.Context, cmd *cli.Command) error {
297305
params,
298306
options...,
299307
)
300-
return ShowJSONIterator(os.Stdout, "messages list", iter, format, transform)
308+
maxItems := int64(-1)
309+
if cmd.IsSet("max-items") {
310+
maxItems = cmd.Value("max-items").(int64)
311+
}
312+
return ShowJSONIterator(os.Stdout, "messages list", iter, format, transform, maxItems)
301313
}
302314
}
303315

@@ -335,7 +347,11 @@ func handleMessagesSearch(ctx context.Context, cmd *cli.Command) error {
335347
return ShowJSON(os.Stdout, "messages search", obj, format, transform)
336348
} else {
337349
iter := client.Messages.SearchAutoPaging(ctx, params, options...)
338-
return ShowJSONIterator(os.Stdout, "messages search", iter, format, transform)
350+
maxItems := int64(-1)
351+
if cmd.IsSet("max-items") {
352+
maxItems = cmd.Value("max-items").(int64)
353+
}
354+
return ShowJSONIterator(os.Stdout, "messages search", iter, format, transform, maxItems)
339355
}
340356
}
341357

pkg/cmd/message_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func TestMessagesList(t *testing.T) {
3737
mocktest.TestRunMockTestWithFlags(
3838
t, "messages", "list",
3939
"--access-token", "string",
40+
"--max-items", "10",
4041
"--chat-id", "!NCdzlIaMjZUmvmvyHU:beeper.com",
4142
"--cursor", "1725489123456|c29tZUltc2dQYWdl",
4243
"--direction", "before",
@@ -49,6 +50,7 @@ func TestMessagesSearch(t *testing.T) {
4950
mocktest.TestRunMockTestWithFlags(
5051
t, "messages", "search",
5152
"--access-token", "string",
53+
"--max-items", "10",
5254
"--account-id", "local-whatsapp_ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc",
5355
"--account-id", "local-instagram_ba_eRfQMmnSNy_p7Ih7HL7RduRpKFU",
5456
"--chat-id", "!NCdzlIaMjZUmvmvyHU:beeper.com",

0 commit comments

Comments
 (0)