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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 77894c8ea71e9f2b91cb3aae48c6bc1311ab45ba
4
+ data.tar.gz: 2af9cc1a8fafdfafa15ac9bf1d088e2cb648ae59
5
+ SHA512:
6
+ metadata.gz: 00faa0d3c37550847f947b5440770e628978593476771c27f26d31516b8ff176540340a00cba098b2fca5ebcd86bc53b29a94d53af57c4b92547be3a3b07f603
7
+ data.tar.gz: 642297885633e8f29efab30ad78faef48d173dd4b4a902cb92cbef118c616b349a01671d27f5b3b38af8b8ad4b341f251d0d23fdff9e1b7b3096b118efd246a6
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  node_modules/
2
2
  log/test.log
3
+ .idea/
3
4
  .ruby-version
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- puffing-billy (0.2.1)
4
+ puffing-billy (0.2.3)
5
5
  capybara
6
6
  em-http-request
7
7
  eventmachine
@@ -12,108 +12,120 @@ PATH
12
12
  GEM
13
13
  remote: https://rubygems.org/
14
14
  specs:
15
- addressable (2.3.3)
16
- builder (3.2.0)
17
- capybara (2.0.2)
15
+ addressable (2.3.5)
16
+ builder (3.2.2)
17
+ capybara (2.1.0)
18
18
  mime-types (>= 1.16)
19
19
  nokogiri (>= 1.3.3)
20
20
  rack (>= 1.0.0)
21
21
  rack-test (>= 0.5.4)
22
- selenium-webdriver (~> 2.0)
23
- xpath (~> 1.0.0)
24
- capybara-webkit (0.14.2)
25
- capybara (~> 2.0, >= 2.0.2)
22
+ xpath (~> 2.0)
23
+ capybara-webkit (1.1.1)
24
+ capybara (>= 2.0.2, < 2.2.0)
26
25
  json
27
- childprocess (0.3.8)
26
+ celluloid (0.15.2)
27
+ timers (~> 1.1.0)
28
+ childprocess (0.4.0)
28
29
  ffi (~> 1.0, >= 1.0.11)
29
- coderay (1.0.9)
30
+ cliver (0.3.2)
31
+ coderay (1.1.0)
30
32
  cookiejar (0.3.0)
31
- cucumber (1.2.5)
33
+ cucumber (1.3.10)
32
34
  builder (>= 2.1.2)
33
35
  diff-lcs (>= 1.1.3)
34
- gherkin (~> 2.11.7)
35
- multi_json (~> 1.3)
36
+ gherkin (~> 2.12)
37
+ multi_json (>= 1.7.5, < 2.0)
38
+ multi_test (>= 0.0.2)
36
39
  daemons (1.1.9)
37
- diff-lcs (1.2.1)
38
- em-http-request (1.0.3)
39
- addressable (>= 2.2.3)
40
+ diff-lcs (1.2.5)
41
+ em-http-request (1.1.2)
42
+ addressable (>= 2.3.4)
40
43
  cookiejar
41
- em-socksify
42
- eventmachine (>= 1.0.0.beta.4)
43
- http_parser.rb (>= 0.5.3)
44
- em-socksify (0.2.1)
44
+ em-socksify (>= 0.3)
45
+ eventmachine (>= 1.0.3)
46
+ http_parser.rb (>= 0.6.0)
47
+ em-socksify (0.3.0)
45
48
  eventmachine (>= 1.0.0.beta.4)
46
- eventmachine (1.0.1)
49
+ eventmachine (1.0.3)
47
50
  eventmachine_httpserver (0.2.1)
48
- faraday (0.8.6)
49
- multipart-post (~> 1.1)
50
- faye-websocket (0.4.7)
51
- eventmachine (>= 0.12.0)
52
- ffi (1.4.0)
53
- gherkin (2.11.8)
51
+ faraday (0.9.0)
52
+ multipart-post (>= 1.2, < 3)
53
+ ffi (1.9.3)
54
+ formatador (0.2.4)
55
+ gherkin (2.12.2)
54
56
  multi_json (~> 1.3)
