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.
data/lib/billy/config.rb CHANGED
@@ -5,7 +5,9 @@ module Billy
5
5
  class Config
6
6
  DEFAULT_WHITELIST = ['127.0.0.1', 'localhost']
7
7
 
8
- attr_accessor :logger, :cache, :whitelist, :ignore_params, :persist_cache, :cache_path
8
+ attr_accessor :logger, :cache, :cache_request_headers, :whitelist, :path_blacklist, :ignore_params,
9
+ :persist_cache, :ignore_cache_port, :non_successful_cache_disabled, :non_successful_error_level,
10
+ :non_whitelisted_requests_disabled, :cache_path
9
11
 
10
12
  def initialize
11
13
  @logger = defined?(Rails) ? Rails.logger : Logger.new(STDOUT)
@@ -14,10 +16,16 @@ module Billy
14
16
 
15
17
  def reset
16
18
  @cache = true
19
+ @cache_request_headers = false
17
20
  @whitelist = DEFAULT_WHITELIST
21
+ @path_blacklist = []
18
22
  @ignore_params = []
19
23
  @persist_cache = false
20
- @cache_path = Dir.tmpdir
24
+ @ignore_cache_port = true
25
+ @non_successful_cache_disabled = false
26
+ @non_successful_error_level = :warn
27
+ @non_whitelisted_requests_disabled = false
28
+ @cache_path = File.join(Dir.tmpdir, 'puffing-billy')
21
29
  end
22
30
  end
23
31
 
@@ -0,0 +1,40 @@
1
+ require 'uri'
2
+ require 'json'
3
+
4
+ module Billy
5
+ module JSONUtils
6
+
7
+ def self.json?(value)
8
+ !!JSON.parse(value)
9
+ rescue JSON::ParserError, TypeError
10
+ false
11
+ end
12
+
13
+ # Recursively sorts the key/value pairs of all hashes within the given
14
+ # data structure while preserving the order of arrays.
15
+ def self.sort_hash_keys(data)
16
+ return data unless data.is_a?(Hash) || data.is_a?(Array)
17
+ if data.is_a? Hash
18
+ data.keys.sort.reduce({}) do |seed, key|
19
+ seed[key] = sort_hash_keys(data[key])
20
+ seed
21
+ end
22
+ else
23
+ data.map do |element|
24
+ sort_hash_keys(element)
25
+ end
26
+ end
27
+ end
28
+
29
+ # Recursively sorts the name/value pairs of JSON objects. For instance,
30
+ # sort_json('{"b" : "2", "a" : "1"}') == sort_json('{"a" : "1", "b" : "2"}')
31
+ # Per http://json.org/, "An object is an unordered set of name/value pairs"
32
+ # and "An array is an ordered collection of values". So, we can sort the
33
+ # name/value pairs by name (key), but we must preserve the order of an array.
34
+ # Processing JSON in this way enables a consistent SHA to be derived from
35
+ # JSON payloads which have the same name/value pairs, but different orders.
36
+ def self.sort_json(json_str)
37
+ JSONUtils::sort_hash_keys(JSON.parse(json_str, symbolize_names: true)).to_json
38
+ end
39
+ end
40
+ end
data/lib/billy/proxy.rb CHANGED
@@ -4,6 +4,8 @@ require 'eventmachine'
4
4
 
5
5
  module Billy
6
6
  class Proxy
7
+ attr_reader :cache
8
+
7
9
  def initialize
8
10
  reset
9
11
  @cache = Billy::Cache.new
@@ -54,8 +56,8 @@ module Billy
54
56
  end
55
57
 
56
58
  def restore_cache
59
+ warn "[DEPRECATION] `restore_cache` is deprecated as cache files are dynamincally checked. Use `reset_cache` if you just want to clear the cache."
57
60
  @cache.reset
58
- @cache.load_dir
59
61
  end
60
62
 
61
63
  protected
