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.
@@ -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