hyperion_http 0.1.2

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