55
- guard (1.6.2)
56
- listen (>= 0.6.0)
57
- lumberjack (>= 1.0.2)
58
- pry (>= 0.9.10)
59
- terminal-table (>= 1.4.3)
60
- thor (>= 0.14.6)
61
- http_parser.rb (0.5.3)
62
- json (1.7.7)
63
- listen (0.7.3)
64
- lumberjack (1.0.2)
65
- method_source (0.8.1)
66
- mime-types (1.21)
67
- multi_json (1.6.1)
68
- multipart-post (1.2.0)
69
- nokogiri (1.5.6)
70
- poltergeist (1.1.0)
71
- capybara (~> 2.0, >= 2.0.1)
72
- faye-websocket (~> 0.4, >= 0.4.4)
73
- http_parser.rb (~> 0.5.3)
74
- pry (0.9.12)
75
- coderay (~> 1.0.5)
57
+ guard (2.4.0)
58
+ formatador (>= 0.2.4)
59
+ listen (~> 2.1)
60
+ lumberjack (~> 1.0)
61
+ pry (>= 0.9.12)
62
+ thor (>= 0.18.1)
63
+ http_parser.rb (0.6.0)
64
+ json (1.8.1)
65
+ listen (2.4.1)
66
+ celluloid (>= 0.15.2)
67
+ rb-fsevent (>= 0.9.3)
68
+ rb-inotify (>= 0.9)
69
+ lumberjack (1.0.4)
70
+ method_source (0.8.2)
71
+ mime-types (2.1)
72
+ mini_portile (0.5.2)
73
+ multi_json (1.8.4)
74
+ multi_test (0.0.3)
75
+ multipart-post (2.0.0)
76
+ nokogiri (1.6.1)
77
+ mini_portile (~> 0.5.0)
78
+ poltergeist (1.5.0)
79
+ capybara (~> 2.1)
80
+ cliver (~> 0.3.1)
81
+ multi_json (~> 1.0)
82
+ websocket-driver (>= 0.2.0)
83
+ pry (0.9.12.6)
84
+ coderay (~> 1.0)
76
85
  method_source (~> 0.8)
77
86
  slop (~> 3.4)
78
87
  rack (1.5.2)
79
88
  rack-test (0.6.2)
80
89
  rack (>= 1.0)
81
- rb-inotify (0.9.0)
90
+ rb-fsevent (0.9.4)
91
+ rb-inotify (0.9.3)
82
92
  ffi (>= 0.5.0)
83
- rspec (2.13.0)
84
- rspec-core (~> 2.13.0)
85
- rspec-expectations (~> 2.13.0)
86
- rspec-mocks (~> 2.13.0)
87
- rspec-core (2.13.0)
88
- rspec-expectations (2.13.0)
93
+ rspec (2.14.1)
94
+ rspec-core (~> 2.14.0)
95
+ rspec-expectations (~> 2.14.0)
96
+ rspec-mocks (~> 2.14.0)
97
+ rspec-core (2.14.7)
98
+ rspec-expectations (2.14.5)
89
99
  diff-lcs (>= 1.1.3, < 2.0)
90
- rspec-mocks (2.13.0)
91
- rubyzip (0.9.9)
92
- selenium-webdriver (2.30.0)
100
+ rspec-mocks (2.14.5)
101
+ rubyzip (1.1.0)
102
+ selenium-webdriver (2.39.0)
93
103
  childprocess (>= 0.2.5)
94
104
  multi_json (~> 1.0)
95
- rubyzip
105
+ rubyzip (~> 1.0)
96
106
  websocket (~> 1.0.4)
97
- slop (3.4.3)
98
- terminal-table (1.4.5)
99
- thin (1.5.0)
107
+ slop (3.4.7)
108
+ thin (1.6.1)
100
109
  daemons (>= 1.0.9)
101
- eventmachine (>= 0.12.6)
110
+ eventmachine (>= 1.0.0)
102
111
  rack (>= 1.0.0)
103
- thor (0.17.0)
112
+ thor (0.18.1)
113
+ timers (1.1.0)
104
114
  websocket (1.0.7)
105
- xpath (1.0.0)
115
+ websocket-driver (0.3.2)
116
+ xpath (2.0.0)
106
117
  nokogiri (~> 1.3)
107
118
 
108
119
  PLATFORMS
109
120
  ruby
110
121
 
111
122
  DEPENDENCIES
112
- capybara-webkit
123
+ capybara-webkit (~> 1.0)
113
124
  cucumber
114
125
  faraday
115
126
  guard
116
127
  poltergeist
128
+ pry
117
129
  puffing-billy!
118
130
  rack
119
131
  rb-inotify
data/README.md CHANGED
@@ -57,6 +57,10 @@ Capybara.javascript_driver = :selenium_billy
57
57
  # Capybara.javascript_driver = :poltergeist_billy
