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.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +4 -0
  4. data/.travis.yml +7 -0
  5. data/CHANGES.md +145 -0
  6. data/Gemfile +5 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +421 -0
  9. data/Rakefile +11 -0
  10. data/hyperion_http.gemspec +34 -0
  11. data/lib/hyperion/aux/bug_error.rb +2 -0
  12. data/lib/hyperion/aux/hash_ext.rb +5 -0
  13. data/lib/hyperion/aux/logger.rb +54 -0
  14. data/lib/hyperion/aux/typho.rb +9 -0
  15. data/lib/hyperion/aux/util.rb +18 -0
  16. data/lib/hyperion/aux/version.rb +3 -0
  17. data/lib/hyperion/formats.rb +69 -0
  18. data/lib/hyperion/headers.rb +43 -0
  19. data/lib/hyperion/hyperion.rb +79 -0
  20. data/lib/hyperion/requestor.rb +88 -0
  21. data/lib/hyperion/result_handling/dispatch_dsl.rb +67 -0
  22. data/lib/hyperion/result_handling/dispatching_hyperion_result.rb +10 -0
  23. data/lib/hyperion/result_handling/result_maker.rb +64 -0
  24. data/lib/hyperion/types/client_error_code.rb +9 -0
  25. data/lib/hyperion/types/client_error_detail.rb +46 -0
  26. data/lib/hyperion/types/client_error_response.rb +50 -0
  27. data/lib/hyperion/types/hyperion_error.rb +6 -0
  28. data/lib/hyperion/types/hyperion_result.rb +24 -0
  29. data/lib/hyperion/types/hyperion_status.rb +10 -0
  30. data/lib/hyperion/types/hyperion_uri.rb +97 -0
  31. data/lib/hyperion/types/payload_descriptor.rb +9 -0
  32. data/lib/hyperion/types/response_descriptor.rb +21 -0
  33. data/lib/hyperion/types/rest_route.rb +20 -0
  34. data/lib/hyperion.rb +15 -0
  35. data/lib/hyperion_test/fake.rb +64 -0
  36. data/lib/hyperion_test/fake_server/config.rb +36 -0
  37. data/lib/hyperion_test/fake_server/dispatcher.rb +74 -0
  38. data/lib/hyperion_test/fake_server/types.rb +7 -0
  39. data/lib/hyperion_test/fake_server.rb +54 -0
  40. data/lib/hyperion_test/spec_helper.rb +19 -0
  41. data/lib/hyperion_test/test_framework_hooks.rb +34 -0
  42. data/lib/hyperion_test.rb +2 -0
  43. data/spec/lib/hyperion/aux/util_spec.rb +29 -0
  44. data/spec/lib/hyperion/formats_spec.rb +84 -0
  45. data/spec/lib/hyperion/headers_spec.rb +61 -0
  46. data/spec/lib/hyperion/logger_spec.rb +60 -0
  47. data/spec/lib/hyperion/test_spec.rb +222 -0
  48. data/spec/lib/hyperion/types/client_error_response_spec.rb +52 -0
  49. data/spec/lib/hyperion/types/hyperion_result_spec.rb +17 -0
  50. data/spec/lib/hyperion/types/hyperion_uri_spec.rb +113 -0
  51. data/spec/lib/hyperion_spec.rb +187 -0
  52. data/spec/lib/superion_spec.rb +151 -0
  53. data/spec/lib/types_spec.rb +46 -0
  54. data/spec/spec_helper.rb +3 -0
  55. data/spec/support/core_helpers.rb +5 -0
  56. 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