@@ -76,7 +78,7 @@ module Billy
76
78
  p.cache = @cache
77
79
  end
78
80
 
79
- Billy.log(:info, "Proxy listening on #{url}")
81
+ Billy.log(:info, "puffing-billy: Proxy listening on #{url}")
80
82
  end
81
83
  end
82
84
  end
@@ -74,21 +74,38 @@ module Billy
74
74
  end
75
75
 
76
76
  if result
77
- Billy.log(:info, "STUB #{@parser.http_method} #{@url}")
78
- response = EM::DelegatedHttpResponse.new(self)
79
- response.status = result[0]
80
- response.headers = result[1].merge('Connection' => 'close')
81
- response.content = result[2]
82
- response.send_response
77
+ Billy.log(:info, "puffing-billy: STUB #{@parser.http_method} for '#{@url}'")
78
+ stub_request(result)
83
79
  elsif cache.cached?(@parser.http_method.downcase, @url, @body)
84
- Billy.log(:info, "CACHE #{@parser.http_method} #{@url}")
80
+ Billy.log(:info, "puffing-billy: CACHE #{@parser.http_method} for '#{@url}'")
85
81
  respond_from_cache
86
- else
87
- Billy.log(:info, "PROXY #{@parser.http_method} #{@url}")
82
+ elsif !disabled_request?
83
+ Billy.log(:info, "puffing-billy: PROXY #{@parser.http_method} for '#{@url}'")
88
84
  proxy_request
85
+ else
86
+ close_connection
87
+ body_msg = @parser.http_method == 'post' ? " with body '#{@body}'" : ''
88
+ raise "puffing-billy: Connection to #{@url}#{body_msg} not cached and new http connections are disabled"
89
89
  end
90
90
  end
91
91
 
92
+ def stub_request(result)
93
+ response = EM::DelegatedHttpResponse.new(self)
94
+ response.status = result[0]
95
+ response.headers = result[1].merge('Connection' => 'close')
96
+ response.content = result[2]
97
+ response.send_response
98
+ end
99
+
100
+ def respond_from_cache
101
+ cached_res = cache.fetch(@parser.http_method.downcase, @url, @body)
102
+ res = EM::DelegatedHttpResponse.new(self)
103
+ res.status = cached_res[:status]
104
+ res.headers = cached_res[:headers]
105
+ res.content = cached_res[:content]
106
+ res.send_response
107
+ end
108
+
92
109
  def proxy_request
93
110
  headers = Hash[@headers.map { |k,v| [k.downcase, v] }]
94
111
  headers.delete('accept-encoding')
@@ -105,7 +122,7 @@ module Billy
105
122
  req = req.send(@parser.http_method.downcase, req_opts)
106
123
 
107
124
  req.errback do
108
- Billy.log(:error, "Request failed: #{@url}")
125
+ Billy.log(:error, "puffing-billy: Request failed: #{@url}")
109
126
  close_connection
110
127
  end
111
128
 
@@ -115,8 +132,11 @@ module Billy
115
132
  res_headers = res_headers.merge('Connection' => 'close')
116
133
  res_headers.delete('Transfer-Encoding')
117
134
  res_content = req.response.force_encoding('BINARY')
118
- if cache.cacheable?(@url, res_headers)
119
- cache.store(@parser.http_method.downcase, @url, @body, res_status, res_headers, res_content)
135
+
136
+ handle_response_code(res_status)
137
+
138
+ if cacheable?(res_headers, res_status)
139
+ cache.store(@parser.http_method.downcase, @url, headers, @body, res_headers, res_status, res_content)
120
140
  end
121
141
 
122
142
  res = EM::DelegatedHttpResponse.new(self)
@@ -127,13 +147,59 @@ module Billy
127
147
  end
128
148
  end
129
149
 
