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 +7 -0
- data/.gitignore +1 -0
- data/Gemfile.lock +78 -66
- data/README.md +111 -6
- data/lib/billy.rb +6 -4
- data/lib/billy/cache.rb +71 -38
- data/lib/billy/config.rb +10 -2
- data/lib/billy/json_utils.rb +40 -0
- data/lib/billy/proxy.rb +4 -2
- data/lib/billy/proxy_connection.rb +85 -19
- data/lib/billy/proxy_request_stub.rb +1 -1
- data/lib/billy/railtie.rb +9 -0
- data/lib/billy/rspec.rb +0 -1
- data/lib/billy/version.rb +1 -1
- data/lib/puffing-billy.rb +2 -0
- data/lib/puffing-billy/rspec.rb +2 -0
- data/lib/tasks/billy.rake +87 -0
- data/puffing-billy.gemspec +2 -2
- data/spec/features/examples/facebook_api_spec.rb +2 -1
- data/spec/features/examples/tumblr_api_spec.rb +4 -4
- data/spec/lib/billy/cache_spec.rb +37 -0
- data/spec/lib/billy/proxy_request_stub_spec.rb +42 -32
- data/spec/lib/billy/resource_utils_spec.rb +55 -0
- data/spec/lib/proxy_spec.rb +233 -45
- data/spec/support/test_server.rb +44 -28
- metadata +55 -82
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, :
|
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
|
-
@
|
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
|
-
|
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
|
-
|
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
|
-
|
119
|
-
|
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
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
data/lib/billy/rspec.rb
CHANGED
data/lib/billy/version.rb
CHANGED
@@ -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
|
data/puffing-billy.gemspec
CHANGED
@@ -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 "
|
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
|
-
|
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.
|
25
|
-
page.
|
26
|
-
page.
|
27
|
-
page.
|
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').
|
8
|
-
Billy::ProxyRequestStub.new('http://example.com').
|
9
|
-
matches?('POST', 'http://example.com').
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
Billy::ProxyRequestStub.new('http://
|
17
|
-
matches?('POST', 'http://example.com').
|
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').
|
23
|
-
Billy::ProxyRequestStub.new(/http:\/\/.+\.co\.uk/, :method => :get).
|
24
|
-
matches?('GET', 'http://example.com').
|
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/').
|
30
|
-
stub.matches?('GET', 'http://example.com/foo/bar/').
|
31
|
-
stub.matches?('GET', 'http://example.com/foo/bar/?baz=bap').
|
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).
|
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).
|
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).
|
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).
|
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).
|
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).
|
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).
|
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).
|
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).
|
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.
|
128
|
-
headers.
|
129
|
-
body.
|
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).
|
142
|
+
expect(subject.call(expected_params, expected_headers, expected_body)).to eql [
|
133
143
|
418,
|
134
144
|
{'Content-Type' => 'text/plain'},
|
135
145
|
'success'
|