puffing-billy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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