1package helix 2 3import ( 4 "net/http" 5 "testing" 6) 7 8func TestGetAuthorizationURL(t *testing.T) { 9 t.Parallel() 10 11 testCases := []struct { 12 state string 13 forceVerify bool 14 options *Options 15 expectedURL string 16 }{ 17 { 18 "", 19 false, 20 &Options{ 21 ClientID: "my-client-id", 22 RedirectURI: "https://example.com/auth/callback", 23 }, 24 "https://id.twitch.tv/oauth2/authorize?response_type=code&client_id=my-client-id&redirect_uri=https://example.com/auth/callback", 25 }, 26 { 27 "some-state", 28 true, 29 &Options{ 30 ClientID: "my-client-id", 31 RedirectURI: "https://example.com/auth/callback", 32 Scopes: []string{"analytics:read:games", "bits:read", "clips:edit", "user:edit", "user:read:email"}, 33 }, 34 "https://id.twitch.tv/oauth2/authorize?response_type=code&client_id=my-client-id&redirect_uri=https://example.com/auth/callback&state=some-state&force_verify=true&scope=analytics:read:games%20bits:read%20clips:edit%20user:edit%20user:read:email", 35 }, 36 } 37 38 for _, testCase := range testCases { 39 40 client, err := NewClient(testCase.options) 41 if err != nil { 42 t.Errorf("Did not expect an error, got \"%s\"", err.Error()) 43 } 44 45 url := client.GetAuthorizationURL(testCase.state, testCase.forceVerify) 46 47 if url != testCase.expectedURL { 48 t.Errorf("expected url to be \"%s\", got \"%s\"", testCase.expectedURL, url) 49 } 50 } 51} 52 53func TestGetAppAccessToken(t *testing.T) { 54 t.Parallel() 55 56 testCases := []struct { 57 statusCode int 58 options *Options 59 respBody string 60 expectedErrMsg string 61 }{ 62 { 63 http.StatusBadRequest, 64 &Options{ 65 ClientID: "invalid-client-id", // invalid client id 66 ClientSecret: "valid-client-secret", 67 }, 68 `{"status":400,"message":"invalid client"}`, 69 "invalid client", 70 }, 71 { 72 http.StatusForbidden, 73 &Options{ 74 ClientID: "valid-client-id", 75 ClientSecret: "invalid-client-secret", // invalid client secret 76 }, 77 `{"status":403,"message":"invalid client secret"}`, 78 "invalid client secret", 79 }, 80 { 81 http.StatusOK, 82 &Options{ 83 ClientID: "valid-client-id", 84 ClientSecret: "valid-client-secret", 85 }, 86 `{"access_token":"ajsdfloehfoihsdfhoasjfdpoiqh","expires_in":4999199}`, 87 "", 88 }, 89 } 90 91 for _, testCase := range testCases { 92 c := newMockClient(testCase.options, newMockHandler(testCase.statusCode, testCase.respBody, nil)) 93 94 resp, err := c.GetAppAccessToken() 95 if err != nil { 96 t.Error(err) 97 } 98 99 if resp.StatusCode != testCase.statusCode { 100 t.Errorf("expected status code to be \"%d\", got \"%d\"", testCase.statusCode, resp.StatusCode) 101 } 102 103 // Test error cases 104 if resp.StatusCode != http.StatusOK { 105 if resp.ErrorStatus != testCase.statusCode { 106 t.Errorf("expected error status to be \"%d\", got \"%d\"", testCase.statusCode, resp.ErrorStatus) 107 } 108 109 if resp.ErrorMessage != testCase.expectedErrMsg { 110 t.Errorf("expected error message to be \"%s\", got \"%s\"", testCase.expectedErrMsg, resp.ErrorMessage) 111 } 112 113 continue 114 } 115 116 // Test success cases 117 if resp.Data.AccessToken == "" { 118 t.Errorf("expected an access token but got an empty string") 119 } 120 121 if resp.Data.ExpiresIn == 0 { 122 t.Errorf("expected ExpiresIn to not be \"0\"") 123 } 124 } 125} 126 127func TestGetUserAccessToken(t *testing.T) { 128 t.Parallel() 129 130 testCases := []struct { 131 statusCode int 132 code string 133 scopes []string 134 options *Options 135 respBody string 136 expectedErrMsg string 137 }{ 138 { 139 http.StatusBadRequest, 140 "invalid-auth-code", // invalid auth code 141 []string{"user:read:email"}, 142 &Options{ 143 ClientID: "valid-client-id", 144 ClientSecret: "valid-client-secret", 145 RedirectURI: "https://example.com/auth/callback", 146 }, 147 `{"status":400,"message":"Invalid authorization code"}`, 148 "Invalid authorization code", 149 }, 150 { 151 http.StatusBadRequest, 152 "valid-auth-code", 153 []string{"user:read:email"}, 154 &Options{ 155 ClientID: "invalid-client-id", // invalid client id 156 ClientSecret: "valid-client-secret", 157 RedirectURI: "https://example.com/auth/callback", 158 }, 159 `{"status":400,"message":"invalid client"}`, 160 "invalid client", 161 }, 162 { 163 http.StatusForbidden, 164 "valid-auth-code", 165 []string{"user:read:email"}, 166 &Options{ 167 ClientID: "valid-client-id", 168 ClientSecret: "invalid-client-secret", // invalid client secret 169 RedirectURI: "https://example.com/auth/callback", 170 }, 171 `{"status":403,"message":"invalid client secret"}`, 172 "invalid client secret", 173 }, 174 { 175 http.StatusBadRequest, 176 "valid-auth-code", 177 []string{"user:read:email"}, 178 &Options{ 179 ClientID: "valid-client-id", 180 ClientSecret: "valid-client-secret", 181 RedirectURI: "https://example.com/invalid/callback", // invalid redirect uri 182 }, 183 `{"status":400,"message":"Parameter redirect_uri does not match registeredURI"}`, 184 "Parameter redirect_uri does not match registeredURI", 185 }, 186 { 187 http.StatusOK, 188 "valid-auth-code", 189 []string{}, // no scopes 190 &Options{ 191 ClientID: "valid-client-id", 192 ClientSecret: "valid-client-secret", 193 RedirectURI: "https://example.com/auth/callback", 194 }, 195 `{"access_token":"kagsfkgiuowegfkjsbdcuiwebf","expires_in":14146,"refresh_token":"fiuhgaofohofhohdflhoiwephvlhowiehfoi"}`, 196 "", 197 }, 198 { 199 http.StatusOK, 200 "valid-auth-code", 201 []string{"analytics:read:games", "bits:read", "clips:edit", "user:edit", "user:read:email"}, 202 &Options{ 203 ClientID: "valid-client-id", 204 ClientSecret: "valid-client-secret", 205 RedirectURI: "https://example.com/auth/callback", 206 }, 207 `{"access_token":"kagsfkgiuowegfkjsbdcuiwebf","expires_in":14154,"refresh_token":"fiuhgaofohofhohdflhoiwephvlhowiehfoi","scope":["analytics:read:games","bits:read","clips:edit","user:edit","user:read:email"]}`, 208 "", 209 }, 210 } 211 212 for _, testCase := range testCases { 213 c := newMockClient(testCase.options, newMockHandler(testCase.statusCode, testCase.respBody, nil)) 214 215 resp, err := c.GetUserAccessToken(testCase.code) 216 if err != nil { 217 t.Error(err) 218 } 219 220 if resp.StatusCode != testCase.statusCode { 221 t.Errorf("expected status code to be \"%d\", got \"%d\"", testCase.statusCode, resp.StatusCode) 222 } 223 224 // Test error cases 225 if resp.StatusCode != http.StatusOK { 226 if resp.ErrorStatus != testCase.statusCode { 227 t.Errorf("expected error status to be \"%d\", got \"%d\"", testCase.statusCode, resp.ErrorStatus) 228 } 229 230 if resp.ErrorMessage != testCase.expectedErrMsg { 231 t.Errorf("expected error message to be \"%s\", got \"%s\"", testCase.expectedErrMsg, resp.ErrorMessage) 232 } 233 234 continue 235 } 236 237 // Test success cases 238 if resp.Data.AccessToken == "" { 239 t.Errorf("expected an access token but got an empty string") 240 } 241 242 if resp.Data.RefreshToken == "" { 243 t.Errorf("expected a refresh token but got an empty string") 244 } 245 246 if resp.Data.ExpiresIn == 0 { 247 t.Errorf("expected ExpiresIn to not be \"0\"") 248 } 249 250 if len(resp.Data.Scopes) != len(testCase.scopes) { 251 t.Errorf("expected number of scope to be \"%d\", got \"%d\"", len(testCase.scopes), len(resp.Data.Scopes)) 252 } 253 } 254} 255 256func TestRefreshUserAccessToken(t *testing.T) { 257 t.Parallel() 258 259 testCases := []struct { 260 statusCode int 261 refreshToken string 262 options *Options 263 respBody string 264 expectedErrMsg string 265 expectedScopes []string 266 }{ 267 { 268 http.StatusBadRequest, 269 "", // no refresh token 270 &Options{ 271 ClientID: "valid-client-id", 272 ClientSecret: "valid-client-secret", 273 }, 274 `{"status":400,"message":"missing refresh token"}`, 275 "missing refresh token", 276 []string{}, 277 }, 278 { 279 http.StatusBadRequest, 280 "invalid-refresh-token", // invalid refresh token 281 &Options{ 282 ClientID: "valid-client-id", 283 ClientSecret: "valid-client-secret", 284 }, 285 `{"status":400,"message":"Invalid refresh token"}`, 286 "Invalid refresh token", 287 []string{}, 288 }, 289 { 290 http.StatusBadRequest, 291 "valid-refresh-token", 292 &Options{ 293 ClientID: "invalid-client-id", // invalid client id 294 ClientSecret: "valid-client-secret", 295 }, 296 `{"status":400,"message":"invalid client"}`, 297 "invalid client", 298 []string{}, 299 }, 300 { 301 http.StatusForbidden, 302 "valid-refresh-token", 303 &Options{ 304 ClientID: "valid-client-id", 305 ClientSecret: "invalid-client-secret", // invalid client secret 306 }, 307 `{"status":403,"message":"invalid client secret"}`, 308 "invalid client secret", 309 []string{}, 310 }, 311 { 312 http.StatusBadRequest, 313 "valid-refresh-token", 314 &Options{ 315 ClientID: "valid-client-id", 316 ClientSecret: "valid-client-secret", 317 }, 318 `{"status":400,"message":"invalid scope requested: 'invalid:scope'"}`, 319 "invalid scope requested: 'invalid:scope'", 320 []string{}, 321 }, 322 { 323 http.StatusOK, 324 "valid-refresh-token", 325 &Options{ 326 ClientID: "valid-client-id", 327 ClientSecret: "valid-client-secret", 328 }, 329 `{"access_token":"oihhkfhsajkhfjksahfkjahsf","expires_in":13669,"refresh_token":"oihhkfhsajkhfjksahfkjahsfahsldhasld","scope":["analytics:read:games","bits:read","clips:edit","user:edit","user:read:email"]}`, 330 "", 331 []string{"analytics:read:games", "bits:read", "clips:edit", "user:edit", "user:read:email"}, 332 }, 333 } 334 335 for _, testCase := range testCases { 336 c := newMockClient(testCase.options, newMockHandler(testCase.statusCode, testCase.respBody, nil)) 337 338 resp, err := c.RefreshUserAccessToken(testCase.refreshToken) 339 if err != nil { 340 t.Error(err) 341 } 342 343 if resp.StatusCode != testCase.statusCode { 344 t.Errorf("expected status code to be \"%d\", got \"%d\"", testCase.statusCode, resp.StatusCode) 345 } 346 347 // Test error cases 348 if resp.StatusCode != http.StatusOK { 349 if resp.ErrorStatus != testCase.statusCode { 350 t.Errorf("expected error status to be \"%d\", got \"%d\"", testCase.statusCode, resp.ErrorStatus) 351 } 352 353 if resp.ErrorMessage != testCase.expectedErrMsg { 354 t.Errorf("expected error message to be \"%s\", got \"%s\"", testCase.expectedErrMsg, resp.ErrorMessage) 355 } 356 357 continue 358 } 359 360 // // Test success cases 361 if resp.Data.AccessToken == "" { 362 t.Errorf("expected an access token but got an empty string") 363 } 364 365 if resp.Data.RefreshToken == "" { 366 t.Errorf("expected a refresh token but got an empty string") 367 } 368 369 if resp.Data.ExpiresIn == 0 { 370 t.Errorf("expected ExpiresIn to not be \"0\"") 371 } 372 373 if len(resp.Data.Scopes) != len(testCase.expectedScopes) { 374 t.Errorf("expected number of scope to be \"%d\", got \"%d\"", len(testCase.expectedScopes), len(resp.Data.Scopes)) 375 } 376 } 377} 378 379func TestRevokeUserAccessToken(t *testing.T) { 380 t.Parallel() 381 382 testCases := []struct { 383 statusCode int 384 accessToken string 385 options *Options 386 respBody string 387 expectedErrMsg string 388 }{ 389 { 390 http.StatusBadRequest, 391 "valid-access-token", 392 &Options{ClientID: "invalid-client-id"}, // invalid client id 393 `{"status":400,"message":"Invalid client_id: invalid-client-id"}`, 394 "Invalid client_id: invalid-client-id", 395 }, 396 { 397 http.StatusBadRequest, 398 "", // no access token 399 &Options{ClientID: "valid-client-id"}, 400 `{"status":400,"message":"missing oauth token"}`, 401 "missing oauth token", 402 }, 403 { 404 http.StatusOK, 405 "invalid-access-token", // invalid token still returns 200 OK response 406 &Options{ClientID: "valid-client-id"}, 407 "", 408 "", 409 }, 410 { 411 http.StatusOK, 412 "valid-access-token", 413 &Options{ClientID: "valid-client-id"}, 414 "", 415 "", 416 }, 417 } 418 419 for _, testCase := range testCases { 420 c := newMockClient(testCase.options, newMockHandler(testCase.statusCode, testCase.respBody, nil)) 421 422 resp, err := c.RevokeUserAccessToken(testCase.accessToken) 423 if err != nil { 424 t.Error(err) 425 } 426 427 if resp.StatusCode != testCase.statusCode { 428 t.Errorf("expected status code to be \"%d\", got \"%d\"", testCase.statusCode, resp.StatusCode) 429 } 430 431 // Test error cases 432 if resp.StatusCode != http.StatusOK { 433 if testCase.expectedErrMsg != "" && resp.ErrorMessage != testCase.expectedErrMsg { 434 t.Errorf("expected error message to be \"%s\", got \"%s\"", testCase.expectedErrMsg, resp.ErrorMessage) 435 } 436 437 if resp.ErrorStatus != testCase.statusCode { 438 t.Errorf("expected error status to be \"%d\", got \"%d\"", testCase.statusCode, resp.ErrorStatus) 439 } 440 441 continue 442 } 443 } 444} 445