44 "bytes"
55 "encoding/json"
66 "errors"
7+ "fmt"
78 "io"
89 "net/http"
910 "os"
@@ -17,13 +18,14 @@ import (
1718// videoListSpec mirrors the hand-written video list command as a Spec,
1819// proving the generic builder produces identical behavior.
1920var videoListSpec = & command.Spec {
20- Group : "video" ,
21- Name : "list" ,
22- Summary : "List videos" ,
23- Endpoint : "/v3/videos" ,
24- Method : "GET" ,
25- // TokenField/DataField for pagination (used by paginator, not tested here)
21+ Group : "video" ,
22+ Name : "list" ,
23+ Summary : "List videos" ,
24+ Endpoint : "/v3/videos" ,
25+ Method : "GET" ,
26+ Paginated : true ,
2627 TokenField : "next_token" ,
28+ TokenParam : "token" ,
2729 DataField : "data" ,
2830 Flags : []command.FlagSpec {
2931 {Name : "limit" , Type : "int" , Source : "query" , JSONName : "limit" },
@@ -92,6 +94,174 @@ func TestGenBuilder_VideoList_Flags(t *testing.T) {
9294 }
9395}
9496
97+ func TestGenBuilder_VideoList_AllPages (t * testing.T ) {
98+ var calls int
99+ srv := setupTestServer (t , map [string ]testHandler {
100+ "GET /v3/videos" : {
101+ StatusCode : 200 ,
102+ ValidateRequest : func (t * testing.T , r * http.Request ) {
103+ t .Helper ()
104+ calls ++
105+ switch calls {
106+ case 1 :
107+ if got := r .URL .Query ().Get ("token" ); got != "" {
108+ t .Fatalf ("first page token = %q, want empty" , got )
109+ }
110+ case 2 :
111+ if got := r .URL .Query ().Get ("token" ); got != "cursor_2" {
112+ t .Fatalf ("second page token = %q, want %q" , got , "cursor_2" )
113+ }
114+ default :
115+ t .Fatalf ("unexpected request count %d" , calls )
116+ }
117+ },
118+ Body : `{"data":[{"id":"v1"},{"id":"v2"}],"next_token":"cursor_2"}` ,
119+ },
120+ })
121+ defer srv .Close ()
122+
123+ originalHandler := srv .Config .Handler
124+ srv .Config .Handler = http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
125+ if r .URL .Query ().Get ("token" ) == "cursor_2" {
126+ w .WriteHeader (http .StatusOK )
127+ _ , _ = w .Write ([]byte (`{"data":[{"id":"v3"}],"next_token":null}` ))
128+ return
129+ }
130+ originalHandler .ServeHTTP (w , r )
131+ })
132+
133+ res := runGenCommand (t , srv .URL , "test-key" , videoListSpec , "list" , "--all" )
134+
135+ if res .ExitCode != 0 {
136+ t .Fatalf ("ExitCode = %d, want 0\n stderr: %s" , res .ExitCode , res .Stderr )
137+ }
138+ var parsed []map [string ]any
139+ if err := json .Unmarshal ([]byte (res .Stdout ), & parsed ); err != nil {
140+ t .Fatalf ("stdout is not valid JSON array: %v\n stdout: %s" , err , res .Stdout )
141+ }
142+ if len (parsed ) != 3 {
143+ t .Fatalf ("len(parsed) = %d, want 3" , len (parsed ))
144+ }
145+ }
146+
147+ func TestGenBuilder_VideoList_AllPages_SinglePage (t * testing.T ) {
148+ srv := setupTestServer (t , map [string ]testHandler {
149+ "GET /v3/videos" : {
150+ StatusCode : 200 ,
151+ Body : `{"data":[{"id":"v1"}],"next_token":null}` ,
152+ },
153+ })
154+ defer srv .Close ()
155+
156+ res := runGenCommand (t , srv .URL , "test-key" , videoListSpec , "list" , "--all" )
157+
158+ if res .ExitCode != 0 {
159+ t .Fatalf ("ExitCode = %d, want 0\n stderr: %s" , res .ExitCode , res .Stderr )
160+ }
161+ var parsed []map [string ]any
162+ if err := json .Unmarshal ([]byte (res .Stdout ), & parsed ); err != nil {
163+ t .Fatalf ("stdout is not valid JSON array: %v\n stdout: %s" , err , res .Stdout )
164+ }
165+ if len (parsed ) != 1 {
166+ t .Fatalf ("len(parsed) = %d, want 1" , len (parsed ))
167+ }
168+ }
169+
170+ func TestGenBuilder_VideoList_AllAndTokenConflict (t * testing.T ) {
171+ srv := setupTestServer (t , map [string ]testHandler {})
172+ defer srv .Close ()
173+
174+ res := runGenCommand (t , srv .URL , "test-key" , videoListSpec , "list" , "--all" , "--token" , "cursor_abc" )
175+
176+ if res .ExitCode != 2 {
177+ t .Fatalf ("ExitCode = %d, want 2\n stderr: %s" , res .ExitCode , res .Stderr )
178+ }
179+ if ! strings .Contains (res .Stderr , "--all and --token are mutually exclusive" ) {
180+ t .Fatalf ("stderr = %s, want conflict message" , res .Stderr )
181+ }
182+ }
183+
184+ func TestGenBuilder_VideoList_NoAllFlag_NonPaginated (t * testing.T ) {
185+ nonPaginated := & command.Spec {
186+ Group : "video" ,
187+ Name : "get" ,
188+ Summary : "Get video" ,
189+ Endpoint : "/v3/videos/{video_id}" ,
190+ Method : "GET" ,
191+ Args : []command.ArgSpec {
192+ {Name : "video-id" , Param : "video_id" },
193+ },
194+ Examples : []string {"heygen video get <video-id>" },
195+ }
196+
197+ srv := setupTestServer (t , map [string ]testHandler {})
198+ defer srv .Close ()
199+
200+ res := runGenCommand (t , srv .URL , "test-key" , nonPaginated , "get" , "vid_123" , "--all" )
201+
202+ if res .ExitCode != 2 {
203+ t .Fatalf ("ExitCode = %d, want 2\n stderr: %s" , res .ExitCode , res .Stderr )
204+ }
205+ if ! strings .Contains (res .Stderr , "unknown flag: --all" ) {
206+ t .Fatalf ("stderr = %s, want unknown flag error" , res .Stderr )
207+ }
208+ }
209+
210+ func TestGenBuilder_VideoList_AllPages_Truncated (t * testing.T ) {
211+ var calls int
212+ srv := setupTestServer (t , map [string ]testHandler {
213+ "GET /v3/videos" : {
214+ StatusCode : 200 ,
215+ ValidateRequest : func (t * testing.T , r * http.Request ) {
216+ t .Helper ()
217+ calls ++
218+ },
219+ Body : truncatedPageBody (0 , 2500 , "cursor_1" ),
220+ },
221+ })
222+ defer srv .Close ()
223+
224+ originalHandler := srv .Config .Handler
225+ srv .Config .Handler = http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
226+ token := r .URL .Query ().Get ("token" )
227+ switch token {
228+ case "" :
229+ originalHandler .ServeHTTP (w , r )
230+ case "cursor_1" :
231+ calls ++
232+ w .WriteHeader (http .StatusOK )
233+ _ , _ = w .Write ([]byte (truncatedPageBody (2500 , 2500 , "cursor_2" )))
234+ case "cursor_2" :
235+ calls ++
236+ w .WriteHeader (http .StatusOK )
237+ _ , _ = w .Write ([]byte (truncatedPageBody (5000 , 2500 , "cursor_3" )))
238+ case "cursor_3" :
239+ calls ++
240+ w .WriteHeader (http .StatusOK )
241+ _ , _ = w .Write ([]byte (truncatedPageBody (7500 , 2500 , "cursor_4" )))
242+ default :
243+ t .Fatalf ("unexpected token %q" , token )
244+ }
245+ })
246+
247+ res := runGenCommand (t , srv .URL , "test-key" , videoListSpec , "list" , "--all" )
248+
249+ if res .ExitCode != 1 {
250+ t .Fatalf ("ExitCode = %d, want 1\n stderr: %s" , res .ExitCode , res .Stderr )
251+ }
252+ if ! strings .Contains (res .Stderr , "Warning: pagination stopped at 10000 items" ) {
253+ t .Fatalf ("stderr = %s, want truncation warning" , res .Stderr )
254+ }
255+
256+ var parsed []map [string ]any
257+ if err := json .Unmarshal ([]byte (res .Stdout ), & parsed ); err != nil {
258+ t .Fatalf ("stdout is not valid JSON array: %v\n stdout: %s" , err , res .Stdout )
259+ }
260+ if len (parsed ) != 10000 {
261+ t .Fatalf ("len(parsed) = %d, want 10000" , len (parsed ))
262+ }
263+ }
264+
95265func TestGenBuilder_PostWithBodyFlags (t * testing.T ) {
96266 var gotBody map [string ]any
97267
@@ -436,3 +606,20 @@ func runGeneratedRootCommand(t *testing.T, serverURL, apiKey string, groups map[
436606 ExitCode : exitCode ,
437607 }
438608}
609+
610+ func truncatedPageBody (start , count int , nextToken string ) string {
611+ items := make ([]map [string ]any , 0 , count )
612+ for i := 0 ; i < count ; i ++ {
613+ items = append (items , map [string ]any {"id" : fmt .Sprintf ("v%d" , start + i )})
614+ }
615+ body := map [string ]any {
616+ "data" : items ,
617+ }
618+ if nextToken == "" {
619+ body ["next_token" ] = nil
620+ } else {
621+ body ["next_token" ] = nextToken
622+ }
623+ raw , _ := json .Marshal (body )
624+ return string (raw )
625+ }
0 commit comments