130
- def respond_from_cache
131
- cached_res = cache.fetch(@parser.http_method.downcase, @url, @body)
132
- res = EM::DelegatedHttpResponse.new(self)
133
- res.status = cached_res[:status]
134
- res.headers = cached_res[:headers]
135
- res.content = cached_res[:content]
136
- res.send_response
150
+ def disabled_request?
151
+ url = URI(@url)
152
+ # In isolated environments, you may want to stop the request from happening
153
+ # or else you get "getaddrinfo: Name or service not known" errors
154
+ if Billy.config.non_whitelisted_requests_disabled
155
+ blacklisted_path?(url.path) || !whitelisted_url?(url)
156
+ end
157
+ end
158
+
159
+ def handle_response_code(status)
160
+ log_level = successful_status?(status) ? :info : Billy.config.non_successful_error_level
161
+ log_message = "puffing-billy: Received response status code #{status} for #{@url}"
162
+ Billy.log(log_level, log_message)
163
+ if log_level == :error
164
+ close_connection
165
+ raise log_message
166
+ end
167
+ end
168
+
169
+ def cacheable?(headers, status)
170
+ if Billy.config.cache
171
+ url = URI(@url)
172
+ # Cache the responses if they aren't whitelisted host[:port]s but always cache blacklisted paths on any hosts
173
+ cacheable_headers?(headers) && cacheable_status?(status) && (!whitelisted_url?(url) || blacklisted_path?(url.path))
174
+ end
175
+ end
176
+
177
+ private
178
+
179
+ def whitelisted_host?(host)
180
+ Billy.config.whitelist.include?(host)
137
181
  end
182
+
183
+ def whitelisted_url?(url)
184
+ whitelisted_host?(url.host) || whitelisted_host?("#{url.host}:#{url.port}")
185
+ end
186
+
187
+ def blacklisted_path?(path)
188
+ Billy.config.path_blacklist.index{|bl| path.include?(bl)}
189
+ end
190
+
191
+ def successful_status?(status)
192
+ (200..299).include?(status) || status == 304
193
+ end
194
+
195
+ def cacheable_headers?(headers)
196
+ #TODO: test headers for cacheability (ie. Cache-Control: no-cache)
197
+ true
198
+ end
199
+
200
+ def cacheable_status?(status)
201
+ Billy.config.non_successful_cache_disabled ? successful_status?(status) : true
202
+ end
203
+
138
204
  end
139
205
  end
@@ -6,7 +6,7 @@ module Billy
6
6
  @options = {:method => :get}.merge(options)
7
7
  @method = @options[:method].to_s.upcase
8
8
  @url = url
9
- @response = [204, {}, ""]
9
+ @response = {code: 204, headers: {}, text: ""}
10
10
  end
11
11
 
12
12
  def and_return(response)
@@ -0,0 +1,9 @@
1
+ module Billy
2
+ class Railtie < Rails::Railtie
3
+ railtie_name 'billy'
4
+
5
+ rake_tasks do
6
+ load 'tasks/billy.rake'
7
+ end
8
+ end
9
+ end
data/lib/billy/rspec.rb CHANGED
@@ -1,4 +1,3 @@
1
- require 'rspec'
2
1
  require 'capybara/rspec'
3
2
  require 'billy'
4
3
 
data/lib/billy/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Billy
2
- VERSION = "0.2.1"
2
+ VERSION = "0.2.3"
3
3
  end
