@@ -152,6 +152,232 @@ func TestRequestAppAccessToken(t *testing.T) {
152152 }
153153}
154154
155+ func TestRequestDeviceVerificationURI (t * testing.T ) {
156+ t .Parallel ()
157+
158+ testCases := []struct {
159+ statusCode int
160+ scopes []string
161+ options * Options
162+ respBody string
163+ expectedErrMsg string
164+ }{
165+ {
166+ http .StatusBadRequest ,
167+ []string {"user:read:email" },
168+ & Options {
169+ ClientID : "invalid-client-id" , // invalid client id
170+ },
171+ `{"status":400,"message":"invalid client"}` ,
172+ "invalid client" ,
173+ },
174+ {
175+ http .StatusOK ,
176+ []string {}, // no scopes
177+ & Options {
178+ ClientID : "valid-client-id" ,
179+ },
180+ `{"device_code":"2mdjwJNygVNDpNiLZnygtCJTQjedpevQsoST7fi1","expires_in":1800,"interval":5,"user_code":"RGVPJWCX","verification_uri":"https://www.twitch.tv/activate?device-code=RGVPJWCX"}` ,
181+ "" ,
182+ },
183+ {
184+ http .StatusOK ,
185+ []string {"user:read:email" , "user:read:subscriptions" , "channel:read:subscriptions" },
186+ & Options {
187+ ClientID : "valid-client-id" ,
188+ },
189+ `{"device_code":"uZqmEQqkOZO1NWsh0gMHWvsiEevIDLyYV3Y3Beku","expires_in":1800,"interval":5,"user_code":"RFFJTTBK","verification_uri":"https://www.twitch.tv/activate?device-code=RFFJTTBK"}` ,
190+ "" ,
191+ },
192+ }
193+
194+ for _ , testCase := range testCases {
195+ c := newMockClient (testCase .options , newMockHandler (testCase .statusCode , testCase .respBody , nil ))
196+
197+ resp , err := c .RequestDeviceVerificationURI (testCase .scopes )
198+ if err != nil {
199+ t .Error (err )
200+ }
201+
202+ if resp .StatusCode != testCase .statusCode {
203+ t .Errorf ("expected status code to be \" %d\" , got \" %d\" " , testCase .statusCode , resp .StatusCode )
204+ }
205+
206+ // Test error cases
207+ if resp .StatusCode != http .StatusOK {
208+ if resp .ErrorStatus != testCase .statusCode {
209+ t .Errorf ("expected error status to be \" %d\" , got \" %d\" " , testCase .statusCode , resp .ErrorStatus )
210+ }
211+
212+ if resp .ErrorMessage != testCase .expectedErrMsg {
213+ t .Errorf ("expected error message to be \" %s\" , got \" %s\" " , testCase .expectedErrMsg , resp .ErrorMessage )
214+ }
215+
216+ continue
217+ }
218+
219+ // Test success cases
220+ if resp .Data .DeviceCode == "" {
221+ t .Errorf ("expected a device code but got an empty string" )
222+ }
223+
224+ if resp .Data .ExpiresIn == 0 {
225+ t .Errorf ("expected ExpiresIn to not be \" 0\" " )
226+ }
227+
228+ if resp .Data .Interval == 0 {
229+ t .Errorf ("expected Interval to not be \" 0\" " )
230+ }
231+
232+ if resp .Data .UserCode == "" {
233+ t .Errorf ("expected an user code but got an empty string" )
234+ }
235+
236+ if resp .Data .VerificationURI == "" {
237+ t .Errorf ("expected a verification uri but got an empty string" )
238+ }
239+ }
240+
241+ // Test with HTTP Failure
242+ options := & Options {
243+ ClientID : "my-client-id" ,
244+ HTTPClient : & badMockHTTPClient {
245+ newMockHandler (0 , "" , nil ),
246+ },
247+ }
248+ c := & Client {
249+ opts : options ,
250+ ctx : context .Background (),
251+ }
252+
253+ _ , err := c .RequestDeviceVerificationURI ([]string {})
254+ if err == nil {
255+ t .Error ("expected error but got nil" )
256+ }
257+
258+ if err .Error () != "Failed to execute API request: Oops, that's bad :(" {
259+ t .Error ("expected error does match return error" )
260+ }
261+ }
262+
263+ func TestRequestDeviceAccessToken (t * testing.T ) {
264+ t .Parallel ()
265+
266+ testCases := []struct {
267+ statusCode int
268+ deviceCode string
269+ scopes []string
270+ options * Options
271+ respBody string
272+ expectedErrMsg string
273+ }{
274+ {
275+ http .StatusBadRequest ,
276+ "invalid-device-code" , // invalid auth code
277+ []string {"user:read:email" },
278+ & Options {
279+ ClientID : "valid-client-id" ,
280+ },
281+ `{"status":400,"message":"invalid device code"}` ,
282+ "invalid device code" ,
283+ },
284+ {
285+ http .StatusBadRequest ,
286+ "valid-device-code" ,
287+ []string {"user:read:email" },
288+ & Options {
289+ ClientID : "invalid-client-id" , // invalid client id
290+ },
291+ `{"status":400,"message":"invalid client"}` ,
292+ "invalid client" ,
293+ },
294+ {
295+ http .StatusOK ,
296+ "valid-auth-code" ,
297+ []string {}, // no scopes
298+ & Options {
299+ ClientID : "valid-client-id" ,
300+ },
301+ `{"access_token":"kagsfkgiuowegfkjsbdcuiwebf","expires_in":14146,"refresh_token":"fiuhgaofohofhohdflhoiwephvlhowiehfoi"}` ,
302+ "" ,
303+ },
304+ {
305+ http .StatusOK ,
306+ "valid-auth-code" ,
307+ []string {"analytics:read:games" , "bits:read" , "clips:edit" , "user:edit" , "user:read:email" },
308+ & Options {
309+ ClientID : "valid-client-id" ,
310+ },
311+ `{"access_token":"kagsfkgiuowegfkjsbdcuiwebf","expires_in":14154,"refresh_token":"fiuhgaofohofhohdflhoiwephvlhowiehfoi","scope":["analytics:read:games","bits:read","clips:edit","user:edit","user:read:email"]}` ,
312+ "" ,
313+ },
314+ }
315+
316+ for _ , testCase := range testCases {
317+ c := newMockClient (testCase .options , newMockHandler (testCase .statusCode , testCase .respBody , nil ))
318+
319+ resp , err := c .RequestDeviceAccessToken (testCase .deviceCode , testCase .scopes )
320+ if err != nil {
321+ t .Error (err )
322+ }
323+
324+ if resp .StatusCode != testCase .statusCode {
325+ t .Errorf ("expected status code to be \" %d\" , got \" %d\" " , testCase .statusCode , resp .StatusCode )
326+ }
327+
328+ // Test error cases
329+ if resp .StatusCode != http .StatusOK {
330+ if resp .ErrorStatus != testCase .statusCode {
331+ t .Errorf ("expected error status to be \" %d\" , got \" %d\" " , testCase .statusCode , resp .ErrorStatus )
332+ }
333+
334+ if resp .ErrorMessage != testCase .expectedErrMsg {
335+ t .Errorf ("expected error message to be \" %s\" , got \" %s\" " , testCase .expectedErrMsg , resp .ErrorMessage )
336+ }
337+
338+ continue
339+ }
340+
341+ // Test success cases
342+ if resp .Data .AccessToken == "" {
343+ t .Errorf ("expected an access token but got an empty string" )
344+ }
345+
346+ if resp .Data .RefreshToken == "" {
347+ t .Errorf ("expected a refresh token but got an empty string" )
348+ }
349+
350+ if resp .Data .ExpiresIn == 0 {
351+ t .Errorf ("expected ExpiresIn to not be \" 0\" " )
352+ }
353+
354+ if len (resp .Data .Scopes ) != len (testCase .scopes ) {
355+ t .Errorf ("expected number of scope to be \" %d\" , got \" %d\" " , len (testCase .scopes ), len (resp .Data .Scopes ))
356+ }
357+ }
358+
359+ // Test with HTTP Failure
360+ options := & Options {
361+ ClientID : "my-client-id" ,
362+ HTTPClient : & badMockHTTPClient {
363+ newMockHandler (0 , "" , nil ),
364+ },
365+ }
366+ c := & Client {
367+ opts : options ,
368+ ctx : context .Background (),
369+ }
370+
371+ _ , err := c .RequestDeviceAccessToken ("valid-device-code" , []string {})
372+ if err == nil {
373+ t .Error ("expected error but got nil" )
374+ }
375+
376+ if err .Error () != "Failed to execute API request: Oops, that's bad :(" {
377+ t .Error ("expected error does match return error" )
378+ }
379+ }
380+
155381func TestRequestUserAccessToken (t * testing.T ) {
156382 t .Parallel ()
157383
0 commit comments