1@cache_clear 2Feature: hub api 3 Background: 4 Given I am "octokitten" on github.com with OAuth token "OTOKEN" 5 6 Scenario: GET resource 7 Given the GitHub API server: 8 """ 9 get('/hello/world') { 10 halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN' 11 halt 401 unless request.env['HTTP_ACCEPT'] == 'application/vnd.github.v3+json;charset=utf-8' 12 json :name => "Ed" 13 } 14 """ 15 When I successfully run `hub api hello/world` 16 Then the output should contain exactly: 17 """ 18 {"name":"Ed"} 19 """ 20 21 Scenario: GET Enterprise resource 22 Given I am "octokitten" on git.my.org with OAuth token "FITOKEN" 23 Given the GitHub API server: 24 """ 25 get('/api/v3/hello/world', :host_name => 'git.my.org') { 26 halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token FITOKEN' 27 json :name => "Ed" 28 } 29 """ 30 And $GITHUB_HOST is "git.my.org" 31 When I successfully run `hub api hello/world` 32 Then the output should contain exactly: 33 """ 34 {"name":"Ed"} 35 """ 36 37 Scenario: Non-success response 38 Given the GitHub API server: 39 """ 40 get('/hello/world') { 41 status 400 42 json :name => "Ed" 43 } 44 """ 45 When I run `hub api hello/world` 46 Then the exit status should be 22 47 And the stdout should contain exactly: 48 """ 49 {"name":"Ed"} 50 """ 51 And the stderr should contain exactly "" 52 53 Scenario: Non-success response flat output 54 Given the GitHub API server: 55 """ 56 get('/hello/world') { 57 status 400 58 json :name => "Ed" 59 } 60 """ 61 When I run `hub api -t hello/world` 62 Then the exit status should be 22 63 And the stdout should contain exactly: 64 """ 65 .name Ed\n 66 """ 67 And the stderr should contain exactly "" 68 69 Scenario: Non-success response doesn't choke on non-JSON 70 Given the GitHub API server: 71 """ 72 get('/hello/world') { 73 status 400 74 content_type :text 75 'Something went wrong' 76 } 77 """ 78 When I run `hub api -t hello/world` 79 Then the exit status should be 22 80 And the stdout should contain exactly: 81 """ 82 Something went wrong 83 """ 84 And the stderr should contain exactly "" 85 86 Scenario: GET query string 87 Given the GitHub API server: 88 """ 89 get('/hello/world') { 90 json Hash[*params.sort.flatten] 91 } 92 """ 93 When I successfully run `hub api -XGET -Fname=Ed -Fnum=12 -Fbool=false -Fvoid=null hello/world` 94 Then the output should contain exactly: 95 """ 96 {"bool":"false","name":"Ed","num":"12","void":""} 97 """ 98 99 Scenario: GET full URL 100 Given the GitHub API server: 101 """ 102 get('/hello/world', :host_name => 'api.github.com') { 103 halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN' 104 json :name => "Faye" 105 } 106 """ 107 When I successfully run `hub api https://api.github.com/hello/world` 108 Then the output should contain exactly: 109 """ 110 {"name":"Faye"} 111 """ 112 113 Scenario: Paginate REST 114 Given the GitHub API server: 115 """ 116 get('/comments') { 117 assert :per_page => "6" 118 page = (params[:page] || 1).to_i 119 response.headers["Link"] = %(<#{request.url}&page=#{page+1}>; rel="next") if page < 3 120 json [{:page => page}] 121 } 122 """ 123 When I successfully run `hub api --paginate comments?per_page=6` 124 Then the output should contain exactly: 125 """ 126 [{"page":1}] 127 [{"page":2}] 128 [{"page":3}] 129 """ 130 131 Scenario: Paginate GraphQL 132 Given the GitHub API server: 133 """ 134 post('/graphql') { 135 variables = params[:variables] || {} 136 page = (variables["endCursor"] || 1).to_i 137 json :data => { 138 :pageInfo => { 139 :hasNextPage => page < 3, 140 :endCursor => (page+1).to_s 141 } 142 } 143 } 144 """ 145 When I successfully run `hub api --paginate graphql -f query=QUERY` 146 Then the output should contain exactly: 147 """ 148 {"data":{"pageInfo":{"hasNextPage":true,"endCursor":"2"}}} 149 {"data":{"pageInfo":{"hasNextPage":true,"endCursor":"3"}}} 150 {"data":{"pageInfo":{"hasNextPage":false,"endCursor":"4"}}} 151 """ 152 153 Scenario: Avoid leaking token to a 3rd party 154 Given the GitHub API server: 155 """ 156 get('/hello/world', :host_name => 'example.com') { 157 halt 401 unless request.env['HTTP_AUTHORIZATION'].nil? 158 json :name => "Jet" 159 } 160 """ 161 When I successfully run `hub api http://example.com/hello/world` 162 Then the output should contain exactly: 163 """ 164 {"name":"Jet"} 165 """ 166 167 Scenario: Request headers 168 Given the GitHub API server: 169 """ 170 get('/hello/world') { 171 json :accept => request.env['HTTP_ACCEPT'], 172 :foo => request.env['HTTP_X_FOO'] 173 } 174 """ 175 When I successfully run `hub api hello/world -H 'x-foo:bar' -H 'Accept: text/json'` 176 Then the output should contain exactly: 177 """ 178 {"accept":"text/json","foo":"bar"} 179 """ 180 181 Scenario: Response headers 182 Given the GitHub API server: 183 """ 184 get('/hello/world') { 185 json({}) 186 } 187 """ 188 When I successfully run `hub api hello/world -i` 189 Then the output should contain "HTTP/1.1 200 OK" 190 And the output should contain "Content-Length: 2" 191 192 Scenario: POST fields 193 Given the GitHub API server: 194 """ 195 post('/hello/world') { 196 json Hash[*params.sort.flatten] 197 } 198 """ 199 When I successfully run `hub api -f name=@hubot -Fnum=12 -Fbool=false -Fvoid=null hello/world` 200 Then the output should contain exactly: 201 """ 202 {"bool":false,"name":"@hubot","num":12,"void":null} 203 """ 204 205 Scenario: POST raw fields 206 Given the GitHub API server: 207 """ 208 post('/hello/world') { 209 json Hash[*params.sort.flatten] 210 } 211 """ 212 When I successfully run `hub api -fnum=12 -fbool=false hello/world` 213 Then the output should contain exactly: 214 """ 215 {"bool":"false","num":"12"} 216 """ 217 218 Scenario: POST from stdin 219 Given the GitHub API server: 220 """ 221 post('/graphql') { 222 json :query => params[:query] 223 } 224 """ 225 When I run `hub api -t -F query=@- graphql` interactively 226 And I pass in: 227 """ 228 query { 229 repository 230 } 231 """ 232 Then the output should contain exactly: 233 """ 234 .query query {\n repository\n}\n 235 """ 236 237 Scenario: POST body from file 238 Given the GitHub API server: 239 """ 240 post('/create') { 241 params[:obj].inspect 242 } 243 """ 244 Given a file named "payload.json" with: 245 """ 246 {"obj": ["one", 2, null]} 247 """ 248 When I successfully run `hub api create --input payload.json` 249 Then the output should contain exactly: 250 """ 251 ["one", 2, nil] 252 """ 253 254 Scenario: POST body from stdin 255 Given the GitHub API server: 256 """ 257 post('/create') { 258 params[:obj].inspect 259 } 260 """ 261 When I run `hub api create --input -` interactively 262 And I pass in: 263 """ 264 {"obj": {"name": "Ein", "datadog": true}} 265 """ 266 Then the output should contain exactly: 267 """ 268 {"name"=>"Ein", "datadog"=>true} 269 """ 270 271 Scenario: Pass extra GraphQL variables 272 Given the GitHub API server: 273 """ 274 post('/graphql') { 275 json(params[:variables]) 276 } 277 """ 278 When I successfully run `hub api -F query='query {}' -Fname=Jet -Fsize=2 graphql` 279 Then the output should contain exactly: 280 """ 281 {"name":"Jet","size":2} 282 """ 283 284 Scenario: Enterprise GraphQL 285 Given I am "octokitten" on git.my.org with OAuth token "FITOKEN" 286 Given the GitHub API server: 287 """ 288 post('/api/graphql', :host_name => 'git.my.org') { 289 halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token FITOKEN' 290 json :name => "Ed" 291 } 292 """ 293 And $GITHUB_HOST is "git.my.org" 294 When I successfully run `hub api graphql -f query=QUERY` 295 Then the output should contain exactly: 296 """ 297 {"name":"Ed"} 298 """ 299 300 Scenario: Repo context 301 Given I am in "git://github.com/octocat/Hello-World.git" git repo 302 Given the GitHub API server: 303 """ 304 get('/repos/octocat/Hello-World/commits') { 305 json :commits => 12 306 } 307 """ 308 When I successfully run `hub api repos/{owner}/{repo}/commits` 309 Then the output should contain exactly: 310 """ 311 {"commits":12} 312 """ 313 314 Scenario: Multiple string interpolation 315 Given I am in "git://github.com/octocat/Hello-World.git" git repo 316 Given the GitHub API server: 317 """ 318 get('/repos/octocat/Hello-World/pulls') { 319 json(params) 320 } 321 """ 322 When I successfully run `hub api repos/{owner}/{repo}/pulls?head={owner}:{repo}` 323 Then the output should contain exactly: 324 """ 325 {"head":"octocat:Hello-World"} 326 """ 327 328 Scenario: Repo context in graphql 329 Given I am in "git://github.com/octocat/Hello-World.git" git repo 330 Given the GitHub API server: 331 """ 332 post('/graphql') { 333 json :query => params[:query] 334 } 335 """ 336 When I run `hub api -t -F query=@- graphql` interactively 337 And I pass in: 338 """ 339 repository(owner: "{owner}", name: "{repo}", nameWithOwner: "{owner}/{repo}") 340 """ 341 Then the output should contain exactly: 342 """ 343 .query repository(owner: "octocat", name: "Hello-World", nameWithOwner: "octocat/Hello-World")\n 344 """ 345 346 Scenario: Cache response 347 Given the GitHub API server: 348 """ 349 count = 0 350 get('/count') { 351 count += 1 352 json :count => count 353 } 354 """ 355 When I run `hub api -t 'count?a=1&b=2' --cache 5` 356 Then it should pass with ".count 1" 357 When I run `hub api -t 'count?b=2&a=1' --cache 5` 358 Then it should pass with ".count 1" 359 360 Scenario: Cache graphql response 361 Given the GitHub API server: 362 """ 363 count = 0 364 post('/graphql') { 365 halt 400 unless params[:query] =~ /^Q\d$/ 366 count += 1 367 json :count => count 368 } 369 """ 370 When I run `hub api -t graphql -F query=Q1 --cache 5` 371 Then it should pass with ".count 1" 372 When I run `hub api -t graphql -F query=Q1 --cache 5` 373 Then it should pass with ".count 1" 374 When I run `hub api -t graphql -F query=Q2 --cache 5` 375 Then it should pass with ".count 2" 376 377 Scenario: Cache client error response 378 Given the GitHub API server: 379 """ 380 count = 0 381 get('/count') { 382 count += 1 383 status 404 if count == 1 384 json :count => count 385 } 386 """ 387 When I run `hub api -t count --cache 5` 388 Then it should fail with ".count 1" 389 When I run `hub api -t count --cache 5` 390 Then it should fail with ".count 1" 391 And the exit status should be 22 392 393 Scenario: Avoid caching server error response 394 Given the GitHub API server: 395 """ 396 count = 0 397 get('/count') { 398 count += 1 399 status 500 if count == 1 400 json :count => count 401 } 402 """ 403 When I run `hub api -t count --cache 5` 404 Then it should fail with ".count 1" 405 When I run `hub api -t count --cache 5` 406 Then it should pass with ".count 2" 407 When I run `hub api -t count --cache 5` 408 Then it should pass with ".count 2" 409 410 Scenario: Avoid caching response if the OAuth token changes 411 Given the GitHub API server: 412 """ 413 count = 0 414 get('/count') { 415 count += 1 416 json :count => count 417 } 418 """ 419 When I run `hub api -t count --cache 5` 420 Then it should pass with ".count 1" 421 Given I am "octocat" on github.com with OAuth token "TOKEN2" 422 When I run `hub api -t count --cache 5` 423 Then it should pass with ".count 2" 424 425 Scenario: Honor rate limit with pagination 426 Given the GitHub API server: 427 """ 428 get('/hello') { 429 page = (params[:page] || 1).to_i 430 if page < 2 431 response.headers['X-Ratelimit-Remaining'] = '0' 432 response.headers['X-Ratelimit-Reset'] = Time.now.utc.to_i.to_s 433 response.headers['Link'] = %(</hello?page=#{page+1}>; rel="next") 434 end 435 json [{}] 436 } 437 """ 438 When I successfully run `hub api --obey-ratelimit --paginate hello` 439 Then the stderr should contain "API rate limit exceeded; pausing until " 440 441 Scenario: Succumb to rate limit with pagination 442 Given the GitHub API server: 443 """ 444 get('/hello') { 445 page = (params[:page] || 1).to_i 446 response.headers['X-Ratelimit-Remaining'] = '0' 447 response.headers['X-Ratelimit-Reset'] = Time.now.utc.to_i.to_s 448 if page == 2 449 status 403 450 json :message => "API rate limit exceeded" 451 else 452 response.headers['Link'] = %(</hello?page=#{page+1}>; rel="next") 453 json [{page:page}] 454 end 455 } 456 """ 457 When I run `hub api --paginate -t hello` 458 Then the exit status should be 22 459 And the stderr should not contain "API rate limit exceeded" 460 And the stdout should contain exactly: 461 """ 462 .[0].page 1 463 .message API rate limit exceeded\n 464 """ 465 466 Scenario: Honor rate limit for 403s 467 Given the GitHub API server: 468 """ 469 count = 0 470 get('/hello') { 471 count += 1 472 if count == 1 473 response.headers['X-Ratelimit-Remaining'] = '0' 474 response.headers['X-Ratelimit-Reset'] = Time.now.utc.to_i.to_s 475 halt 403 476 end 477 json [{}] 478 } 479 """ 480 When I successfully run `hub api --obey-ratelimit hello` 481 Then the stderr should contain "API rate limit exceeded; pausing until " 482 483 Scenario: 403 unrelated to rate limit 484 Given the GitHub API server: 485 """ 486 get('/hello') { 487 response.headers['X-Ratelimit-Remaining'] = '1' 488 status 403 489 } 490 """ 491 When I run `hub api --obey-ratelimit hello` 492 Then the exit status should be 22 493 Then the stderr should not contain "API rate limit exceeded" 494 495 Scenario: Warn about insufficient OAuth scopes 496 Given the GitHub API server: 497 """ 498 get('/hello') { 499 response.headers['X-Accepted-Oauth-Scopes'] = 'repo, admin' 500 response.headers['X-Oauth-Scopes'] = 'public_repo' 501 status 403 502 json({}) 503 } 504 """ 505 When I run `hub api hello` 506 Then the exit status should be 22 507 And the output should contain exactly: 508 """ 509 {} 510 Your access token may have insufficient scopes. Visit http://github.com/settings/tokens 511 to edit the 'hub' token and enable one of the following scopes: admin, repo 512 """ 513 514 Scenario: Print the SSO challenge to stderr 515 Given the GitHub API server: 516 """ 517 get('/orgs/acme') { 518 response.headers['X-GitHub-SSO'] = 'required; url=http://example.com?auth=HASH' 519 status 403 520 json({}) 521 } 522 """ 523 When I run `hub api orgs/acme` 524 Then the exit status should be 22 525 And the stderr should contain exactly: 526 """ 527 528 You must authorize your token to access this organization: 529 http://example.com?auth=HASH 530 """ 531