@@ -0,0 +1,2 @@
1
+ require 'billy'
2
+ PuffingBilly ||= Billy
@@ -0,0 +1,2 @@
1
+ require 'billy/rspec'
2
+ PuffingBilly ||= Billy
@@ -0,0 +1,87 @@
1
+ namespace :cache do
2
+
3
+ desc 'Print out all cache file information'
4
+ task :print_all do
5
+ cache_array = load_cache
6
+
7
+ sort_cache(cache_array).each do |cache|
8
+ print_cache_details(cache)
9
+ end
10
+ end
11
+
12
+ desc 'Print out specific cache file information'
13
+ task :print_details, :sha do |t, args|
14
+ raise "Missing sha; usage: rake cache:print_details['<sha>']" unless args[:sha]
15
+ cache_array = load_cache(Billy.config.cache_path, '*'+args[:sha]+'*.yml')
16
+
17
+ sort_cache(cache_array).each do |cache|
18
+ print_cache_details(cache)
19
+ end
20
+ end
21
+
22
+ desc 'Find specific cache files by URL'
23
+ task :find_by_url, :api_path do |t, args|
24
+ raise "Missing api path; usage: rake cache:find_by_url['<api_path>']" unless args[:api_path]
25
+ cache_array = load_cache
26
+ filtered_cache_array = cache_array.select {|f| f[:url_path].include?(args[:api_path]) }
27
+
28
+ sort_cache(filtered_cache_array).each do |cache|
29
+ print_cache_details(cache)
30
+ end
31
+ end
32
+
33
+ desc 'Find specific cache files by scope'
34
+ task :find_by_scope, :scope do |t, args|
35
+ raise "Missing scope; usage: rake cache:find_by_scope['<scope>']" unless args[:scope]
36
+ cache_array = load_cache
37
+ filtered_cache_array = cache_array.select {|f| f[:scope] && f[:scope].include?(args[:scope]) }
38
+
39
+ sort_cache(filtered_cache_array).each do |cache|
40
+ print_cache_details(cache)
41
+ end
42
+ end
43
+
44
+ desc 'Find cache files with non-successful status codes'
45
+ task :find_non_successful do
46
+ cache_array = load_cache
47
+ filtered_cache_array = cache_array.select {|f| !(200..299).include?(f[:status]) }
48
+
49
+ sort_cache(filtered_cache_array).each do |cache|
50
+ print_cache_details(cache)
51
+ end
52
+ end
53
+
54
+ def load_cache(cache_directory = Billy.config.cache_path, file_pattern = '*.yml')
55
+ cache_path = Rails.root.join(cache_directory)
56
+ cache_array = []
57
+
58
+ Dir.glob(cache_path+file_pattern) do |filename|
59
+ data = load_cache_file(filename)
60
+ url = URI(data[:url])
61
+ data[:url_path] = "#{url.path}#{url.query ? '?'+url.query : ''}#{url.fragment ? '#'+url.fragment : ''}"
62
+ data[:filename] = filename.gsub(Rails.root.to_s+'/','')
63
+ cache_array << data
64
+ end
65
+ cache_array
66
+ end
67
+
68
+ def load_cache_file(filename)
69
+ YAML.load(File.open(filename))
70
+ rescue ArgumentError => e
71
+ puts "Could not parse YAML: #{e.message}"
72
+ end
73
+
74
+ def print_cache_details(cache)
75
+ puts " Scope: #{cache[:scope]}" if cache[:scope]
76
+ puts " URL: #{cache[:url]}"
77
+ puts " Body: #{cache[:body]}" if cache[:method] == 'post'
78
+ puts " Details: Request method '#{cache[:method]}' returned response status code: '#{cache[:status]}'"
79
+ puts "Filename: #{cache[:filename]}"
80
+ puts "\n\n"
81
+ end
82
+
83
+ def sort_cache(cache, key = :url_path)
84
+ cache.sort_by { |hsh| hsh[key] }
85
+ end
86
+
87
+ end
@@ -20,11 +20,11 @@ Gem::Specification.new do |gem|
20
20
  gem.add_development_dependency "faraday"
21
21
  gem.add_development_dependency "poltergeist"
22
22
  gem.add_development_dependency "selenium-webdriver"
23
- gem.add_development_dependency "capybara-webkit"
23
+ gem.add_development_dependency "capybara-webkit", "~> 1.0"
24
24
  gem.add_development_dependency "rack"
25
25
  gem.add_development_dependency "guard"
26
26
  gem.add_development_dependency "rb-inotify"
