faraday_cage 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,32 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'rack'
4
+
5
+ require 'faraday_cage/rack_middleware'
6
+
7
+ module FaradayCage
8
+ class Server
9
+
10
+ attr_reader :app
11
+
12
+ def initialize(app = FaradayCage.app)
13
+ if app
14
+ @app = app
15
+ else
16
+ raise Error, 'No Rack app configured. Please see documentation for details.'
17
+ end
18
+
19
+ @middleware = RackMiddleware.new(app)
20
+ end
21
+
22
+ def connection
23
+ @connection ||= Faraday.new(FaradayCage.default_host) do |conn|
24
+ if FaradayCage.middleware.respond_to?(:call)
25
+ FaradayCage.middleware.call(conn)
26
+ end
27
+
28
+ conn.adapter :rack, app
29
+ end
30
+ end
31
+ end # Server
32
+ end # FaradayCage
@@ -0,0 +1,164 @@
1
+ module FaradayCage
2
+ ##
3
+ # Wraps status codes to provide various helper methods for testing a response.
4
+ #
5
+ # @example Testing whether a status is successful.
6
+ #
7
+ # status.success?
8
+ #
9
+ # @example Getting a status name.
10
+ #
11
+ # status.name
12
+ # # => :service_unavailable
13
+ #
14
+ # @example Comparing a status.
15
+ #
16
+ # status == 200
17
+ # # => true
18
+ #
19
+ # @example Converting a status.
20
+ #
21
+ # status.to_i
22
+ # # => 422
23
+ # status.to_sym
24
+ # # => :unprocessable_entity
25
+ #
26
+ # @example Testing a status by name.
27
+ #
28
+ # status.forbidden?
29
+ # # => false
30
+ class Status
31
+ MAPPINGS = {
32
+ 100 => :continue,
33
+ 101 => :switching_protocols,
34
+ 102 => :processing,
35
+ 200 => :ok,
36
+ 201 => :created,
37
+ 202 => :accepted,
38
+ 203 => :non_authoritative_information,
39
+ 204 => :no_content,
40
+ 205 => :reset_content,
41
+ 206 => :partial_content,
42
+ 207 => :multi_status,
43
+ 226 => :im_used,
44
+ 300 => :multiple_choices,
45
+ 301 => :moved_permanently,
46
+ 302 => :found,
47
+ 303 => :see_other,
48
+ 304 => :not_modified,
49
+ 305 => :use_proxy,
50
+ 307 => :temporary_redirect,
51
+ 400 => :bad_request,
52
+ 401 => :unauthorized,
53
+ 402 => :payment_required,
54
+ 403 => :forbidden,
55
+ 404 => :not_found,
56
+ 405 => :method_not_allowed,
57
+ 406 => :not_acceptable,
58
+ 407 => :proxy_authentication_required,
59
+ 408 => :request_timeout,
60
+ 409 => :conflict,
61
+ 410 => :gone,
62
+ 411 => :length_required,
63
+ 412 => :precondition_failed,
64
+ 413 => :request_entity_too_large,
65
+ 414 => :request_uri_too_long,
66
+ 415 => :unsupported_media_type,
67
+ 416 => :requested_range_not_satisfiable,
68
+ 417 => :expectation_failed,
69
+ 422 => :unprocessable_entity,
70
+ 423 => :locked,
71
+ 424 => :failed_dependency,
72
+ 426 => :upgrade_required,
73
+ 500 => :internal_server_error,
74
+ 501 => :not_implemented,
75
+ 502 => :bad_gateway,
76
+ 503 => :service_unavailable,
77
+ 504 => :gateway_timeout,
78
+ 505 => :http_version_not_supported,
79
+ 507 => :insufficient_storage,
80
+ 510 => :not_extended
81
+ }
82
+
83
+ MAPPINGS.each do |code, method|
84
+ class_eval <<-RUBY
85
+ def #{method}?
86
+ code == #{code}
87
+ end
88
+ RUBY
89
+ end
90
+
91
+ attr_reader :code
92
+
93
+ def initialize(code)
94
+ @code = code.to_i
95
+ end
96
+
97
+ def ==(object)
98
+ if object.respond_to?(:code)
99
+ object.code == code
100
+ else
101
+ object == code || object == name
102
+ end
103
+ end
104
+
105
+ def name
106
+ @name ||= MAPPINGS[code]
107
+ end
108
+
109
+ def to_i
110
+ code
111
+ end
112
+
113
+ def to_s
114
+ name.to_s
115
+ end
116
+
117
+ def to_sym
118
+ name
119
+ end
120
+
121
+ def inspect
122
+ "#<#{self.class} code: #{code.inspect} name: #{name.inspect}>"
123
+ end
124
+
125
+ def type
126
+ @type ||= case code
127
+ when 100..199 then :informational
128
+ when 200..299 then :success
129
+ when 300..399 then :redirect
130
+ when 400..499 then :client_error
131
+ when 500..599 then :server_error
132
+ else :unknown
133
+ end
134
+ end
135
+
136
+ def informational?
137
+ type == :informational
138
+ end
139
+
140
+ def success?
141
+ type == :success
142
+ end
143
+
144
+ def redirect?
145
+ type == :redirect
146
+ end
147
+
148
+ def client_error?
149
+ type == :client_error
150
+ end
151
+
152
+ def server_error?
153
+ type == :server_error
154
+ end
155
+
156
+ def error?
157
+ client_error? || server_error?
158
+ end
159
+
160
+ def unknown?
161
+ type == :unknown
162
+ end
163
+ end # Status
164
+ end # FaradayCage
@@ -0,0 +1,3 @@
1
+ module FaradayCage
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ describe FaradayCage::Config do
4
+
5
+ describe '#initialize' do
6
+
7
+ it 'sets the default host' do
8
+ expect(described_class.new.default_host).to eq('http://example.com')
9
+ end
10
+ end
11
+
12
+ describe '#app' do
13
+
14
+ let(:app) do
15
+ nil
16
+ end
17
+
18
+ subject(:config) do
19
+ described_class.new.tap do |config|
20
+ config.app = app
21
+ end
22
+ end
23
+
24
+ context 'when an app is set' do
25
+
26
+ let(:app) do
27
+ :foo
28
+ end
29
+
30
+ it 'returns the set app' do
31
+ expect(config.app).to eq(:foo)
32
+ end
33
+ end
34
+
35
+ context 'when an app is not set' do
36
+
37
+ context 'when Rails is defined' do
38
+
39
+ it 'returns #default_rails_app' do
40
+ stub_const('Rails', Class.new)
41
+ config.should_receive(:default_rails_app).and_return(:bar)
42
+ expect(config.app).to eq(:bar)
43
+ end
44
+ end
45
+
46
+ context 'when Rails is not defined' do
47
+
48
+ it 'returns nil' do
49
+ expect(config.app).to be_nil
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ describe '#middleware' do
56
+
57
+ subject(:config) do
58
+ described_class.new
59
+ end
60
+
61
+ context 'when a block is given' do
62
+
63
+ it 'stores the provided block as a proc' do
64
+ block = -> { 'blah blah blah' }
65
+ config.middleware(&block)
66
+ expect(config.middleware).to eq(block)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ describe FaradayCage::RackMiddleware do
4
+
5
+ describe '#initialize' do
6
+
7
+ it 'initializes with the provided app' do
8
+ expect(described_class.new(:foo).app).to eq(:foo)
9
+ end
10
+ end
11
+
12
+ describe '#call' do
13
+
14
+ let(:app) do
15
+ ->(env) { [123, { foo: 'bar' }, ['it works!']] }
16
+ end
17
+
18
+ subject(:middleware) do
19
+ described_class.new(app)
20
+ end
21
+
22
+ context 'when request is for /__identify__' do
23
+
24
+ let(:env) do
25
+ { 'PATH_INFO' => '/__identify__' }
26
+ end
27
+
28
+ it 'has a 200 status code' do
29
+ expect(middleware.call(env)[0]).to eq(200)
30
+ end
31
+
32
+ it 'has a content type of text/plain' do
33
+ expect(middleware.call(env)[1]).to eq({ 'Content-Type' => 'text/plain' })
34
+ end
35
+
36
+ it 'returns the object ID of the app' do
37
+ expect(middleware.call(env)[2]).to eq([middleware.app.object_id.to_s])
38
+ end
39
+ end
40
+
41
+ context 'when request is not for /__identify__' do
42
+
43
+ let(:env) do
44
+ { 'PATH_INFO' => '/something_else' }
45
+ end
46
+
47
+ it 'calls the app and returns the response' do
48
+ expect(middleware.call(env)).to eq([123, { foo: 'bar' }, ['it works!']])
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe FaradayCage::Server do
4
+
5
+ describe '#initialize' do
6
+
7
+ let(:app) do
8
+ double('Rack app')
9
+ end
10
+
11
+ it 'wraps the provided app in RackMiddleware' do
12
+ expect(described_class.new(app).app).to eq(app)
13
+ end
14
+
15
+ context 'when there is no default app configured' do
16
+
17
+ it 'raises an error' do
18
+ expect { described_class.new }.to raise_error(FaradayCage::Error)
19
+ end
20
+ end
21
+ end
22
+
23
+ describe '#connection' do
24
+
25
+ subject(:server) do
26
+ described_class.new(double('Rack app'))
27
+ end
28
+
29
+ it 'returns a Faraday::Connection' do
30
+ expect(server.connection).to be_a(Faraday::Connection)
31
+ end
32
+
33
+ it 'sets the URL to FaradayCage.default_host' do
34
+ expect(server.connection.url_prefix).to eq(URI.parse(FaradayCage.default_host))
35
+ end
36
+
37
+ it 'uses the Rack adapter' do
38
+ expect(server.connection.builder.handlers.first).to eq(Faraday::Adapter::Rack)
39
+ end
40
+
41
+ context 'when FaradayCage.middleware is set' do
42
+
43
+ let(:middleware) do
44
+ double('middleware')
45
+ end
46
+
47
+ it 'calls the stored proc with the connection' do
48
+ FaradayCage.should_receive(:middleware).at_least(1).times.and_return(middleware)
49
+ middleware.should_receive(:respond_to?).with(:call).and_return(true)
50
+ middleware.should_receive(:call) { |conn| expect(conn).to be_a(Faraday::Connection) }
51
+ server.connection
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,375 @@
1
+ require 'spec_helper'
2
+
3
+ describe FaradayCage::Status do
4
+
5
+ describe '#initialize' do
6
+
7
+ it 'initializes with a status code' do
8
+ described_class.new(200)
9
+ end
10
+
11
+ it 'converts the status code to an integer' do
12
+ expect(described_class.new('500').code).to eq(500)
13
+ end
14
+ end
15
+
16
+ FaradayCage::Status::MAPPINGS.each do |code, mapping|
17
+
18
+ describe "##{mapping}?" do
19
+
20
+ it "returns true when #code equals #{code}" do
21
+ expect(described_class.new(code).send(:"#{mapping}?")).to be_true
22
+ end
23
+
24
+ it "returns false when #code does not equal #{code}" do
25
+ expect(described_class.new(0).send(:"#{mapping}?")).to be_false
26
+ end
27
+ end
28
+ end
29
+
30
+ describe '#name' do
31
+
32
+ it 'returns the mapping for the code' do
33
+ FaradayCage::Status::MAPPINGS.should_receive(:[]).with(5678).and_return(:foo)
34
+ expect(described_class.new(5678).name).to eq(:foo)
35
+ end
36
+
37
+ it 'caches the mapping' do
38
+ status = described_class.new(200)
39
+ expect(status.name.object_id).to eq(status.name.object_id)
40
+ end
41
+
42
+ context 'when there is no mapping for the code' do
43
+
44
+ it 'returns nil' do
45
+ expect(described_class.new(0).name).to be_nil
46
+ end
47
+ end
48
+ end
49
+
50
+ describe '#==' do
51
+
52
+ subject(:status) do
53
+ described_class.new(404)
54
+ end
55
+
56
+ context 'when provided object responds to #code' do
57
+
58
+ context 'when codes match' do
59
+
60
+ it 'returns true' do
61
+ expect(status == double('object', code: 404)).to be_true
62
+ end
63
+ end
64
+
65
+ context 'when codes do not match' do
66
+
67
+ it 'returns false' do
68
+ expect(status == double('object', code: 444)).to be_false
69
+ end
70
+ end
71
+ end
72
+
73
+ context 'when provided object does not respond to #code' do
74
+
75
+ context 'when object matches #code' do
76
+
77
+ it 'returns true' do
78
+ expect(status == 404).to be_true
79
+ end
80
+ end
81
+
82
+ context 'when object matches #name' do
83
+
84
+ it 'returns true' do
85
+ expect(status == :not_found).to be_true
86
+ end
87
+ end
88
+
89
+ context 'when object matches neither #code nor #name' do
90
+
91
+ it 'returns false' do
92
+ expect(status == nil).to be_false
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ describe '#to_i' do
99
+
100
+ it 'returns the code' do
101
+ status = described_class.new(42)
102
+ status.should_receive(:code).and_return(12)
103
+ expect(status.to_i).to eq(12)
104
+ end
105
+ end
106
+
107
+ describe '#to_s' do
108
+
109
+ it 'returns the name as a string' do
110
+ status = described_class.new(420)
111
+ status.should_receive(:name).and_return(:enhance_your_cool)
112
+ expect(status.to_s).to eq('enhance_your_cool')
113
+ end
114
+ end
115
+
116
+ describe '#to_sym' do
117
+
118
+ it 'returns the name' do
119
+ status = described_class.new(100)
120
+ status.should_receive(:name).and_return(:bar)
121
+ expect(status.to_sym).to eq(:bar)
122
+ end
123
+ end
124
+
125
+ describe '#inspect' do
126
+
127
+ subject(:inspection) do
128
+ described_class.new(200).inspect
129
+ end
130
+
131
+ it 'returns a string' do
132
+ expect(inspection).to be_a(String)
133
+ end
134
+
135
+ it 'includes the class name' do
136
+ expect(inspection).to include('#<FaradayCage::Status')
137
+ end
138
+
139
+ it 'includes the status code' do
140
+ expect(inspection).to include('code: 200')
141
+ end
142
+
143
+ it 'includes the status name' do
144
+ expect(inspection).to include('name: :ok')
145
+ end
146
+ end
147
+
148
+ describe '#type' do
149
+
150
+ context 'when #code is between 100 and 199' do
151
+ it 'returns :informational' do
152
+ expect(described_class.new(99).type).to_not eq(:informational)
153
+
154
+ (100..199).each do |code|
155
+ expect(described_class.new(code).type).to eq(:informational)
156
+ end
157
+
158
+ expect(described_class.new(200).type).to_not eq(:informational)
159
+ end
160
+ end
161
+
162
+ context 'when #code is between 200 and 299' do
163
+
164
+ it 'returns :success' do
165
+ expect(described_class.new(199).type).to_not eq(:success)
166
+
167
+ (200..299).each do |code|
168
+ expect(described_class.new(code).type).to eq(:success)
169
+ end
170
+
171
+ expect(described_class.new(300).type).to_not eq(:success)
172
+ end
173
+ end
174
+
175
+ context 'when #code is between 300 and 399' do
176
+
177
+ it 'returns :redirect' do
178
+ expect(described_class.new(299).type).to_not eq(:redirect)
179
+
180
+ (300..399).each do |code|
181
+ expect(described_class.new(code).type).to eq(:redirect)
182
+ end
183
+
184
+ expect(described_class.new(400).type).to_not eq(:redirect)
185
+ end
186
+ end
187
+
188
+ context 'when #code is between 400 and 499' do
189
+
190
+ it 'returns :client_error' do
191
+ expect(described_class.new(399).type).to_not eq(:client_error)
192
+
193
+ (400..499).each do |code|
194
+ expect(described_class.new(code).type).to eq(:client_error)
195
+ end
196
+
197
+ expect(described_class.new(500).type).to_not eq(:client_error)
198
+ end
199
+ end
200
+
201
+ context 'when #code is between 500 and 599' do
202
+
203
+ it 'returns :redirect' do
204
+ expect(described_class.new(499).type).to_not eq(:server_error)
205
+
206
+ (500..599).each do |code|
207
+ expect(described_class.new(code).type).to eq(:server_error)
208
+ end
209
+
210
+ expect(described_class.new(600).type).to_not eq(:server_error)
211
+ end
212
+ end
213
+
214
+ context 'when #code is not between 100 and 599' do
215
+
216
+ it 'returns :unknown' do
217
+ (0..99).each do |code|
218
+ expect(described_class.new(code).type).to eq(:unknown)
219
+ end
220
+
221
+ (600..1000).each do |code|
222
+ expect(described_class.new(code).type).to eq(:unknown)
223
+ end
224
+ end
225
+ end
226
+ end
227
+
228
+ describe 'status type matchers' do
229
+
230
+ subject(:status) do
231
+ described_class.new(rand(0..1000))
232
+ end
233
+
234
+ describe '#informational?' do
235
+
236
+ context 'when #type is :informational' do
237
+
238
+ it 'returns true' do
239
+ status.should_receive(:type).and_return(:informational)
240
+ expect(status.informational?).to be_true
241
+ end
242
+ end
243
+
244
+ context 'when #type is not :informational' do
245
+
246
+ it 'returns false' do
247
+ status.should_receive(:type).and_return(:dreadful)
248
+ expect(status.informational?).to be_false
249
+ end
250
+ end
251
+ end
252
+
253
+ describe '#success?' do
254
+
255
+ context 'when #type is :success' do
256
+
257
+ it 'returns true' do
258
+ status.should_receive(:type).and_return(:success)
259
+ expect(status.success?).to be_true
260
+ end
261
+ end
262
+
263
+ context 'when #type is not :success' do
264
+
265
+ it 'returns false' do
266
+ status.should_receive(:type).and_return(:failure)
267
+ expect(status.success?).to be_false
268
+ end
269
+ end
270
+ end
271
+
272
+ describe '#redirect?' do
273
+
274
+ describe 'when #type is :redirect' do
275
+
276
+ it 'returns true' do
277
+ status.should_receive(:type).and_return(:redirect)
278
+ expect(status.redirect?).to be_true
279
+ end
280
+ end
281
+
282
+ describe 'when #type is not redirect' do
283
+
284
+ it 'returns false' do
285
+ status.should_receive(:type).and_return(:stay_here)
286
+ expect(status.redirect?).to be_false
287
+ end
288
+ end
289
+ end
290
+
291
+ describe '#client_error?' do
292
+
293
+ context 'when #type is :client_error' do
294
+
295
+ it 'returns true' do
296
+ status.should_receive(:type).at_least(1).times.and_return(:client_error)
297
+ expect(status.client_error?).to be_true
298
+ end
299
+ end
300
+
301
+ context 'when #type is not :client_error' do
302
+
303
+ it 'returns false' do
304
+ status.should_receive(:type).at_least(1).times.and_return(:not_an_error)
305
+ expect(status.client_error?).to be_false
306
+ end
307
+ end
308
+ end
309
+
310
+ describe '#server_error?' do
311
+
312
+ context 'when #type is :server_error' do
313
+
314
+ it 'returns true' do
315
+ status.should_receive(:type).and_return(:server_error)
316
+ expect(status.server_error?).to be_true
317
+ end
318
+ end
319
+
320
+ context 'when #type is not :server_error' do
321
+
322
+ it 'returns false' do
323
+ status.should_receive(:type).and_return(:useless)
324
+ expect(status.server_error?).to be_false
325
+ end
326
+ end
327
+ end
328
+
329
+ describe '#error?' do
330
+
331
+ context 'when #type is :client_error' do
332
+
333
+ it 'returns true' do
334
+ status.should_receive(:type).at_least(1).times.and_return(:client_error)
335
+ expect(status.error?).to be_true
336
+ end
337
+ end
338
+
339
+ context 'when #type is :server_error' do
340
+
341
+ it 'returns true' do
342
+ status.should_receive(:type).at_least(1).times.and_return(:server_error)
343
+ expect(status.error?).to be_true
344
+ end
345
+ end
346
+
347
+ context 'when #type is neither :client_error nor :server_error' do
348
+
349
+ it 'returns false' do
350
+ status.should_receive(:type).at_least(1).times.and_return(:operator_error)
351
+ expect(status.error?).to be_false
352
+ end
353
+ end
354
+ end
355
+
356
+ describe '#unknown?' do
357
+
358
+ context 'when #type is :unknown' do
359
+
360
+ it 'returns true' do
361
+ status.should_receive(:type).and_return(:unknown)
362
+ expect(status.unknown?).to be_true
363
+ end
364
+ end
365
+
366
+ context 'when #type is not unknown' do
367
+
368
+ it 'returns false' do
369
+ status.should_receive(:type).and_return(:known)
370
+ expect(status.unknown?).to be_false
371
+ end
372
+ end
373
+ end
374
+ end
375
+ end