puffing-billy 0.2.3 → 0.3.0

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,124 @@
1
+ require 'spec_helper'
2
+
3
+ describe Billy::CacheHandler do
4
+ let(:handler) { Billy::CacheHandler.new }
5
+ let(:request) { {
6
+ method: 'post',
7
+ url: 'http://example.test:8080/index?some=param&callback=dynamicCallback5678',
8
+ headers: {'Accept-Encoding' => 'gzip',
9
+ 'Cache-Control' => 'no-cache' },
10
+ body: 'Some body'
11
+ } }
12
+
13
+ it 'delegates #reset to the cache' do
14
+ expect(Billy::Cache.instance).to receive(:reset).at_least(:once)
15
+ handler.reset
16
+ end
17
+
18
+ it 'delegates #cached? to the cache' do
19
+ expect(Billy::Cache.instance).to receive :cached?
20
+ handler.cached?
21
+ end
22
+
23
+ describe '#handles_request?' do
24
+ it 'handles the request if it is cached' do
25
+ expect(Billy::Cache.instance).to receive(:cached?).and_return(true)
26
+ expect(handler.handles_request?(nil,nil,nil,nil)).to be true
27
+ end
28
+
29
+ it 'does not handle the request if it is not cached' do
30
+ expect(Billy::Cache.instance).to receive(:cached?).and_return(false)
31
+ expect(handler.handles_request?(nil,nil,nil,nil)).to be false
32
+ end
33
+ end
34
+
35
+ describe '#handle_request' do
36
+ it 'returns nil if the request cannot be handled' do
37
+ expect(Billy::Cache.instance).to receive(:cached?).and_return(false)
38
+ expect(handler.handle_request(request[:method],
39
+ request[:url],
40
+ request[:headers],
41
+ request[:body])).to be nil
42
+ end
43
+
44
+ it 'returns a cached response if the request can be handled' do
45
+ expect(Billy::Cache.instance).to receive(:cached?).and_return(true)
46
+ expect(Billy::Cache.instance).to receive(:fetch).and_return({:status=>200, :headers=>{"Connection"=>"close"}, :content=>"The response body"})
47
+ expect(handler.handle_request(request[:method],
48
+ request[:url],
49
+ request[:headers],
50
+ request[:body])).to eql({:status=>200, :headers=>{"Connection"=>"close"}, :content=>"The response body"})
51
+ end
52
+
53
+ context 'updating jsonp callback names enabled' do
54
+ before do
55
+ Billy.config.dynamic_jsonp = true
56
+ end
57
+
58
+ it 'updates the cached response if the callback is dynamic' do
59
+ expect(Billy::Cache.instance).to receive(:cached?).and_return(true)
60
+ expect(Billy::Cache.instance).to receive(:fetch).and_return({:status=>200, :headers=>{"Connection"=>"close"}, :content=> 'dynamicCallback1234({"yolo":"kitten"})'})
61
+ expect(handler.handle_request(request[:method],
62
+ request[:url],
63
+ request[:headers],
64
+ request[:body])).to eql({:status=>200, :headers=>{"Connection"=>"close"}, :content=>'dynamicCallback5678({"yolo":"kitten"})'})
65
+ end
66
+
67
+ it 'is flexible about the format of the response body' do
68
+ expect(Billy::Cache.instance).to receive(:cached?).and_return(true)
69
+ expect(Billy::Cache.instance).to receive(:fetch).and_return({:status=>200, :headers=>{"Connection"=>"close"}, :content=> "/**/ dynamicCallback1234(\n{\"yolo\":\"kitten\"})"})
70
+ expect(handler.handle_request(request[:method],
71
+ request[:url],
72
+ request[:headers],
73
+ request[:body])).to eql({:status=>200, :headers=>{"Connection"=>"close"}, :content=>"/**/ dynamicCallback5678(\n{\"yolo\":\"kitten\"})"})
74
+ end
75
+
76
+ it 'does not interfere with non-jsonp requests' do
77
+ jsonp_request = request
78
+ other_request = {
79
+ method: 'get',
80
+ url: 'http://example.test:8080/index?hanukkah=latkes',
81
+ headers: {'Accept-Encoding'=>'gzip', 'Cache-Control'=>'no-cache' },
82
+ body: 'no jsonp'
83
+ }
84
+
85
+ allow(Billy::Cache.instance).to receive(:cached?).and_return(true)
86
+ allow(Billy::Cache.instance).to receive(:fetch).with(jsonp_request[:method], jsonp_request[:url], jsonp_request[:body]).and_return({:status=>200,
87
+ :headers=>{"Connection"=>"close"},
88
+ :content=> 'dynamicCallback1234({"yolo":"kitten"})'})
89
+ allow(Billy::Cache.instance).to receive(:fetch).with(other_request[:method], other_request[:url], other_request[:body]).and_return({:status=>200,
90
+ :headers=>{"Connection"=>"close"},
91
+ :content=> 'no jsonp but has parentheses()'})
92
+
93
+ expect(handler.handle_request(other_request[:method],
94
+ other_request[:url],
95
+ other_request[:headers],
96
+ other_request[:body])).to eql({:status=>200, :headers=>{"Connection"=>"close"}, :content=>'no jsonp but has parentheses()'})
97
+ end
98
+ end
99
+
100
+ context 'updating jsonp callback names disabled' do
101
+ before do
102
+ Billy.config.dynamic_jsonp = false
103
+ end
104
+
105
+ it 'does not change the response' do
106
+ expect(Billy::Cache.instance).to receive(:cached?).and_return(true)
107
+ expect(Billy::Cache.instance).to receive(:fetch).and_return({:status=>200, :headers=>{"Connection"=>"close"}, :content=> 'dynamicCallback1234({"yolo":"kitten"})'})
108
+ expect(handler.handle_request(request[:method],
109
+ request[:url],
110
+ request[:headers],
111
+ request[:body])).to eql({:status=>200, :headers=>{"Connection"=>"close"}, :content=>'dynamicCallback1234({"yolo":"kitten"})'})
112
+ end
113
+ end
114
+
115
+ it 'returns nil if the Cache fails to handle the response for some reason' do
116
+ expect(Billy::Cache.instance).to receive(:cached?).and_return(true)
117
+ expect(Billy::Cache.instance).to receive(:fetch).and_return(nil)
118
+ expect(handler.handle_request(request[:method],
119
+ request[:url],
120
+ request[:headers],
121
+ request[:body])).to be nil
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Billy::Handler do
4
+ let(:handler) { Class.new{ include Billy::Handler }.new }
5
+ it '#handle_request raises an error if not overridden' do
6
+ expect(handler.handle_request(nil,nil,nil,nil)).to eql({ error: 'The handler has not overridden the handle_request method!' })
7
+ end
8
+
9
+ it '#handles_request returns false by default' do
10
+ expect(handler.handles_request?(nil,nil,nil,nil)).to be false
11
+ end
12
+
13
+ it 'responds to #reset' do
14
+ expect(handler).to respond_to :reset
15
+ end
16
+ end
@@ -0,0 +1,171 @@
1
+ require 'spec_helper'
2
+
3
+ describe Billy::ProxyHandler do
4
+ subject { Billy::ProxyHandler.new }
5
+ let(:request) { {
6
+ method: 'post',
7
+ url: 'http://example.test:8080/index?some=param',
8
+ headers: {'Accept-Encoding' => 'gzip',
9
+ 'Cache-Control' => 'no-cache' },
10
+ body: 'Some body'
11
+ } }
12
+
13
+ describe '#handles_request?' do
14
+ context 'with non-whitelisted requests enabled' do
15
+ before do
16
+ expect(Billy.config).to receive(:non_whitelisted_requests_disabled).and_return(false)
17
+ end
18
+
19
+ it 'handles all requests' do
20
+ expect(subject.handles_request?(request[:method],
21
+ request[:url],
22
+ request[:headers],
23
+ request[:body])).to be true
24
+ end
25
+ end
26
+ context 'with non-whitelisted requests disabled' do
27
+ before do
28
+ expect(Billy.config).to receive(:non_whitelisted_requests_disabled).and_return(true)
29
+ end
30
+
31
+ it 'does not handle requests that are not white or black listed' do
32
+ expect(subject.handles_request?(request[:method],
33
+ request[:url],
34
+ request[:headers],
35
+ request[:body])).to be false
36
+ end
37
+
38
+ context 'a whitelisted host' do
39
+ context 'with a blacklisted path' do
40
+ before do
41
+ expect(Billy.config).to receive(:path_blacklist) { ['/index'] }
42
+ end
43
+
44
+ it 'does not handle requests for blacklisted paths' do
45
+ expect(subject.handles_request?(request[:method],
46
+ 'http://example.test:8080/index?some=param',
47
+ request[:headers],
48
+ request[:body])).to be false
49
+ end
50
+ end
51
+ context 'without a port' do
52
+ before do
53
+ expect(Billy.config).to receive(:whitelist) { ['example.test'] }
54
+ end
55
+
56
+ it 'handles requests for the host without a port' do
57
+ expect(subject.handles_request?(request[:method],
58
+ 'http://example.test',
59
+ request[:headers],
60
+ request[:body])).to be true
61
+ end
62
+
63
+ it 'handles requests for the host with a port' do
64
+ expect(subject.handles_request?(request[:method],
65
+ 'http://example.test:8080',
66
+ request[:headers],
67
+ request[:body])).to be true
68
+ end
69
+ end
70
+
71
+ context 'with a port' do
72
+ before do
73
+ expect(Billy.config).to receive(:whitelist) { ['example.test:8080'] }
74
+ end
75
+
76
+ it 'does not handle requests whitelisted for a specific port' do
77
+ expect(subject.handles_request?(request[:method],
78
+ 'http://example.test',
79
+ request[:headers],
80
+ request[:body])).to be false
81
+ end
82
+
83
+ it 'handles requests for the host with a port' do
84
+ expect(subject.handles_request?(request[:method],
85
+ 'http://example.test:8080',
86
+ request[:headers],
87
+ request[:body])).to be true
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ describe '#handle_request' do
95
+ it 'returns nil if it does not handle the request' do
96
+ expect(subject).to receive(:handles_request?).and_return(false)
97
+ expect(subject.handle_request(request[:method],
98
+ request[:url],
99
+ request[:headers],
100
+ request[:body])).to be nil
101
+ end
102
+
103
+ context 'with a handled request' do
104
+ let(:response_header) do
105
+ header = Struct.new(:status, :raw).new
106
+ header.status = 200
107
+ header.raw = {}
108
+ header
109
+ end
110
+
111
+ let(:em_response) { double("response") }
112
+ let(:em_request) {
113
+ double("EM::HttpRequest", :error => nil, :response => em_response, :response_header => response_header)
114
+ }
115
+
116
+ before do
117
+ allow(subject).to receive(:handles_request?).and_return(true)
118
+ allow(em_response).to receive(:force_encoding).and_return("The response body")
119
+ allow(EventMachine::HttpRequest).to receive(:new).and_return(em_request)
120
+ expect(em_request).to receive(:post).and_return(em_request)
121
+ end
122
+
123
+ it 'returns any error in the response' do
124
+ allow(em_request).to receive(:error).and_return("ERROR!")
125
+ expect(subject.handle_request(request[:method],
126
+ request[:url],
127
+ request[:headers],
128
+ request[:body])).to eql({ :error => "Request to #{request[:url]} failed with error: ERROR!"})
129
+ end
130
+
131
+ it 'returns a hashed response if the request succeeds' do
132
+ expect(subject.handle_request(request[:method],
133
+ request[:url],
134
+ request[:headers],
135
+ request[:body])).to eql({:status=>200, :headers=>{"Connection"=>"close"}, :content=>"The response body"})
136
+ end
137
+
138
+ it 'returns nil if both the error and response are for some reason nil' do
139
+ allow(em_request).to receive(:response).and_return(nil)
140
+ expect(subject.handle_request(request[:method],
141
+ request[:url],
142
+ request[:headers],
143
+ request[:body])).to be nil
144
+ end
145
+
146
+ it 'caches the response if cacheable' do
147
+ expect(subject).to receive(:allowed_response_code?).and_return(true)
148
+ expect(Billy::Cache.instance).to receive(:store)
149
+ subject.handle_request(request[:method],
150
+ request[:url],
151
+ request[:headers],
152
+ request[:body])
153
+ end
154
+
155
+ it 'uses the timeouts defined in configuration' do
156
+ allow(Billy.config).to receive(:proxied_request_inactivity_timeout).and_return(42)
157
+ allow(Billy.config).to receive(:proxied_request_connect_timeout).and_return(24)
158
+
159
+ expect(EventMachine::HttpRequest).to receive(:new).with(request[:url], {
160
+ inactivity_timeout: 42,
161
+ connect_timeout: 24
162
+ })
163
+
164
+ subject.handle_request(request[:method],
165
+ request[:url],
166
+ request[:headers],
167
+ request[:body])
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,144 @@
1
+ require 'spec_helper'
2
+
3
+ describe Billy::RequestHandler do
4
+ subject { Billy::RequestHandler.new }
5
+
6
+ it 'implements Handler' do
7
+ expect(subject).to be_a Billy::Handler
8
+ end
9
+
10
+ describe '#handlers' do
11
+ it 'has a stub handler' do
12
+ expect(subject.handlers[:stubs]).to be_a Billy::StubHandler
13
+ end
14
+
15
+ it 'has a cache handler' do
16
+ expect(subject.handlers[:cache]).to be_a Billy::CacheHandler
17
+ end
18
+
19
+ it 'has a proxy handler' do
20
+ expect(subject.handlers[:proxy]).to be_a Billy::ProxyHandler
21
+ end
22
+ end
23
+
24
+ context 'with stubbed handlers' do
25
+ let(:args) { ["get","url","headers","body"] }
26
+ let(:stub_handler) { double("StubHandler") }
27
+ let(:cache_handler) { double("CacheHandler") }
28
+ let(:proxy_handler) { double("ProxyHandler") }
29
+ let(:handlers) { {
30
+ :stubs => stub_handler,
31
+ :cache => cache_handler,
32
+ :proxy => proxy_handler
33
+ } }
34
+
35
+ before do
36
+ allow(subject).to receive(:handlers).and_return(handlers)
37
+ end
38
+
39
+ describe '#handles_request?' do
40
+ it 'returns false if no handlers handle the request' do
41
+ handlers.each do |key,handler|
42
+ expect(handler).to receive(:handles_request?).with(*args).and_return(false)
43
+ end
44
+ expect(subject.handles_request?(*args)).to be false
45
+ end
46
+
47
+ it 'returns true immediately if the stub handler handles the request' do
48
+ expect(stub_handler).to receive(:handles_request?).with(*args).and_return(true)
49
+ expect(cache_handler).to_not receive(:handles_request?)
50
+ expect(proxy_handler).to_not receive(:handles_request?)
51
+ expect(subject.handles_request?(*args)).to be true
52
+ end
53
+
54
+ it 'returns true if the cache handler handles the request' do
55
+ expect(stub_handler).to receive(:handles_request?).with(*args).and_return(false)
56
+ expect(cache_handler).to receive(:handles_request?).with(*args).and_return(true)
57
+ expect(proxy_handler).to_not receive(:handles_request?)
58
+ expect(subject.handles_request?(*args)).to be true
59
+ end
60
+
61
+ it 'returns true if the proxy handler handles the request' do
62
+ expect(stub_handler).to receive(:handles_request?).with(*args).and_return(false)
63
+ expect(cache_handler).to receive(:handles_request?).with(*args).and_return(false)
64
+ expect(proxy_handler).to receive(:handles_request?).with(*args).and_return(true)
65
+ expect(subject.handles_request?(*args)).to be true
66
+ end
67
+ end
68
+
69
+ describe '#handle_request' do
70
+ it 'returns stubbed responses' do
71
+ expect(stub_handler).to receive(:handle_request).with(*args).and_return("foo")
72
+ expect(cache_handler).to_not receive(:handle_request)
73
+ expect(proxy_handler).to_not receive(:handle_request)
74
+ expect(subject.handle_request(*args)).to eql "foo"
75
+ end
76
+
77
+ it 'returns cached responses' do
78
+ expect(stub_handler).to receive(:handle_request).with(*args)
79
+ expect(cache_handler).to receive(:handle_request).with(*args).and_return("bar")
80
+ expect(proxy_handler).to_not receive(:handle_request)
81
+ expect(subject.handle_request(*args)).to eql "bar"
82
+ end
83
+
84
+ it 'returns proxied responses' do
85
+ expect(stub_handler).to receive(:handle_request).with(*args)
86
+ expect(cache_handler).to receive(:handle_request).with(*args)
87
+ expect(proxy_handler).to receive(:handle_request).with(*args).and_return("baz")
88
+ expect(subject.handle_request(*args)).to eql "baz"
89
+ end
90
+
91
+ it 'returns an error hash if request is not handled' do
92
+ expect(stub_handler).to receive(:handle_request).with(*args)
93
+ expect(cache_handler).to receive(:handle_request).with(*args)
94
+ expect(proxy_handler).to receive(:handle_request).with(*args)
95
+ expect(subject.handle_request(*args)).to eql({ :error => "Connection to url not cached and new http connections are disabled" })
96
+ end
97
+
98
+ it 'returns an error hash with body message if POST request is not handled' do
99
+ args[0] = 'post'
100
+ expect(stub_handler).to receive(:handle_request).with(*args)
101
+ expect(cache_handler).to receive(:handle_request).with(*args)
102
+ expect(proxy_handler).to receive(:handle_request).with(*args)
103
+ expect(subject.handle_request(*args)).to eql({ :error => "Connection to url with body 'body' not cached and new http connections are disabled" })
104
+ end
105
+ end
106
+
107
+ describe '#stub' do
108
+ it 'delegates to the stub_handler' do
109
+ expect(stub_handler).to receive(:stub).with("some args")
110
+ subject.stub("some args")
111
+ end
112
+ end
113
+
114
+ describe '#reset' do
115
+ it 'resets all of the handlers' do
116
+ handlers.each do |key,handler|
117
+ expect(handler).to receive(:reset)
118
+ end
119
+ subject.reset
120
+ end
121
+ end
122
+
123
+ describe '#reset_stubs' do
124
+ it 'resets the stub handler' do
125
+ expect(stub_handler).to receive(:reset)
126
+ subject.reset_stubs
127
+ end
128
+ end
129
+
130
+ describe '#reset_cache' do
131
+ it 'resets the cache handler' do
132
+ expect(cache_handler).to receive(:reset)
133
+ subject.reset_cache
134
+ end
135
+ end
136
+
137
+ describe '#restore_cache' do
138
+ it 'resets the cache handler' do
139
+ expect(cache_handler).to receive(:reset)
140
+ subject.reset_cache
141
+ end
142
+ end
143
+ end
144
+ end