27
- gem.add_development_dependency "rspec"
27
+ gem.add_development_dependency "pry"
28
28
  gem.add_development_dependency "cucumber"
29
29
  gem.add_runtime_dependency "eventmachine"
30
30
  gem.add_runtime_dependency "em-http-request"
@@ -17,6 +17,7 @@ describe 'Facebook API example', :type => :feature, :js => true do
17
17
 
18
18
  it 'should show me as logged-in', :js => true do
19
19
  visit '/facebook_api.html'
20
- page.should have_content "Hi, Tester 1"
20
+ click_on "Login"
21
+ expect(page).to have_content "Hi, Tester 1"
21
22
  end
22
23
  end
@@ -21,9 +21,9 @@ describe 'Tumblr API example', :type => :feature, :js => true do
21
21
 
22
22
  it 'should show news stories', :js => true do
23
23
  visit '/tumblr_api.html'
24
- page.should have_link('News Item 1', :href => 'http://example.com/news/1')
25
- page.should have_content('News item 1 content here')
26
- page.should have_link('News Item 2', :href => 'http://example.com/news/2')
27
- page.should have_content('News item 2 content here')
24
+ expect(page).to have_link('News Item 1', :href => 'http://example.com/news/1')
25
+ expect(page).to have_content('News item 1 content here')
26
+ expect(page).to have_link('News Item 2', :href => 'http://example.com/news/2')
27
+ expect(page).to have_content('News item 2 content here')
28
28
  end
29
29
  end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Billy::Cache do
4
+ describe 'format_url' do
5
+ let(:cache) { Billy::Cache.new }
6
+ let(:params) { '?foo=bar' }
7
+ let(:fragment) { '#baz' }
8
+ let(:base_url) { 'http://example.com' }
9
+ let(:fragment_url) { "#{base_url}/#{fragment}" }
10
+ let(:params_url) { "#{base_url}#{params}" }
11
+ let(:params_fragment_url) { "#{base_url}#{params}#{fragment}" }
12
+
13
+ context 'with ignore_params set to false' do
14
+ it 'is a no-op if there are no params' do
15
+ expect(cache.format_url(base_url)).to eq base_url
16
+ end
17
+ it 'appends params if there are params' do
18
+ expect(cache.format_url(params_url)).to eq params_url
19
+ end
20
+ it 'appends params and fragment if both are present' do
21
+ expect(cache.format_url(params_fragment_url)).to eq params_fragment_url
22
+ end
23
+ end
24
+
25
+ context 'with ignore_params set to true' do
26
+ it 'is a no-op if there are no params' do
27
+ expect(cache.format_url(base_url, true)).to eq base_url
28
+ end
29
+ it 'omits params if there are params' do
30
+ expect(cache.format_url(params_url, true)).to eq base_url
31
+ end
32
+ it 'omits params and fragment if both are present' do
33
+ expect(cache.format_url(params_fragment_url, true)).to eq base_url
34
+ end
35
+ end
36
+ end
37
+ end
@@ -3,32 +3,42 @@ require 'spec_helper'
3
3
  describe Billy::ProxyRequestStub do
4
4
  context '#matches?' do
5
5
  it 'should match urls and methods' do
