puffing-billy 0.2.1 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe Billy::JSONUtils do
4
+ describe 'sorting' do
5
+ describe '#sort_hash_keys' do
6
+ it 'sorts simple Hashes' do
7
+ data = {c: 'three',a: 'one',b: 'two'}
8
+ expected = {a: 'one',b: 'two',c: 'three'}
9
+ expect(Billy::JSONUtils::sort_hash_keys(data)).to eq expected
10
+ end
11
+
12
+ it 'does not sort simple Arrays' do
13
+ data = [3,1,2,'two','three','one']
14
+ expect(Billy::JSONUtils::sort_hash_keys(data)).to eq data
15
+ end
16
+
17
+ it 'does not sort multi-dimensional Arrays' do
18
+ data = [[3,2,1],[5,4,6],['b','c','a']]
19
+ expect(Billy::JSONUtils::sort_hash_keys(data)).to eq data
20
+ end
21
+
22
+ it 'sorts multi-dimensional Hashes' do
23
+ data = {c: {l: 2,m: 3,k: 1},a: {f: 3,e: 2,d: 1},b: {i: 2,h: 1,j: 3}}
24
+ expected = {a: {d: 1,e: 2,f: 3},b: {h: 1,i: 2,j: 3},c: {k: 1,l: 2,m: 3}}
25
+ expect(Billy::JSONUtils::sort_hash_keys(data)).to eq expected
26
+ end
27
+
28
+ it 'sorts abnormal data structures' do
29
+ data = {b: [['b','c','a'],{ab: 5,aa: 4, ac: 6},[3,2,1],{ba: true,bc: false, bb: nil}],a: {f: 3,e: 2,d: 1}}
30
+ expected = {a: {d: 1,e: 2,f: 3},b: [['b','c','a'],{aa: 4,ab: 5,ac: 6},[3,2,1],{ba: true, bb: nil,bc: false}]}
31
+ expect(Billy::JSONUtils::sort_hash_keys(data)).to eq expected
32
+ end
33
+ end
34
+
35
+ describe 'sort_json' do
36
+ it 'sorts JSON' do
37
+ data = '{"c":"three","a":"one","b":"two"}'
38
+ expected = '{"a":"one","b":"two","c":"three"}'
39
+ expect(Billy::JSONUtils::sort_json(data)).to eq expected
40
+ end
41
+ end
42
+ end
43
+
44
+ describe 'json?' do
45
+ let(:json) { {a: '1'}.to_json }
46
+ let(:non_json) { 'Not JSON.' }
47
+
48
+ it 'identifies JSON' do
49
+ expect(Billy::JSONUtils::json?(json)).to be_true
50
+ end
51
+ it 'identifies non-JSON' do
52
+ expect(Billy::JSONUtils::json?(non_json)).to be_false
53
+ end
54
+ end
55
+ end
@@ -4,23 +4,23 @@ require 'resolv'
4
4
 
5
5
  shared_examples_for 'a proxy server' do
6
6
  it 'should proxy GET requests' do
7
- http.get('/echo').body.should == 'GET /echo'
7
+ expect(http.get('/echo').body).to eql 'GET /echo'
8
8
  end
9
9
 
10
10
  it 'should proxy POST requests' do
11
- http.post('/echo', :foo => 'bar').body.should == "POST /echo\nfoo=bar"
11
+ expect(http.post('/echo', :foo => 'bar').body).to eql "POST /echo\nfoo=bar"
12
12
  end
13
13
 
14
14
  it 'should proxy PUT requests' do
15
- http.post('/echo', :foo => 'bar').body.should == "POST /echo\nfoo=bar"
15
+ expect(http.post('/echo', :foo => 'bar').body).to eql "POST /echo\nfoo=bar"
16
16
  end
17
17
 
18
18
  it 'should proxy HEAD requests' do
19
- http.head('/echo').headers['HTTP-X-EchoServer'].should == 'HEAD /echo'
19
+ expect(http.head('/echo').headers['HTTP-X-EchoServer']).to eql 'HEAD /echo'
20
20
  end
21
21
 
22
22
  it 'should proxy DELETE requests' do
23
- http.delete('/echo').body.should == 'DELETE /echo'
23
+ expect(http.delete('/echo').body).to eql 'DELETE /echo'
24
24
  end
25
25
  end
26
26
 
@@ -28,31 +28,37 @@ shared_examples_for 'a request stub' do
28
28
  it 'should stub GET requests' do
29
29
  proxy.stub("#{url}/foo").
30
30
  and_return(:text => 'hello, GET!')
31
- http.get('/foo').body.should == 'hello, GET!'
31
+ expect(http.get('/foo').body).to eql 'hello, GET!'
32
+ end
33
+
34
+ it 'should stub GET response statuses' do
35
+ proxy.stub("#{url}/foo").
36
+ and_return(:code => 200)
37
+ expect(http.get('/foo').status).to eql 200
32
38
  end