58
58
  ```
59
59
 
60
+ Note: :poltergeist_billy doesn't support proxying any localhosts, so you must use
61
+ :webkit_billy for headless specs when using puffing-billy for other local rack apps.
62
+ See [this phantomjs issue](https://github.com/ariya/phantomjs/issues/11342) for any updates.
63
+
60
64
  In your tests:
61
65
 
62
66
  ```ruby
@@ -150,6 +154,16 @@ Billy.configure do |c|
150
154
  end
151
155
  ```
152
156
 
157
+ If you would like to cache other local rack apps, you must whitelist only the
158
+ specific port for the application that is executing tests. If you are using
159
+ [Capybara](https://github.com/jnicklas/capybara), this can be accomplished by
160
+ adding this in your `spec_helper.rb`:
161
+
162
+ ```ruby
163
+ server = Capybara.current_session.server
164
+ Billy.config.whitelist = ["#{server.host}:#{server.port}"]
165
+ ```
166
+
153
167
  If you want to use puffing-billy like you would [VCR](https://github.com/vcr/vcr)
154
168
  you can turn on cache persistence. This way you don't have to manually mock out
155
169
  everything as requests are automatically recorded and played back. With cache
@@ -158,6 +172,7 @@ persistence you can take tests completely offline.
158
172
  ```ruby
159
173
  Billy.configure do |c|
160
174
  c.cache = true
175
+ c.cache_request_headers = false
161
176
  c.ignore_params = ["http://www.google-analytics.com/__utm.gif",
162
177
  "https://r.twimg.com/jot",
163
178
  "http://p.twitter.com/t.gif",
@@ -165,22 +180,112 @@ Billy.configure do |c|
165
180
  "http://www.facebook.com/plugins/like.php",
166
181
  "https://www.facebook.com/dialog/oauth",
167
182
  "http://cdn.api.twitter.com/1/urls/count.json"]
183
+ c.path_blacklist = []
168
184
  c.persist_cache = true
185
+ c.ignore_cache_port = true # defaults to true
186
+ c.non_successful_cache_disabled = false
187
+ c.non_successful_error_level = :warn
188
+ c.non_whitelisted_requests_disabled = false
169
189
  c.cache_path = 'spec/req_cache/'
170
190
  end
