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