hyperion_http 0.1.2
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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +4 -0
- data/.travis.yml +7 -0
- data/CHANGES.md +145 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +421 -0
- data/Rakefile +11 -0
- data/hyperion_http.gemspec +34 -0
- data/lib/hyperion/aux/bug_error.rb +2 -0
- data/lib/hyperion/aux/hash_ext.rb +5 -0
- data/lib/hyperion/aux/logger.rb +54 -0
- data/lib/hyperion/aux/typho.rb +9 -0
- data/lib/hyperion/aux/util.rb +18 -0
- data/lib/hyperion/aux/version.rb +3 -0
- data/lib/hyperion/formats.rb +69 -0
- data/lib/hyperion/headers.rb +43 -0
- data/lib/hyperion/hyperion.rb +79 -0
- data/lib/hyperion/requestor.rb +88 -0
- data/lib/hyperion/result_handling/dispatch_dsl.rb +67 -0
- data/lib/hyperion/result_handling/dispatching_hyperion_result.rb +10 -0
- data/lib/hyperion/result_handling/result_maker.rb +64 -0
- data/lib/hyperion/types/client_error_code.rb +9 -0
- data/lib/hyperion/types/client_error_detail.rb +46 -0
- data/lib/hyperion/types/client_error_response.rb +50 -0
- data/lib/hyperion/types/hyperion_error.rb +6 -0
- data/lib/hyperion/types/hyperion_result.rb +24 -0
- data/lib/hyperion/types/hyperion_status.rb +10 -0
- data/lib/hyperion/types/hyperion_uri.rb +97 -0
- data/lib/hyperion/types/payload_descriptor.rb +9 -0
- data/lib/hyperion/types/response_descriptor.rb +21 -0
- data/lib/hyperion/types/rest_route.rb +20 -0
- data/lib/hyperion.rb +15 -0
- data/lib/hyperion_test/fake.rb +64 -0
- data/lib/hyperion_test/fake_server/config.rb +36 -0
- data/lib/hyperion_test/fake_server/dispatcher.rb +74 -0
- data/lib/hyperion_test/fake_server/types.rb +7 -0
- data/lib/hyperion_test/fake_server.rb +54 -0
- data/lib/hyperion_test/spec_helper.rb +19 -0
- data/lib/hyperion_test/test_framework_hooks.rb +34 -0
- data/lib/hyperion_test.rb +2 -0
- data/spec/lib/hyperion/aux/util_spec.rb +29 -0
- data/spec/lib/hyperion/formats_spec.rb +84 -0
- data/spec/lib/hyperion/headers_spec.rb +61 -0
- data/spec/lib/hyperion/logger_spec.rb +60 -0
- data/spec/lib/hyperion/test_spec.rb +222 -0
- data/spec/lib/hyperion/types/client_error_response_spec.rb +52 -0
- data/spec/lib/hyperion/types/hyperion_result_spec.rb +17 -0
- data/spec/lib/hyperion/types/hyperion_uri_spec.rb +113 -0
- data/spec/lib/hyperion_spec.rb +187 -0
- data/spec/lib/superion_spec.rb +151 -0
- data/spec/lib/types_spec.rb +46 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/core_helpers.rb +5 -0
- metadata +280 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'hyperion'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
describe Hyperion::Logger do
|
6
|
+
include Hyperion::Logger
|
7
|
+
|
8
|
+
it 'logs to $stdout by default' do
|
9
|
+
output = StringIO.new
|
10
|
+
with_stdout(output) do
|
11
|
+
logger.debug 'xyzzy'
|
12
|
+
logger.debug 'qwerty'
|
13
|
+
end
|
14
|
+
output_str = output.string
|
15
|
+
expect(output_str).to include 'xyzzy'
|
16
|
+
expect(output_str).to include 'qwerty'
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'logs to Rails.logger if present' do
|
20
|
+
rails, logger = double, double
|
21
|
+
allow(rails).to receive(:logger).and_return(logger)
|
22
|
+
expect(logger).to receive(:debug).with('xyzzy')
|
23
|
+
|
24
|
+
with_rails(rails) do
|
25
|
+
logger.debug 'xyzzy'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'respects the log level' do
|
30
|
+
output = StringIO.new
|
31
|
+
with_stdout(output) do
|
32
|
+
Hyperion::Logger.level = Logger::ERROR
|
33
|
+
logger.debug 'xyzzy'
|
34
|
+
logger.error 'qwerty'
|
35
|
+
Hyperion::Logger.level = Logger::DEBUG
|
36
|
+
end
|
37
|
+
output_str = output.string
|
38
|
+
expect(output_str).to include 'qwert'
|
39
|
+
expect(output_str).to_not include 'xyzzy'
|
40
|
+
end
|
41
|
+
|
42
|
+
def with_stdout(tmp_stdout)
|
43
|
+
old = $stdout
|
44
|
+
$stdout = tmp_stdout
|
45
|
+
begin
|
46
|
+
yield
|
47
|
+
ensure
|
48
|
+
$stdout = old
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def with_rails(rails)
|
53
|
+
Kernel.const_set(:Rails, rails)
|
54
|
+
begin
|
55
|
+
yield
|
56
|
+
ensure
|
57
|
+
Kernel.send(:remove_const, :Rails)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'hyperion_test'
|
3
|
+
|
4
|
+
describe Hyperion do
|
5
|
+
include Hyperion::Formats
|
6
|
+
|
7
|
+
before :each do
|
8
|
+
Hyperion.configure do |config|
|
9
|
+
config.vendor_string = 'indigobio-ascent'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:user_response_params) { ResponseDescriptor.new('user', 1, :json) }
|
14
|
+
|
15
|
+
context 'given some routes' do
|
16
|
+
let!(:get_user_route){RestRoute.new(:get, 'http://somesite.org/users/0', user_response_params)}
|
17
|
+
let!(:post_greeting_route){RestRoute.new(:post, 'http://somesite.org/say_hello',
|
18
|
+
ResponseDescriptor.new('greeting', 1, :json),
|
19
|
+
PayloadDescriptor.new(:json))}
|
20
|
+
before :each do
|
21
|
+
Hyperion.fake('http://somesite.org') do |svr|
|
22
|
+
svr.allow(get_user_route) do
|
23
|
+
{'name' => 'freddy'}
|
24
|
+
end
|
25
|
+
svr.allow(post_greeting_route) do |req|
|
26
|
+
{'greeting' => "hello, #{req.body['name']}"}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
it 'implements specific routes' do
|
31
|
+
result = Hyperion.request(get_user_route)
|
32
|
+
expect_success(result, {'name' => 'freddy'})
|
33
|
+
|
34
|
+
result = Hyperion.request(post_greeting_route, write({'name' => 'freddy'}, :json))
|
35
|
+
expect_success(result, {'greeting' => 'hello, freddy'})
|
36
|
+
end
|
37
|
+
it 'returns 404 if a requested route is not stubbed' do
|
38
|
+
bad_path = RestRoute.new(:get, 'http://somesite.org/abc', user_response_params)
|
39
|
+
bad_headers = RestRoute.new(:get, 'http://somesite.org/users/0', ResponseDescriptor.new('abc', 1, :json))
|
40
|
+
|
41
|
+
result = Hyperion.request(bad_path)
|
42
|
+
expect(result.code).to eql 404
|
43
|
+
|
44
|
+
result = Hyperion.request(bad_headers)
|
45
|
+
expect(result.code).to eql 404
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'when a rack result is provided for the response' do
|
49
|
+
|
50
|
+
it 'allows rack results to be returned' do
|
51
|
+
arrange([400, {}, nil])
|
52
|
+
act
|
53
|
+
expect(@result.code).to eql 400
|
54
|
+
expect(@result.body).to be_nil
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'serializes the body as json if it is not a string' do
|
58
|
+
arrange([200, {}, {'foo' => 'bar'}])
|
59
|
+
act
|
60
|
+
expect(@result.body).to eql({'foo' => 'bar'})
|
61
|
+
end
|
62
|
+
|
63
|
+
def arrange(response)
|
64
|
+
Hyperion.fake(get_user_route.uri.base) do |svr|
|
65
|
+
svr.allow(get_user_route) { response }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def act
|
70
|
+
@result = Hyperion.request(get_user_route)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'logs requests for unstubbed routes' do
|
76
|
+
route1 = RestRoute.new(:get, 'http://site.org/stuff', user_response_params)
|
77
|
+
route2 = RestRoute.new(:get, 'http://site.org/things', user_response_params)
|
78
|
+
Hyperion.fake(route1.uri.base) do |svr|
|
79
|
+
svr.allow(route1) {{}}
|
80
|
+
end
|
81
|
+
Hyperion.request(route2)
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'considers the HTTP method to be part of the route' do
|
85
|
+
Hyperion.fake('http://somesite.org') do |svr|
|
86
|
+
svr.allow(:get, '/users/0') do
|
87
|
+
success_response({'name' => 'freddy'})
|
88
|
+
end
|
89
|
+
svr.allow(:post, '/users/0') do |req|
|
90
|
+
success_response({'updated' => {'name' => req.body['name']}})
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
result = Hyperion.request(RestRoute.new(:get, 'http://somesite.org/users/0', user_response_params))
|
95
|
+
expect(result.body).to eql({'name' => 'freddy'})
|
96
|
+
|
97
|
+
result = Hyperion.request(RestRoute.new(:post, 'http://somesite.org/users/0',
|
98
|
+
user_response_params, PayloadDescriptor.new(:json)),
|
99
|
+
write({'name' => 'annie'}, :json))
|
100
|
+
expect(result.body).to eql({'updated' => {'name' => 'annie'}})
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'considers the path to be part of the route' do
|
104
|
+
Hyperion.fake('http://somesite.org') do |svr|
|
105
|
+
svr.allow(:get, '/users/0') do
|
106
|
+
success_response({'name' => 'freddy'})
|
107
|
+
end
|
108
|
+
svr.allow(:get, '/users/1') do
|
109
|
+
success_response({'name' => 'annie'})
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
result = Hyperion.request(RestRoute.new(:get, 'http://somesite.org/users/0', user_response_params))
|
114
|
+
expect(result.body).to eql({'name' => 'freddy'})
|
115
|
+
|
116
|
+
result = Hyperion.request(RestRoute.new(:get, 'http://somesite.org/users/1', user_response_params))
|
117
|
+
expect(result.body).to eql({'name' => 'annie'})
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'considers the headers to be part of the route' do
|
121
|
+
Hyperion.fake('http://somesite.org') do |svr|
|
122
|
+
svr.allow(:get, '/users/0', {'Accept' => 'application/vnd.indigobio-ascent.user-v1+json'}) do
|
123
|
+
success_response({'name' => 'freddy'})
|
124
|
+
end
|
125
|
+
svr.allow(:get, '/users/0', {'Accept' => 'application/vnd.indigobio-ascent.full_user-v1+json'}) do
|
126
|
+
success_response({'first_name' => 'freddy', 'last_name' => 'kruger', 'address' => 'Elm Street'})
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
result = Hyperion.request(RestRoute.new(:get, 'http://somesite.org/users/0', user_response_params))
|
131
|
+
expect(result.body).to eql({'name' => 'freddy'})
|
132
|
+
|
133
|
+
full_user_response_params = ResponseDescriptor.new('full_user', 1, :json)
|
134
|
+
result = Hyperion.request(RestRoute.new(:get, 'http://somesite.org/users/0', full_user_response_params))
|
135
|
+
expect(result.body).to eql({'first_name' => 'freddy', 'last_name' => 'kruger', 'address' => 'Elm Street'})
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'allows multiple fake servers to be created' do
|
139
|
+
Hyperion.fake('http://google.com') do |svr|
|
140
|
+
svr.allow(:get, '/welcome') { success_response({'text' => 'hello from google'}) }
|
141
|
+
end
|
142
|
+
|
143
|
+
Hyperion.fake('http://indigo.com:3000') do |svr|
|
144
|
+
svr.allow(:get, '/welcome') { success_response({'text' => 'hello from indigo@3000'}) }
|
145
|
+
end
|
146
|
+
|
147
|
+
Hyperion.fake('http://indigo.com:4000') do |svr|
|
148
|
+
svr.allow(:get, '/welcome') { success_response({'text' => 'hello from indigo@4000'}) }
|
149
|
+
end
|
150
|
+
|
151
|
+
result = Hyperion.request(RestRoute.new(:get, 'http://google.com/welcome', user_response_params))
|
152
|
+
expect(result.body).to eql({'text' => 'hello from google'})
|
153
|
+
|
154
|
+
result = Hyperion.request(RestRoute.new(:get, 'http://indigo.com:3000/welcome', user_response_params))
|
155
|
+
expect(result.body).to eql({'text' => 'hello from indigo@3000'})
|
156
|
+
|
157
|
+
result = Hyperion.request(RestRoute.new(:get, 'http://indigo.com:4000/welcome', user_response_params))
|
158
|
+
expect(result.body).to eql({'text' => 'hello from indigo@4000'})
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'defaults the base port to port 80' do
|
162
|
+
Hyperion.fake('http://indigo.com') do |svr|
|
163
|
+
svr.allow(:get, '/welcome') { success_response({'text' => 'old handler'}) }
|
164
|
+
end
|
165
|
+
|
166
|
+
# override the previous one
|
167
|
+
Hyperion.fake('http://indigo.com:80') do |svr|
|
168
|
+
svr.allow(:get, '/welcome') { success_response({'text' => 'new handler'}) }
|
169
|
+
end
|
170
|
+
|
171
|
+
result = Hyperion.request(RestRoute.new(:get, 'http://indigo.com/welcome', user_response_params))
|
172
|
+
expect(result.body).to eql({'text' => 'new handler'})
|
173
|
+
|
174
|
+
result = Hyperion.request(RestRoute.new(:get, 'http://indigo.com:80/welcome', user_response_params))
|
175
|
+
expect(result.body).to eql({'text' => 'new handler'})
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'allows routes to be augmented' do
|
179
|
+
Hyperion.fake('http://google.com') do |svr|
|
180
|
+
svr.allow(:get, '/old') { success_response({'text' => 'old'}) }
|
181
|
+
svr.allow(:get, '/hello') { success_response({'text' => 'hello'}) }
|
182
|
+
svr.allow(RestRoute.new(:get, '/users/0', user_response_params)) { success_response({'user' => 'old user'}) }
|
183
|
+
end
|
184
|
+
|
185
|
+
# smoke test that the server is up and running
|
186
|
+
result = Hyperion.request(RestRoute.new(:get, 'http://google.com/hello', user_response_params))
|
187
|
+
expect(result.body).to eql({'text' => 'hello'})
|
188
|
+
|
189
|
+
# augment the routes
|
190
|
+
Hyperion.fake('http://google.com') do |svr|
|
191
|
+
svr.allow(:get, '/hello') { success_response({'text' => 'aloha'}) }
|
192
|
+
svr.allow(:get, '/goodbye') { success_response({'text' => 'goodbye'}) }
|
193
|
+
svr.allow(RestRoute.new(:get, '/users/0', user_response_params)) { success_response({'user' => 'new user'}) }
|
194
|
+
end
|
195
|
+
|
196
|
+
# untouched routes are left alone
|
197
|
+
result = Hyperion.request(RestRoute.new(:get, 'http://google.com/old', user_response_params))
|
198
|
+
expect(result.body).to eql({'text' => 'old'})
|
199
|
+
|
200
|
+
# restating the route replaces it (last one wins)
|
201
|
+
result = Hyperion.request(RestRoute.new(:get, 'http://google.com/hello', user_response_params))
|
202
|
+
expect(result.body).to eql({'text' => 'aloha'})
|
203
|
+
|
204
|
+
# new routes can be added
|
205
|
+
result = Hyperion.request(RestRoute.new(:get, 'http://google.com/goodbye', user_response_params))
|
206
|
+
expect(result.body).to eql({'text' => 'goodbye'})
|
207
|
+
|
208
|
+
# restating a route routes that uses headers to differentiate replaces it (last one wins)
|
209
|
+
result = Hyperion.request(RestRoute.new(:get, 'http://google.com/users/0', user_response_params))
|
210
|
+
expect(result.body).to eql({'user' => 'new user'})
|
211
|
+
end
|
212
|
+
|
213
|
+
def success_response(body)
|
214
|
+
[200, {'Content-Type' => 'application/json'}, write(body, :json)]
|
215
|
+
end
|
216
|
+
|
217
|
+
def expect_success(result, body)
|
218
|
+
expect(result.status).to eql HyperionStatus::SUCCESS
|
219
|
+
expect(result.code).to eql 200
|
220
|
+
expect(result.body).to eql body
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'hyperion'
|
2
|
+
|
3
|
+
describe ClientErrorResponse do
|
4
|
+
describe '::from_attrs' do
|
5
|
+
it 'creates a ClientErrorResponse from a hash' do
|
6
|
+
hash = {
|
7
|
+
'message' => 'oops',
|
8
|
+
'code' => 'missing',
|
9
|
+
'errors' => [ make_error_attrs('assay') ],
|
10
|
+
'content' => 'stuff'
|
11
|
+
}
|
12
|
+
result = ClientErrorResponse.from_attrs(hash)
|
13
|
+
expect(result.message).to eql 'oops'
|
14
|
+
expect(result.code).to eql ClientErrorCode::MISSING
|
15
|
+
expect(result.content).to eql 'stuff'
|
16
|
+
expect(result.errors.size).to eql 1
|
17
|
+
error = result.errors.first
|
18
|
+
expect(error.code).to eql ClientErrorCode::MISSING
|
19
|
+
expect(error.resource).to eql 'assay'
|
20
|
+
expect(error.field).to eql 'name'
|
21
|
+
expect(error.value).to eql 'foo'
|
22
|
+
expect(error.reason).to eql 'because'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '::as_json' do
|
27
|
+
it 'converts to a hash' do
|
28
|
+
errors = [ ClientErrorDetail.new(ClientErrorCode::MISSING, 'x'),
|
29
|
+
ClientErrorDetail.new(ClientErrorCode::INVALID, 'x') ]
|
30
|
+
err = ClientErrorResponse.new('oops', errors, ClientErrorCode::INVALID, 'the_content')
|
31
|
+
result = err.as_json
|
32
|
+
expect(result['message']).to eql 'oops'
|
33
|
+
expect(result['code']).to eql 'invalid'
|
34
|
+
expect(result['content']).to eql 'the_content'
|
35
|
+
expect(result['errors'].map(&['code'])).to eql %w(missing invalid)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def make_error(resource)
|
40
|
+
ClientErrorDetail.from_attrs(make_error_attrs(resource))
|
41
|
+
end
|
42
|
+
|
43
|
+
def make_error_attrs(resource)
|
44
|
+
{
|
45
|
+
'code' => 'missing',
|
46
|
+
'resource' => resource,
|
47
|
+
'field' => 'name',
|
48
|
+
'value' => 'foo',
|
49
|
+
'reason' => 'because'
|
50
|
+
}
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'hyperion'
|
3
|
+
|
4
|
+
describe HyperionResult do
|
5
|
+
describe '#to_s' do
|
6
|
+
let!(:route) { RestRoute.new(:get, 'http://site.com/things') }
|
7
|
+
it 'returns something reasonable' do
|
8
|
+
verify_to_s(HyperionStatus::TIMED_OUT, nil, "Timed out: #{route.to_s}")
|
9
|
+
verify_to_s(HyperionStatus::CLIENT_ERROR, 400, "Client error: #{route.to_s}")
|
10
|
+
verify_to_s(HyperionStatus::CHECK_CODE, 321, "HTTP 321: #{route.to_s}")
|
11
|
+
end
|
12
|
+
|
13
|
+
def verify_to_s(status, code, expected)
|
14
|
+
expect(HyperionResult.new(route, status, code).to_s).to eql expected
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'hyperion'
|
3
|
+
|
4
|
+
describe HyperionUri do
|
5
|
+
|
6
|
+
it 'behaves like a normal URI' do
|
7
|
+
u = HyperionUri.new('http://yum.com:44/res?id=1&name=foo')
|
8
|
+
expect(u.scheme).to eql 'http'
|
9
|
+
expect(u.host).to eql 'yum.com'
|
10
|
+
expect(u.port).to eql 44
|
11
|
+
expect(u.path).to eql '/res'
|
12
|
+
expect(u.query).to eql 'id=1&name=foo'
|
13
|
+
expect(u.to_s).to eql 'http://yum.com:44/res?id=1&name=foo'
|
14
|
+
expect(u.inspect).to include 'http://yum.com:44/res?id=1&name=foo'
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'accepts additional query parameters as a hash' do
|
18
|
+
u = HyperionUri.new('http://yum.com:44/res?id=1&name=foo', site: 'box')
|
19
|
+
expect(u.query).to eql 'id=1&name=foo&site=box'
|
20
|
+
expect(u.to_s).to eql 'http://yum.com:44/res?id=1&name=foo&site=box'
|
21
|
+
expect(u.inspect).to match %r{#<HyperionUri:0x[0-9a-f]+ http://yum\.com:44/res\?id=1&name=foo&site=box>}
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'hash parameters override parameters in the uri' do
|
25
|
+
u = HyperionUri.new('http://yum.com:44/res?id=1&name=foo', name: 'bar')
|
26
|
+
expect(u.query).to eql 'id=1&name=bar'
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'escapes query param values' do
|
30
|
+
u = HyperionUri.new('http://yum.com:44/res', name: 'foo bar', title: 'x&y')
|
31
|
+
expect(u.query).to eql 'name=foo+bar&title=x%26y'
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'allows the query hash to be modified after construction' do
|
35
|
+
u = HyperionUri.new('http://yum.com:44/res')
|
36
|
+
u.query_hash[:name] = 'foo bar'
|
37
|
+
expect(u.query).to eql 'name=foo+bar'
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'allows both string or symbol keys' do
|
41
|
+
u = HyperionUri.new('http://yum.com:44/res')
|
42
|
+
u.query_hash[:name] = 'foo bar'
|
43
|
+
u.query_hash['id'] = '123'
|
44
|
+
expect(u.query).to eql 'id=123&name=foo+bar'
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'setting a query string replaces the query hash' do
|
48
|
+
u = HyperionUri.new('http://yum.com:44/res', name: 'foo bar')
|
49
|
+
u.query = 'a=1'
|
50
|
+
expect(u.query).to eql 'a=1'
|
51
|
+
expect(u.query_hash).to eql({'a' => '1'})
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'orders query params by name' do
|
55
|
+
u = HyperionUri.new('http://yum.com:44/res', {b: 1, a: 2})
|
56
|
+
expect(u.query).to eql 'a=2&b=1'
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'supports query params with array values' do
|
60
|
+
u = HyperionUri.new('http://yum.com:44/res?c[]=6&c[]=7', {a: 5, b: [1, '2', :three]})
|
61
|
+
expect(u.query).to eql 'a=5&b[]=1&b[]=2&b[]=three&c[]=6&c[]=7'
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'raises an error when an invalid query is provided' do
|
65
|
+
not_a_hash = 'query must be a hash'
|
66
|
+
not_simple = 'query values must be simple'
|
67
|
+
expect_query_error(1, not_a_hash)
|
68
|
+
expect_query_error([], not_a_hash)
|
69
|
+
expect_query_error({a: {b: 1}}, not_simple)
|
70
|
+
expect_query_error({a: [1, [2, 3]]}, not_simple)
|
71
|
+
expect_query_error({a: [{b: 1}, {b: 2}]}, not_simple)
|
72
|
+
expect{HyperionUri.new('http://yum.com:44/res', nil)}.to_not raise_error
|
73
|
+
end
|
74
|
+
|
75
|
+
def expect_query_error(query, expected_error)
|
76
|
+
expect{HyperionUri.new('http://yum.com:44/res', query)}.to raise_error expected_error
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '#initialize' do
|
80
|
+
it 'accepts strings' do
|
81
|
+
u = HyperionUri.new('http://yum.com')
|
82
|
+
expect(u.to_s).to eql 'http://yum.com'
|
83
|
+
end
|
84
|
+
it 'accepts HTTP URIs' do
|
85
|
+
u = HyperionUri.new(URI('http://yum.com'))
|
86
|
+
expect(u.to_s).to eql 'http://yum.com'
|
87
|
+
end
|
88
|
+
it 'accepts HTTPS URIs' do
|
89
|
+
u = HyperionUri.new(URI('https://yum.com'))
|
90
|
+
expect(u.to_s).to eql 'https://yum.com'
|
91
|
+
end
|
92
|
+
it 'accepts HyperionUris' do
|
93
|
+
a = HyperionUri.new('http://yum.com', name: 'foo bar')
|
94
|
+
u = HyperionUri.new(a)
|
95
|
+
expect(u.to_s).to eql 'http://yum.com?name=foo+bar'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '#base' do
|
100
|
+
it 'returns the base uri' do
|
101
|
+
u = HyperionUri.new('http://yum.com:44/res')
|
102
|
+
expect(u.base).to eql 'http://yum.com:44'
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe '#base=' do
|
107
|
+
it 'sets the base uri' do
|
108
|
+
u = HyperionUri.new('http://yum.com:44/res/123')
|
109
|
+
u.base = 'https://hello.com:55'
|
110
|
+
expect(u.to_s).to eql 'https://hello.com:55/res/123'
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'hyperion'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
describe Hyperion do
|
6
|
+
describe '::request' do
|
7
|
+
include Hyperion::Formats
|
8
|
+
|
9
|
+
it 'delegates to Typhoeus' do
|
10
|
+
method = :post
|
11
|
+
uri = 'http://somesite.org:5000/path/to/resource'
|
12
|
+
rd = ResponseDescriptor.new('data_type', 1, :json)
|
13
|
+
pd = PayloadDescriptor.new(:protobuf)
|
14
|
+
route = RestRoute.new(method, uri, rd, pd)
|
15
|
+
body = 'Ventura'
|
16
|
+
additional_headers = {'From' => 'dev@indigobio.com'}
|
17
|
+
|
18
|
+
expected_headers = {
|
19
|
+
'Accept' => "application/vnd.indigobio-ascent.#{rd.type}-v#{rd.version}+#{rd.format}",
|
20
|
+
'Content-Type' => 'application/x-protobuf',
|
21
|
+
'From' => 'dev@indigobio.com',
|
22
|
+
'Expect' => 'x'
|
23
|
+
}
|
24
|
+
|
25
|
+
expect(Hyperion::Typho).to receive(:request).
|
26
|
+
with(uri, {method: method, headers: expected_headers, body: 'Ventura'}).
|
27
|
+
and_return(make_typho_response(200, write({'foo' => 'bar'}, :json)))
|
28
|
+
|
29
|
+
result = Hyperion.request(route, body, additional_headers)
|
30
|
+
expect(result).to be_a HyperionResult
|
31
|
+
expect(result.status).to eql HyperionStatus::SUCCESS
|
32
|
+
expect(result.code).to eql 200
|
33
|
+
expect(result.body).to eql({'foo' => 'bar'})
|
34
|
+
expect(result.route).to eql route
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'serialization' do
|
38
|
+
let!(:method){:post}
|
39
|
+
let!(:uri){'http://somesite.org:5000/path/to/resource'}
|
40
|
+
let!(:rd){ResponseDescriptor.new('data_type', 1, :json)}
|
41
|
+
let!(:pd){PayloadDescriptor.new(:json)}
|
42
|
+
let!(:route){RestRoute.new(method, uri, rd, pd)}
|
43
|
+
let!(:expected_headers){{
|
44
|
+
'Accept' => "application/vnd.indigobio-ascent.#{rd.type}-v#{rd.version}+#{rd.format}",
|
45
|
+
'Content-Type' => 'application/json',
|
46
|
+
'Expect' => 'x'
|
47
|
+
}}
|
48
|
+
it 'deserializes the response' do
|
49
|
+
allow(Hyperion::Typho).to receive(:request).and_return(make_typho_response(200, '{"a":"b"}'))
|
50
|
+
result = Hyperion.request(route)
|
51
|
+
expect(result.body).to eql({'a' => 'b'})
|
52
|
+
end
|
53
|
+
it 'serializes the payload' do
|
54
|
+
expect(Hyperion::Typho).to receive(:request).
|
55
|
+
with(uri, {method: method, headers: expected_headers, body: '{"c":"d"}'}).
|
56
|
+
and_return(make_typho_response(200, write({}, :json)))
|
57
|
+
Hyperion.request(route, {'c' => 'd'})
|
58
|
+
end
|
59
|
+
it 'deserializes 400-level errors to ClientErrorResponse' do
|
60
|
+
client_error = ClientErrorResponse.new('oops', [], ClientErrorCode::MISSING)
|
61
|
+
allow(Hyperion::Typho).to receive(:request).
|
62
|
+
with(uri, {method: method, headers: expected_headers, body: '{"c":"d"}'}).
|
63
|
+
and_return(make_typho_response(400, write(client_error.as_json, :json)))
|
64
|
+
result = Hyperion.request(route, {'c' => 'd'})
|
65
|
+
expect(result.body).to be_a ClientErrorResponse
|
66
|
+
expect(result.body.message).to eql 'oops'
|
67
|
+
expect(result.body.code).to eql ClientErrorCode::MISSING
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'when a block is provided' do
|
72
|
+
let!(:route){RestRoute.new(:get, 'http://yum.com', ResponseDescriptor.new('x', 1, :json))}
|
73
|
+
it 'calls the block with the result' do
|
74
|
+
stub_typho_response(200)
|
75
|
+
yielded = nil
|
76
|
+
Hyperion.request(route) do |r|
|
77
|
+
yielded = r
|
78
|
+
end
|
79
|
+
expect(yielded).to be_a HyperionResult
|
80
|
+
end
|
81
|
+
it 'returns the return value of the block' do
|
82
|
+
stub_typho_response(200)
|
83
|
+
request_and_expect(123) do |_r|
|
84
|
+
123
|
85
|
+
end
|
86
|
+
end
|
87
|
+
context 'when switching on result properties' do
|
88
|
+
it 'returns the value of the matched block' do
|
89
|
+
stub_typho_response(200)
|
90
|
+
request_and_expect(456) do |r|
|
91
|
+
r.when(200) { 456 }
|
92
|
+
123
|
93
|
+
end
|
94
|
+
end
|
95
|
+
it 'returns the value of the request block if nothing matched' do
|
96
|
+
stub_typho_response(999)
|
97
|
+
request_and_expect(123) do |r|
|
98
|
+
r.when(200) { 456 }
|
99
|
+
123
|
100
|
+
end
|
101
|
+
request_and_expect(nil) do |r|
|
102
|
+
r.when(200) { 456 }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
it 'matches return codes' do
|
106
|
+
stub_typho_response(200)
|
107
|
+
request_and_expect(true) do |r|
|
108
|
+
r.when(200) { true }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
it 'matches return code ranges' do
|
112
|
+
stub_typho_response(404)
|
113
|
+
request_and_expect(true) do |r|
|
114
|
+
r.when(400..499) { true }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
it 'matches result states' do
|
118
|
+
stub_typho_response(999, true)
|
119
|
+
request_and_expect(true) do |r|
|
120
|
+
r.when(HyperionStatus::TIMED_OUT) { true }
|
121
|
+
end
|
122
|
+
end
|
123
|
+
it 'matches client error codes' do
|
124
|
+
response = ClientErrorResponse.new('oops', [ClientErrorDetail.new(ClientErrorCode::MISSING, 'thing')])
|
125
|
+
stub_typho_response(400, false, response)
|
126
|
+
request_and_expect(true) do |r|
|
127
|
+
r.when(ClientErrorCode::MISSING) { true }
|
128
|
+
end
|
129
|
+
end
|
130
|
+
it 'matches arbitrary predicates' do
|
131
|
+
stub_typho_response(999)
|
132
|
+
request_and_expect(true) do |r|
|
133
|
+
r.when(->r{r.code == 999 && r.status == HyperionStatus::CHECK_CODE}) { true }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
it 'the predicate can be arity 0' do
|
137
|
+
stub_typho_response(999)
|
138
|
+
request_and_expect(123) do |r|
|
139
|
+
r.when(->{true}) { 123 }
|
140
|
+
end
|
141
|
+
end
|
142
|
+
it 'when a predicate raises an error, it is caught and the predicate does not match' do
|
143
|
+
stub_typho_response(400)
|
144
|
+
request_and_expect(true) do |r|
|
145
|
+
r.when(->r{raise 'oops'}) { false }
|
146
|
+
r.when(400) { true }
|
147
|
+
end
|
148
|
+
end
|
149
|
+
it 'when the action raises an error, it is not caught' do
|
150
|
+
stub_typho_response(400)
|
151
|
+
expect do
|
152
|
+
request_and_expect(true) do |r|
|
153
|
+
r.when(400) { raise 'oops' }
|
154
|
+
end
|
155
|
+
end.to raise_error 'oops'
|
156
|
+
end
|
157
|
+
it 'stops after the first match' do
|
158
|
+
stub_typho_response(404)
|
159
|
+
request_and_expect('got 404') do |r|
|
160
|
+
r.when(HyperionStatus::TIMED_OUT) { 'timed out' }
|
161
|
+
r.when(300) { 'got 400-level' }
|
162
|
+
r.when(404) { 'got 404' }
|
163
|
+
r.when(400..499) { 'got 400-level' }
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def request_and_expect(return_value, &block)
|
169
|
+
expect(Hyperion.request(route, &block)).to eql return_value
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def stub_typho_response(code, timed_out=false, response={})
|
174
|
+
allow(Hyperion::Typho).to receive(:request).and_return(make_typho_response(code, write(response, :json), timed_out))
|
175
|
+
end
|
176
|
+
|
177
|
+
def make_typho_response(code, body, timed_out=false)
|
178
|
+
r = double
|
179
|
+
allow(r).to receive(:success?) { 200 <= code && code < 300 }
|
180
|
+
allow(r).to receive(:code) { code }
|
181
|
+
allow(r).to receive(:body) { body }
|
182
|
+
allow(r).to receive(:timed_out?) { timed_out }
|
183
|
+
r
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
end
|