hyperion_http 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|