heroku_api_stub 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/heroku-api-stub +19 -0
- data/data/doc.json +1573 -0
- data/lib/heroku_api_stub.rb +5 -0
- data/lib/heroku_api_stub/generator.rb +77 -0
- data/lib/heroku_api_stub/service_stub.rb +59 -0
- data/lib/heroku_api_stub/test.rb +22 -0
- data/spec/heroku_api_stub/generator_spec.rb +55 -0
- data/spec/heroku_api_stub/service_stub_spec.rb +154 -0
- data/spec/spec_helper.rb +7 -0
- metadata +117 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
module HerokuAPIStub
|
2
|
+
class Generator
|
3
|
+
def initialize(doc=nil)
|
4
|
+
@doc = doc || read_default
|
5
|
+
end
|
6
|
+
|
7
|
+
def run
|
8
|
+
@app = Sinatra.new(ServiceStub)
|
9
|
+
@doc["resources"].each do |_, resource|
|
10
|
+
example = build_example(resource["attributes"])
|
11
|
+
resource["actions"].each do |name, action|
|
12
|
+
method = action["method"]
|
13
|
+
path = action["path"]
|
14
|
+
status = action["statuses"][0]
|
15
|
+
required_params, optional_params = build_param_logic(action)
|
16
|
+
# "{app}" to ":app"
|
17
|
+
path.gsub!(/{([a-z_]*)}/, ':\1')
|
18
|
+
#puts "method=#{method} path=#{path}"
|
19
|
+
@app.send(method.downcase, path) do
|
20
|
+
require_params!(required_params) if required_params
|
21
|
+
validate_params!(optional_params) if optional_params
|
22
|
+
if name == "List"
|
23
|
+
[status, MultiJson.encode([example], pretty: true)]
|
24
|
+
else
|
25
|
+
[status, MultiJson.encode(example, pretty: true)]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
@app
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def build_example(attributes)
|
36
|
+
example = {}
|
37
|
+
attributes.each do |name, info|
|
38
|
+
next if !info["serialized"]
|
39
|
+
keys = name.split(":")
|
40
|
+
hash = if (leading_keys = keys[0...-1]).size > 0
|
41
|
+
initialize_subhashes(example, leading_keys)
|
42
|
+
else
|
43
|
+
example
|
44
|
+
end
|
45
|
+
hash[keys.last] = info["example"]
|
46
|
+
end
|
47
|
+
example
|
48
|
+
end
|
49
|
+
|
50
|
+
def build_param_logic(action)
|
51
|
+
required_params =
|
52
|
+
action["attributes"] && action["attributes"]["required"]
|
53
|
+
optional_params =
|
54
|
+
action["attributes"] && action["attributes"]["optional"]
|
55
|
+
if required_params && !optional_params
|
56
|
+
optional_params = required_params
|
57
|
+
end
|
58
|
+
[required_params, optional_params]
|
59
|
+
end
|
60
|
+
|
61
|
+
# returns the last subhash that was initialized
|
62
|
+
def initialize_subhashes(hash, keys)
|
63
|
+
key = keys.shift
|
64
|
+
subhash = hash[key] || (hash[key] = {})
|
65
|
+
if keys.size > 0
|
66
|
+
initialize_subhashes(subhash, keys)
|
67
|
+
else
|
68
|
+
subhash
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def read_default
|
73
|
+
path = File.expand_path("../../../data/doc.json", __FILE__)
|
74
|
+
MultiJson.decode(File.read(path))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module HerokuAPIStub
|
2
|
+
class ServiceStub < Sinatra::Base
|
3
|
+
before do
|
4
|
+
@body = MultiJson.decode(request.body.read) rescue {}
|
5
|
+
@keys = materialize_keys(@body)
|
6
|
+
check_authorized!
|
7
|
+
check_version!
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def check_authorized!
|
13
|
+
if !request.env["HTTP_AUTHORIZATION"] ||
|
14
|
+
!(request.env["HTTP_AUTHORIZATION"] =~ /\A(Basic|Bearer)\s+(.*)/)
|
15
|
+
halt(401, MultiJson.encode(
|
16
|
+
id: "unauthorized",
|
17
|
+
message: "Access denied."
|
18
|
+
))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def check_version!
|
23
|
+
if request.env["HTTP_ACCEPT"] != "application/vnd.heroku+json; version=3"
|
24
|
+
halt(404, MultiJson.encode(
|
25
|
+
id: "not_found",
|
26
|
+
message: "Not found."
|
27
|
+
))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def materialize_keys(hash, prefix="")
|
32
|
+
keys = []
|
33
|
+
hash.each do |k, v|
|
34
|
+
if v.is_a?(Hash)
|
35
|
+
keys += materialize_keys(v, "#{prefix}#{k}:")
|
36
|
+
else
|
37
|
+
keys << prefix + k
|
38
|
+
end
|
39
|
+
end
|
40
|
+
keys
|
41
|
+
end
|
42
|
+
|
43
|
+
def require_params!(required)
|
44
|
+
missing = required - @keys
|
45
|
+
halt(400, MultiJson.encode(
|
46
|
+
id: "invalid_params",
|
47
|
+
message: "Require params: #{missing.join(', ')}."
|
48
|
+
)) if missing.size > 0
|
49
|
+
end
|
50
|
+
|
51
|
+
def validate_params!(optional)
|
52
|
+
extra = @keys - optional
|
53
|
+
halt(400, MultiJson.encode(
|
54
|
+
id: "invalid_params",
|
55
|
+
message: "Unknown params: #{extra.join(', ')}."
|
56
|
+
)) if extra.size > 0
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "webmock"
|
2
|
+
|
3
|
+
include WebMock::API
|
4
|
+
|
5
|
+
module HerokuAPIStub
|
6
|
+
Stub = HerokuAPIStub::Generator.new.run
|
7
|
+
|
8
|
+
def self.initialize(&block)
|
9
|
+
url = ENV["HEROKU_API_URL"] || "https://api.heroku.com"
|
10
|
+
stub_service(url, Stub, &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def self.stub_service(uri, stub, &block)
|
16
|
+
uri = URI.parse(uri)
|
17
|
+
port = uri.port != uri.default_port ? ":#{uri.port}" : ""
|
18
|
+
stub = block ? Sinatra.new(stub, &block) : stub
|
19
|
+
stub_request(:any, /^#{uri.scheme}:\/\/(.*:.*@)?#{uri.host}#{port}\/.*$/).
|
20
|
+
to_rack(stub)
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative "../spec_helper"
|
2
|
+
|
3
|
+
describe HerokuAPIStub::Generator do
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
App = HerokuAPIStub::Generator.new.run
|
7
|
+
|
8
|
+
def app
|
9
|
+
App
|
10
|
+
end
|
11
|
+
|
12
|
+
before do
|
13
|
+
header "Accept", "application/vnd.heroku+json; version=3"
|
14
|
+
header "Authorization", "Bearer fake-access-token"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "correctly serializes an app" do
|
18
|
+
get "/apps/anything"
|
19
|
+
assert_equal 200, last_response.status
|
20
|
+
assert_equal serialized_app, MultiJson.decode(last_response.body)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "correctly serializes an app list" do
|
24
|
+
get "/apps"
|
25
|
+
assert_equal 200, last_response.status
|
26
|
+
assert_equal [serialized_app], MultiJson.decode(last_response.body)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def serialized_app
|
32
|
+
{
|
33
|
+
"buildpack_provided_description" => "Ruby/Rack",
|
34
|
+
"created_at" => "2012-01-01T12:00:00-00:00",
|
35
|
+
"git_url" => "git@heroku.com/example.git",
|
36
|
+
"id" => "01234567-89ab-cdef-0123-456789abcdef",
|
37
|
+
"maintenance" => false,
|
38
|
+
"name" => "example",
|
39
|
+
"owner" => {
|
40
|
+
"email" => "username@example.com",
|
41
|
+
"id" => "01234567-89ab-cdef-0123-456789abcdef"
|
42
|
+
},
|
43
|
+
"region" => {
|
44
|
+
"id" => "01234567-89ab-cdef-0123-456789abcdef",
|
45
|
+
"name" => "us"
|
46
|
+
},
|
47
|
+
"released_at" => "2012-01-01T12:00:00-00:00",
|
48
|
+
"repo_size" => 1024,
|
49
|
+
"slug_size" => 512,
|
50
|
+
"stack" => "cedar",
|
51
|
+
"updated_at" => "2012-01-01T12:00:00-00:00",
|
52
|
+
"web_url" => "http://example.herokuapp.com"
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require_relative "../spec_helper"
|
2
|
+
|
3
|
+
describe HerokuAPIStub::ServiceStub do
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
class TestApp < HerokuAPIStub::ServiceStub
|
7
|
+
get "/" do
|
8
|
+
200
|
9
|
+
end
|
10
|
+
|
11
|
+
post "/require" do
|
12
|
+
require_params!(["key"])
|
13
|
+
200
|
14
|
+
end
|
15
|
+
|
16
|
+
post "/require-nested" do
|
17
|
+
require_params!(["key1:key2"])
|
18
|
+
200
|
19
|
+
end
|
20
|
+
|
21
|
+
post "/validate" do
|
22
|
+
validate_params!(["key"])
|
23
|
+
200
|
24
|
+
end
|
25
|
+
|
26
|
+
post "/validate-nested" do
|
27
|
+
validate_params!(["key1:key2"])
|
28
|
+
200
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def app
|
33
|
+
TestApp
|
34
|
+
end
|
35
|
+
|
36
|
+
before do
|
37
|
+
header "Accept", "application/vnd.heroku+json; version=3"
|
38
|
+
header "Authorization", "Bearer fake-access-token"
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "authorization" do
|
42
|
+
it "responds to basic authorization" do
|
43
|
+
header "Authorization", "Bearer fake-access-token"
|
44
|
+
get "/"
|
45
|
+
assert_equal 200, last_response.status
|
46
|
+
end
|
47
|
+
|
48
|
+
it "responds to bearer token authorization" do
|
49
|
+
header "Authorization", "Bearer fake-access-token"
|
50
|
+
get "/"
|
51
|
+
assert_equal 200, last_response.status
|
52
|
+
end
|
53
|
+
|
54
|
+
it "401s if unauthorized" do
|
55
|
+
header "Authorization", ""
|
56
|
+
get "/"
|
57
|
+
assert_equal 401, last_response.status
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "versioning" do
|
62
|
+
it "responds to V3" do
|
63
|
+
header "Accept", "application/vnd.heroku+json; version=3"
|
64
|
+
get "/"
|
65
|
+
assert_equal 200, last_response.status
|
66
|
+
end
|
67
|
+
|
68
|
+
it "404s on other versions" do
|
69
|
+
header "Accept", "application/vnd.heroku+json; version=2"
|
70
|
+
get "/"
|
71
|
+
assert_equal 404, last_response.status
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "parameter requirement" do
|
76
|
+
before do
|
77
|
+
header "Content-Type", "application/json"
|
78
|
+
end
|
79
|
+
|
80
|
+
it "recognizes a required key" do
|
81
|
+
post "/require", MultiJson.encode({
|
82
|
+
key: "val"
|
83
|
+
})
|
84
|
+
assert_equal 200, last_response.status
|
85
|
+
end
|
86
|
+
|
87
|
+
it "recognizes a nested required key" do
|
88
|
+
post "/require-nested", MultiJson.encode({
|
89
|
+
key1: { key2: "val" }
|
90
|
+
})
|
91
|
+
assert_equal 200, last_response.status
|
92
|
+
end
|
93
|
+
|
94
|
+
it "requires a key" do
|
95
|
+
post "/require", "{}"
|
96
|
+
assert_equal 400, last_response.status
|
97
|
+
assert_equal({
|
98
|
+
"id" => "invalid_params",
|
99
|
+
"message" => "Require params: key."
|
100
|
+
}, MultiJson.decode(last_response.body))
|
101
|
+
end
|
102
|
+
|
103
|
+
it "requires a nested key" do
|
104
|
+
post "/require-nested", "{}"
|
105
|
+
assert_equal 400, last_response.status
|
106
|
+
assert_equal({
|
107
|
+
"id" => "invalid_params",
|
108
|
+
"message" => "Require params: key1:key2."
|
109
|
+
}, MultiJson.decode(last_response.body))
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe "validation requirement" do
|
114
|
+
before do
|
115
|
+
header "Content-Type", "application/json"
|
116
|
+
end
|
117
|
+
|
118
|
+
it "recognizes a valid key" do
|
119
|
+
post "/validate", MultiJson.encode({
|
120
|
+
key: "val"
|
121
|
+
})
|
122
|
+
assert_equal 200, last_response.status
|
123
|
+
end
|
124
|
+
|
125
|
+
it "recognizes a nested valid key" do
|
126
|
+
post "/validate-nested", MultiJson.encode({
|
127
|
+
key1: { key2: "val" }
|
128
|
+
})
|
129
|
+
assert_equal 200, last_response.status
|
130
|
+
end
|
131
|
+
|
132
|
+
it "detects an invalid key" do
|
133
|
+
post "/validate", MultiJson.encode({
|
134
|
+
other: "val"
|
135
|
+
})
|
136
|
+
assert_equal 400, last_response.status
|
137
|
+
assert_equal({
|
138
|
+
"id" => "invalid_params",
|
139
|
+
"message" => "Unknown params: other."
|
140
|
+
}, MultiJson.decode(last_response.body))
|
141
|
+
end
|
142
|
+
|
143
|
+
it "requires a nested key" do
|
144
|
+
post "/validate-nested", MultiJson.encode({
|
145
|
+
key1: { other: "val" }
|
146
|
+
})
|
147
|
+
assert_equal 400, last_response.status
|
148
|
+
assert_equal({
|
149
|
+
"id" => "invalid_params",
|
150
|
+
"message" => "Unknown params: key1:other."
|
151
|
+
}, MultiJson.decode(last_response.body))
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: heroku_api_stub
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.7
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Brandur
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-08-05 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: multi_json
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>'
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>'
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: sinatra
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>'
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0.0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>'
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0.0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rack-test
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: ! 'Service stub for the Heroku API.
|
63
|
+
|
64
|
+
|
65
|
+
Will respond to all public endpoints of the Heroku API with a sample serialized
|
66
|
+
|
67
|
+
response representing what data that endpoint would normal return. Useful in
|
68
|
+
|
69
|
+
development and testing situations where real API calls might result in
|
70
|
+
|
71
|
+
inconvienent manipulation of real data.
|
72
|
+
|
73
|
+
'
|
74
|
+
email: brandur@mutelight.org
|
75
|
+
executables:
|
76
|
+
- heroku-api-stub
|
77
|
+
extensions: []
|
78
|
+
extra_rdoc_files: []
|
79
|
+
files:
|
80
|
+
- ./data/doc.json
|
81
|
+
- ./lib/heroku_api_stub/generator.rb
|
82
|
+
- ./lib/heroku_api_stub/service_stub.rb
|
83
|
+
- ./lib/heroku_api_stub/test.rb
|
84
|
+
- ./lib/heroku_api_stub.rb
|
85
|
+
- ./spec/heroku_api_stub/generator_spec.rb
|
86
|
+
- ./spec/heroku_api_stub/service_stub_spec.rb
|
87
|
+
- ./spec/spec_helper.rb
|
88
|
+
- bin/heroku-api-stub
|
89
|
+
homepage: https://github.com/heroku/heroku-api-stub
|
90
|
+
licenses:
|
91
|
+
- MIT
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ! '>='
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
requirements: []
|
109
|
+
rubyforge_project:
|
110
|
+
rubygems_version: 1.8.24
|
111
|
+
signing_key:
|
112
|
+
specification_version: 3
|
113
|
+
summary: Service stub for the Heroku API.
|
114
|
+
test_files:
|
115
|
+
- ./spec/heroku_api_stub/generator_spec.rb
|
116
|
+
- ./spec/heroku_api_stub/service_stub_spec.rb
|
117
|
+
- ./spec/spec_helper.rb
|