hub/features/api.feature

531 строка
15 KiB
Gherkin

@cache_clear
Feature: hub api
Background:
Given I am "octokitten" on github.com with OAuth token "OTOKEN"
Scenario: GET resource
Given the GitHub API server:
"""
get('/hello/world') {
halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN'
halt 401 unless request.env['HTTP_ACCEPT'] == 'application/vnd.github.v3+json;charset=utf-8'
json :name => "Ed"
}
"""
When I successfully run `hub api hello/world`
Then the output should contain exactly:
"""
{"name":"Ed"}
"""
Scenario: GET Enterprise resource
Given I am "octokitten" on git.my.org with OAuth token "FITOKEN"
Given the GitHub API server:
"""
get('/api/v3/hello/world', :host_name => 'git.my.org') {
halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token FITOKEN'
json :name => "Ed"
}
"""
And $GITHUB_HOST is "git.my.org"
When I successfully run `hub api hello/world`
Then the output should contain exactly:
"""
{"name":"Ed"}
"""
Scenario: Non-success response
Given the GitHub API server:
"""
get('/hello/world') {
status 400
json :name => "Ed"
}
"""
When I run `hub api hello/world`
Then the exit status should be 22
And the stdout should contain exactly:
"""
{"name":"Ed"}
"""
And the stderr should contain exactly ""
Scenario: Non-success response flat output
Given the GitHub API server:
"""
get('/hello/world') {
status 400
json :name => "Ed"
}
"""
When I run `hub api -t hello/world`
Then the exit status should be 22
And the stdout should contain exactly:
"""
.name Ed\n
"""
And the stderr should contain exactly ""
Scenario: Non-success response doesn't choke on non-JSON
Given the GitHub API server:
"""
get('/hello/world') {
status 400
content_type :text
'Something went wrong'
}
"""
When I run `hub api -t hello/world`
Then the exit status should be 22
And the stdout should contain exactly:
"""
Something went wrong
"""
And the stderr should contain exactly ""
Scenario: GET query string
Given the GitHub API server:
"""
get('/hello/world') {
json Hash[*params.sort.flatten]
}
"""
When I successfully run `hub api -XGET -Fname=Ed -Fnum=12 -Fbool=false -Fvoid=null hello/world`
Then the output should contain exactly:
"""
{"bool":"false","name":"Ed","num":"12","void":""}
"""
Scenario: GET full URL
Given the GitHub API server:
"""
get('/hello/world', :host_name => 'api.github.com') {
halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN'
json :name => "Faye"
}
"""
When I successfully run `hub api https://api.github.com/hello/world`
Then the output should contain exactly:
"""
{"name":"Faye"}
"""
Scenario: Paginate REST
Given the GitHub API server:
"""
get('/comments') {
assert :per_page => "6"
page = (params[:page] || 1).to_i
response.headers["Link"] = %(<#{request.url}&page=#{page+1}>; rel="next") if page < 3
json [{:page => page}]
}
"""
When I successfully run `hub api --paginate comments?per_page=6`
Then the output should contain exactly:
"""
[{"page":1}]
[{"page":2}]
[{"page":3}]
"""
Scenario: Paginate GraphQL
Given the GitHub API server:
"""
post('/graphql') {
variables = params[:variables] || {}
page = (variables["endCursor"] || 1).to_i
json :data => {
:pageInfo => {
:hasNextPage => page < 3,
:endCursor => (page+1).to_s
}
}
}
"""
When I successfully run `hub api --paginate graphql -f query=QUERY`
Then the output should contain exactly:
"""
{"data":{"pageInfo":{"hasNextPage":true,"endCursor":"2"}}}
{"data":{"pageInfo":{"hasNextPage":true,"endCursor":"3"}}}
{"data":{"pageInfo":{"hasNextPage":false,"endCursor":"4"}}}
"""
Scenario: Avoid leaking token to a 3rd party
Given the GitHub API server:
"""
get('/hello/world', :host_name => 'example.com') {
halt 401 unless request.env['HTTP_AUTHORIZATION'].nil?
json :name => "Jet"
}
"""
When I successfully run `hub api http://example.com/hello/world`
Then the output should contain exactly:
"""
{"name":"Jet"}
"""
Scenario: Request headers
Given the GitHub API server:
"""
get('/hello/world') {
json :accept => request.env['HTTP_ACCEPT'],
:foo => request.env['HTTP_X_FOO']
}
"""
When I successfully run `hub api hello/world -H 'x-foo:bar' -H 'Accept: text/json'`
Then the output should contain exactly:
"""
{"accept":"text/json","foo":"bar"}
"""
Scenario: Response headers
Given the GitHub API server:
"""
get('/hello/world') {
json({})
}
"""
When I successfully run `hub api hello/world -i`
Then the output should contain "HTTP/1.1 200 OK"
And the output should contain "Content-Length: 2"
Scenario: POST fields
Given the GitHub API server:
"""
post('/hello/world') {
json Hash[*params.sort.flatten]
}
"""
When I successfully run `hub api -f name=@hubot -Fnum=12 -Fbool=false -Fvoid=null hello/world`
Then the output should contain exactly:
"""
{"bool":false,"name":"@hubot","num":12,"void":null}
"""
Scenario: POST raw fields
Given the GitHub API server:
"""
post('/hello/world') {
json Hash[*params.sort.flatten]
}
"""
When I successfully run `hub api -fnum=12 -fbool=false hello/world`
Then the output should contain exactly:
"""
{"bool":"false","num":"12"}
"""
Scenario: POST from stdin
Given the GitHub API server:
"""
post('/graphql') {
json :query => params[:query]
}
"""
When I run `hub api -t -F query=@- graphql` interactively
And I pass in:
"""
query {
repository
}
"""
Then the output should contain exactly:
"""
.query query {\\n repository\\n}\\n\n
"""
Scenario: POST body from file
Given the GitHub API server:
"""
post('/create') {
params[:obj].inspect
}
"""
Given a file named "payload.json" with:
"""
{"obj": ["one", 2, null]}
"""
When I successfully run `hub api create --input payload.json`
Then the output should contain exactly:
"""
["one", 2, nil]
"""
Scenario: POST body from stdin
Given the GitHub API server:
"""
post('/create') {
params[:obj].inspect
}
"""
When I run `hub api create --input -` interactively
And I pass in:
"""
{"obj": {"name": "Ein", "datadog": true}}
"""
Then the output should contain exactly:
"""
{"name"=>"Ein", "datadog"=>true}
"""
Scenario: Pass extra GraphQL variables
Given the GitHub API server:
"""
post('/graphql') {
json(params[:variables])
}
"""
When I successfully run `hub api -F query='query {}' -Fname=Jet -Fsize=2 graphql`
Then the output should contain exactly:
"""
{"name":"Jet","size":2}
"""
Scenario: Enterprise GraphQL
Given I am "octokitten" on git.my.org with OAuth token "FITOKEN"
Given the GitHub API server:
"""
post('/api/graphql', :host_name => 'git.my.org') {
halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token FITOKEN'
json :name => "Ed"
}
"""
And $GITHUB_HOST is "git.my.org"
When I successfully run `hub api graphql -f query=QUERY`
Then the output should contain exactly:
"""
{"name":"Ed"}
"""
Scenario: Repo context
Given I am in "git://github.com/octocat/Hello-World.git" git repo
Given the GitHub API server:
"""
get('/repos/octocat/Hello-World/commits') {
json :commits => 12
}
"""
When I successfully run `hub api repos/{owner}/{repo}/commits`
Then the output should contain exactly:
"""
{"commits":12}
"""
Scenario: Multiple string interpolation
Given I am in "git://github.com/octocat/Hello-World.git" git repo
Given the GitHub API server:
"""
get('/repos/octocat/Hello-World/pulls') {
json(params)
}
"""
When I successfully run `hub api repos/{owner}/{repo}/pulls?head={owner}:{repo}`
Then the output should contain exactly:
"""
{"head":"octocat:Hello-World"}
"""
Scenario: Repo context in graphql
Given I am in "git://github.com/octocat/Hello-World.git" git repo
Given the GitHub API server:
"""
post('/graphql') {
json :query => params[:query]
}
"""
When I run `hub api -t -F query=@- graphql` interactively
And I pass in:
"""
repository(owner: "{owner}", name: "{repo}", nameWithOwner: "{owner}/{repo}")
"""
Then the output should contain exactly:
"""
.query repository(owner: "octocat", name: "Hello-World", nameWithOwner: "octocat/Hello-World")\\n\n
"""
Scenario: Cache response
Given the GitHub API server:
"""
count = 0
get('/count') {
count += 1
json :count => count
}
"""
When I run `hub api -t 'count?a=1&b=2' --cache 5`
Then it should pass with ".count 1"
When I run `hub api -t 'count?b=2&a=1' --cache 5`
Then it should pass with ".count 1"
Scenario: Cache graphql response
Given the GitHub API server:
"""
count = 0
post('/graphql') {
halt 400 unless params[:query] =~ /^Q\d$/
count += 1
json :count => count
}
"""
When I run `hub api -t graphql -F query=Q1 --cache 5`
Then it should pass with ".count 1"
When I run `hub api -t graphql -F query=Q1 --cache 5`
Then it should pass with ".count 1"
When I run `hub api -t graphql -F query=Q2 --cache 5`
Then it should pass with ".count 2"
Scenario: Cache client error response
Given the GitHub API server:
"""
count = 0
get('/count') {
count += 1
status 404 if count == 1
json :count => count
}
"""
When I run `hub api -t count --cache 5`
Then it should fail with ".count 1"
When I run `hub api -t count --cache 5`
Then it should fail with ".count 1"
And the exit status should be 22
Scenario: Avoid caching server error response
Given the GitHub API server:
"""
count = 0
get('/count') {
count += 1
status 500 if count == 1
json :count => count
}
"""
When I run `hub api -t count --cache 5`
Then it should fail with ".count 1"
When I run `hub api -t count --cache 5`
Then it should pass with ".count 2"
When I run `hub api -t count --cache 5`
Then it should pass with ".count 2"
Scenario: Avoid caching response if the OAuth token changes
Given the GitHub API server:
"""
count = 0
get('/count') {
count += 1
json :count => count
}
"""
When I run `hub api -t count --cache 5`
Then it should pass with ".count 1"
Given I am "octocat" on github.com with OAuth token "TOKEN2"
When I run `hub api -t count --cache 5`
Then it should pass with ".count 2"
Scenario: Honor rate limit with pagination
Given the GitHub API server:
"""
get('/hello') {
page = (params[:page] || 1).to_i
if page < 2
response.headers['X-Ratelimit-Remaining'] = '0'
response.headers['X-Ratelimit-Reset'] = Time.now.utc.to_i.to_s
response.headers['Link'] = %(</hello?page=#{page+1}>; rel="next")
end
json [{}]
}
"""
When I successfully run `hub api --obey-ratelimit --paginate hello`
Then the stderr should contain "API rate limit exceeded; pausing until "
Scenario: Succumb to rate limit with pagination
Given the GitHub API server:
"""
get('/hello') {
page = (params[:page] || 1).to_i
response.headers['X-Ratelimit-Remaining'] = '0'
response.headers['X-Ratelimit-Reset'] = Time.now.utc.to_i.to_s
if page == 2
status 403
json :message => "API rate limit exceeded"
else
response.headers['Link'] = %(</hello?page=#{page+1}>; rel="next")
json [{page:page}]
end
}
"""
When I run `hub api --paginate -t hello`
Then the exit status should be 22
And the stderr should not contain "API rate limit exceeded"
And the stdout should contain exactly:
"""
.[0].page 1
.message API rate limit exceeded\n
"""
Scenario: Honor rate limit for 403s
Given the GitHub API server:
"""
count = 0
get('/hello') {
count += 1
if count == 1
response.headers['X-Ratelimit-Remaining'] = '0'
response.headers['X-Ratelimit-Reset'] = Time.now.utc.to_i.to_s
halt 403
end
json [{}]
}
"""
When I successfully run `hub api --obey-ratelimit hello`
Then the stderr should contain "API rate limit exceeded; pausing until "
Scenario: 403 unrelated to rate limit
Given the GitHub API server:
"""
get('/hello') {
response.headers['X-Ratelimit-Remaining'] = '1'
status 403
}
"""
When I run `hub api --obey-ratelimit hello`
Then the exit status should be 22
Then the stderr should not contain "API rate limit exceeded"
Scenario: Warn about insufficient OAuth scopes
Given the GitHub API server:
"""
get('/hello') {
response.headers['X-Accepted-Oauth-Scopes'] = 'repo, admin'
response.headers['X-Oauth-Scopes'] = 'public_repo'
status 403
json({})
}
"""
When I run `hub api hello`
Then the exit status should be 22
And the output should contain exactly:
"""
{}
Your access token may have insufficient scopes. Visit http://github.com/settings/tokens
to edit the 'hub' token and enable one of the following scopes: admin, repo\n
"""
Scenario: Print the SSO challenge to stderr
Given the GitHub API server:
"""
get('/orgs/acme') {
response.headers['X-GitHub-SSO'] = 'required; url=http://example.com?auth=HASH'
status 403
json({})
}
"""
When I run `hub api orgs/acme`
Then the exit status should be 22
And the stderr should contain exactly:
"""
You must authorize your token to access this organization:
http://example.com?auth=HASH\n
"""