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