33
39
 
34
40
  it 'should stub POST requests' do
35
41
  proxy.stub("#{url}/bar", :method => :post).
36
42
  and_return(:text => 'hello, POST!')
37
- http.post('/bar', :foo => :bar).body.should == 'hello, POST!'
43
+ expect(http.post('/bar', :foo => :bar).body).to eql 'hello, POST!'
38
44
  end
39
45
 
40
46
  it 'should stub PUT requests' do
41
47
  proxy.stub("#{url}/baz", :method => :put).
42
48
  and_return(:text => 'hello, PUT!')
43
- http.put('/baz', :foo => :bar).body.should == 'hello, PUT!'
49
+ expect(http.put('/baz', :foo => :bar).body).to eql 'hello, PUT!'
44
50
  end
45
51
 
46
52
  it 'should stub HEAD requests' do
47
53
  proxy.stub("#{url}/bap", :method => :head).
48
54
  and_return(:headers => {'HTTP-X-Hello' => 'hello, HEAD!'})
49
- http.head('/bap').headers['http_x_hello'] == 'hello, HEAD!'
55
+ expect(http.head('/bap').headers['http-x-hello']).to eql 'hello, HEAD!'
50
56
  end
51
57
 
52
58
  it 'should stub DELETE requests' do
53
59
  proxy.stub("#{url}/bam", :method => :delete).
54
60
  and_return(:text => 'hello, DELETE!')
55
- http.delete('/bam').body.should == 'hello, DELETE!'
61
+ expect(http.delete('/bam').body).to eql 'hello, DELETE!'
56
62
  end
57
63
  end
58
64
 
@@ -60,29 +66,39 @@ shared_examples_for 'a cache' do
60
66
 
61
67
  context 'whitelisted GET requests' do
62
68
  it 'should not be cached' do
63
- r = http.get('/foo')
64
- r.body.should == 'GET /foo'
65
- expect {
66
- expect {
67
- r = http.get('/foo')
68
- }.to change { r.headers['HTTP-X-EchoCount'].to_i }.by(1)
69
- }.to_not change { r.body }
69
+ assert_noncached_url
70
+ end
71
+
72
+ context 'with ports' do
73
+ before do
74
+ rack_app_url = URI(http.url_prefix)
75
+ Billy.config.whitelist = ["#{rack_app_url.host}:#{rack_app_url.port}"]
76
+ end
77
+
78
+ it 'should not be cached ' do
79
+ assert_noncached_url
80
+ end
70
81
  end
71
82
  end
72
83
 
73
- context 'other GET requests' do
84
+ context 'non-whitelisted GET requests' do
74
85
  before do
75
86
  Billy.config.whitelist = []
76
87
  end
77
88
 
78
89
  it 'should be cached' do
79
- r = http.get('/foo')
80
- r.body.should == 'GET /foo'
81
- expect {
82
- expect {
83
- r = http.get('/foo')
84
- }.to_not change { r.headers['HTTP-X-EchoCount'] }
85
- }.to_not change { r.body }
90
+ assert_cached_url
91
+ end
92
+
93
+ context 'with ports' do
94
+ before do
95
+ rack_app_url = URI(http.url_prefix)
96
+ Billy.config.whitelist = ["#{rack_app_url.host}:#{rack_app_url.port+1}"]
97
+ end
98
+
99
+ it 'should be cached' do
100
+ assert_cached_url
101
+ end
86
102
  end
87
103
  end
88
104
 
@@ -93,7 +109,7 @@ shared_examples_for 'a cache' do
93
109
 
94
110
  it 'should be cached' do
95
111
  r = http.get('/analytics?some_param=5')
