heroku_api_stub 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ require "multi_json"
2
+ require "sinatra"
3
+
4
+ require_relative "heroku_api_stub/generator"
5
+ require_relative "heroku_api_stub/service_stub"
@@ -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
@@ -0,0 +1,7 @@
1
+ ENV["RACK_ENV"] = "test"
2
+
3
+ require "minitest/autorun"
4
+ require "minitest/spec"
5
+ require "rack/test"
6
+
7
+ require_relative "../lib/heroku_api_stub"
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