diff options
Diffstat (limited to 'lib/vendor/excon/tests')
52 files changed, 3824 insertions, 0 deletions
diff --git a/lib/vendor/excon/tests/authorization_header_tests.rb b/lib/vendor/excon/tests/authorization_header_tests.rb new file mode 100644 index 0000000..400a362 --- /dev/null +++ b/lib/vendor/excon/tests/authorization_header_tests.rb @@ -0,0 +1,29 @@ +Shindo.tests('Excon basics (Authorization data redacted)') do + with_rackup('basic_auth.ru') do + cases = [ + ['user & pass', 'http://user1:pass1@foo.com/', 'Basic dXNlcjE6cGFzczE='], + ['email & pass', 'http://foo%40bar.com:pass1@foo.com/', 'Basic Zm9vQGJhci5jb206cGFzczE='], + ['user no pass', 'http://three_user@foo.com/', 'Basic dGhyZWVfdXNlcjo='], + ['pass no user', 'http://:derppass@foo.com/', 'Basic OmRlcnBwYXNz'] + ] + cases.each do |desc,url,auth_header| + conn = nil + + test("authorization header concealed for #{desc}") do + conn = Excon.new(url) + !conn.inspect.include?(auth_header) + end + + if conn.data[:password] + test("password param concealed for #{desc}") do + !conn.inspect.include?(conn.data[:password]) + end + end + + test("password param remains correct for #{desc}") do + conn.data[:password] == URI.parse(url).password + end + + end + end +end diff --git a/lib/vendor/excon/tests/bad_tests.rb b/lib/vendor/excon/tests/bad_tests.rb new file mode 100644 index 0000000..1069cf3 --- /dev/null +++ b/lib/vendor/excon/tests/bad_tests.rb @@ -0,0 +1,47 @@ +Shindo.tests('Excon bad server interaction') do + + with_server('bad') do + + tests('bad server: causes EOFError') do + + tests('with no content length and no chunking') do + tests('without a block') do + tests('response.body').returns('hello') do + connection = Excon.new('http://127.0.0.1:9292') + + connection.request(:method => :get, :path => '/eof/no_content_length_and_no_chunking').body + end + end + + tests('with a block') do + tests('body from chunks').returns('hello') do + connection = Excon.new('http://127.0.0.1:9292') + + body = "" + response_block = lambda {|chunk, remaining, total| body << chunk } + + connection.request(:method => :get, :path => '/eof/no_content_length_and_no_chunking', :response_block => response_block) + + body + end + end + + end + + end + + end + + with_server('eof') do + + tests('eof server: causes EOFError') do + + tests('request').raises(Excon::Errors::SocketError) do + Excon.get('http://127.0.0.1:9292/eof') + end + + end + + end + +end diff --git a/lib/vendor/excon/tests/basic_tests.rb b/lib/vendor/excon/tests/basic_tests.rb new file mode 100644 index 0000000..f8ee529 --- /dev/null +++ b/lib/vendor/excon/tests/basic_tests.rb @@ -0,0 +1,349 @@ +require 'json' + +Shindo.tests('Excon basics') do + with_rackup('basic.ru') do + basic_tests + + tests('explicit uri passed to connection') do + tests('GET /content-length/100').returns(200) do + connection = Excon::Connection.new({ + :host => '127.0.0.1', + :hostname => '127.0.0.1', + :nonblock => false, + :port => 9292, + :scheme => 'http', + :ssl_verify_peer => false + }) + response = connection.request(:method => :get, :path => '/content-length/100') + response[:status] + end + end + end +end + +Shindo.tests('Excon streaming basics') do + pending if RUBY_PLATFORM == 'java' # need to find suitable server for jruby + with_unicorn('streaming.ru') do + # expected values: the response, in pieces, and a timeout after each piece + res = %w{Hello streamy world} + timeout = 0.1 + + # expect the full response as a string + # and expect it to take a (timeout * pieces) seconds + tests('simple blocking request on streaming endpoint').returns([res.join(''),'response time ok']) do + start = Time.now + ret = Excon.get('http://127.0.0.1:9292/streamed/simple').body + + if Time.now - start <= timeout*3 + [ret, 'streaming response came too quickly'] + else + [ret, 'response time ok'] + end + end + + # expect the full response as a string and expect it to + # take a (timeout * pieces) seconds (with fixed Content-Length header) + tests('simple blocking request on streaming endpoint with fixed length').returns([res.join(''),'response time ok']) do + start = Time.now + ret = Excon.get('http://127.0.0.1:9292/streamed/fixed_length').body + + if Time.now - start <= timeout*3 + [ret, 'streaming response came too quickly'] + else + [ret, 'response time ok'] + end + end + + # expect each response piece to arrive to the body right away + # and wait for timeout until next one arrives + def timed_streaming_test(endpoint, timeout) + ret = [] + timing = 'response times ok' + start = Time.now + Excon.get(endpoint, :response_block => lambda do |c,r,t| + # add the response + ret.push(c) + # check if the timing is ok + # each response arrives after timeout and before timeout + 1 + cur_time = Time.now - start + if cur_time < ret.length * timeout or cur_time > (ret.length+1) * timeout + timing = 'response time not ok!' + end + end) + # validate the final timing + if Time.now - start <= timeout*3 + timing = 'final timing was not ok!' + end + [ret, timing] + end + + tests('simple request with response_block on streaming endpoint').returns([res,'response times ok']) do + timed_streaming_test('http://127.0.0.1:9292/streamed/simple', timeout) + end + + tests('simple request with response_block on streaming endpoint with fixed length').returns([res,'response times ok']) do + timed_streaming_test('http://127.0.0.1:9292/streamed/fixed_length', timeout) + end + + end +end + +Shindo.tests('Excon basics (Basic Auth Pass)') do + with_rackup('basic_auth.ru') do + basic_tests('http://test_user:test_password@127.0.0.1:9292') + user, pass, uri = ['test_user', 'test_password', 'http://127.0.0.1:9292'].map(&:freeze) + + tests('with frozen args').returns(200) do + connection = Excon.new(uri, :method => :get, :password => pass, :path => '/content-length/100', :user => user) + response = connection.request + response.status + end + + tests('with user/pass on request').returns(200) do + connection = Excon.new(uri, :method => :get, :path => '/content-length/100') + response = connection.request(:user => user, :password => pass) + response.status + end + + tests('with user/pass on connection and request').returns(200) do + connection = Excon.new(uri, :method => :get, :password => 'incorrect_password', :path => '/content-length/100', :user => 'incorrect_user') + response = connection.request(user: user, password: pass) + response.status + end + end +end + +Shindo.tests('Excon basics (Basic Auth Fail)') do + with_rackup('basic_auth.ru') do + cases = [ + ['correct user, no password', 'http://test_user@127.0.0.1:9292'], + ['correct user, wrong password', 'http://test_user:fake_password@127.0.0.1:9292'], + ['wrong user, correct password', 'http://fake_user:test_password@127.0.0.1:9292'] + ] + cases.each do |desc,url| + tests("response.status for #{desc}").returns(401) do + connection = Excon.new(url) + response = connection.request(:method => :get, :path => '/content-length/100') + response.status + end + end + end +end + +Shindo.tests('Excon basics (ssl)') do + with_rackup('ssl.ru') do + basic_tests('https://127.0.0.1:9443') + end +end + +Shindo.tests('Excon ssl verify peer (ssl)') do + with_rackup('ssl.ru') do + connection = nil + test do + ssl_ca_file = File.join(File.dirname(__FILE__), 'data', '127.0.0.1.cert.crt') + connection = Excon.new('https://127.0.0.1:9443', :ssl_verify_peer => true, :ssl_ca_file => ssl_ca_file ) + true + end + + tests('response.status').returns(200) do + response = connection.request(:method => :get, :path => '/content-length/100') + + response.status + end + end + + with_rackup('ssl_mismatched_cn.ru') do + connection = nil + test do + ssl_ca_file = File.join(File.dirname(__FILE__), 'data', 'excon.cert.crt') + connection = Excon.new('https://127.0.0.1:9443', :ssl_verify_peer => true, :ssl_ca_file => ssl_ca_file, :ssl_verify_peer_host => 'Excon' ) + true + end + + tests('response.status').returns(200) do + response = connection.request(:method => :get, :path => '/content-length/100') + + response.status + end + end +end + +Shindo.tests('Excon ssl verify peer (ssl cert store)') do + with_rackup('ssl.ru') do + connection = nil + test do + ssl_ca_cert = File.read(File.join(File.dirname(__FILE__), 'data', '127.0.0.1.cert.crt')) + ssl_cert_store = OpenSSL::X509::Store.new + ssl_cert_store.add_cert OpenSSL::X509::Certificate.new ssl_ca_cert + connection = Excon.new('https://127.0.0.1:9443', :ssl_verify_peer => true, :ssl_cert_store => ssl_cert_store ) + true + end + + tests('response.status').returns(200) do + response = connection.request(:method => :get, :path => '/content-length/100') + + response.status + end + end +end + +Shindo.tests('Excon basics (ssl file)',['focus']) do + with_rackup('ssl_verify_peer.ru') do + + tests('GET /content-length/100').raises(Excon::Errors::SocketError) do + connection = Excon::Connection.new({ + :host => '127.0.0.1', + :hostname => '127.0.0.1', + :nonblock => false, + :port => 8443, + :scheme => 'https', + :ssl_verify_peer => false + }) + connection.request(:method => :get, :path => '/content-length/100') + end + + cert_key_path = File.join(File.dirname(__FILE__), 'data', 'excon.cert.key') + cert_crt_path = File.join(File.dirname(__FILE__), 'data', 'excon.cert.crt') + basic_tests('https://127.0.0.1:8443', client_key: cert_key_path, client_cert: cert_crt_path) + + cert_key_data = File.read cert_key_path + cert_crt_data = File.read cert_crt_path + basic_tests('https://127.0.0.1:8443', client_key_data: cert_key_data, client_cert_data: cert_crt_data) + end +end + +Shindo.tests('Excon basics (ssl file paths)',['focus']) do + with_rackup('ssl_verify_peer.ru') do + + tests('GET /content-length/100').raises(Excon::Errors::SocketError) do + connection = Excon::Connection.new({ + :host => '127.0.0.1', + :hostname => '127.0.0.1', + :nonblock => false, + :port => 8443, + :scheme => 'https', + :ssl_verify_peer => false + }) + connection.request(:method => :get, :path => '/content-length/100') + end + + basic_tests('https://127.0.0.1:8443', + :private_key_path => File.join(File.dirname(__FILE__), 'data', 'excon.cert.key'), + :certificate_path => File.join(File.dirname(__FILE__), 'data', 'excon.cert.crt') + ) + + end +end + +Shindo.tests('Excon basics (ssl string)', ['focus']) do + with_rackup('ssl_verify_peer.ru') do + basic_tests('https://127.0.0.1:8443', + :private_key => File.read(File.join(File.dirname(__FILE__), 'data', 'excon.cert.key')), + :certificate => File.read(File.join(File.dirname(__FILE__), 'data', 'excon.cert.crt')) + ) + end +end + +Shindo.tests('Excon basics (Unix socket)') do + pending if RUBY_PLATFORM == 'java' # need to find suitable server for jruby + + file_name = '/tmp/unicorn.sock' + with_unicorn('basic.ru', 'unix://'+file_name) do + basic_tests("unix:/", :socket => file_name) + + tests('explicit uri passed to connection') do + tests('GET /content-length/100').returns(200) do + connection = Excon::Connection.new({ + :socket => file_name, + :nonblock => false, + :scheme => 'unix', + :ssl_verify_peer => false + }) + response = connection.request(:method => :get, :path => '/content-length/100') + response[:status] + end + end + + tests('http Host header is empty') do + tests('GET /headers').returns("") do + connection = Excon::Connection.new({ + :socket => file_name, + :nonblock => false, + :scheme => 'unix', + :ssl_verify_peer => false + }) + response = connection.request(:method => :get, :path => '/headers') + JSON.parse(response.body)['HTTP_HOST'] + end + end + end +end + +Shindo.tests('Excon basics (reusable local port)') do + class CustomSocket < Socket + def initialize + super(AF_INET, SOCK_STREAM, 0) + setsockopt(SOL_SOCKET, SO_REUSEADDR, true) + if defined?(SO_REUSEPORT) + setsockopt(SOL_SOCKET, SO_REUSEPORT, true) + end + end + + def bind(address, port) + super(Socket.pack_sockaddr_in(port, address)) + end + + def connect(address, port) + super(Socket.pack_sockaddr_in(port, address)) + end + + def http_get(path) + print "GET /content-length/10 HTTP/1.0\r\n\r\n" + read.split("\r\n\r\n", 2)[1] + end + + def self.ip_address_list + if Socket.respond_to?(:ip_address_list) + Socket.ip_address_list.select(&:ipv4?).map(&:ip_address) + else + `ifconfig`.scan(/inet.*?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/).flatten + end + end + + def self.find_alternate_ip(ip) + ip_address_list.detect {|a| a != ip } || '127.0.0.1' + end + end + + with_rackup('basic.ru', '0.0.0.0') do + connection = Excon.new("http://127.0.0.1:9292/echo", + :reuseaddr => true, # enable address and port reuse + :persistent => true # keep the socket open + ) + response = connection.get + + tests('has a local port').returns(true) do + response.local_port.to_s =~ /\d{4,5}/ ? true : false + end + + tests('local port can be re-bound').returns('x' * 10) do + # create a socket with address/port reuse enabled + s = CustomSocket.new + + # bind to the same local port and address used in the get above (won't work without reuse options on both sockets) + s.bind(response.local_address, response.local_port) + + # connect to the server on a different address than was used for the initial connection to avoid duplicate 5-tuples of: {protcol, src_port, src_addr, dst_port, dst_addr} + s.connect(CustomSocket.find_alternate_ip(response.local_address), 9292) + + # send the request + body = s.http_get("/content-length/10") + + # close both the sockets + s.close + connection.reset + + body + end + end +end diff --git a/lib/vendor/excon/tests/complete_responses.rb b/lib/vendor/excon/tests/complete_responses.rb new file mode 100644 index 0000000..1dc79f7 --- /dev/null +++ b/lib/vendor/excon/tests/complete_responses.rb @@ -0,0 +1,31 @@ +Shindo.tests('Excon Response Validation') do + env_init + + with_server('good') do + tests('good responses with complete headers') do + 100.times do + res = Excon.get('http://127.0.0.1:9292/chunked/simple') + returns(true) { res.body == "hello world" } + returns(true) { res.status_line == "HTTP/1.1 200 OK\r\n" } + returns(true) { res.status == 200} + returns(true) { res.reason_phrase == "OK" } + returns(true) { res.remote_ip == "127.0.0.1" } + end + end + end + + with_server('error') do + tests('error responses with complete headers') do + 100.times do + res = Excon.get('http://127.0.0.1:9292/error/not_found') + returns(true) { res.body == "server says not found" } + returns(true) { res.status_line == "HTTP/1.1 404 Not Found\r\n" } + returns(true) { res.status == 404} + returns(true) { res.reason_phrase == "Not Found" } + returns(true) { res.remote_ip == "127.0.0.1" } + end + end + end + + env_restore +end diff --git a/lib/vendor/excon/tests/data/127.0.0.1.cert.crt b/lib/vendor/excon/tests/data/127.0.0.1.cert.crt new file mode 100644 index 0000000..b2684a9 --- /dev/null +++ b/lib/vendor/excon/tests/data/127.0.0.1.cert.crt @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICEzCCAXwCCQC94mWSE0+JcjANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV +UzELMAkGA1UECBMCQ0ExDjAMBgNVBAoTBUV4Y29uMQ4wDAYDVQQLEwVFeGNvbjES +MBAGA1UEAxMJMTI3LjAuMC4xMB4XDTE0MTAyODIwMjMzMVoXDTE5MTAyNzIwMjMz +MVowTjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMQ4wDAYDVQQKEwVFeGNvbjEO +MAwGA1UECxMFRXhjb24xEjAQBgNVBAMTCTEyNy4wLjAuMTCBnzANBgkqhkiG9w0B +AQEFAAOBjQAwgYkCgYEAvqlKlQMoS4q9jgsm+sBh7B9jEJVYHqNBluqgLubMEmjs +xFZUIicx+LmMPfUdnqtGDihR7q3yh/xeJuzzux38FBwTBDl8NRXWSyRkJqdi9XUA +qihAlkqDoZ6Eb867isF7C5FEqohAuCE0FUaYU1HY3bV/foLqxEbyvQVwaRZ4rjkC +AwEAATANBgkqhkiG9w0BAQUFAAOBgQCRxnrtbFJrBT4duYtOVuG/j8G46bf1DPrF +wuRf38gdO2Ldu+kdNRMhQrgSA9CfkjwwQpcVK2gZTuGTdmtqTnvIKilsomtG3tFK +ThWxuW6HrU9XgZ5KXIguVnL5tjYBNslsCFiQUeU/b8GF2MyMkOGUIC0p411ZB9v/ +mTKRgzf/qQ== +-----END CERTIFICATE----- diff --git a/lib/vendor/excon/tests/data/127.0.0.1.cert.key b/lib/vendor/excon/tests/data/127.0.0.1.cert.key new file mode 100644 index 0000000..9e8a2c9 --- /dev/null +++ b/lib/vendor/excon/tests/data/127.0.0.1.cert.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQC+qUqVAyhLir2OCyb6wGHsH2MQlVgeo0GW6qAu5swSaOzEVlQi +JzH4uYw99R2eq0YOKFHurfKH/F4m7PO7HfwUHBMEOXw1FdZLJGQmp2L1dQCqKECW +SoOhnoRvzruKwXsLkUSqiEC4ITQVRphTUdjdtX9+gurERvK9BXBpFniuOQIDAQAB +AoGAEwJB41VrQQzWFUFbY4imuqnucIrTPEq+kVNXIRX1pqg7Yt/Qh48s1kV5i/vS +Ni2RUHwInylMku5AXNUm/7LfnN1zCHiYVkddL6df73BdzKfM86j+eQJdqye3AOkZ +GKrutsE8AEwOBCPtM9z3EbbAQZQpBBGyvAH3z8/GFLa34LkCQQDwEhEJleUGxiSR +anm43iFWsNBqW680YSz3kh1O7aC+09u8BOvOJ6azOMBYgxBno0IR9Oe7k0iBl+8e +YJmAuCVHAkEAy0/wdYeKwv9Dd3y9I+lS11VvaQaY2dmFmhbkPl/AAjUHju5ZF7me +Znwpq0jLlKRlKatVjkO/mkOeWs1/8MhQfwJBAO5VkVKJ3IjAF7fCFDvjUwfEm/Sr +NyJyQvk5tx0PrqEkpSZhYFUXaljNQ6/b1mJ9Yu9+yrye+MGnu73Vuy9eIasCQFT4 +fejA0y+X+5xul6Xwl9zDKiLczPkPPhUeSBoBbn/9pcEIwFd4DkmKzud1LxBafKUj +pEgm7GcOp5oPlM8PCQUCQQCtPFpgobUK9nRewxWagUL+xlEo6C1CPFhTwtQvnyi9 +6UwgxZtOdzAc3xRvHo4uK3OwGuOklqkpIeiZg3hoZb6B +-----END RSA PRIVATE KEY----- diff --git a/lib/vendor/excon/tests/data/excon.cert.crt b/lib/vendor/excon/tests/data/excon.cert.crt new file mode 100644 index 0000000..8218e49 --- /dev/null +++ b/lib/vendor/excon/tests/data/excon.cert.crt @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICLzCCAZgCCQCidUHPgYe2GTANBgkqhkiG9w0BAQUFADBcMQswCQYDVQQGEwJV +UzELMAkGA1UECBMCQ0ExDjAMBgNVBAoTBUV4Y29uMQ4wDAYDVQQDEwVFeGNvbjEg +MB4GCSqGSIb3DQEJARYRYWRtaW5AZXhhbXBsZS5jb20wHhcNMTMwMjI4MTczOTE4 +WhcNMTYxMTI0MTczOTE4WjBcMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExDjAM +BgNVBAoTBUV4Y29uMQ4wDAYDVQQDEwVFeGNvbjEgMB4GCSqGSIb3DQEJARYRYWRt +aW5AZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKM+fgjX +QfkMpepEWQBO7pJLQgoYY0EntYSgmFF/9E+fcokCWAHtmF5SBtpbWJRz3BcZ/bfq +zYYlbTAWu3nsXwsp/6Fn4dv1jvUAreAH3QV4nm8kU8FcoQ8O085UruEHIDTpZec2 +hBLTcU7dDbxAdMJLZSpkxi75I8iB9+PKGYfRAgMBAAEwDQYJKoZIhvcNAQEFBQAD +gYEAMJvD5vR3k3EkWodu318aQEgQWpy+KONsqVuL48qYevbNiEnc91Gao9i7bu66 +LIJFcJ/OKLvPuIOXY5KDaCo8zo/RYD2uFJK4uauQVUltfnz9CM3bMxIZpChAipNW +Crnwin6S9W9SGjJ8PY4kwRv7T9NBOsjP7YG3Zpb1YSETKug= +-----END CERTIFICATE----- diff --git a/lib/vendor/excon/tests/data/excon.cert.key b/lib/vendor/excon/tests/data/excon.cert.key new file mode 100644 index 0000000..c28c0a0 --- /dev/null +++ b/lib/vendor/excon/tests/data/excon.cert.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQCjPn4I10H5DKXqRFkATu6SS0IKGGNBJ7WEoJhRf/RPn3KJAlgB +7ZheUgbaW1iUc9wXGf236s2GJW0wFrt57F8LKf+hZ+Hb9Y71AK3gB90FeJ5vJFPB +XKEPDtPOVK7hByA06WXnNoQS03FO3Q28QHTCS2UqZMYu+SPIgffjyhmH0QIDAQAB +AoGACOChIgHyyIRzkWXeITIprzMAiGQDIcvzBx9kqSn4M0xMgj7qYlB1dMupK77D +9m7GjUsQjSvruVvXsEHMODkugeqo/3pZWMj+WnXBtc+30Jfi1kzkTjk/dL0jG0hq +rrb+10dS/jfhlUptgzG3tnDHOS1AqEhwH3zXSqn/AoNy5DECQQDV1f+1pGhLTf66 +6u9IJc6MZz5zw2avdk9O6qo6et+gmtq1FIpvw7S3zP8M0uOq0+g88PHQSgBeJ8BZ +9YUWdH9XAkEAw264uoNVCpEpscfbGPRkDZXZE8nhmqhzkWBZAMcsqjdTcMdW0tVB +8zdNSGg7GWM5svSKrJcx++Xxpyu1AftBFwJABvF77Bn6iPdvXgJi4qTXoBd6H6go +nWnqCVX1URDMUhq1H0wbcqWYKJ+vaGswmUtoLxJjx6+fc282/7TJLYF64QJBAIbQ +ZVw8ZriwZLu/61Mum6qHeUTeWePPWlGpzhvsSdJt8gB1cl5kQGdf+c7+H+6mdVIO +wW7HqfJjsCyqyOXCBicCQQDS8aqD1iSfOi7J8DNrX1LnGudqdn+I7YITeQWZd07a +uEiTDzuaxaDtvUrN+jS0I9K1Vnn2UQCwqhvFaARHzgVB +-----END RSA PRIVATE KEY----- diff --git a/lib/vendor/excon/tests/data/xs b/lib/vendor/excon/tests/data/xs new file mode 100644 index 0000000..762ca2a --- /dev/null +++ b/lib/vendor/excon/tests/data/xs @@ -0,0 +1 @@ +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx diff --git a/lib/vendor/excon/tests/error_tests.rb b/lib/vendor/excon/tests/error_tests.rb new file mode 100644 index 0000000..c1bf15f --- /dev/null +++ b/lib/vendor/excon/tests/error_tests.rb @@ -0,0 +1,145 @@ + + +Shindo.tests('HTTPStatusError request/response debugging') do + + # Regression against e300458f2d9330cb265baeb8973120d08c665d9 + tests('Excon::Error knows about pertinent errors') do + expected = [ + 100, + 101, + (200..206).to_a, + (300..307).to_a, + (400..417).to_a, + 422, + 429, + (500..504).to_a + ] + expected.flatten == Excon::Error.status_errors.keys + end + + tests('new returns an Error').returns(true) do + Excon::Error.new('bar').class == Excon::Error + end + + tests('new raises errors for bad URIs').returns(true) do + begin + Excon.new('foo') + false + rescue => err + err.to_s.include? 'foo' + end + end + + tests('new raises errors for bad paths').returns(true) do + begin + Excon.new('http://localhost', path: "foo\r\nbar: baz") + false + rescue => err + err.to_s.include? "foo\r\nbar: baz" + end + end + + tests('can raise standard error and catch standard error').returns(true) do + begin + raise Excon::Error::Client.new('foo') + rescue Excon::Error => e + true + end + end + + tests('can raise legacy errors and catch legacy errors').returns(true) do + begin + raise Excon::Errors::Error.new('bar') + rescue Excon::Errors::Error => e + true + end + end + + tests('can raise standard error and catch legacy errors').returns(true) do + begin + raise Excon::Error::NotFound.new('bar') + rescue Excon::Errors::Error => e + true + end + end + + tests('can raise with status_error() and catch with standard error').returns(true) do + begin + raise Excon::Error.status_error({expects: 200}, {status: 400}) + rescue Excon::Error + true + end + end + + + tests('can raise with status_error() and catch with legacy error').returns(true) do + begin + raise Excon::Error.status_error({expects: 200}, {status: 400}) + rescue Excon::Errors::BadRequest + true + end + end + + tests('can raise with legacy status_error() and catch with legacy').returns(true) do + begin + raise Excon::Errors.status_error({expects: 200}, {status: 400}) + rescue Excon::Errors::BadRequest + true + end + end + + + tests('can raise with legacy status_error() and catch with standard').returns(true) do + begin + raise Excon::Errors.status_error({expects: 200}, {status: 400}) + rescue Excon::Error + true + end + end + + with_server('error') do + + tests('message does not include response or response info').returns(true) do + begin + Excon.get('http://127.0.0.1:9292/error/not_found', :expects => 200) + rescue Excon::Errors::HTTPStatusError => err + err.message.include?('Expected(200) <=> Actual(404 Not Found)') && + !err.message.include?('excon.error.request') && + !err.message.include?('excon.error.response') + end + end + + tests('message includes only request info').returns(true) do + begin + Excon.get('http://127.0.0.1:9292/error/not_found', :expects => 200, + :debug_request => true) + rescue Excon::Errors::HTTPStatusError => err + err.message.include?('Expected(200) <=> Actual(404 Not Found)') && + err.message.include?('excon.error.request') && + !err.message.include?('excon.error.response') + end + end + + tests('message includes only response info').returns(true) do + begin + Excon.get('http://127.0.0.1:9292/error/not_found', :expects => 200, + :debug_response => true) + rescue Excon::Errors::HTTPStatusError => err + err.message.include?('Expected(200) <=> Actual(404 Not Found)') && + !err.message.include?('excon.error.request') && + err.message.include?('excon.error.response') + end + end + + tests('message include request and response info').returns(true) do + begin + Excon.get('http://127.0.0.1:9292/error/not_found', :expects => 200, + :debug_request => true, :debug_response => true) + rescue Excon::Errors::HTTPStatusError => err + err.message.include?('Expected(200) <=> Actual(404 Not Found)') && + err.message.include?('excon.error.request') && + err.message.include?('excon.error.response') + end + end + end +end diff --git a/lib/vendor/excon/tests/header_tests.rb b/lib/vendor/excon/tests/header_tests.rb new file mode 100644 index 0000000..a779b05 --- /dev/null +++ b/lib/vendor/excon/tests/header_tests.rb @@ -0,0 +1,119 @@ +Shindo.tests('Excon response header support') do + env_init + + tests('Excon::Headers storage') do + headers = Excon::Headers.new + headers['Exact-Case'] = 'expected' + headers['Another-Fixture'] = 'another' + + tests('stores and retrieves as received').returns('expected') do + headers['Exact-Case'] + end + + tests('enumerates keys as received') do + ks = headers.keys + tests('contains Exact-Case').returns(true) { ks.include? 'Exact-Case' } + tests('contains Another-Fixture').returns(true) { ks.include? 'Another-Fixture' } + end + + tests('supports case-insensitive access').returns('expected') do + headers['EXACT-CASE'] + end + + tests('but still returns nil for missing keys').returns(nil) do + headers['Missing-Header'] + end + + tests('Hash methods that should support case-insensitive access') do + if {}.respond_to? :assoc + tests('#assoc').returns(%w{exact-case expected}) do + headers.assoc('exact-Case') + end + end + + tests('#delete') do + tests('with just a key').returns('yes') do + headers['Extra'] = 'yes' + headers.delete('extra') + end + + tests('with a proc').returns('called with notpresent') do + headers.delete('notpresent') { |k| "called with #{k}" } + end + end + + tests('#fetch') do + tests('when present').returns('expected') { headers.fetch('exact-CASE') } + tests('with a default value').returns('default') { headers.fetch('missing', 'default') } + tests('with a default proc').returns('got missing') do + headers.fetch('missing') { |k| "got #{k}" } + end + end + + tests('#has_key?') do + tests('when present').returns(true) { headers.has_key?('EXACT-case') } + tests('when absent').returns(false) { headers.has_key?('missing') } + end + + tests('#values_at') do + tests('all present').returns(%w{expected another}) do + headers.values_at('exACT-cASE', 'anotheR-fixturE') + end + tests('some missing').returns(['expected', nil]) do + headers.values_at('exact-case', 'missing-header') + end + end + end + end + + with_rackup('response_header.ru') do + + tests('Response#get_header') do + connection = nil + response = nil + + tests('with variable header capitalization') do + + tests('response.get_header("mixedcase-header")').returns('MixedCase') do + connection = Excon.new('http://foo.com:8080', :proxy => 'http://127.0.0.1:9292') + response = connection.request(:method => :get, :path => '/foo') + + response.get_header("mixedcase-header") + end + + tests('response.get_header("uppercase-header")').returns('UPPERCASE') do + response.get_header("uppercase-header") + end + + tests('response.get_header("lowercase-header")').returns('lowercase') do + response.get_header("lowercase-header") + end + + end + + tests('when provided key capitalization varies') do + + tests('response.get_header("MIXEDCASE-HEADER")').returns('MixedCase') do + response.get_header("MIXEDCASE-HEADER") + end + + tests('response.get_header("MiXeDcAsE-hEaDeR")').returns('MixedCase') do + response.get_header("MiXeDcAsE-hEaDeR") + end + + end + + tests('when header is unavailable') do + + tests('response.get_header("missing")').returns(nil) do + response.get_header("missing") + end + + end + + end + + end + + env_restore +end diff --git a/lib/vendor/excon/tests/middlewares/canned_response_tests.rb b/lib/vendor/excon/tests/middlewares/canned_response_tests.rb new file mode 100644 index 0000000..e4a70c1 --- /dev/null +++ b/lib/vendor/excon/tests/middlewares/canned_response_tests.rb @@ -0,0 +1,34 @@ +Shindo.tests("Excon support for middlewares that return canned responses") do + the_body = "canned" + + canned_response_middleware = Class.new(Excon::Middleware::Base) do + define_method :request_call do |params| + params[:response] = { + :body => the_body, + :headers => {}, + :status => 200 + } + super(params) + end + end + + tests('does not mutate the canned response body').returns(the_body) do + Excon.get( + 'http://some-host.com/some-path', + :middlewares => [canned_response_middleware] + Excon.defaults[:middlewares] + ).body + end + + tests('yields non-mutated body to response_block').returns(the_body) do + body = '' + response_block = lambda { |chunk, _, _| body << chunk } + Excon.get( + 'http://some-host.com/some-path', + :middlewares => [canned_response_middleware] + Excon.defaults[:middlewares], + :response_block => response_block + ) + body + end + +end + diff --git a/lib/vendor/excon/tests/middlewares/capture_cookies_tests.rb b/lib/vendor/excon/tests/middlewares/capture_cookies_tests.rb new file mode 100644 index 0000000..0e681ce --- /dev/null +++ b/lib/vendor/excon/tests/middlewares/capture_cookies_tests.rb @@ -0,0 +1,34 @@ +Shindo.tests("Excon redirecting with cookie preserved") do + env_init + + with_rackup('redirecting_with_cookie.ru') do + tests('second request will send cookies set by the first').returns('ok') do + Excon.get( + 'http://127.0.0.1:9292', + :path => '/sets_cookie', + :middlewares => Excon.defaults[:middlewares] + [Excon::Middleware::CaptureCookies, Excon::Middleware::RedirectFollower] + ).body + end + + tests('second request will send multiple cookies set by the first').returns('ok') do + Excon.get( + 'http://127.0.0.1:9292', + :path => '/sets_multi_cookie', + :middlewares => Excon.defaults[:middlewares] + [Excon::Middleware::CaptureCookies, Excon::Middleware::RedirectFollower] + ).body + end + end + + with_rackup('redirecting.ru') do + tests("runs normally when there are no cookies set").returns('ok') do + Excon.post( + 'http://127.0.0.1:9292', + :path => '/first', + :middlewares => Excon.defaults[:middlewares] + [Excon::Middleware::CaptureCookies, Excon::Middleware::RedirectFollower], + :body => "a=Some_content" + ).body + end + end + + env_restore +end diff --git a/lib/vendor/excon/tests/middlewares/decompress_tests.rb b/lib/vendor/excon/tests/middlewares/decompress_tests.rb new file mode 100644 index 0000000..aebba34 --- /dev/null +++ b/lib/vendor/excon/tests/middlewares/decompress_tests.rb @@ -0,0 +1,157 @@ +Shindo.tests('Excon Decompress Middleware') do + env_init + + with_server('good') do + + before do + @connection ||= Excon.new( + 'http://127.0.0.1:9292/echo/content-encoded', + :method => :post, + :body => 'hello world', + :middlewares => Excon.defaults[:middlewares] + [Excon::Middleware::Decompress] + ) + end + + tests('gzip') do + resp = nil + + tests('response body decompressed').returns('hello world') do + resp = @connection.request( + :headers => { 'Accept-Encoding' => 'gzip, deflate;q=0' } + ) + resp[:body] + end + + tests('server sent content-encoding').returns('gzip') do + resp[:headers]['Content-Encoding-Sent'] + end + + tests('removes processed encoding from header').returns('') do + resp[:headers]['Content-Encoding'] + end + + tests('empty response body').returns('') do + resp = @connection.request(:body => '') + resp[:body] + end + end + + tests('deflate') do + resp = nil + + tests('response body decompressed').returns('hello world') do + resp = @connection.request( + :headers => { 'Accept-Encoding' => 'gzip;q=0, deflate' } + ) + resp[:body] + end + + tests('server sent content-encoding').returns('deflate') do + resp[:headers]['Content-Encoding-Sent'] + end + + tests('removes processed encoding from header').returns('') do + resp[:headers]['Content-Encoding'] + end + end + + tests('with pre-encoding') do + resp = nil + + tests('server sent content-encoding').returns('other, gzip') do + resp = @connection.request( + :headers => { 'Accept-Encoding' => 'gzip, deflate;q=0', + 'Content-Encoding-Pre' => 'other' } + ) + resp[:headers]['Content-Encoding-Sent'] + end + + tests('processed encoding removed from header').returns('other') do + resp[:headers]['Content-Encoding'] + end + + tests('response body decompressed').returns('hello world') do + resp[:body] + end + + end + + tests('with post-encoding') do + resp = nil + + tests('server sent content-encoding').returns('gzip, other') do + resp = @connection.request( + :headers => { 'Accept-Encoding' => 'gzip, deflate;q=0', + 'Content-Encoding-Post' => 'other' } + ) + resp[:headers]['Content-Encoding-Sent'] + end + + tests('unprocessed since last applied is unknown').returns('gzip, other') do + resp[:headers]['Content-Encoding'] + end + + tests('response body still compressed').returns('hello world') do + Zlib::GzipReader.new(StringIO.new(resp[:body])).read + end + + end + + tests('with a :response_block') do + captures = nil + resp = nil + + tests('server sent content-encoding').returns('gzip') do + captures = capture_response_block do |block| + resp = @connection.request( + :headers => { 'Accept-Encoding' => 'gzip'}, + :response_block => block + ) + end + resp[:headers]['Content-Encoding-Sent'] + end + + tests('unprocessed since :response_block was used').returns('gzip') do + resp[:headers]['Content-Encoding'] + end + + tests(':response_block passed unprocessed data').returns('hello world') do + body = captures.map {|capture| capture[0] }.join + Zlib::GzipReader.new(StringIO.new(body)).read + end + + end + + tests('adds Accept-Encoding if needed') do + + tests('without a :response_block').returns('deflate, gzip') do + resp = Excon.post( + 'http://127.0.0.1:9292/echo/request', + :body => 'hello world', + :middlewares => Excon.defaults[:middlewares] + + [Excon::Middleware::Decompress] + ) + request = Marshal.load(resp.body) + request[:headers]['Accept-Encoding'] + end + + tests('with a :response_block').returns(nil) do + captures = capture_response_block do |block| + Excon.post( + 'http://127.0.0.1:9292/echo/request', + :body => 'hello world', + :response_block => block, + :middlewares => Excon.defaults[:middlewares] + + [Excon::Middleware::Decompress] + ) + end + request = Marshal.load(captures.map {|capture| capture[0] }.join) + request[:headers]['Accept-Encoding'] + end + + end + + end + + env_restore +end diff --git a/lib/vendor/excon/tests/middlewares/escape_path_tests.rb b/lib/vendor/excon/tests/middlewares/escape_path_tests.rb new file mode 100644 index 0000000..f354a0f --- /dev/null +++ b/lib/vendor/excon/tests/middlewares/escape_path_tests.rb @@ -0,0 +1,36 @@ +Shindo.tests('Excon Decompress Middleware') do + env_init + with_rackup('basic.ru') do + tests('encoded uri passed to connection') do + tests('GET /echo%20dirty').returns(200) do + connection = Excon::Connection.new({ + :host => '127.0.0.1', + :hostname => '127.0.0.1', + :middlewares => Excon.defaults[:middlewares] + [Excon::Middleware::EscapePath], + :nonblock => false, + :port => 9292, + :scheme => 'http', + :ssl_verify_peer => false + }) + response = connection.request(:method => :get, :path => '/echo%20dirty') + response[:status] + end + end + + tests('unencoded uri passed to connection') do + tests('GET /echo dirty').returns(200) do + connection = Excon::Connection.new({ + :host => '127.0.0.1', + :hostname => '127.0.0.1', + :middlewares => Excon.defaults[:middlewares] + [Excon::Middleware::EscapePath], + :nonblock => false, + :port => 9292, + :scheme => 'http', + :ssl_verify_peer => false + }) + response = connection.request(:method => :get, :path => '/echo dirty') + response[:status] + end + end + end +end diff --git a/lib/vendor/excon/tests/middlewares/idempotent_tests.rb b/lib/vendor/excon/tests/middlewares/idempotent_tests.rb new file mode 100644 index 0000000..4c58bce --- /dev/null +++ b/lib/vendor/excon/tests/middlewares/idempotent_tests.rb @@ -0,0 +1,131 @@ +Shindo.tests('Excon request idempotencey') do + + before do + @connection = Excon.new('http://127.0.0.1:9292', :mock => true) + end + + after do + # flush any existing stubs after each test + Excon.stubs.clear + end + + tests("Non-idempotent call with an erroring socket").raises(Excon::Errors::SocketError) do + run_count = 0 + Excon.stub({:method => :get}) { |params| + run_count += 1 + if run_count <= 3 # First 3 calls fail. + raise Excon::Errors::SocketError.new(Exception.new "Mock Error") + else + {:body => params[:body], :headers => params[:headers], :status => 200} + end + } + + @connection.request(:method => :get, :path => '/some-path') + end + + tests("Idempotent request with socket erroring first 3 times").returns(200) do + run_count = 0 + Excon.stub({:method => :get}) { |params| + run_count += 1 + if run_count <= 3 # First 3 calls fail. + raise Excon::Errors::SocketError.new(Exception.new "Mock Error") + else + {:body => params[:body], :headers => params[:headers], :status => 200} + end + } + + response = @connection.request(:method => :get, :idempotent => true, :path => '/some-path') + response.status + end + + tests("Idempotent request with socket erroring first 5 times").raises(Excon::Errors::SocketError) do + run_count = 0 + Excon.stub({:method => :get}) { |params| + run_count += 1 + if run_count <= 5 # First 5 calls fail. + raise Excon::Errors::SocketError.new(Exception.new "Mock Error") + else + {:body => params[:body], :headers => params[:headers], :status => 200} + end + } + + response = @connection.request(:method => :get, :idempotent => true, :path => '/some-path') + response.status + end + + tests("Lowered retry limit with socket erroring first time").returns(200) do + run_count = 0 + Excon.stub({:method => :get}) { |params| + run_count += 1 + if run_count <= 1 # First call fails. + raise Excon::Errors::SocketError.new(Exception.new "Mock Error") + else + {:body => params[:body], :headers => params[:headers], :status => 200} + end + } + + response = @connection.request(:method => :get, :idempotent => true, :path => '/some-path', :retry_limit => 2) + response.status + end + + tests("Lowered retry limit with socket erroring first 3 times").raises(Excon::Errors::SocketError) do + run_count = 0 + Excon.stub({:method => :get}) { |params| + run_count += 1 + if run_count <= 3 # First 3 calls fail. + raise Excon::Errors::SocketError.new(Exception.new "Mock Error") + else + {:body => params[:body], :headers => params[:headers], :status => 200} + end + } + + response = @connection.request(:method => :get, :idempotent => true, :path => '/some-path', :retry_limit => 2) + response.status + end + + tests("Raised retry limit with socket erroring first 5 times").returns(200) do + run_count = 0 + Excon.stub({:method => :get}) { |params| + run_count += 1 + if run_count <= 5 # First 5 calls fail. + raise Excon::Errors::SocketError.new(Exception.new "Mock Error") + else + {:body => params[:body], :headers => params[:headers], :status => 200} + end + } + + response = @connection.request(:method => :get, :idempotent => true, :path => '/some-path', :retry_limit => 8) + response.status + end + + tests("Raised retry limit with socket erroring first 9 times").raises(Excon::Errors::SocketError) do + run_count = 0 + Excon.stub({:method => :get}) { |params| + run_count += 1 + if run_count <= 9 # First 9 calls fail. + raise Excon::Errors::SocketError.new(Exception.new "Mock Error") + else + {:body => params[:body], :headers => params[:headers], :status => 200} + end + } + + response = @connection.request(:method => :get, :idempotent => true, :path => '/some-path', :retry_limit => 8) + response.status + end + + tests("Retry limit in constructor with socket erroring first 5 times").returns(200) do + run_count = 0 + Excon.stub({:method => :get}) { |params| + run_count += 1 + if run_count <= 5 # First 5 calls fail. + raise Excon::Errors::SocketError.new(Exception.new "Mock Error") + else + {:body => params[:body], :headers => params[:headers], :status => 200} + end + } + + response = @connection.request(:method => :get, :idempotent => true, :path => '/some-path', :retry_limit => 6) + response.status + end + +end diff --git a/lib/vendor/excon/tests/middlewares/instrumentation_tests.rb b/lib/vendor/excon/tests/middlewares/instrumentation_tests.rb new file mode 100644 index 0000000..cc2f57d --- /dev/null +++ b/lib/vendor/excon/tests/middlewares/instrumentation_tests.rb @@ -0,0 +1,315 @@ +require 'active_support/notifications' +require 'securerandom' + +class SimpleInstrumentor + class << self + attr_accessor :events, :blocks + + def instrument(name, params = {}, &block) + @events << name + @blocks << name if block_given? + + yield if block_given? + end + + def reset! + @events = [] + @blocks = [] + end + end +end + +Shindo.tests('Excon instrumentation') do + + after do + ActiveSupport::Notifications.unsubscribe("excon") + ActiveSupport::Notifications.unsubscribe("excon.request") + ActiveSupport::Notifications.unsubscribe("excon.response") + ActiveSupport::Notifications.unsubscribe("excon.retry") + ActiveSupport::Notifications.unsubscribe("excon.error") + ActiveSupport::Notifications.unsubscribe("gug") + Delorean.back_to_the_present + Excon.stubs.clear + end + + before do + SimpleInstrumentor.reset! + end + + def subscribe(match) + @events = [] + ActiveSupport::Notifications.subscribe(match) do |*args| + @events << ActiveSupport::Notifications::Event.new(*args) + end + end + + def make_request(idempotent = false, params = {}) + connection = Excon.new( + 'http://127.0.0.1:9292', + :instrumentor => ActiveSupport::Notifications, + :mock => true + ) + if idempotent + params[:idempotent] = :true + end + connection.get(params) + end + + REQUEST_DELAY_SECONDS = 30 + def stub_success + Excon.stub({:method => :get}) { |params| + Delorean.jump REQUEST_DELAY_SECONDS + {:body => params[:body], :headers => params[:headers], :status => 200} + } + end + + def stub_retries + run_count = 0 + Excon.stub({:method => :get}) { |params| + run_count += 1 + if run_count <= 3 # First 3 calls fail. + raise Excon::Errors::SocketError.new(Exception.new "Mock Error") + else + {:body => params[:body], :headers => params[:headers], :status => 200} + end + } + end + + def stub_failure + Excon.stub({:method => :get}) { |params| + raise Excon::Errors::SocketError.new(Exception.new "Mock Error") + } + end + + tests('basic notification').returns(['excon.request', 'excon.response']) do + subscribe(/excon/) + stub_success + make_request + @events.map(&:name) + end + + tests('captures scheme, host, port, and path').returns([:host, :path, :port, :scheme]) do + subscribe(/excon/) + stub_success + make_request + [:host, :path, :port, :scheme].select {|k| @events.first.payload.has_key? k} + end + + tests('params in request overwrite those in constructor').returns('/cheezburger') do + subscribe(/excon/) + stub_success + make_request(false, :path => '/cheezburger') + @events.first.payload[:path] + end + + tests('notify on retry').returns(3) do + subscribe(/excon/) + stub_retries + make_request(true) + @events.count{|e| e.name =~ /retry/} + end + + tests('notify on error').returns(true) do + subscribe(/excon/) + stub_failure + raises(Excon::Errors::SocketError) do + make_request + end + + @events.any?{|e| e.name =~ /error/} + end + + tests('filtering').returns(['excon.request', 'excon.error']) do + subscribe(/excon.request/) + subscribe(/excon.error/) + stub_failure + raises(Excon::Errors::SocketError) do + make_request(true) + end + + @events.map(&:name) + end + + tests('more filtering').returns(['excon.retry', 'excon.retry', 'excon.retry']) do + subscribe(/excon.retry/) + stub_failure + raises(Excon::Errors::SocketError) do + make_request(true) + end + + @events.map(&:name) + end + + tests('indicates duration').returns(true) do + subscribe(/excon/) + stub_success + make_request + (@events.first.duration/1000 - REQUEST_DELAY_SECONDS).abs < 1 + end + + tests('standard instrumentor') do + + tests('success').returns( + ['excon.request', 'excon.retry', 'excon.retry', 'excon.retry', 'excon.error']) do + + begin + original_stderr = $stderr + $stderr = captured_stderr = StringIO.new + stub_failure + connection = Excon.new( + 'http://127.0.0.1:9292', + :instrumentor => Excon::StandardInstrumentor, + :mock => true + ) + raises(Excon::Errors::SocketError) do + connection.get(:idempotent => true) + end + + captured_stderr.string.split("\n").reject {|line| line =~ %r{^ }}.map {|event| event.split(' ').first} + ensure + $stderr = original_stderr + end + end + + tests('authorization header REDACT') do + + @auth_header = 'Basic dXNlcjpwYXNz' + + begin + original_stderr = $stderr + $stderr = @captured_stderr = StringIO.new + stub_failure + raises(Excon::Errors::SocketError) do + @connection = Excon.new( + 'http://user:pass@127.0.0.1:9292', + :headers => { + 'Authorization' => @auth_header + }, + :instrumentor => Excon::StandardInstrumentor, + :mock => true + ) + @connection.get(:idempotent => true) + end + ensure + $stderr = original_stderr + end + + test('does not appear in response') do + !@captured_stderr.string.include?(@auth_header) + end + + test('does not mutate Authorization value') do + @connection.data[:headers]['Authorization'] == @auth_header + end + + end + + tests('password REDACT') do + + begin + original_stderr = $stderr + $stderr = @captured_stderr = StringIO.new + stub_failure + raises(Excon::Errors::SocketError) do + @connection = Excon.new( + 'http://user:pass@127.0.0.1:9292', + :instrumentor => Excon::StandardInstrumentor, + :mock => true + ) + @connection.get(:idempotent => true) + end + ensure + $stderr = original_stderr + end + + @password_param = '"pass"' + + test('does not appear in response') do + !@captured_stderr.string.include?(@password_param) + end + + test('does not mutate password value') do + @connection.data[:password] == "pass" + end + + end + + end + + tests('use our own instrumentor').returns( + ['excon.request', 'excon.retry', 'excon.retry', 'excon.retry', 'excon.error']) do + stub_failure + connection = Excon.new( + 'http://127.0.0.1:9292', + :instrumentor => SimpleInstrumentor, + :mock => true + ) + raises(Excon::Errors::SocketError) do + connection.get(:idempotent => true) + end + + SimpleInstrumentor.events + end + + tests('always passes the block').returns( + ['excon.request', 'excon.response']) do + stub_success + connection = Excon.new( + 'http://127.0.0.1:9292', + :instrumentor => SimpleInstrumentor, + :mock => true + ) + connection.get(:idempotent => true) + + SimpleInstrumentor.blocks + end + + tests('does not generate events when not provided').returns(0) do + subscribe(/excon/) + stub_success + connection = Excon.new('http://127.0.0.1:9292', :mock => true) + connection.get(:idempotent => true) + @events.count + end + + tests('allows setting the prefix').returns( + ['gug.request', 'gug.retry', 'gug.retry','gug.retry', 'gug.error']) do + subscribe(/gug/) + stub_failure + connection = Excon.new( + 'http://127.0.0.1:9292', + :instrumentor => ActiveSupport::Notifications, + :instrumentor_name => 'gug', + :mock => true + ) + raises(Excon::Errors::SocketError) do + connection.get(:idempotent => true) + end + @events.map(&:name) + end + + tests('allows setting the prefix when not idempotent', 'foo').returns( + ['gug.request', 'gug.error']) do + subscribe(/gug/) + stub_failure + connection = Excon.new( + 'http://127.0.0.1:9292', + :instrumentor => ActiveSupport::Notifications, + :instrumentor_name => 'gug', + :mock => true + ) + raises(Excon::Errors::SocketError) do + connection.get() + end + @events.map(&:name) + end + + with_rackup('basic.ru') do + tests('works unmocked').returns(['excon.request', 'excon.response']) do + subscribe(/excon/) + make_request(false, :mock => false) + @events.map(&:name) + end + end +end + diff --git a/lib/vendor/excon/tests/middlewares/mock_tests.rb b/lib/vendor/excon/tests/middlewares/mock_tests.rb new file mode 100644 index 0000000..49c25aa --- /dev/null +++ b/lib/vendor/excon/tests/middlewares/mock_tests.rb @@ -0,0 +1,304 @@ +Shindo.tests('Excon stubs') do + env_init + + tests("missing stub").raises(Excon::Errors::StubNotFound) do + connection = Excon.new('http://127.0.0.1:9292', :mock => true) + connection.request(:method => :get, :path => '/content-length/100') + end + + tests("stub({})").raises(ArgumentError) do + Excon.stub({}) + end + + tests("stub({}, {}) {}").raises(ArgumentError) do + Excon.stub({}, {}) {} + end + + tests("stub({:method => :get}, {:body => 'body', :status => 200})") do + connection = nil + response = nil + + tests('response.body').returns('body') do + Excon.stub({:method => :get}, {:body => 'body', :status => 200}) + + connection = Excon.new('http://127.0.0.1:9292', :mock => true) + response = connection.request(:method => :get, :path => '/content-length/100') + + response.body + end + + tests('response.headers').returns({}) do + response.headers + end + + tests('response.status').returns(200) do + response.status + end + + tests('response_block yields body').returns('body') do + body = '' + response_block = lambda do |chunk, remaining_bytes, total_bytes| + body << chunk + end + connection.request(:method => :get, :path => '/content-length/100', :response_block => response_block) + body + end + + tests('response.body empty with response_block').returns('') do + response_block = lambda { |_, _, _| } + connection.request(:method => :get, :path => '/content-length/100', :response_block => response_block).body + end + + Excon.stubs.clear + + end + + tests("stub({:path => %r{/tests/(\S+)}}, {:body => $1, :status => 200})") do + connection = nil + response = nil + + tests('response.body').returns('test') do + Excon.stub({:path => %r{/tests/(\S+)}}) do |params| + { + :body => params[:captures][:path].first, + :status => 200 + } + end + + connection = Excon.new('http://127.0.0.1:9292', :mock => true) + response = connection.request(:method => :get, :path => '/tests/test') + + response.body + end + + tests('response.headers').returns({}) do + response.headers + end + + tests('response.status').returns(200) do + response.status + end + + Excon.stubs.clear + + end + + tests("stub({:body => 'body', :method => :get}) {|params| {:body => params[:body], :headers => params[:headers], :status => 200}}") do + connection = nil + response = nil + + tests('response.body').returns('body') do + Excon.stub({:body => 'body', :method => :get}) {|params| {:body => params[:body], :headers => params[:headers], :status => 200}} + + connection = Excon.new('http://127.0.0.1:9292', :mock => true) + response = connection.request(:body => 'body', :method => :get, :path => '/content-length/100') + + response.body + end + + tests('response.headers').returns({'Host' => '127.0.0.1:9292', 'User-Agent' => "excon/#{Excon::VERSION}"}) do + response.headers + end + + tests('response.status').returns(200) do + response.status + end + + tests('response_block yields body').returns('body') do + body = '' + response_block = lambda do |chunk, remaining_bytes, total_bytes| + body << chunk + end + connection.request(:body => 'body', :method => :get, :path => '/content-length/100', :response_block => response_block) + body + end + + tests('response.body empty with response_block').returns('') do + response_block = lambda { |_, _, _| } + connection.request(:body => 'body', :method => :get, :path => '/content-length/100', :response_block => response_block).body + end + + Excon.stubs.clear + + end + + tests("stub({:body => File.open(...), :method => :get}, { :status => 200 })") do + + tests('response.status').returns(200) do + file_path = File.join(File.dirname(__FILE__), '..', 'data', 'xs') + + Excon.stub( + { :body => File.read(file_path), :method => :get }, + { :status => 200 } + ) + + connection = Excon.new('http://127.0.0.1:9292', :mock => true) + response = connection.request(:body => File.open(file_path), :method => :get, :path => '/') + + response.status + end + + Excon.stubs.clear + + end + + tests("invalid stub response").raises(Excon::Errors::InvalidStub) do + Excon.stub({:body => 42, :method => :get}, {:status => 200}) + connection = Excon.new('http://127.0.0.1:9292', :mock => true) + connection.request(:body => 42, :method => :get, :path => '/').status + end + + tests("mismatched stub").raises(Excon::Errors::StubNotFound) do + Excon.stub({:method => :post}, {:body => 'body'}) + Excon.get('http://127.0.0.1:9292/', :mock => true) + end + + with_server('good') do + tests('allow mismatched stub').returns(200) do + Excon.stub({:path => '/echo/request_count'}, {:body => 'body'}) + Excon.get( + 'http://127.0.0.1:9292/echo/request', + :mock => true, + :allow_unstubbed_requests => true + ).status + end + end + + Excon.stubs.clear + + tests("stub({}, {:body => 'x' * (Excon::DEFAULT_CHUNK_SIZE + 1)})") do + + test("response_block yields body") do + connection = Excon.new('http://127.0.0.1:9292', :mock => true) + Excon.stub({}, {:body => 'x' * (Excon::DEFAULT_CHUNK_SIZE + 1)}) + + chunks = [] + response_block = lambda do |chunk, remaining_bytes, total_bytes| + chunks << chunk + end + connection.request(:method => :get, :path => '/content-length/100', :response_block => response_block) + chunks == ['x' * Excon::DEFAULT_CHUNK_SIZE, 'x'] + end + + tests("response.body empty with response_block").returns('') do + connection = Excon.new('http://127.0.0.1:9292', :mock => true) + Excon.stub({}, {:body => 'x' * (Excon::DEFAULT_CHUNK_SIZE + 1)}) + response_block = lambda { |_, _, _| } + connection.request(:method => :get, :path => '/content-length/100', :response_block => response_block).body + end + + end + + Excon.stubs.clear + + tests("stub({:url => 'https://user:pass@foo.bar.com:9999/baz?quux=true'}, {:status => 200})") do + test("get(:expects => 200)") do + Excon.stub({:url => 'https://user:pass@foo.bar.com:9999/baz?quux=true'}, {:status => 200}) + Excon.new("https://user:pass@foo.bar.com:9999/baz?quux=true", :mock => true).get(:expects => 200) + true + end + end + + Excon.stubs.clear + + tests("stub({}, {:status => 404, :body => 'Not Found'}") do + connection = nil + + tests("request(:expects => 200, :method => :get, :path => '/')").raises(Excon::Errors::NotFound) do + connection = Excon.new('http://127.0.0.1:9292', :mock => true) + Excon.stub({}, {:status => 404, :body => 'Not Found'}) + + connection.request(:expects => 200, :method => :get, :path => '/') + end + + tests("Expects exception should contain response object").returns(Excon::Response) do + begin + connection.request(:expects => 200, :method => :get, :path => '/') + rescue Excon::Errors::NotFound => e + e.response.class + end + end + + test("request(:expects => 200, :method => :get, :path => '/') with block does not invoke the block since it raises an error") do + block_called = false + begin + response_block = lambda do |_,_,_| + block_called = true + end + connection.request(:expects => 200, :method => :get, :path => '/', :response_block => response_block) + rescue Excon::Errors::NotFound + end + !block_called + end + + Excon.stubs.clear + + end + + tests("stub_for({})") do + tests("stub_for({})").returns([{}, {}]) do + Excon.new('http://127.0.0.1:9292', :mock => true) + Excon.stub({}, {}) + + Excon.stub_for({}) + end + + Excon.stubs.clear + end + + tests("unstub({})") do + connection = nil + + tests("unstub({})").returns([{}, {}]) do + connection = Excon.new('http://127.0.0.1:9292', :mock => true) + Excon.stub({}, {}) + + Excon.unstub({}) + end + + tests("request(:method => :get)").raises(Excon::Errors::StubNotFound) do + connection.request(:method => :get) + end + + Excon.stubs.clear + end + + tests("global stubs") do + connection = Excon.new('http://127.0.0.1:9292', :mock => true) + Excon.stub({}, {:body => '1'}) + t = Thread.new do + Excon.stub({}, {:body => '2'}) + connection.request(:method => :get).body + end + tests("get on a different thread").returns('2') do + t.join.value + end + tests("get on main thread").returns('2') do + connection.request(:method => :get).body + end + Excon.stubs.clear + end + + tests("thread-local stubs") do + original_stubs_value = Excon.defaults[:stubs] + Excon.defaults[:stubs] = :local + + connection = Excon.new('http://127.0.0.1:9292', :mock => true) + Excon.stub({}, {:body => '1'}) + t = Thread.new do + Excon.stub({}, {:body => '2'}) + connection.request(:method => :get).body + end + tests("get on a different thread").returns('2') do + t.join.value + end + tests("get on main thread").returns('1') do + connection.request(:method => :get).body + end + Excon.stubs.clear + + Excon.defaults[:stubs] = original_stubs_value + end + + env_restore +end diff --git a/lib/vendor/excon/tests/middlewares/redirect_follower_tests.rb b/lib/vendor/excon/tests/middlewares/redirect_follower_tests.rb new file mode 100644 index 0000000..d99271b --- /dev/null +++ b/lib/vendor/excon/tests/middlewares/redirect_follower_tests.rb @@ -0,0 +1,80 @@ +Shindo.tests('Excon redirector support') do + env_init + + tests("request(:method => :get, :path => '/old').body").returns('new') do + Excon.stub( + { :path => '/old' }, + { + :headers => { 'Location' => 'http://127.0.0.1:9292/new' }, + :body => 'old', + :status => 301 + } + ) + + Excon.stub( + { :path => '/new' }, + { + :body => 'new', + :status => 200 + } + ) + + Excon.get( + 'http://127.0.0.1:9292', + :path => '/old', + :middlewares => Excon.defaults[:middlewares] + [Excon::Middleware::RedirectFollower], + :mock => true + ).body + end + + env_restore +end + +Shindo.tests('Excon redirect support for relative Location headers') do + env_init + + tests("request(:method => :get, :path => '/old').body").returns('new') do + Excon.stub( + { :path => '/old' }, + { + :headers => { 'Location' => '/new' }, + :body => 'old', + :status => 301 + } + ) + + Excon.stub( + { :path => '/new' }, + { + :body => 'new', + :status => 200 + } + ) + + Excon.get( + 'http://127.0.0.1:9292', + :path => '/old', + :middlewares => Excon.defaults[:middlewares] + [Excon::Middleware::RedirectFollower], + :mock => true + ).body + end + + env_restore +end + +Shindo.tests("Excon redirecting post request") do + env_init + + with_rackup('redirecting.ru') do + tests("request not have content-length and body").returns('ok') do + Excon.post( + 'http://127.0.0.1:9292', + :path => '/first', + :middlewares => Excon.defaults[:middlewares] + [Excon::Middleware::RedirectFollower], + :body => "a=Some_content" + ).body + end + end + + env_restore +end diff --git a/lib/vendor/excon/tests/pipeline_tests.rb b/lib/vendor/excon/tests/pipeline_tests.rb new file mode 100644 index 0000000..578762b --- /dev/null +++ b/lib/vendor/excon/tests/pipeline_tests.rb @@ -0,0 +1,40 @@ +Shindo.tests('Pipelined Requests') do + with_server('good') do + + tests('with default :persistent => true') do + returns(%w{ 1 2 3 4 }, 'connection is persistent') do + connection = Excon.new('http://127.0.0.1:9292', :persistent => true) + + ret = [] + ret << connection.requests([ + {:method => :get, :path => '/echo/request_count'}, + {:method => :get, :path => '/echo/request_count'} + ]).map(&:body) + ret << connection.requests([ + {:method => :get, :path => '/echo/request_count'}, + {:method => :get, :path => '/echo/request_count'} + ]).map(&:body) + ret.flatten + end + end + + tests('with default :persistent => false') do + returns(%w{ 1 2 1 2 }, 'connection is persistent per call to #requests') do + connection = Excon.new('http://127.0.0.1:9292', :persistent => false) + + ret = [] + ret << connection.requests([ + {:method => :get, :path => '/echo/request_count'}, + {:method => :get, :path => '/echo/request_count'} + ]).map(&:body) + ret << connection.requests([ + {:method => :get, :path => '/echo/request_count'}, + {:method => :get, :path => '/echo/request_count'} + ]).map(&:body) + ret.flatten + end + + end + + end +end diff --git a/lib/vendor/excon/tests/proxy_tests.rb b/lib/vendor/excon/tests/proxy_tests.rb new file mode 100644 index 0000000..b1a5e2f --- /dev/null +++ b/lib/vendor/excon/tests/proxy_tests.rb @@ -0,0 +1,306 @@ +Shindo.tests('Excon proxy support') do + env_init + + tests('proxy configuration') do + + tests('no proxy') do + tests('connection.data[:proxy]').returns(nil) do + connection = Excon.new('http://foo.com') + connection.data[:proxy] + end + end + + tests('empty proxy') do + tests('connection.data[:proxy]').returns(nil) do + connection = Excon.new('http://foo.com', :proxy => '') + connection.data[:proxy] + end + end + + tests('with fully-specified proxy: https://myproxy.net:8080') do + connection = nil + + tests('connection.data[:proxy][:host]').returns('myproxy.net') do + connection = Excon.new('http://foo.com', :proxy => 'https://myproxy.net:8080') + connection.data[:proxy][:host] + end + + tests('connection.data[:proxy][:port]').returns(8080) do + connection.data[:proxy][:port] + end + + tests('connection.data[:proxy][:scheme]').returns('https') do + connection.data[:proxy][:scheme] + end + end + + tests('with fully-specified Unix socket proxy: unix:///') do + connection = nil + + tests('connection.data[:proxy][:host]').returns(nil) do + connection = Excon.new('http://foo.com', :proxy => 'unix:///tmp/myproxy.sock') + connection.data[:proxy][:host] + end + + tests('connection.data[:proxy][:port]').returns(nil) do + connection.data[:proxy][:port] + end + + tests('connection.data[:proxy][:scheme]').returns('unix') do + connection.data[:proxy][:scheme] + end + + tests('connection.data[:proxy][:path]').returns('/tmp/myproxy.sock') do + connection.data[:proxy][:path] + end + end + + def env_proxy_tests(env) + env_init(env) + + tests('an http connection') do + connection = nil + + tests('connection.data[:proxy][:host]').returns('myproxy') do + connection = Excon.new('http://foo.com') + connection.data[:proxy][:host] + end + + tests('connection.data[:proxy][:port]').returns(8080) do + connection.data[:proxy][:port] + end + + tests('connection.data[:proxy][:scheme]').returns('http') do + connection.data[:proxy][:scheme] + end + + tests('with disable_proxy set') do + connection = nil + + tests('connection.data[:proxy]').returns(nil) do + connection = Excon.new('http://foo.com', :disable_proxy => true) + connection.data[:proxy] + end + end + end + + tests('an https connection') do + connection = nil + + tests('connection.data[:proxy][:host]').returns('mysecureproxy') do + connection = Excon.new('https://secret.com') + connection.data[:proxy][:host] + end + + tests('connection.data[:proxy][:port]').returns(8081) do + connection.data[:proxy][:port] + end + + tests('connection.data[:proxy][:scheme]').returns('http') do + connection.data[:proxy][:scheme] + end + + tests('with disable_proxy set') do + connection = nil + + tests('connection.data[:proxy]').returns(nil) do + connection = Excon.new('https://foo.com', :disable_proxy => true) + connection.data[:proxy] + end + end + end + + tests('http proxy from the environment overrides config') do + connection = nil + + tests('connection.data[:proxy][:host]').returns('myproxy') do + connection = Excon.new('http://foo.com', :proxy => 'http://hard.coded.proxy:6666') + connection.data[:proxy][:host] + end + + tests('connection.data[:proxy][:port]').returns(8080) do + connection.data[:proxy][:port] + end + end + + tests('an http connection in no_proxy') do + tests('connection.data[:proxy]').returns(nil) do + connection = Excon.new('http://somesubdomain.noproxy') + connection.data[:proxy] + end + end + + tests('an http connection not completely matching no_proxy') do + tests('connection.data[:proxy][:host]').returns('myproxy') do + connection = Excon.new('http://noproxy2') + connection.data[:proxy][:host] + end + end + + tests('an http connection with subdomain in no_proxy') do + tests('connection.data[:proxy]').returns(nil) do + connection = Excon.new('http://a.subdomain.noproxy2') + connection.data[:proxy] + end + end + + env_restore + end + + tests('with complete proxy config from the environment') do + env = { + 'http_proxy' => 'http://myproxy:8080', + 'https_proxy' => 'http://mysecureproxy:8081', + 'no_proxy' => 'noproxy, subdomain.noproxy2' + } + tests('lowercase') { env_proxy_tests(env) } + upperenv = {} + env.each do |k, v| + upperenv[k.upcase] = v + end + tests('uppercase') { env_proxy_tests(upperenv) } + end + + tests('with only http_proxy config from the environment') do + env_init({'http_proxy' => 'http://myproxy:8080' }) + + tests('an https connection') do + connection = nil + + tests('connection.data[:proxy][:host]').returns('myproxy') do + connection = Excon.new('https://secret.com') + connection.data[:proxy][:host] + end + + tests('connection.data[:proxy][:port]').returns(8080) do + connection.data[:proxy][:port] + end + + tests('connection.data[:proxy][:scheme]').returns('http') do + connection.data[:proxy][:scheme] + end + end + + env_restore + end + + tests('with a unix socket proxy config from the environment') do + env_init({ + 'http_proxy' => 'unix:///tmp/myproxy.sock', + }) + + tests('an https connection') do + connection = nil + + tests('connection.data[:proxy][:host]').returns(nil) do + connection = Excon.new('https://secret.com') + connection.data[:proxy][:host] + end + + tests('connection.data[:proxy][:port]').returns(nil) do + connection.data[:proxy][:port] + end + + tests('connection.data[:proxy][:scheme]').returns('unix') do + connection.data[:proxy][:scheme] + end + + tests('connection.data[:proxy][:path]').returns('/tmp/myproxy.sock') do + connection.data[:proxy][:path] + end + end + + env_restore + end + + end + + with_rackup('proxy.ru') do + + tests('http proxying: http://foo.com:8080') do + response = nil + + tests('response.status').returns(200) do + connection = Excon.new('http://foo.com:8080', :proxy => 'http://127.0.0.1:9292') + response = connection.request(:method => :get, :path => '/bar', :query => {:alpha => 'kappa'}) + + response.status + end + + # must be absolute form for proxy requests + tests('sent Request URI').returns('http://foo.com:8080/bar?alpha=kappa') do + response.headers['Sent-Request-Uri'] + end + + tests('sent Sent-Host header').returns('foo.com:8080') do + response.headers['Sent-Host'] + end + + tests('sent Proxy-Connection header').returns('Keep-Alive') do + response.headers['Sent-Proxy-Connection'] + end + + tests('response.body (proxied content)').returns('proxied content') do + response.body + end + end + + tests('http proxying: http://user:pass@foo.com:8080') do + response = nil + + tests('response.status').returns(200) do + connection = Excon.new('http://foo.com:8080', :proxy => 'http://user:pass@127.0.0.1:9292') + response = connection.request(:method => :get, :path => '/bar', :query => {:alpha => 'kappa'}) + + response.status + end + + # must be absolute form for proxy requests + tests('sent Request URI').returns('http://foo.com:8080/bar?alpha=kappa') do + response.headers['Sent-Request-Uri'] + end + + tests('sent Host header').returns('foo.com:8080') do + response.headers['Sent-Host'] + end + + tests('sent Proxy-Connection header').returns('Keep-Alive') do + response.headers['Sent-Proxy-Connection'] + end + + tests('response.body (proxied content)').returns('proxied content') do + response.body + end + end + + end + + with_unicorn('proxy.ru', 'unix:///tmp/myproxy.sock') do + pending if RUBY_PLATFORM == 'java' # need to find suitable server for jruby + + tests('http proxying over unix socket: http://foo.com:8080') do + response = nil + + tests('response.status').returns(200) do + connection = Excon.new('http://foo.com:8080', :proxy => 'unix:///tmp/myproxy.sock') + response = connection.request(:method => :get, :path => '/bar', :query => {:alpha => 'kappa'}) + + response.status + end + + tests('sent Sent-Host header').returns('foo.com:8080') do + response.headers['Sent-Host'] + end + + tests('sent Proxy-Connection header').returns('Keep-Alive') do + response.headers['Sent-Proxy-Connection'] + end + + tests('response.body (proxied content)').returns('proxied content') do + response.body + end + end + end + + env_restore +end diff --git a/lib/vendor/excon/tests/query_string_tests.rb b/lib/vendor/excon/tests/query_string_tests.rb new file mode 100644 index 0000000..0304250 --- /dev/null +++ b/lib/vendor/excon/tests/query_string_tests.rb @@ -0,0 +1,87 @@ +Shindo.tests('Excon query string variants') do + with_rackup('query_string.ru') do + connection = Excon.new('http://127.0.0.1:9292') + + tests(":query => {:foo => 'bar'}") do + response = connection.request(:method => :get, :path => '/query', :query => {:foo => 'bar'}) + query_string = response.body[7..-1] # query string sent + + tests("query string sent").returns('foo=bar') do + query_string + end + end + + tests(":query => {:foo => nil}") do + response = connection.request(:method => :get, :path => '/query', :query => {:foo => nil}) + query_string = response.body[7..-1] # query string sent + + tests("query string sent").returns('foo') do + query_string + end + end + + tests(":query => {:foo => 'bar', :me => nil}") do + response = connection.request(:method => :get, :path => '/query', :query => {:foo => 'bar', :me => nil}) + query_string = response.body[7..-1] # query string sent + + test("query string sent includes 'foo=bar'") do + query_string.split('&').include?('foo=bar') + end + + test("query string sent includes 'me'") do + query_string.split('&').include?('me') + end + end + + tests(":query => {:foo => 'bar', :me => 'too'}") do + response = connection.request(:method => :get, :path => '/query', :query => {:foo => 'bar', :me => 'too'}) + query_string = response.body[7..-1] # query string sent + + test("query string sent includes 'foo=bar'") do + query_string.split('&').include?('foo=bar') + end + + test("query string sent includes 'me=too'") do + query_string.split('&').include?('me=too') + end + end + + # You can use an atom or a string for the hash keys, what is shown here is emulating + # the Rails and PHP style of serializing a query array with a square brackets suffix. + tests(":query => {'foo[]' => ['bar', 'baz'], :me => 'too'}") do + response = connection.request(:method => :get, :path => '/query', :query => {'foo[]' => ['bar', 'baz'], :me => 'too'}) + query_string = response.body[7..-1] # query string sent + + test("query string sent includes 'foo%5B%5D=bar'") do + query_string.split('&').include?('foo%5B%5D=bar') + end + + test("query string sent includes 'foo%5B%5D=baz'") do + query_string.split('&').include?('foo%5B%5D=baz') + end + + test("query string sent includes 'me=too'") do + query_string.split('&').include?('me=too') + end + end + + tests(":query => {'foo%=#' => 'bar%=#'}") do + response = connection.request(:method => :get, :path => '/query', :query => {'foo%=#' => 'bar%=#'}) + query_string = response.body[7..-1] # query string sent + + tests("query string sent").returns('foo%25%3D%23=bar%25%3D%23') do + query_string + end + end + + tests(":query => {'foo%=#' => nil}") do + response = connection.request(:method => :get, :path => '/query', :query => {'foo%=#' => nil}) + query_string = response.body[7..-1] # query string sent + + tests("query string sent").returns('foo%25%3D%23') do + query_string + end + end + + end +end diff --git a/lib/vendor/excon/tests/rackups/basic.rb b/lib/vendor/excon/tests/rackups/basic.rb new file mode 100644 index 0000000..0bddd9a --- /dev/null +++ b/lib/vendor/excon/tests/rackups/basic.rb @@ -0,0 +1,41 @@ +require 'sinatra' +require 'json' +require File.join(File.dirname(__FILE__), 'webrick_patch') + +class Basic < Sinatra::Base + set :environment, :production + enable :dump_errors + + get('/content-length/:value') do |value| + headers("Custom" => "Foo: bar") + 'x' * value.to_i + end + + get('/headers') do + content_type :json + request.env.select{|key, _| key.start_with? 'HTTP_'}.to_json + end + + post('/body-sink') do + request.body.read.size.to_s + end + + post('/echo') do + echo + end + + put('/echo') do + echo + end + + get('/echo dirty') do + echo + end + + private + + def echo + request.body.read + end + +end diff --git a/lib/vendor/excon/tests/rackups/basic.ru b/lib/vendor/excon/tests/rackups/basic.ru new file mode 100644 index 0000000..90704a2 --- /dev/null +++ b/lib/vendor/excon/tests/rackups/basic.ru @@ -0,0 +1,3 @@ +require File.join(File.dirname(__FILE__), 'basic') + +run Basic diff --git a/lib/vendor/excon/tests/rackups/basic_auth.ru b/lib/vendor/excon/tests/rackups/basic_auth.ru new file mode 100644 index 0000000..d4565fb --- /dev/null +++ b/lib/vendor/excon/tests/rackups/basic_auth.ru @@ -0,0 +1,14 @@ +require File.join(File.dirname(__FILE__), 'basic') + +class BasicAuth < Basic + before do + auth ||= Rack::Auth::Basic::Request.new(request.env) + user, pass = auth.provided? && auth.basic? && auth.credentials + unless [user, pass] == ["test_user", "test_password"] + response['WWW-Authenticate'] = %(Basic realm="Restricted Area") + throw(:halt, [401, "Not authorized\n"]) + end + end +end + +run BasicAuth diff --git a/lib/vendor/excon/tests/rackups/deflater.ru b/lib/vendor/excon/tests/rackups/deflater.ru new file mode 100644 index 0000000..1c881f1 --- /dev/null +++ b/lib/vendor/excon/tests/rackups/deflater.ru @@ -0,0 +1,4 @@ +require File.join(File.dirname(__FILE__), 'basic') + +use Rack::Deflater +run Basic diff --git a/lib/vendor/excon/tests/rackups/proxy.ru b/lib/vendor/excon/tests/rackups/proxy.ru new file mode 100644 index 0000000..e6d7b65 --- /dev/null +++ b/lib/vendor/excon/tests/rackups/proxy.ru @@ -0,0 +1,18 @@ +require 'sinatra' +require File.join(File.dirname(__FILE__), 'webrick_patch') + +class App < Sinatra::Base + set :environment, :production + enable :dump_errors + + get('*') do + headers( + "Sent-Request-Uri" => request.env['REQUEST_URI'].to_s, + "Sent-Host" => request.env['HTTP_HOST'].to_s, + "Sent-Proxy-Connection" => request.env['HTTP_PROXY_CONNECTION'].to_s + ) + 'proxied content' + end +end + +run App diff --git a/lib/vendor/excon/tests/rackups/query_string.ru b/lib/vendor/excon/tests/rackups/query_string.ru new file mode 100644 index 0000000..b5bca66 --- /dev/null +++ b/lib/vendor/excon/tests/rackups/query_string.ru @@ -0,0 +1,13 @@ +require 'sinatra' +require File.join(File.dirname(__FILE__), 'webrick_patch') + +class App < Sinatra::Base + set :environment, :production + enable :dump_errors + + get('/query') do + "query: " << request.query_string + end +end + +run App diff --git a/lib/vendor/excon/tests/rackups/redirecting.ru b/lib/vendor/excon/tests/rackups/redirecting.ru new file mode 100644 index 0000000..fff61e4 --- /dev/null +++ b/lib/vendor/excon/tests/rackups/redirecting.ru @@ -0,0 +1,23 @@ +require 'sinatra' +require 'json' +require File.join(File.dirname(__FILE__), 'webrick_patch') + +class App < Sinatra::Base + set :environment, :production + enable :dump_errors + + post('/first') do + redirect "/second" + end + + get('/second') do + post_body = request.body.read + if post_body == "" && request["CONTENT_LENGTH"].nil? + "ok" + else + JSON.pretty_generate(request.env) + end + end +end + +run App diff --git a/lib/vendor/excon/tests/rackups/redirecting_with_cookie.ru b/lib/vendor/excon/tests/rackups/redirecting_with_cookie.ru new file mode 100644 index 0000000..f0401f8 --- /dev/null +++ b/lib/vendor/excon/tests/rackups/redirecting_with_cookie.ru @@ -0,0 +1,40 @@ +require 'sinatra' +require 'sinatra/cookies' +require 'json' +require File.join(File.dirname(__FILE__), 'webrick_patch') + +class App < Sinatra::Base + helpers Sinatra::Cookies + set :environment, :production + enable :dump_errors + + get('/sets_cookie') do + cookies[:chocolatechip] = "chunky" + redirect "/requires_cookie" + end + + get('/requires_cookie') do + cookie = cookies[:chocolatechip] + unless cookie.nil? || cookie != "chunky" + "ok" + else + JSON.pretty_generate(headers) + end + end + + get('/sets_multi_cookie') do + cookies[:chocolatechip] = "chunky" + cookies[:thinmints] = "minty" + redirect "/requires_cookie" + end + + get('/requires_cookie') do + if cookies[:chocolatechip] == "chunky" && cookies[:thinmints] == "minty" + "ok" + else + JSON.pretty_generate(headers) + end + end +end + +run App diff --git a/lib/vendor/excon/tests/rackups/request_headers.ru b/lib/vendor/excon/tests/rackups/request_headers.ru new file mode 100644 index 0000000..e1cc0f8 --- /dev/null +++ b/lib/vendor/excon/tests/rackups/request_headers.ru @@ -0,0 +1,15 @@ +require 'sinatra' +require File.join(File.dirname(__FILE__), 'webrick_patch') + +class App < Sinatra::Base + set :environment, :production + enable :dump_errors + + post '/' do + h = "" + env.each { |k,v| h << "#{$1.downcase}: #{v}\n" if k =~ /http_(.*)/i } + h + end +end + +run App diff --git a/lib/vendor/excon/tests/rackups/request_methods.ru b/lib/vendor/excon/tests/rackups/request_methods.ru new file mode 100644 index 0000000..1fc08f4 --- /dev/null +++ b/lib/vendor/excon/tests/rackups/request_methods.ru @@ -0,0 +1,21 @@ +require 'sinatra' +require File.join(File.dirname(__FILE__), 'webrick_patch') + +class App < Sinatra::Base + set :environment, :production + enable :dump_errors + + get '/' do + 'GET' + end + + post '/' do + 'POST' + end + + delete '/' do + 'DELETE' + end +end + +run App diff --git a/lib/vendor/excon/tests/rackups/response_header.ru b/lib/vendor/excon/tests/rackups/response_header.ru new file mode 100644 index 0000000..be0088f --- /dev/null +++ b/lib/vendor/excon/tests/rackups/response_header.ru @@ -0,0 +1,18 @@ +require 'sinatra' +require File.join(File.dirname(__FILE__), 'webrick_patch') + +class App < Sinatra::Base + set :environment, :production + enable :dump_errors + + get('/foo') do + headers( + "MixedCase-Header" => 'MixedCase', + "UPPERCASE-HEADER" => 'UPPERCASE', + "lowercase-header" => 'lowercase' + ) + 'primary content' + end +end + +run App diff --git a/lib/vendor/excon/tests/rackups/ssl.ru b/lib/vendor/excon/tests/rackups/ssl.ru new file mode 100644 index 0000000..fc17065 --- /dev/null +++ b/lib/vendor/excon/tests/rackups/ssl.ru @@ -0,0 +1,16 @@ +require 'openssl' +require 'webrick' +require 'webrick/https' + +require File.join(File.dirname(__FILE__), 'basic') + +key_file = File.join(File.dirname(__FILE__), '..', 'data', '127.0.0.1.cert.key') +cert_file = File.join(File.dirname(__FILE__), '..', 'data', '127.0.0.1.cert.crt') +Rack::Handler::WEBrick.run(Basic, { + :Port => 9443, + :SSLEnable => true, + :SSLPrivateKey => OpenSSL::PKey::RSA.new(File.open(key_file).read), + :SSLCertificate => OpenSSL::X509::Certificate.new(File.open(cert_file).read), + :SSLCACertificateFile => cert_file, + :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE, +}) diff --git a/lib/vendor/excon/tests/rackups/ssl_mismatched_cn.ru b/lib/vendor/excon/tests/rackups/ssl_mismatched_cn.ru new file mode 100644 index 0000000..d9c5edf --- /dev/null +++ b/lib/vendor/excon/tests/rackups/ssl_mismatched_cn.ru @@ -0,0 +1,15 @@ +require 'openssl' +require 'webrick' +require 'webrick/https' + +require File.join(File.dirname(__FILE__), 'basic') +key_file = File.join(File.dirname(__FILE__), '..', 'data', 'excon.cert.key') +cert_file = File.join(File.dirname(__FILE__), '..', 'data', 'excon.cert.crt') +Rack::Handler::WEBrick.run(Basic, { + :Port => 9443, + :SSLEnable => true, + :SSLPrivateKey => OpenSSL::PKey::RSA.new(File.open(key_file).read), + :SSLCertificate => OpenSSL::X509::Certificate.new(File.open(cert_file).read), + :SSLCACertificateFile => cert_file, + :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE, +}) diff --git a/lib/vendor/excon/tests/rackups/ssl_verify_peer.ru b/lib/vendor/excon/tests/rackups/ssl_verify_peer.ru new file mode 100644 index 0000000..44d64c7 --- /dev/null +++ b/lib/vendor/excon/tests/rackups/ssl_verify_peer.ru @@ -0,0 +1,16 @@ +require 'openssl' +require 'webrick' +require 'webrick/https' + +require File.join(File.dirname(__FILE__), 'basic') +key_file = File.join(File.dirname(__FILE__), '..', 'data', 'excon.cert.key') +cert_file = File.join(File.dirname(__FILE__), '..', 'data', 'excon.cert.crt') +Rack::Handler::WEBrick.run(Basic, { + :Port => 8443, + :SSLCertName => [["CN", WEBrick::Utils::getservername]], + :SSLEnable => true, + :SSLPrivateKey => OpenSSL::PKey::RSA.new(File.open(key_file).read), + :SSLCertificate => OpenSSL::X509::Certificate.new(File.open(cert_file).read), + :SSLCACertificateFile => cert_file, + :SSLVerifyClient => OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT, +}) diff --git a/lib/vendor/excon/tests/rackups/streaming.ru b/lib/vendor/excon/tests/rackups/streaming.ru new file mode 100644 index 0000000..101c064 --- /dev/null +++ b/lib/vendor/excon/tests/rackups/streaming.ru @@ -0,0 +1,30 @@ +use Rack::ContentType, "text/plain" + +app = lambda do |env| + # streamed pieces to be sent + pieces = %w{Hello streamy world} + + response_headers = {} + + # set a fixed content length in the header if requested + if env['REQUEST_PATH'] == '/streamed/fixed_length' + response_headers['Content-Length'] = pieces.join.length.to_s + end + + response_headers["rack.hijack"] = lambda do |io| + # Write directly to IO of the response + begin + # return the response in pieces + pieces.each do |x| + sleep(0.1) + io.write(x) + io.flush + end + ensure + io.close + end + end + [200, response_headers, nil] +end + +run app diff --git a/lib/vendor/excon/tests/rackups/thread_safety.ru b/lib/vendor/excon/tests/rackups/thread_safety.ru new file mode 100644 index 0000000..436c596 --- /dev/null +++ b/lib/vendor/excon/tests/rackups/thread_safety.ru @@ -0,0 +1,17 @@ +require 'sinatra' +require File.join(File.dirname(__FILE__), 'webrick_patch') + +class App < Sinatra::Base + set :environment, :production + enable :dump_errors + + get('/id/:id/wait/:wait') do |id, wait| + sleep(wait.to_i) + id.to_s + end +end + +# get everything autoloaded, mainly for rbx +App.new + +run App diff --git a/lib/vendor/excon/tests/rackups/timeout.ru b/lib/vendor/excon/tests/rackups/timeout.ru new file mode 100644 index 0000000..060436f --- /dev/null +++ b/lib/vendor/excon/tests/rackups/timeout.ru @@ -0,0 +1,14 @@ +require 'sinatra' +require File.join(File.dirname(__FILE__), 'webrick_patch') + +class App < Sinatra::Base + set :environment, :production + enable :dump_errors + + get('/timeout') do + sleep(2) + '' + end +end + +run App diff --git a/lib/vendor/excon/tests/rackups/webrick_patch.rb b/lib/vendor/excon/tests/rackups/webrick_patch.rb new file mode 100644 index 0000000..d65cf58 --- /dev/null +++ b/lib/vendor/excon/tests/rackups/webrick_patch.rb @@ -0,0 +1,34 @@ +# The ruby 2.0 stdlib includes the following changes +# to avoid "can't add a new key into hash during iteration" errors. +# https://github.com/ruby/ruby/commit/3c491a92f6fbfecc065f7687c51c7d6d52a38883 +# https://github.com/ruby/ruby/commit/7b18633804c606e8bcccfbb44e7d7b795e777ea6 +# However, these changes were not backported to the 1.9.x stdlib. +# These errors are causing intermittent errors in the tests (frequently in jruby), +# so we're applying those changes here. This is loaded by all rackups using WEBrick. +if RUBY_VERSION =~ /^1\.9/ + require 'webrick/utils' + module WEBrick + module Utils + class TimeoutHandler + def initialize + @timeout_info = Hash.new + Thread.start{ + while true + now = Time.now + @timeout_info.keys.each{|thread| + ary = @timeout_info[thread] + next unless ary + ary.dup.each{|info| + time, exception = *info + interrupt(thread, info.object_id, exception) if time < now + } + } + sleep 0.5 + end + } + end + end + end + end +end + diff --git a/lib/vendor/excon/tests/request_headers_tests.rb b/lib/vendor/excon/tests/request_headers_tests.rb new file mode 100644 index 0000000..12ebbc7 --- /dev/null +++ b/lib/vendor/excon/tests/request_headers_tests.rb @@ -0,0 +1,21 @@ +Shindo.tests('Excon request methods') do + + with_rackup('request_headers.ru') do + + tests 'empty headers sent' do + + test('Excon.post') do + headers = { + :one => 1, + :two => nil, + :three => 3, + } + r = Excon.post('http://localhost:9292', :headers => headers).body + !r.match(/two:/).nil? + end + + end + + end + +end diff --git a/lib/vendor/excon/tests/request_method_tests.rb b/lib/vendor/excon/tests/request_method_tests.rb new file mode 100644 index 0000000..3cc9855 --- /dev/null +++ b/lib/vendor/excon/tests/request_method_tests.rb @@ -0,0 +1,47 @@ +Shindo.tests('Excon request methods') do + + with_rackup('request_methods.ru') do + + tests 'one-offs' do + + tests('Excon.get').returns('GET') do + Excon.get('http://localhost:9292').body + end + + tests('Excon.post').returns('POST') do + Excon.post('http://localhost:9292').body + end + + tests('Excon.delete').returns('DELETE') do + Excon.delete('http://localhost:9292').body + end + + end + + tests 'with a connection object' do + connection = nil + + tests('connection.get').returns('GET') do + connection = Excon.new('http://localhost:9292') + connection.get.body + end + + tests('connection.post').returns('POST') do + connection.post.body + end + + tests('connection.delete').returns('DELETE') do + connection.delete.body + end + + tests('not modifies path argument').returns('path') do + path = 'path' + connection.get(:path => path) + path + end + + end + + end + +end diff --git a/lib/vendor/excon/tests/request_tests.rb b/lib/vendor/excon/tests/request_tests.rb new file mode 100644 index 0000000..ad2662c --- /dev/null +++ b/lib/vendor/excon/tests/request_tests.rb @@ -0,0 +1,59 @@ +Shindo.tests('Request Tests') do + with_server('good') do + + tests('persistent connections') do + ip_ports = %w(127.0.0.1:9292) + ip_ports << "[::1]:9293" unless RUBY_PLATFORM == 'java' + ip_ports.each do |ip_port| + + tests("with default :persistent => true, #{ip_port}") do + connection = nil + + returns(['1', '2'], 'uses a persistent connection') do + connection = Excon.new("http://#{ip_port}", :persistent => true) + 2.times.map do + connection.request(:method => :get, :path => '/echo/request_count').body + end + end + + returns(['3', '1', '2'], ':persistent => false resets connection') do + ret = [] + ret << connection.request(:method => :get, + :path => '/echo/request_count', + :persistent => false).body + ret << connection.request(:method => :get, + :path => '/echo/request_count').body + ret << connection.request(:method => :get, + :path => '/echo/request_count').body + end + end + + tests("with default :persistent => false, #{ip_port}") do + connection = nil + + returns(['1', '1'], 'does not use a persistent connection') do + connection = Excon.new("http://#{ip_port}", :persistent => false) + 2.times.map do + connection.request(:method => :get, :path => '/echo/request_count').body + end + end + + returns(['1', '2', '3', '1'], ':persistent => true enables persistence') do + ret = [] + ret << connection.request(:method => :get, + :path => '/echo/request_count', + :persistent => true).body + ret << connection.request(:method => :get, + :path => '/echo/request_count', + :persistent => true).body + ret << connection.request(:method => :get, + :path => '/echo/request_count').body + ret << connection.request(:method => :get, + :path => '/echo/request_count').body + end + end + + end + end + end +end diff --git a/lib/vendor/excon/tests/response_tests.rb b/lib/vendor/excon/tests/response_tests.rb new file mode 100644 index 0000000..2a989dd --- /dev/null +++ b/lib/vendor/excon/tests/response_tests.rb @@ -0,0 +1,197 @@ +Shindo.tests('Excon Response Parsing') do + env_init + + with_server('good') do + + tests('responses with chunked transfer-encoding') do + + tests('simple response').returns('hello world') do + Excon.get('http://127.0.0.1:9292/chunked/simple').body + end + + tests('with :response_block') do + + tests('simple response'). + returns([['hello ', nil, nil], ['world', nil, nil]]) do + capture_response_block do |block| + Excon.get('http://127.0.0.1:9292/chunked/simple', + :response_block => block, + :chunk_size => 5) # not used + end + end + + tests('simple response has empty body').returns('') do + response_block = lambda { |_, _, _| } + Excon.get('http://127.0.0.1:9292/chunked/simple', :response_block => response_block).body + end + + tests('with expected response status'). + returns([['hello ', nil, nil], ['world', nil, nil]]) do + capture_response_block do |block| + Excon.get('http://127.0.0.1:9292/chunked/simple', + :response_block => block, + :expects => 200) + end + end + + tests('with unexpected response status').returns('hello world') do + begin + Excon.get('http://127.0.0.1:9292/chunked/simple', + :response_block => Proc.new { raise 'test failed' }, + :expects => 500) + rescue Excon::Errors::HTTPStatusError => err + err.response[:body] + end + end + + end + + tests('merges trailers into headers'). + returns('one, two, three, four, five, six') do + Excon.get('http://127.0.0.1:9292/chunked/trailers').headers['Test-Header'] + end + + tests("removes 'chunked' from Transfer-Encoding").returns(nil) do + Excon.get('http://127.0.0.1:9292/chunked/simple').headers['Transfer-Encoding'] + end + + end + + tests('responses with content-length') do + + tests('simple response').returns('hello world') do + Excon.get('http://127.0.0.1:9292/content-length/simple').body + end + + tests('with :response_block') do + + tests('simple response'). + returns([['hello', 6, 11], [' worl', 1, 11], ['d', 0, 11]]) do + capture_response_block do |block| + Excon.get('http://127.0.0.1:9292/content-length/simple', + :response_block => block, + :chunk_size => 5) + end + end + + tests('simple response has empty body').returns('') do + response_block = lambda { |_, _, _| } + Excon.get('http://127.0.0.1:9292/content-length/simple', :response_block => response_block).body + end + + tests('with expected response status'). + returns([['hello', 6, 11], [' worl', 1, 11], ['d', 0, 11]]) do + capture_response_block do |block| + Excon.get('http://127.0.0.1:9292/content-length/simple', + :response_block => block, + :chunk_size => 5, + :expects => 200) + end + end + + tests('with unexpected response status').returns('hello world') do + begin + Excon.get('http://127.0.0.1:9292/content-length/simple', + :response_block => Proc.new { raise 'test failed' }, + :chunk_size => 5, + :expects => 500) + rescue Excon::Errors::HTTPStatusError => err + err.response[:body] + end + end + + end + + end + + tests('responses with unknown length') do + + tests('simple response').returns('hello world') do + Excon.get('http://127.0.0.1:9292/unknown/simple').body + end + + tests('with :response_block') do + + tests('simple response'). + returns([['hello', nil, nil], [' worl', nil, nil], ['d', nil, nil]]) do + capture_response_block do |block| + Excon.get('http://127.0.0.1:9292/unknown/simple', + :response_block => block, + :chunk_size => 5) + end + end + + tests('simple response has empty body').returns('') do + response_block = lambda { |_, _, _| } + Excon.get('http://127.0.0.1:9292/unknown/simple', :response_block => response_block).body + end + + tests('with expected response status'). + returns([['hello', nil, nil], [' worl', nil, nil], ['d', nil, nil]]) do + capture_response_block do |block| + Excon.get('http://127.0.0.1:9292/unknown/simple', + :response_block => block, + :chunk_size => 5, + :expects => 200) + end + end + + tests('with unexpected response status').returns('hello world') do + begin + Excon.get('http://127.0.0.1:9292/unknown/simple', + :response_block => Proc.new { raise 'test failed' }, + :chunk_size => 5, + :expects => 500) + rescue Excon::Errors::HTTPStatusError => err + err.response[:body] + end + end + + end + + end + + tests('cookies') do + + tests('parses cookies into array').returns(['one, two', 'three, four']) do + resp = Excon.get('http://127.0.0.1:9292/unknown/cookies') + resp[:cookies] + end + + end + + tests('header continuation') do + + tests('proper continuation').returns('one, two, three, four, five, six') do + resp = Excon.get('http://127.0.0.1:9292/unknown/header_continuation') + resp.headers['Test-Header'] + end + + tests('malformed header').raises(Excon::Errors::SocketError) do + Excon.get('http://127.0.0.1:9292/bad/malformed_header') + end + + tests('malformed header continuation').raises(Excon::Errors::SocketError) do + Excon.get('http://127.0.0.1:9292/bad/malformed_header_continuation') + end + + end + + tests('status line parsing') do + + tests('proper status code').returns(404) do + resp = Excon.get('http://127.0.0.1:9292/not-found') + resp.status + end + + tests('proper reason phrase').returns("Not Found") do + resp = Excon.get('http://127.0.0.1:9292/not-found') + resp.reason_phrase + end + + end + + end + + env_restore +end diff --git a/lib/vendor/excon/tests/servers/bad.rb b/lib/vendor/excon/tests/servers/bad.rb new file mode 100755 index 0000000..e502d83 --- /dev/null +++ b/lib/vendor/excon/tests/servers/bad.rb @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby + +require "eventmachine" + +module BadServer + def receive_data(data) + case data + when %r{^GET /eof/no_content_length_and_no_chunking\s} + send_data "HTTP/1.1 200 OK\r\n" + send_data "\r\n" + send_data "hello" + close_connection(true) + end + end +end + +EM.run do + EM.start_server("127.0.0.1", 9292, BadServer) + $stderr.puts "ready" +end diff --git a/lib/vendor/excon/tests/servers/eof.rb b/lib/vendor/excon/tests/servers/eof.rb new file mode 100755 index 0000000..a82c729 --- /dev/null +++ b/lib/vendor/excon/tests/servers/eof.rb @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby + +require "eventmachine" + +module EOFServer + def receive_data(data) + case data + when %r{^GET /eof\s} + close_connection(true) + end + end +end + +EM.run do + EM.start_server("127.0.0.1", 9292, EOFServer) + $stderr.puts "ready" +end diff --git a/lib/vendor/excon/tests/servers/error.rb b/lib/vendor/excon/tests/servers/error.rb new file mode 100755 index 0000000..ab3cd1f --- /dev/null +++ b/lib/vendor/excon/tests/servers/error.rb @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby + +require "eventmachine" + +module ErrorServer + def receive_data(data) + case data + when %r{^GET /error/not_found\s} + send_data "HTTP/1.1 404 Not Found\r\n" + send_data "\r\n" + send_data "server says not found" + close_connection(true) + end + end +end + +EM.run do + EM.start_server("127.0.0.1", 9292, ErrorServer) + $stderr.puts "ready" +end diff --git a/lib/vendor/excon/tests/servers/good.rb b/lib/vendor/excon/tests/servers/good.rb new file mode 100755 index 0000000..1cb6d7f --- /dev/null +++ b/lib/vendor/excon/tests/servers/good.rb @@ -0,0 +1,350 @@ +#!/usr/bin/env ruby + +require 'eventmachine' +require 'stringio' +require 'uri' +require 'zlib' + +module GoodServer + # This method will be called with each request received. + # + # request = { + # :method => method, + # :uri => URI.parse(uri), + # :headers => {}, + # :body => '' + # } + # + # Each connection to this server is persistent unless the client sends + # "Connection: close" in the request. If a response requires the connection + # to be closed, use `start_response(:persistent => false)`. + def send_response(request) + type, path = request[:uri].path.split('/', 3)[1, 2] + case type + when 'echo' + case path + when 'request' + data = Marshal.dump(request) + start_response + send_data "Content-Length: #{ data.size }\r\n" + send_data "\r\n" + send_data data + + when 'request_count' + (@request_count ||= '0').next! + start_response + send_data "Content-Length: #{ @request_count.size }\r\n" + send_data "Connection: Keep-Alive\r\n" + send_data "\r\n" + send_data @request_count + + when /(content|transfer)-encoded\/?(.*)/ + if (encoding_type = $1) == 'content' + accept_header = 'Accept-Encoding' + encoding_header = 'Content-Encoding' + else + accept_header = 'TE' + encoding_header = 'Transfer-Encoding' + end + chunked = $2 == 'chunked' + + encodings = parse_encodings(request[:headers][accept_header]) + while encoding = encodings.pop + break if ['gzip', 'deflate'].include?(encoding) + end + + case encoding + when 'gzip' + body = request[:body] + if(body.nil? || body.empty?) + body = '' + else + io = (Zlib::GzipWriter.new(StringIO.new) << request[:body]).finish + io.rewind + body = io.read + end + when 'deflate' + # drops the zlib header + deflator = Zlib::Deflate.new(nil, -Zlib::MAX_WBITS) + body = deflator.deflate(request[:body], Zlib::FINISH) + deflator.close + else + body = request[:body] + end + + # simulate server pre/post content encoding + encodings = [ + request[:headers]["#{ encoding_header }-Pre"], + encoding, + request[:headers]["#{ encoding_header }-Post"], + ] + if chunked && encoding_type == 'transfer' + encodings << 'chunked' + end + encodings = encodings.compact.join(', ') + + start_response + # let the test know what the server sent + send_data "#{ encoding_header }-Sent: #{ encodings }\r\n" + send_data "#{ encoding_header }: #{ encodings }\r\n" unless encodings.empty? + if chunked + if encoding_type == 'content' + send_data "Transfer-Encoding: chunked\r\n" + end + send_data "\r\n" + send_data chunks_for(body) + send_data "\r\n" + else + send_data "Content-Length: #{ body.size }\r\n" + send_data "\r\n" + send_data body + end + end + + when 'chunked' + case path + when 'simple' + start_response + send_data "Transfer-Encoding: chunked\r\n" + send_data "\r\n" + # chunk-extension is currently ignored. + # this works because "6; chunk-extension".to_i => "6" + send_data "6; chunk-extension\r\n" + send_data "hello \r\n" + send_data "5; chunk-extension\r\n" + send_data "world\r\n" + send_data "0; chunk-extension\r\n" # last-chunk + send_data "\r\n" + + # merged trailers also support continuations + when 'trailers' + start_response + send_data "Transfer-Encoding: chunked\r\n" + send_data "Test-Header: one, two\r\n" + send_data "\r\n" + send_data chunks_for('hello world') + send_data "Test-Header: three, four,\r\n" + send_data "\tfive, six\r\n" + send_data "\r\n" + end + + when 'content-length' + case path + when 'simple' + start_response + send_data "Content-Length: 11\r\n" + send_data "\r\n" + send_data "hello world" + end + + when 'unknown' + case path + when 'cookies' + start_response(:persistent => false) + send_data "Set-Cookie: one, two\r\n" + send_data "Set-Cookie: three, four\r\n" + send_data "\r\n" + send_data "hello world" + + when 'simple' + start_response(:persistent => false) + send_data "\r\n" + send_data "hello world" + + when 'header_continuation' + start_response(:persistent => false) + send_data "Test-Header: one, two\r\n" + send_data "Test-Header: three, four,\r\n" + send_data " five, six\r\n" + send_data "\r\n" + send_data "hello world" + end + + when 'bad' + # Excon will close these connections due to the errors. + case path + when 'malformed_header' + start_response + send_data "Bad-Header\r\n" # no ':' + send_data "\r\n" + send_data "hello world" + + when 'malformed_header_continuation' + send_data "HTTP/1.1 200 OK\r\n" + send_data " Bad-Header: one, two\r\n" # no previous header + send_data "\r\n" + send_data "hello world" + end + + when 'not-found' + start_response(:status => "404 Not Found") + send_data "Content-Length: 11\r\n" + send_data "\r\n" + send_data "hello world" + end + end + + # Sends response status-line, plus headers common to all responses. + def start_response(opts = {}) + opts = { + :status => '200 OK', + :persistent => @persistent # true unless client sent Connection: close + }.merge!(opts) + + @persistent = opts[:persistent] + send_data "HTTP/1.1 #{ opts[:status] }\r\n" + send_data "Connection: close\r\n" unless @persistent + end + + def post_init + @buffer = StringIO.new + @buffer.set_encoding('BINARY') if @buffer.respond_to?(:set_encoding) + end + + # Receives a String of +data+ sent from the client. + # +data+ may only be a portion of what the client sent. + # The data is buffered, then processed and removed from the buffer + # as data becomes available until the @request is complete. + def receive_data(data) + @buffer.seek(0, IO::SEEK_END) + @buffer.write(data) + + parse_headers unless @request + parse_body if @request + + if @request_complete + send_response(@request) + if @persistent + @request = nil + @request_complete = false + # process remaining buffer for next request + receive_data('') unless @buffer.eof? + else + close_connection(true) + end + end + end + + # Removes the processed portion of the buffer + # by replacing the buffer with it's contents from the current pos. + def sync_buffer + @buffer.string = @buffer.read + end + + def parse_headers + @buffer.rewind + # wait until buffer contains the end of the headers + if /\sHTTP\/\d+\.\d+\r\n.*?\r\n\r\n/m =~ @buffer.read + @buffer.rewind + # For persistent connections, the buffer could start with the + # \r\n chunked-message terminator from the previous request. + # This will discard anything up to the request-line. + until m = /^(\w+)\s(.*)\sHTTP\/\d+\.\d+$/.match(@buffer.readline.chop!); end + method, uri = m[1, 2] + + headers = {} + last_key = nil + until (line = @buffer.readline.chop!).empty? + if !line.lstrip!.nil? + headers[last_key] << ' ' << line.rstrip + else + key, value = line.split(':', 2) + headers[key] = ([headers[key]] << value.strip).compact.join(', ') + last_key = key + end + end + + sync_buffer + + @chunked = headers['Transfer-Encoding'] =~ /chunked/i + @content_length = headers['Content-Length'].to_i + @persistent = headers['Connection'] !~ /close/i + @request = { + :method => method, + :uri => URI.parse(uri), + :headers => headers, + :body => '' + } + end + end + + def parse_body + if @chunked + @buffer.rewind + until @request_complete || @buffer.eof? + unless @chunk_size + # in case buffer only contains a portion of the chunk-size line + if (line = @buffer.readline) =~ /\r\n\z/ + @chunk_size = line.to_i(16) + if @chunk_size > 0 + sync_buffer + else # last-chunk + @buffer.read(2) # the final \r\n may or may not be in the buffer + sync_buffer + @chunk_size = nil + @request_complete = true + end + end + end + if @chunk_size + if @buffer.size >= @chunk_size + 2 + @request[:body] << @buffer.read(@chunk_size + 2).chop! + @chunk_size = nil + sync_buffer + else + break # wait for more data + end + end + end + elsif @content_length > 0 + @buffer.rewind + unless @buffer.eof? # buffer only contained the headers + @request[:body] << @buffer.read(@content_length - @request[:body].size) + sync_buffer + if @request[:body].size == @content_length + @request_complete = true + end + end + else + # no body + @request_complete = true + end + end + + def chunks_for(str) + chunks = '' + str.force_encoding('BINARY') if str.respond_to?(:force_encoding) + chunk_size = str.size / 2 + until (chunk = str.slice!(0, chunk_size)).empty? + chunks << chunk.size.to_s(16) << "\r\n" + chunks << chunk << "\r\n" + end + chunks << "0\r\n" # last-chunk + end + + # only supports a single quality parameter for tokens + def parse_encodings(encodings) + return [] if encodings.nil? + split_header_value(encodings).map do |value| + token, q_val = /^(.*?)(?:;q=(.*))?$/.match(value.strip)[1, 2] + if q_val && q_val.to_f == 0 + nil + else + [token, (q_val || 1).to_f] + end + end.compact.sort_by {|_, q_val| q_val }.map {|token, _| token } + end + + # Splits a header value +str+ according to HTTP specification. + def split_header_value(str) + return [] if str.nil? + str.strip.scan(%r'\G((?:"(?:\\.|[^"])+?"|[^",]+)+) + (?:,\s*|\Z)'xn).flatten + end +end + +EM.run do + EM.start_server("127.0.0.1", 9292, GoodServer) + EM.start_server("::1", 9293, GoodServer) unless RUBY_PLATFORM == 'java' + $stderr.puts "ready" +end diff --git a/lib/vendor/excon/tests/test_helper.rb b/lib/vendor/excon/tests/test_helper.rb new file mode 100644 index 0000000..a3c6d63 --- /dev/null +++ b/lib/vendor/excon/tests/test_helper.rb @@ -0,0 +1,306 @@ +require 'rubygems' if RUBY_VERSION < '1.9' +require 'bundler/setup' +require 'excon' +require 'delorean' +require 'open4' + +Excon.defaults.merge!( + :connect_timeout => 5, + :read_timeout => 5, + :write_timeout => 5 +) + +def basic_tests(url = 'http://127.0.0.1:9292', options = {}) + ([true, false] * 2).combination(2).to_a.uniq.each do |nonblock, persistent| + connection = nil + test do + options = options.merge({:ssl_verify_peer => false, :nonblock => nonblock, :persistent => persistent }) + connection = Excon.new(url, options) + true + end + + tests("nonblock => #{nonblock}, persistent => #{persistent}") do + + tests('GET /content-length/100') do + response = nil + + tests('response.status').returns(200) do + response = connection.request(:method => :get, :path => '/content-length/100') + + response.status + end + + tests('response[:status]').returns(200) do + response[:status] + end + + tests("response.headers['Content-Length']").returns('100') do + response.headers['Content-Length'] + end + + tests("response.headers['Content-Type']").returns('text/html;charset=utf-8') do + response.headers['Content-Type'] + end + + test("Time.parse(response.headers['Date']).is_a?(Time)") do + pending if connection.data[:scheme] == Excon::UNIX + Time.parse(response.headers['Date']).is_a?(Time) + end + + test("!!(response.headers['Server'] =~ /^WEBrick/)") do + pending if connection.data[:scheme] == Excon::UNIX + !!(response.headers['Server'] =~ /^WEBrick/) + end + + tests("response.headers['Custom']").returns("Foo: bar") do + response.headers['Custom'] + end + + tests("response.remote_ip").returns("127.0.0.1") do + pending if connection.data[:scheme] == Excon::UNIX + response.remote_ip + end + + tests("response.body").returns('x' * 100) do + response.body + end + + tests("deprecated block usage").returns(['x' * 100, 0, 100]) do + data = [] + silence_warnings do + connection.request(:method => :get, :path => '/content-length/100') do |chunk, remaining_length, total_length| + data = [chunk, remaining_length, total_length] + end + end + data + end + + tests("response_block usage").returns(['x' * 100, 0, 100]) do + data = [] + response_block = lambda do |chunk, remaining_length, total_length| + data = [chunk, remaining_length, total_length] + end + connection.request(:method => :get, :path => '/content-length/100', :response_block => response_block) + data + end + + end + + tests('POST /body-sink') do + + tests('response.body').returns("5000000") do + response = connection.request(:method => :post, :path => '/body-sink', :headers => { 'Content-Type' => 'text/plain' }, :body => 'x' * 5_000_000) + response.body + end + + tests('empty body').returns('0') do + response = connection.request(:method => :post, :path => '/body-sink', :headers => { 'Content-Type' => 'text/plain' }, :body => '') + response.body + end + + end + + tests('POST /echo') do + + tests('with file').returns('x' * 100 + "\n") do + file_path = File.join(File.dirname(__FILE__), "data", "xs") + response = connection.request(:method => :post, :path => '/echo', :body => File.open(file_path)) + response.body + end + + tests('without request_block').returns('x' * 100) do + response = connection.request(:method => :post, :path => '/echo', :body => 'x' * 100) + response.body + end + + tests('with request_block').returns('x' * 100) do + data = ['x'] * 100 + request_block = lambda do + data.shift.to_s + end + response = connection.request(:method => :post, :path => '/echo', :request_block => request_block) + response.body + end + + tests('with multi-byte strings') do + body = "\xC3\xBC" * 100 + headers = { 'Custom' => body.dup } + if RUBY_VERSION >= '1.9' + body.force_encoding('BINARY') + headers['Custom'].force_encoding('UTF-8') + end + + returns(body, 'properly concatenates request+headers and body') do + response = connection.request(:method => :post, :path => '/echo', :headers => headers, :body => body) + response.body + end + end + + end + + tests('PUT /echo') do + + tests('with file').returns('x' * 100 + "\n") do + file_path = File.join(File.dirname(__FILE__), "data", "xs") + response = connection.request(:method => :put, :path => '/echo', :body => File.open(file_path)) + response.body + end + + tests('without request_block').returns('x' * 100) do + response = connection.request(:method => :put, :path => '/echo', :body => 'x' * 100) + response.body + end + + tests('request_block usage').returns('x' * 100) do + data = ['x'] * 100 + request_block = lambda do + data.shift.to_s + end + response = connection.request(:method => :put, :path => '/echo', :request_block => request_block) + response.body + end + + tests('with multi-byte strings') do + body = "\xC3\xBC" * 100 + headers = { 'Custom' => body.dup } + if RUBY_VERSION >= '1.9' + body.force_encoding('BINARY') + headers['Custom'].force_encoding('UTF-8') + end + + returns(body, 'properly concatenates request+headers and body') do + response = connection.request(:method => :put, :path => '/echo', :headers => headers, :body => body) + response.body + end + end + + end + + tests('should succeed with tcp_nodelay').returns(200) do + options = options.merge(:ssl_verify_peer => false, :nonblock => nonblock, :tcp_nodelay => true) + connection = Excon.new(url, options) + response = connection.request(:method => :get, :path => '/content-length/100') + response.status + end + + end + end +end + + +PROXY_ENV_VARIABLES = %w{http_proxy https_proxy no_proxy} # All lower-case + +def env_init(env={}) + current = {} + PROXY_ENV_VARIABLES.each do |key| + current[key] = ENV.delete(key) + current[key.upcase] = ENV.delete(key.upcase) + end + env_stack << current + + env.each do |key, value| + ENV[key] = value + end +end + +def env_restore + ENV.update(env_stack.pop) +end + +def env_stack + @env_stack ||= [] +end + +def silence_warnings + orig_verbose = $VERBOSE + $VERBOSE = nil + yield +ensure + $VERBOSE = orig_verbose +end + +def capture_response_block + captures = [] + yield lambda {|chunk, remaining_bytes, total_bytes| + captures << [chunk, remaining_bytes, total_bytes] + } + captures +end + +def rackup_path(*parts) + File.expand_path(File.join(File.dirname(__FILE__), 'rackups', *parts)) +end + +def with_rackup(name, host="127.0.0.1") + unless RUBY_PLATFORM == 'java' + GC.disable if RUBY_VERSION < '1.9' + pid, w, r, e = Open4.popen4("rackup", "-s", "webrick", "--host", host, rackup_path(name)) + else + pid, w, r, e = IO.popen4("rackup", "-s", "webrick", "--host", host, rackup_path(name)) + end + until e.gets =~ /HTTPServer#start:/; end + yield +ensure + Process.kill(9, pid) + unless RUBY_PLATFORM == 'java' + GC.enable if RUBY_VERSION < '1.9' + Process.wait(pid) + end + + # dump server errors + lines = e.read.split($/) + while line = lines.shift + case line + when /(ERROR|Error)/ + unless line =~ /(null cert chain|did not return a certificate|SSL_read:: internal error)/ + in_err = true + puts + end + when /^(127|localhost)/ + in_err = false + end + puts line if in_err + end +end + +def with_unicorn(name, listen='127.0.0.1:9292') + unless RUBY_PLATFORM == 'java' + GC.disable if RUBY_VERSION < '1.9' + unix_socket = listen.sub('unix://', '') if listen.start_with? 'unix://' + pid, w, r, e = Open4.popen4("unicorn", "--no-default-middleware","-l", listen, rackup_path(name)) + until e.gets =~ /worker=0 ready/; end + else + # need to find suitable server for jruby + end + yield +ensure + unless RUBY_PLATFORM == 'java' + Process.kill(9, pid) + GC.enable if RUBY_VERSION < '1.9' + Process.wait(pid) + end + if not unix_socket.nil? and File.exist?(unix_socket) + File.delete(unix_socket) + end +end + +def server_path(*parts) + File.expand_path(File.join(File.dirname(__FILE__), 'servers', *parts)) +end + +def with_server(name) + unless RUBY_PLATFORM == 'java' + GC.disable if RUBY_VERSION < '1.9' + pid, w, r, e = Open4.popen4(server_path("#{name}.rb")) + else + pid, w, r, e = IO.popen4(server_path("#{name}.rb")) + end + until e.gets =~ /ready/; end + yield +ensure + Process.kill(9, pid) + unless RUBY_PLATFORM == 'java' + GC.enable if RUBY_VERSION < '1.9' + Process.wait(pid) + end +end diff --git a/lib/vendor/excon/tests/thread_safety_tests.rb b/lib/vendor/excon/tests/thread_safety_tests.rb new file mode 100644 index 0000000..32a76e0 --- /dev/null +++ b/lib/vendor/excon/tests/thread_safety_tests.rb @@ -0,0 +1,39 @@ +Shindo.tests('Excon thread safety') do + + tests('thread_safe_sockets configuration') do + tests('thread_safe_sockets default').returns(true) do + connection = Excon.new('http://foo.com') + connection.data[:thread_safe_sockets] + end + + tests('with thread_safe_sockets set false').returns(false) do + connection = Excon.new('http://foo.com', :thread_safe_sockets => false) + connection.data[:thread_safe_sockets] + end + end + + with_rackup('thread_safety.ru') do + connection = Excon.new('http://127.0.0.1:9292') + + long_thread = Thread.new { + response = connection.request(:method => 'GET', :path => '/id/1/wait/2') + Thread.current[:success] = response.body == '1' + } + + short_thread = Thread.new { + response = connection.request(:method => 'GET', :path => '/id/2/wait/1') + Thread.current[:success] = response.body == '2' + } + + test('long_thread') do + long_thread.join + short_thread.join + + long_thread[:success] + end + + test('short_thread') do + short_thread[:success] + end + end +end diff --git a/lib/vendor/excon/tests/timeout_tests.rb b/lib/vendor/excon/tests/timeout_tests.rb new file mode 100644 index 0000000..88a5706 --- /dev/null +++ b/lib/vendor/excon/tests/timeout_tests.rb @@ -0,0 +1,12 @@ +Shindo.tests('read should timeout') do + with_rackup('timeout.ru') do + + [false, true].each do |nonblock| + tests("nonblock => #{nonblock} hits read_timeout").raises(Excon::Errors::Timeout) do + connection = Excon.new('http://127.0.0.1:9292', :nonblock => nonblock) + connection.request(:method => :get, :path => '/timeout', :read_timeout => 1) + end + end + + end +end diff --git a/lib/vendor/excon/tests/utils_tests.rb b/lib/vendor/excon/tests/utils_tests.rb new file mode 100644 index 0000000..65f0382 --- /dev/null +++ b/lib/vendor/excon/tests/utils_tests.rb @@ -0,0 +1,81 @@ +Shindo.tests('Excon::Utils') do + + tests('#connection_uri') do + + expected_uri = 'unix:///tmp/some.sock' + tests('using UNIX scheme').returns(expected_uri) do + connection = Excon.new('unix:///some/path', :socket => '/tmp/some.sock') + Excon::Utils.connection_uri(connection.data) + end + + tests('using HTTP scheme') do + + expected_uri = 'http://foo.com:80' + tests('with default port').returns(expected_uri) do + connection = Excon.new('http://foo.com/some/path') + Excon::Utils.connection_uri(connection.data) + end + + expected_uri = 'http://foo.com' + tests('without default port').returns(expected_uri) do + connection = Excon.new('http://foo.com/some/path', :omit_default_port => true) + Excon::Utils.connection_uri(connection.data) + end + + end + + end + + tests('#request_uri') do + + tests('using UNIX scheme') do + + expected_uri = 'unix:///tmp/some.sock/some/path' + tests('without query').returns(expected_uri) do + connection = Excon.new('unix:/', :socket => '/tmp/some.sock') + params = { :path => '/some/path' } + Excon::Utils.request_uri(connection.data.merge(params)) + end + + expected_uri = 'unix:///tmp/some.sock/some/path?bar=that&foo=this' + tests('with query').returns(expected_uri) do + connection = Excon.new('unix:/', :socket => '/tmp/some.sock') + params = { :path => '/some/path', :query => { :foo => 'this', :bar => 'that' } } + Excon::Utils.request_uri(connection.data.merge(params)) + end + + end + + tests('using HTTP scheme') do + + expected_uri = 'http://foo.com:80/some/path' + tests('without query').returns(expected_uri) do + connection = Excon.new('http://foo.com') + params = { :path => '/some/path' } + Excon::Utils.request_uri(connection.data.merge(params)) + end + + expected_uri = 'http://foo.com:80/some/path?bar=that&foo=this' + tests('with query').returns(expected_uri) do + connection = Excon.new('http://foo.com') + params = { :path => '/some/path', :query => { :foo => 'this', :bar => 'that' } } + Excon::Utils.request_uri(connection.data.merge(params)) + end + + end + + end + + tests('#escape_uri').returns('/hello%20excon') do + Excon::Utils.escape_uri('/hello excon') + end + + tests('#unescape_uri').returns('/hello excon') do + Excon::Utils.unescape_uri('/hello%20excon') + end + + tests('#unescape_form').returns('message=We love excon!') do + Excon::Utils.unescape_form('message=We+love+excon!') + end + +end |