puffing-billy 0.1.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,65 @@
1
+ require 'yajl'
2
+
3
+ module Billy
4
+ class ProxyRequestStub
5
+ def initialize(url, options = {})
6
+ @options = {:method => :get}.merge(options)
7
+ @method = @options[:method].to_s.upcase
8
+ @url = url
9
+ @response = [204, {}, ""]
10
+ end
11
+
12
+ def and_return(response)
13
+ @response = response
14
+ self
15
+ end
16
+
17
+ def call(params, headers, body)
18
+ if @response.respond_to?(:call)
19
+ res = @response.call(params, headers, body)
20
+ else
21
+ res = @response
22
+ end
23
+
24
+ code = res[:code] || 200
25
+
26
+ headers = res[:headers] || {}
27
+ headers['Content-Type'] = res[:content_type] if res[:content_type]
28
+
29
+ if res[:json]
30
+ headers = {'Content-Type' => 'application/json'}.merge(headers)
31
+ body = Yajl::Encoder.encode(res[:json])
32
+ elsif res[:jsonp]
33
+ headers = {'Content-Type' => 'application/javascript'}.merge(headers)
34
+ if res[:callback]
35
+ callback = res[:callback]
36
+ elsif res[:callback_param]
37
+ callback = params[res[:callback_param]][0]
38
+ else
39
+ callback = params['callback'][0]
40
+ end
41
+ body = "#{callback}(#{Yajl::Encoder::encode(res[:jsonp])})"
42
+ elsif res[:text]
43
+ headers = {'Content-Type' => 'text/plain'}.merge(headers)
44
+ body = res[:text]
45
+ elsif res[:redirect_to]
46
+ code = 302
47
+ headers = {'Location' => res[:redirect_to]}
48
+ else
49
+ body = res[:body]
50
+ end
51
+
52
+ [code, headers, body]
53
+ end
54
+
55
+ def matches?(method, url)
56
+ if method == @method
57
+ if @url.is_a?(Regexp)
58
+ url.match(@url)
59
+ else
60
+ url.split('?')[0] == @url
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,60 @@
1
+ require 'rspec'
2
+ require 'billy'
3
+
4
+ $billy_proxy = Billy::Proxy.new
5
+ $billy_proxy.start
6
+
7
+ module Billy
8
+ def self.proxy
9
+ $billy_proxy
10
+ end
11
+
12
+ module RspecHelper
13
+ def proxy
14
+ Billy.proxy
15
+ end
16
+ end
17
+ end
18
+
19
+ RSpec.configure do |config|
20
+ config.include(Billy::RspecHelper)
21
+
22
+ config.after(:each) do
23
+ proxy.reset
24
+ end
25
+ end
26
+
27
+ if defined?(Capybara)
28
+
29
+ if defined?(Capybara::Poltergeist)
30
+ Capybara.register_driver :poltergeist_billy do |app|
31
+ options = {
32
+ phantomjs_options: [
33
+ '--ignore-ssl-errors=yes',
34
+ "--proxy=#{Billy.proxy.host}:#{Billy.proxy.port}"
35
+ ]
36
+ }
37
+ Capybara::Poltergeist::Driver.new(app, options)
38
+ end
39
+ end
40
+
41
+ if defined?(Capybara::Driver::Webkit)
42
+ Capybara.register_driver :webkit_billy do |app|
43
+ driver = Capybara::Driver::Webkit.new(app)
44
+ driver.browser.set_proxy(:host => Billy.proxy.host,
45
+ :port => Billy.proxy.port)
46
+ driver.browser.ignore_ssl_errors
47
+ driver
48
+ end
49
+ end
50
+
51
+ if defined?(Selenium::WebDriver)
52
+ Capybara.register_driver :selenium_billy do |app|
53
+ profile = Selenium::WebDriver::Firefox::Profile.new
54
+ profile.proxy = Selenium::WebDriver::Proxy.new(
55
+ :http => "#{Billy.proxy.host}:#{Billy.proxy.port}",
56
+ :ssl => "#{Billy.proxy.host}:#{Billy.proxy.port}")
57
+ Capybara::Selenium::Driver.new(app, :profile => profile)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,3 @@
1
+ module Billy
2
+ VERSION = "0.1.0"
3
+ end
File without changes
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/billy/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Olly Smith"]
6
+ gem.email = ["olly.smith@gmail.com"]
7
+ gem.description = %q{A stubbing proxy server for ruby. Connect it to your browser in integration tests to fake interactions with remote HTTP(S) servers.}
8
+ gem.summary = %q{Easy request stubs for browser tests.}
9
+ gem.homepage = "https://github.com/oesmith/puffing-billy"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "puffing-billy"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Billy::VERSION
17
+
18
+ gem.add_development_dependency "rspec"
19
+ gem.add_development_dependency "thin"
20
+ gem.add_development_dependency "faraday"
21
+ gem.add_development_dependency "capybara"
22
+ gem.add_development_dependency "poltergeist"
23
+ gem.add_development_dependency "selenium-webdriver"
24
+ gem.add_development_dependency "capybara-webkit"
25
+ gem.add_development_dependency "rack"
26
+ gem.add_runtime_dependency "eventmachine"
27
+ gem.add_runtime_dependency "em-http-request"
28
+ gem.add_runtime_dependency "eventmachine_httpserver"
29
+ gem.add_runtime_dependency "http_parser.rb"
30
+ gem.add_runtime_dependency "yajl-ruby"
31
+ end
@@ -0,0 +1,15 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIICazCCAdQCCQD9MxXmqmRKtTANBgkqhkiG9w0BAQUFADB6MQswCQYDVQQGEwJV
3
+ UzELMAkGA1UECBMCQ0ExITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0
4
+ ZDEWMBQGA1UEAxMNUHVmZmluZyBCaWxseTEjMCEGCSqGSIb3DQEJARYUb2xseS5z
5
+ bWl0aEBnbWFpbC5jb20wHhcNMTIxMDA0MDgwNzMxWhcNMTIxMTAzMDgwNzMxWjB6
6
+ MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExITAfBgNVBAoTGEludGVybmV0IFdp
7
+ ZGdpdHMgUHR5IEx0ZDEWMBQGA1UEAxMNUHVmZmluZyBCaWxseTEjMCEGCSqGSIb3
8
+ DQEJARYUb2xseS5zbWl0aEBnbWFpbC5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0A
9
+ MIGJAoGBAKsUa8bzUeTfC05JFwjBXnD+nBlil5aVTDOB719WJkiq1eDY5WWBaMhk
10
+ OqlZkNhl1rg7LmG/0NhBBUdICYTblNRXzhEfaejqOIGQ3FZMR3W/b87cBp4S+8b/
11
+ q9xJPSUgFvQUzX4v+2+//wdiO8qPioinDdRG9RhxwNvS34bqJqpfAgMBAAEwDQYJ
12
+ KoZIhvcNAQEFBQADgYEADnHq4MP+LgTdp4+ycyearUZh4uhAPzxYXTGDirkkFFQ4
13
+ YA2QT0zXgPq1JmrZwV5Wo1wKoIq8LtfeYoDou0VDKtZ6Up13c98R+1yuQE2kgNoG
14
+ 9VGn/2la4mvLpj+9Zt8wfNxibFi+ajZ7/zsebI7UM+pW0a3SDowDeU0nVPAmDRM=
15
+ -----END CERTIFICATE-----
@@ -0,0 +1,15 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIICXgIBAAKBgQCrFGvG81Hk3wtOSRcIwV5w/pwZYpeWlUwzge9fViZIqtXg2OVl
3
+ gWjIZDqpWZDYZda4Oy5hv9DYQQVHSAmE25TUV84RH2no6jiBkNxWTEd1v2/O3Aae
4
+ EvvG/6vcST0lIBb0FM1+L/tvv/8HYjvKj4qIpw3URvUYccDb0t+G6iaqXwIDAQAB
5
+ AoGBAJccJ4KIYzqUZGkWmBjsq92Elx64/gpM/wyz5VpBPvmKo/XBvwWkg4gVN9dj
6
+ vFPXyAvcgkBm7DJHZEEs+PN3/IEMyLHWVA+C+C3AHisR/E2yl/AyD9oX8F0KDu+Q
7
+ 8bnsL1rFL5zP1saw+QiyofQ13HKtCOj8zjhAUCyZl1NBdiqxAkEA2PNxBHFBSnPU
8
+ L6+PiCvDOJcbqrHQa5kYcS312SNqX/VQ9BtIJfIQ7QA/ueKIUd40OfNBc/jiOC1x
9
+ mlSwlsLnFwJBAMnfWagBvlinIwXw4yhAgsqt012gOzPIHCM7FWipJPbCm9LtJykE
10
+ dGuA06TRE2ZKQq+Oh2yomNni4/LfPVQ6Y/kCQHkej/4W7IiQWem1bcBsDjVNx1ho
11
+ pR8s/YRSUGrFZuHjpyphAMqOdfyaovk4CzsJfsbLk8MXM9SBKmcq2NuSPEkCQQCM
12
+ MvTmTIewtCsLtjdcvijXsA9KR7y2ArUf9qmwrUABrDhiLdfzkad0/dx+68FIWiyk
13
+ Fh2RZin5sKzVARtrwr2pAkEAgIDCCl3YC7Jk5GOm1xVjb999hZSyasOasiVsAafa
14
+ 4UzoA4HlDjTWEROObX8ijbsWRU16/yotbWXDa9XXxnDV3A==
15
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,139 @@
1
+ require 'spec_helper'
2
+
3
+ describe Billy::ProxyRequestStub do
4
+ context '#matches?' do
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
18
+ end
19
+
20
+ 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
25
+ end
26
+
27
+ it 'should match up to but not including query strings' do
28
+ 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
32
+ end
33
+ end
34
+
35
+ context '#and_return + #call' do
36
+ let(:subject) { Billy::ProxyRequestStub.new('url') }
37
+
38
+ it 'should generate bare responses' do
39
+ subject.and_return :body => 'baz foo bar'
40
+ subject.call({}, {}, nil).should == [
41
+ 200,
42
+ {},
43
+ 'baz foo bar'
44
+ ]
45
+ end
46
+
47
+ it 'should generate text responses' do
48
+ subject.and_return :text => 'foo bar baz'
49
+ subject.call({}, {}, nil).should == [
50
+ 200,
51
+ {'Content-Type' => 'text/plain'},
52
+ 'foo bar baz'
53
+ ]
54
+ end
55
+
56
+ it 'should generate JSON responses' do
57
+ subject.and_return :json => { :foo => 'bar' }
58
+ subject.call({}, {}, nil).should == [
59
+ 200,
60
+ {'Content-Type' => 'application/json'},
61
+ '{"foo":"bar"}'
62
+ ]
63
+ end
64
+
65
+ context 'JSONP' do
66
+ it 'should generate JSONP responses' do
67
+ subject.and_return :jsonp => { :foo => 'bar' }
68
+ subject.call({ 'callback' => ['baz'] }, {}, nil).should == [
69
+ 200,
70
+ {'Content-Type' => 'application/javascript'},
71
+ 'baz({"foo":"bar"})'
72
+ ]
73
+ end
74
+
75
+ it 'should generate JSONP responses with custom callback parameter' do
76
+ subject.and_return :jsonp => { :foo => 'bar' }, :callback_param => 'cb'
77
+ subject.call({ 'cb' => ['bap'] }, {}, nil).should == [
78
+ 200,
79
+ {'Content-Type' => 'application/javascript'},
80
+ 'bap({"foo":"bar"})'
81
+ ]
82
+ end
83
+
84
+ it 'should generate JSONP responses with custom callback name' do
85
+ subject.and_return :jsonp => { :foo => 'bar' }, :callback => 'cb'
86
+ subject.call({}, {}, nil).should == [
87
+ 200,
88
+ {'Content-Type' => 'application/javascript'},
89
+ 'cb({"foo":"bar"})'
90
+ ]
91
+ end
92
+ end
93
+
94
+ it 'should generate redirection responses' do
95
+ subject.and_return :redirect_to => 'http://example.com'
96
+ subject.call({}, {}, nil).should == [
97
+ 302,
98
+ {'Location' => 'http://example.com'},
99
+ nil
100
+ ]
101
+ end
102
+
103
+ it 'should set headers' do
104
+ subject.and_return :text => 'foo', :headers => {'HTTP-X-Foo' => 'bar'}
105
+ subject.call({}, {}, nil).should == [
106
+ 200,
107
+ {'Content-Type' => 'text/plain', 'HTTP-X-Foo' => 'bar'},
108
+ 'foo'
109
+ ]
110
+ end
111
+
112
+ it 'should set status codes' do
113
+ subject.and_return :text => 'baz', :code => 410
114
+ subject.call({}, {}, nil).should == [
115
+ 410,
116
+ {'Content-Type' => 'text/plain'},
117
+ 'baz'
118
+ ]
119
+ end
120
+
121
+ it 'should use a callable' do
122
+ expected_params = { 'param1' => ['one'], 'param2' => ['two'] }
123
+ expected_headers = { 'header1' => 'three', 'header2' => 'four' }
124
+ expected_body = 'body text'
125
+
126
+ subject.and_return(Proc.new { |params, headers, body|
127
+ params.should == expected_params
128
+ headers.should == expected_headers
129
+ body.should == 'body text'
130
+ {:code => 418, :text => 'success'}
131
+ })
132
+ subject.call(expected_params, expected_headers, expected_body).should == [
133
+ 418,
134
+ {'Content-Type' => 'text/plain'},
135
+ 'success'
136
+ ]
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+ require 'base64'
3
+
4
+ describe 'Facebook API example', :type => :request, :js => true do
5
+ before do
6
+ proxy.stub('https://www.facebook.com:443/dialog/oauth').and_return(Proc.new { |params,_,_|
7
+ # mock a signed request from facebook. the JS api never verifies the
8
+ # signature, so all it needs is the base64-encoded payload
9
+ signed_request = "xxxxxxxxxx.#{Base64.encode64('{"user_id":"1234567"}')}"
10
+ # redirect to the 'redirect_uri', with some extra crap in the query
11
+ # string
12
+ {:redirect_to => "#{params['redirect_uri'][0]}&access_token=foobar&expires_in=600&base_domain=localhost&https=1&signed_request=#{signed_request}"}
13
+ })
14
+
15
+ proxy.stub('https://graph.facebook.com:443/me').and_return(:jsonp => {:name => 'Tester 1'})
16
+ end
17
+
18
+ it 'should show me as logged-in', :js => true do
19
+ visit '/facebook_api.html'
20
+ page.should have_content "Hi, Tester 1"
21
+ end
22
+ end
23
+
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+ require 'billy'
3
+
4
+ shared_examples_for 'a proxy server' do
5
+ it 'should proxy GET requests' do
6
+ http.get('/echo').body.should == 'GET /echo'
7
+ end
8
+
9
+ it 'should proxy POST requests' do
10
+ http.post('/echo', :foo => 'bar').body.should == "POST /echo\nfoo=bar"
11
+ end
12
+
13
+ it 'should proxy PUT requests' do
14
+ http.post('/echo', :foo => 'bar').body.should == "POST /echo\nfoo=bar"
15
+ end
16
+
17
+ it 'should proxy HEAD requests' do
18
+ http.head('/echo').headers['http_x_echoserver'].should == 'HEAD /echo'
19
+ end
20
+
21
+ it 'should proxy DELETE requests' do
22
+ http.delete('/echo').body.should == 'DELETE /echo'
23
+ end
24
+ end
25
+
26
+ shared_examples_for 'a request stub' do
27
+ it 'should stub GET requests' do
28
+ proxy.stub("#{url}/foo").
29
+ and_return(:text => 'hello, GET!')
30
+ http.get('/foo').body.should == 'hello, GET!'
31
+ end
32
+
33
+ it 'should stub POST requests' do
34
+ proxy.stub("#{url}/bar", :method => :post).
35
+ and_return(:text => 'hello, POST!')
36
+ http.post('/bar', :foo => :bar).body.should == 'hello, POST!'
37
+ end
38
+
39
+ it 'should stub PUT requests' do
40
+ proxy.stub("#{url}/baz", :method => :put).
41
+ and_return(:text => 'hello, PUT!')
42
+ http.put('/baz', :foo => :bar).body.should == 'hello, PUT!'
43
+ end
44
+
45
+ it 'should stub HEAD requests' do
46
+ proxy.stub("#{url}/bap", :method => :head).
47
+ and_return(:headers => {'HTTP-X-Hello' => 'hello, HEAD!'})
48
+ http.head('/bap').headers['http_x_hello'] == 'hello, HEAD!'
49
+ end
50
+
51
+ it 'should stub DELETE requests' do
52
+ proxy.stub("#{url}/bam", :method => :delete).
53
+ and_return(:text => 'hello, DELETE!')
54
+ http.delete('/bam').body.should == 'hello, DELETE!'
55
+ end
56
+ end
57
+
58
+ describe Billy::Proxy do
59
+
60
+ before do
61
+ @http = Faraday.new @http_url,
62
+ :proxy => { :uri => proxy.url },
63
+ :keepalive => false,
64
+ :timeout => 0.5
65
+ @https = Faraday.new @https_url,
66
+ :ssl => { :verify => false },
67
+ :proxy => { :uri => proxy.url },
68
+ :keepalive => false,
69
+ :timeout => 0.5
70
+ end
71
+
72
+ context 'proxying' do
73
+
74
+ context 'HTTP' do
75
+ let!(:http) { @http }
76
+ it_should_behave_like 'a proxy server'
77
+ end
78
+
79
+ context 'HTTPS' do
80
+ let!(:http) { @https }
81
+ it_should_behave_like 'a proxy server'
82
+ end
83
+
84
+ end
85
+
86
+ context 'stubbing' do
87
+
88
+ context 'HTTP' do
89
+ let!(:url) { @http_url }
90
+ let!(:http) { @http }
91
+ it_should_behave_like 'a request stub'
92
+ end
93
+
94
+ context 'HTTPS' do
95
+ let!(:url) { @https_url }
96
+ let!(:http) { @https }
97
+ it_should_behave_like 'a request stub'
98
+ end
99
+
100
+ end
101
+
102
+ end