6
- Billy::ProxyRequestStub.new('http://example.com').
7
- matches?('GET', 'http://example.com').should be
8
- Billy::ProxyRequestStub.new('http://example.com').
9
- matches?('POST', 'http://example.com').should_not be
10
- Billy::ProxyRequestStub.new('http://example.com', :method => :get).
11
- matches?('GET', 'http://example.com').should be
12
- Billy::ProxyRequestStub.new('http://example.com', :method => :post).
13
- matches?('GET', 'http://example.com').should_not be
14
- Billy::ProxyRequestStub.new('http://example.com', :method => :post).
15
- matches?('POST', 'http://example.com').should be
16
- Billy::ProxyRequestStub.new('http://fooxample.com', :method => :post).
17
- matches?('POST', 'http://example.com').should_not be
6
+ expect(Billy::ProxyRequestStub.new('http://example.com').
7
+ matches?('GET', 'http://example.com')).to be
8
+ expect(Billy::ProxyRequestStub.new('http://example.com').
9
+ matches?('POST', 'http://example.com')).to_not be
10
+
11
+ expect(Billy::ProxyRequestStub.new('http://example.com', :method => :get).
12
+ matches?('GET', 'http://example.com')).to be
13
+ expect(Billy::ProxyRequestStub.new('http://example.com', :method => :post).
14
+ matches?('GET', 'http://example.com')).to_not be
15
+
16
+ expect(Billy::ProxyRequestStub.new('http://example.com', :method => :post).
17
+ matches?('POST', 'http://example.com')).to be
18
+ expect(Billy::ProxyRequestStub.new('http://fooxample.com', :method => :post).
19
+ matches?('POST', 'http://example.com')).to_not be
18
20
  end
19
21
 
20
22
  it 'should match regexps' do
21
- Billy::ProxyRequestStub.new(/http:\/\/.+\.com/, :method => :post).
22
- matches?('POST', 'http://example.com').should be
23
- Billy::ProxyRequestStub.new(/http:\/\/.+\.co\.uk/, :method => :get).
24
- matches?('GET', 'http://example.com').should_not be
23
+ expect(Billy::ProxyRequestStub.new(/http:\/\/.+\.com/, :method => :post).
24
+ matches?('POST', 'http://example.com')).to be
25
+ expect(Billy::ProxyRequestStub.new(/http:\/\/.+\.co\.uk/, :method => :get).
26
+ matches?('GET', 'http://example.com')).to_not be
25
27
  end
26
28
 
27
29
  it 'should match up to but not including query strings' do
28
30
  stub = Billy::ProxyRequestStub.new('http://example.com/foo/bar/')
29
- stub.matches?('GET', 'http://example.com/foo/').should_not be
30
- stub.matches?('GET', 'http://example.com/foo/bar/').should be
31
- stub.matches?('GET', 'http://example.com/foo/bar/?baz=bap').should be
31
+ expect(stub.matches?('GET', 'http://example.com/foo/')).to_not be
32
+ expect(stub.matches?('GET', 'http://example.com/foo/bar/')).to be
33
+ expect(stub.matches?('GET', 'http://example.com/foo/bar/?baz=bap')).to be
34
+ end
35
+ end
36
+
37
+ context "#call (without #and_return)" do
38
+ let(:subject) { Billy::ProxyRequestStub.new('url') }
39
+
40
+ it "returns a 204 empty response" do
41
+ expect(subject.call({}, {}, nil)).to eql [204, {"Content-Type" => "text/plain"}, ""]
32
42
  end
33
43
  end
34
44
 
@@ -37,7 +47,7 @@ describe Billy::ProxyRequestStub do
37
47
 
38
48
  it 'should generate bare responses' do
39
49
  subject.and_return :body => 'baz foo bar'