96
- r.body.should == 'GET /analytics'
112
+ expect(r.body).to eql 'GET /analytics'
97
113
  expect {
98
114
  expect {
99
115
  r = http.get('/analytics?some_param=20')
@@ -102,9 +118,20 @@ shared_examples_for 'a cache' do
102
118
  end
103
119
  end
104
120
 
121
+ context 'path_blacklist GET requests' do
122
+ before do
123
+ Billy.config.path_blacklist = ['/api']
124
+ end
125
+
126
+ it 'should be cached' do
127
+ assert_cached_url('/api')
128
+ end
129
+ end
130
+
105
131
  context "cache persistence" do
132
+ let(:cached_key) { proxy.cache.key('get',"#{url}/foo","") }
106
133
  let(:cached_file) do
107
- f = "GET_localhost_#{Digest::SHA1.hexdigest("#{url}/foo")}.yml"
134
+ f = cached_key + ".yml"
108
135
  File.join(Billy.config.cache_path, f)
109
136
  end
110
137
 
@@ -119,8 +146,99 @@ shared_examples_for 'a cache' do
119
146
 
120
147
  it 'should persist' do
121
148
  r = http.get('/foo')
122
- File.exists?(cached_file).should be_true
149
+ expect(File.exists?(cached_file)).to be_true
123
150
  end
151
+
152
+ it 'should be read initially from persistent cache' do
153
+ File.open(cached_file, 'w') do |f|
154
+ cached = {
155
+ :headers => {},
156
+ :content => "GET /foo cached"
157
+ }
158
+ f.write(cached.to_yaml(:Encoding => :Utf8))
159
+ end
160
+
161
+ r = http.get('/foo')
162
+ expect(r.body).to eql 'GET /foo cached'
163
+ end
164
+
165
+ context 'cache_request_headers requests' do
166
+ it 'should not be cached by default' do
167
+ r = http.get('/foo')
168
+ saved_cache = Billy.proxy.cache.fetch_from_persistence(cached_key)
169
+ expect(saved_cache.keys).not_to include :request_headers
170
+ end
171
+
172
+ context 'when enabled' do
173
+ before do
174
+ Billy.config.cache_request_headers = true
175
+ end
176
+
177
+ it 'should be cached' do
178
+ r = http.get('/foo')
179
+ saved_cache = Billy.proxy.cache.fetch_from_persistence(cached_key)
180
+ expect(saved_cache.keys).to include :request_headers
181
+ end
182
+ end
183
+ end
184
+
185
+ context 'ignore_cache_port requests' do
186
+ it 'should be cached without port' do
187
+ r = http.get('/foo')
188
+ url = URI(r.env[:url])
189
+ saved_cache = Billy.proxy.cache.fetch_from_persistence(cached_key)
190
+
191
+ expect(saved_cache[:url]).to_not eql(url.to_s)
192
+ expect(saved_cache[:url]).to eql(url.to_s.gsub(":#{url.port}", ''))
193
+ end
194
+ end
195
+
196
+ context 'non_whitelisted_requests_disabled requests' do
197
+ before { Billy.config.non_whitelisted_requests_disabled = true }
198
+
199
+ it 'should raise error when disabled' do
200
+ #TODO: Suppress stderr output: https://gist.github.com/adamstegman/926858
201
+ expect{http.get('/foo')}.to raise_error(Faraday::Error::ConnectionFailed, "end of file reached")
202
+ end
203
+ end
204
+
205
+ context 'non_successful_cache_disabled requests' do
206
+ before do
207
+ rack_app_url = URI(http_error.url_prefix)
208
+ Billy.config.whitelist = ["#{rack_app_url.host}:#{rack_app_url.port}"]
209
+ Billy.config.non_successful_cache_disabled = true
210
+ end
211
+
212
+ it 'should not cache non-successful response when enabled' do
213
+ http_error.get('/foo')
214
+ expect(File.exists?(cached_file)).to be_false
215
+ end
216
+
217
+ it 'should cache successful response when enabled' do
218
+ assert_cached_url
219
+ end
220
+ end
221
+
222
+ context 'non_successful_error_level requests' do
223
+ before do
224
+ rack_app_url = URI(http_error.url_prefix)
225
+ Billy.config.whitelist = ["#{rack_app_url.host}:#{rack_app_url.port}"]
226
+ Billy.config.non_successful_error_level = :error
227
+ end
228
+
229
+ it 'should raise error for non-successful responses when :error' do
230
+ # When this config setting is set, the EventMachine running the test servers is killed upon error raising
231
+ # The `raise` is required to bubble up the error to the test running it
232
+ # The Faraday error is raised upon `close_connection` so this can be non-pending if we can do one of the following:
233
+ # 1) Remove the `raise error_message` conditionally for this test
234
+ # 2) Restart the test servers if they aren't running
235
+ # 3) Change the test servers to start/stop for each test instead of before all
236
+ # 4) Remove the test server completely and rely on the server instantiated by the app
237
+ pending "Unable to test this without affecting the running test servers"
238
+ expect{http_error.get('/foo')}.to raise_error(Faraday::Error::ConnectionFailed)
239
+ end
240
+ end
241
+
124
242
  end
125
243
 
126
244
  context "disabled" do
@@ -128,24 +246,45 @@ shared_examples_for 'a cache' do
128
246
 
129
247
  it 'shouldnt persist' do
130
248
  r = http.get('/foo')
131
- File.exists?(cached_file).should be_false
249
+ expect(File.exists?(cached_file)).to be_false
132
250
  end
133
251
  end
134
252
  end
253
+
254
+ def assert_noncached_url(url = '/foo')
255
+ r = http.get(url)
256
+ expect(r.body).to eql "GET #{url}"
257
+ expect {
258
+ expect {
259
+ r = http.get(url)
260
+ }.to change { r.headers['HTTP-X-EchoCount'].to_i }.by(1)
261
+ }.to_not change { r.body }
262
+ end
263
+
264
+ def assert_cached_url(url = '/foo')
265
+ r = http.get(url)
266
+ expect(r.body).to eql "GET #{url}"
267
+ expect {
268
+ expect {
269
+ r = http.get(url)
270
+ }.to_not change { r.headers['HTTP-X-EchoCount'] }
271
+ }.to_not change { r.body }
272
+ end
135
273
  end
136
274
 
137
275
  describe Billy::Proxy do
138
276
 
139
277
  before do
140
- @http = Faraday.new @http_url,
141
- :proxy => { :uri => proxy.url },
142
- :keepalive => false,
143
- :timeout => 0.5
144
- @https = Faraday.new @https_url,
145
- :ssl => { :verify => false },
146
- :proxy => { :uri => proxy.url },
147
- :keepalive => false,
148
- :timeout => 0.5
278
+ # Adding non-valid Faraday options throw an error: https://github.com/arsduo/koala/pull/311
279
+ # Valid options: :request, :proxy, :ssl, :builder, :url, :parallel_manager, :params, :headers, :builder_class
280
+ faraday_options = {
281
+ :proxy => { :uri => proxy.url },
282
+ :request => { :timeout => 0.5 }
283
+ }
284
+
285
+ @http = Faraday.new @http_url, faraday_options
286
+ @https = Faraday.new @https_url, faraday_options.merge(:ssl => { :verify => false })
287
+ @http_error = Faraday.new @error_url, faraday_options
149
288
  end
150
289
 
151
290
  context 'proxying' do
@@ -165,13 +304,13 @@ describe Billy::Proxy do
165
304
  context 'stubbing' do
166
305
 
167
306
  context 'HTTP' do
168
- let!(:url) { @http_url }
307
+ let!(:url) { @http_url }
169
308
  let!(:http) { @http }
170
309
  it_should_behave_like 'a request stub'
171
310
  end
172
311
 
173
312
  context 'HTTPS' do
174
- let!(:url) { @https_url }
313
+ let!(:url) { @https_url }
175
314
  let!(:http) { @https }
176
315
  it_should_behave_like 'a request stub'
177
316
  end
@@ -180,18 +319,67 @@ describe Billy::Proxy do
180
319
 
181
320
  context 'caching' do
182
321
 
322
+ it 'defaults to nil scope' do
323
+ expect(proxy.cache.scope).to be_nil
324
+ end
325
+
183
326
  context 'HTTP' do
184
- let!(:url) { @http_url }
185
- let!(:http) { @http }
327
+ let!(:url) { @http_url }
328
+ let!(:http) { @http }
329
+ let!(:http_error) { @http_error }
186
330
  it_should_behave_like 'a cache'
187
331
  end
188
332
 
189
333
  context 'HTTPS' do
190
- let!(:url) { @https_url }
191
- let!(:http) { @https }
334
+ let!(:url) { @https_url }
335
+ let!(:http) { @https }
336
+ let!(:http_error) { @http_error }
192
337
  it_should_behave_like 'a cache'
193
338
  end
194
339
 
195
- end
340
+ context 'with a cache scope' do
341
+ let!(:url) { @http_url }
342
+ let!(:http) { @http }
343
+ let!(:http_error) { @http_error }
344
+
345
+ before do
346
+ proxy.cache.scope_to "my_cache"
347
+ end
348
+
349
+ after do
350
+ proxy.cache.use_default_scope
351
+ end
352
+
353
+ it_should_behave_like 'a cache'
354
+
355
+ it 'uses the cache scope' do
356
+ expect(proxy.cache.scope).to eq("my_cache")
357
+ end
196
358
 
359
+ it 'can be reset to the default scope' do
360
+ proxy.cache.use_default_scope
361
+ expect(proxy.cache.scope).to be_nil
362
+ end
363
+
364
+ it 'can execute a block against a cache scope' do
365
+ expect(proxy.cache.scope).to eq "my_cache"
366
+ proxy.cache.with_scope "another_cache" do
367
+ expect(proxy.cache.scope).to eq "another_cache"
368
+ end
369
+ expect(proxy.cache.scope).to eq "my_cache"
370
+ end
371
+
372
+ it 'requires a block to be passed to with_scope' do
373
+ expect {proxy.cache.with_scope "some_scope"}.to raise_error ArgumentError
374
+ end
375
+
376
+ it 'should have different keys for the same request under a different scope' do
377
+ args = ['get',"#{url}/foo",""]
378
+ key = proxy.cache.key(*args)
379
+ proxy.cache.with_scope "another_cache" do
380
+ expect(proxy.cache.key(*args)).to_not eq key
381
+ end
382
+ end
383
+ end
384
+ end
197
385
  end