puffing-billy 0.2.1 → 0.2.3

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.
@@ -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