40
- subject.call({}, {}, nil).should == [
50
+ expect(subject.call({}, {}, nil)).to eql [
41
51
  200,
42
52
  {},
43
53
  'baz foo bar'
@@ -46,7 +56,7 @@ describe Billy::ProxyRequestStub do
46
56
 
47
57
  it 'should generate text responses' do
48
58
  subject.and_return :text => 'foo bar baz'
49
- subject.call({}, {}, nil).should == [
59
+ expect(subject.call({}, {}, nil)).to eql [
50
60
  200,
51
61
  {'Content-Type' => 'text/plain'},
52
62
  'foo bar baz'
@@ -55,7 +65,7 @@ describe Billy::ProxyRequestStub do
55
65
 
56
66
  it 'should generate JSON responses' do
57
67
  subject.and_return :json => { :foo => 'bar' }
58
- subject.call({}, {}, nil).should == [
68
+ expect(subject.call({}, {}, nil)).to eql [
59
69
  200,
60
70
  {'Content-Type' => 'application/json'},
61
71
  '{"foo":"bar"}'
@@ -65,7 +75,7 @@ describe Billy::ProxyRequestStub do
65
75
  context 'JSONP' do
66
76
  it 'should generate JSONP responses' do
67
77
  subject.and_return :jsonp => { :foo => 'bar' }
68
- subject.call({ 'callback' => ['baz'] }, {}, nil).should == [
78
+ expect(subject.call({ 'callback' => ['baz'] }, {}, nil)).to eql [
69
79
  200,
70
80
  {'Content-Type' => 'application/javascript'},
71
81
  'baz({"foo":"bar"})'
@@ -74,7 +84,7 @@ describe Billy::ProxyRequestStub do
74
84
 
75
85
  it 'should generate JSONP responses with custom callback parameter' do
76
86
  subject.and_return :jsonp => { :foo => 'bar' }, :callback_param => 'cb'
77
- subject.call({ 'cb' => ['bap'] }, {}, nil).should == [
87
+ expect(subject.call({ 'cb' => ['bap'] }, {}, nil)).to eql [
78
88
  200,
79
89
  {'Content-Type' => 'application/javascript'},
80
90
  'bap({"foo":"bar"})'
@@ -83,7 +93,7 @@ describe Billy::ProxyRequestStub do
83
93
 
84
94
  it 'should generate JSONP responses with custom callback name' do
85
95
  subject.and_return :jsonp => { :foo => 'bar' }, :callback => 'cb'
86
- subject.call({}, {}, nil).should == [
96
+ expect(subject.call({}, {}, nil)).to eql [
87
97
  200,
88
98
  {'Content-Type' => 'application/javascript'},
89
99
  'cb({"foo":"bar"})'
@@ -93,7 +103,7 @@ describe Billy::ProxyRequestStub do
93
103
 
94
104
  it 'should generate redirection responses' do
95
105
  subject.and_return :redirect_to => 'http://example.com'
96
- subject.call({}, {}, nil).should == [
106
+ expect(subject.call({}, {}, nil)).to eql [
97
107
  302,
98
108
  {'Location' => 'http://example.com'},
99
109
  nil
@@ -102,7 +112,7 @@ describe Billy::ProxyRequestStub do
102
112
 
103
113
  it 'should set headers' do
104
114
  subject.and_return :text => 'foo', :headers => {'HTTP-X-Foo' => 'bar'}
105
- subject.call({}, {}, nil).should == [
115
+ expect(subject.call({}, {}, nil)).to eql [
106
116
  200,
107
117
  {'Content-Type' => 'text/plain', 'HTTP-X-Foo' => 'bar'},
108
118
  'foo'
@@ -111,7 +121,7 @@ describe Billy::ProxyRequestStub do
111
121
 
112
122
  it 'should set status codes' do
113
123
  subject.and_return :text => 'baz', :code => 410
114
- subject.call({}, {}, nil).should == [
124
+ expect(subject.call({}, {}, nil)).to eql [
115
125
  410,
116
126
  {'Content-Type' => 'text/plain'},
117
127
  'baz'
@@ -124,12 +134,12 @@ describe Billy::ProxyRequestStub do
124
134
  expected_body = 'body text'
125
135
 
126
136
  subject.and_return(Proc.new { |params, headers, body|
127
- params.should == expected_params
128
- headers.should == expected_headers
129
- body.should == 'body text'
137
+ expect(params).to eql expected_params
138
+ expect(headers).to eql expected_headers
139
+ expect(body).to eql 'body text'
130
140
  {:code => 418, :text => 'success'}
131
141
  })
132
- subject.call(expected_params, expected_headers, expected_body).should == [
142
+ expect(subject.call(expected_params, expected_headers, expected_body)).to eql [
133
143
  418,
134
144
  {'Content-Type' => 'text/plain'},
135
145
  'success'