171
-
172
- # need to call this because of a race condition between persist_cache
173
- # being set and the proxy being loaded for the first time
174
- Billy.proxy.restore_cache
175
191
  ```
176
192
 
193
+ The cache works with all types of requests and will distinguish between
194
+ different POST requests to the same URL.
195
+
196
+ `c.cache_request_headers` is used to store the outgoing request headers in the cache.
197
+ It is also saved to yml if `persist_cache` is enabled. This additional information
198
+ is useful for debugging (for example: viewing the referer of the request).
199
+
177
200
  `c.ignore_params` is used to ignore parameters of certain requests when
178
201
  caching. You should mostly use this for analytics and various social buttons as
179
202
  they use cache avoidance techniques, but return practically the same response
180
203
  that most often does not affect your test results.
181
204
 
182
- The cache works with all types of requests and will distinguish between
183
- different POST requests to the same URL.
205
+ `c.path_blacklist = []` is used to always cache specific paths on any hostnames,
206
+ including whitelisted ones. This is useful if your AUT has routes that get data
207
+ from external services, such as `/api` where the ajax request is a local URL but
208
+ the actual data is coming from a different application that you want to cache.
209
+
210
+ `c.ignore_cache_port` is used to strip the port from the URL if it exists. This
211
+ is useful when caching local paths (via `path_blacklist`) or other local rack apps
212
+ that are running on random ports.
213
+
214
+ `c.non_successful_cache_disabled` is used to not cache responses without 200-series
215
+ or 304 status codes. This prevents unauthorized or internal server errors from
216
+ being cached and used for future test runs.
217
+
218
+ `c.non_successful_error_level` is used to log when non-successful resposnes are
219
+ received. By default, it just writes to the log file, but when set to `:error`
220
+ it throws an error with the URL and status code received for easier debugging.
221
+
222
+ `c.non_whitelisted_requests_disabled` is used to disable hitting new URLs when
223
+ no cache file exists. Only whitelisted URLs (on non-blacklisted paths) are
224
+ allowed, all others will throw an error with the URL attempted to be accessed.
225
+ This is useful for debugging issues in isolated environments (ie.
226
+ continuous integration).
227
+
228
+ ### Cache Scopes
229
+
230
+ If you need to cache different responses to the same HTTP request, you can use
231
+ cache scoping.
232
+
233
+ For example, an index page may return zero or more items in a list, with or
234
+ without pagination, depending on the number of entries in a database.
235
+
236
+ There are a few different ways to use cache scopes:
237
+
238
+ ```ruby
239
+ # If you do nothing, it uses the default cache scope:
240
+ it 'defaults to nil scope' do
241
+ expect(proxy.cache.scope).to be_nil
242
+ end
243
+
244
+ # You can change context indefinitely to a specific cache scope:
245
+ context 'with a cache scope' do
246
+ before do
247
+ proxy.cache.scope_to "my_cache"
248
+ end
249
+
250
+ # Remember to set the cache scope back to the default in an after block
251
+ # within the context it is used, and/or at the global spec_helper level!
252
+ after do
253
+ proxy.cache.use_default_scope
254
+ end
255
+
256
+ it 'uses the cache scope' do
257
+ expect(proxy.cache.scope).to eq("my_cache")
258
+ end
259
+
260
+ it 'can be reset to the default scope' do
261
+ proxy.cache.use_default_scope
262
+ expect(proxy.cache.scope).to be_nil
263
+ end
264
+
265
+ # Or you can run a block within the context of a cache scope:
266
+ # Note: When using scope blocks, be sure that both the action that triggers a
267
+ # request and the assertion that a response has been received are within the block
268
+ it 'can execute a block against a named cache' do
269
+ expect(proxy.cache.scope).to eq("my_cache")
270
+ proxy.cache.with_scope "another_cache" do
271
+ expect(proxy.cache.scope).to eq "another_cache"
272
+ end
273
+ # It
274
+ expect(proxy.cache.scope).to eq("my_cache")
275
+ end
276
+ end
277
+ ```
278
+
279
+ If you use named caches it is highly recommend that you use a global
280
+ hook to set the cache back to the default before or after each test.
281
+
282
+ In Rspec:
283
+
284
+ ```ruby
285
+ RSpec.configure do |config|
286
+ config.before :each { proxy.cache.use_default_scope }
287
+ end
288
+ ```
184
289
 
185
290
  ## Customising the javascript driver
186
291
 
data/lib/billy.rb CHANGED
@@ -4,13 +4,15 @@ require "billy/proxy_request_stub"
4
4
  require "billy/cache"
5
5
  require "billy/proxy"
6
6
  require "billy/proxy_connection"
7
-
8
- $billy_proxy = Billy::Proxy.new
9
- $billy_proxy.start
7
+ require "billy/railtie" if defined?(Rails)
10
8
 
11
9
  module Billy
12
10
  def self.proxy
13
- $billy_proxy
11
+ @billy_proxy ||= (
12
+ proxy = Billy::Proxy.new
13
+ proxy.start
14
+ proxy
15
+ )
14
16
  end
15
17
 
16
18
  def self.register_drivers
data/lib/billy/cache.rb CHANGED
@@ -1,49 +1,61 @@
1
1
  require 'resolv'
2
2
  require 'uri'
3
3
  require 'yaml'
4
+ require 'billy/json_utils'
4
5
 
5
6
  module Billy
6
7
  class Cache
8
+ attr_reader :scope
9
+
7
10
  def initialize
8
11
  reset
9
- load_dir
10
12
  end
11
13
 
12
- def cacheable?(url, headers)
13
- if Billy.config.cache
14
- host = URI(url).host
15
- !Billy.config.whitelist.include?(host)
16
- # TODO test headers for cacheability
17
- end
14
+ def cached?(method, url, body)
15
+ # Only log the key the first time it's looked up (in this method)
16
+ key = key(method, url, body, true)
17
+ !@cache[key].nil? or persisted?(key)
18
18
  end
19
19
 
20
- def cached?(method, url, body)
21
- !@cache[key(method, url, body)].nil?
20
+ def persisted?(key)
21
+ Billy.config.persist_cache and File.exists?(cache_file(key))
22
22
  end
23
23
 
24
24
  def fetch(method, url, body)
25
- @cache[key(method, url, body)]
25
+ key = key(method, url, body)
26
+ @cache[key] or fetch_from_persistence(key)
26
27
  end
27
28
 
28
- def store(method, url, body, status, headers, content)
29
+ def fetch_from_persistence(key)
30
+ begin
31
+ @cache[key] = YAML.load(File.open(cache_file(key))) if persisted?(key)
32
+ rescue ArgumentError => e
33
+ puts "Could not parse YAML: #{e.message}"
34
+ nil
35
+ end
36
+ end
37
+
38
+ def store(method, url, request_headers, body, response_headers, status, content)
29
39
  cached = {
30
- :url => url,
40
+ :scope => scope,
41
+ :url => format_url(url),
31
42
  :body => body,
32
43
  :status => status,
33
44
  :method => method,
34
- :headers => headers,
45
+ :headers => response_headers,
35
46
  :content => content
36
47
  }
37
48
 
38
- @cache[key(method, url, body)] = cached
49
+ cached.merge!({:request_headers => request_headers}) if Billy.config.cache_request_headers
50
+
51
+ key = key(method, url, body)
52
+ @cache[key] = cached
39
53
 
40
54
  if Billy.config.persist_cache
41
55
  Dir.mkdir(Billy.config.cache_path) unless File.exists?(Billy.config.cache_path)
42
56
 
43
57
  begin
44
- path = File.join(Billy.config.cache_path,
45
- "#{key(method, url, body)}.yml")
46
- File.open(path, 'w') do |f|
58
+ File.open(cache_file(key), 'w') do |f|
47
59
  f.write(cached.to_yaml(:Encoding => :Utf8))
48
60
  end
49
61
  rescue StandardError => e
@@ -55,35 +67,56 @@ module Billy
55
67
  @cache = {}
56
68
  end
57
69
 
58
- def load_dir
59
- if Billy.config.persist_cache
60
- Dir.glob(Billy.config.cache_path+"*.yml") { |filename|
61
- data = begin
62
- YAML.load(File.open(filename))
63
- rescue ArgumentError => e
64
- puts "Could not parse YAML: #{e.message}"
65
- end
66
-
67
- @cache[key(data[:method], data[:url], data[:body])] = data
68
- }
70
+ def key(method, orig_url, body, log_key = false)
71
+ ignore_params = Billy.config.ignore_params.include?(format_url(orig_url, true))
72
+ url = URI(format_url(orig_url, ignore_params))
73
+ key = method+'_'+url.host+'_'+Digest::SHA1.hexdigest(scope.to_s + url.to_s)
74
+ body_msg = ''
75
+
76
+ if method == 'post' and !ignore_params
77
+ body_formatted = JSONUtils::json?(body.to_s) ? JSONUtils::sort_json(body.to_s) : body.to_s
78
+ body_msg = " with body '#{body_formatted}'"
79
+ key += '_'+Digest::SHA1.hexdigest(body_formatted)
69
80
  end
81
+
82
+ Billy.log(:info, "puffing-billy: CACHE KEY for '#{orig_url}#{body_msg}' is '#{key}'") if log_key
83
+ key
70
84
  end
71
85
 
72
- def key(method, url, body)
86
+ def format_url(url, ignore_params=false)
73
87
  url = URI(url)
74
- no_params = url.scheme+'://'+url.host+url.path
75
-
76
- if Billy.config.ignore_params.include?(no_params)
77
- url = URI(no_params)
88
+ port_to_include = Billy.config.ignore_cache_port ? '' : ":#{url.port}"
89
+ formatted_url = url.scheme+'://'+url.host+port_to_include+url.path
90
+ unless ignore_params
91
+ formatted_url += '?'+url.query if url.query
92
+ formatted_url += '#'+url.fragment if url.fragment
78
93
  end
94
+ formatted_url
95
+ end
79
96
 
80
- key = method+'_'+url.host+'_'+Digest::SHA1.hexdigest(url.to_s)
97
+ def cache_file(key)
98
+ File.join(Billy.config.cache_path, "#{key}.yml")
99
+ end
81
100
 
82
- if method == 'post' and !Billy.config.ignore_params.include?(no_params)
83
- key += '_'+Digest::SHA1.hexdigest(body.to_s)
84
- end
101
+ def scope_to(new_scope = nil)
102
+ self.scope = new_scope
103
+ end
85
104
 
86
- key
105
+ def with_scope(use_scope = nil, &block)
106
+ raise ArgumentError, 'Expected a block but none was received.' if block.nil?
107
+ original_scope = scope
108
+ scope_to use_scope
109
+ block.call()
110
+ ensure
111
+ scope_to original_scope
112
+ end
113
+
114
+ def use_default_scope
115
+ scope_to nil
87
116
  end
117
+
118
+ private
119
+
120
+ attr_writer :scope
88
121
  end
89
122
  end