proxytest 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org" # http to fix 1.9.3
2
+
3
+ gem "thin"
4
+ gemspec
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ proxytest (0.0.0)
5
+ thin
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ daemons (1.1.9)
11
+ diff-lcs (1.1.3)
12
+ eventmachine (1.0.0)
13
+ rack (1.5.0)
14
+ rake (10.0.3)
15
+ rspec (2.12.0)
16
+ rspec-core (~> 2.12.0)
17
+ rspec-expectations (~> 2.12.0)
18
+ rspec-mocks (~> 2.12.0)
19
+ rspec-core (2.12.2)
20
+ rspec-expectations (2.12.1)
21
+ diff-lcs (~> 1.1.3)
22
+ rspec-mocks (2.12.1)
23
+ thin (1.5.0)
24
+ daemons (>= 1.0.9)
25
+ eventmachine (>= 0.12.6)
26
+ rack (>= 1.0.0)
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ proxytest!
33
+ rake
34
+ rspec
35
+ thin
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2013 Richo Healey <richo@psych0tik.net>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
22
+
@@ -0,0 +1,24 @@
1
+ Test driven varnish ftw yo.
2
+
3
+ ## Usage
4
+
5
+ ```ruby
6
+
7
+ # Sample Rakefile
8
+ ProxyTest::config do |conf|
9
+ conf.backend_port = 2000
10
+ conf.proxy_port = 6868
11
+
12
+ #conf.pattern = "proxytest/**/*_test.rb"
13
+
14
+ #conf.backend_host = "localhost"
15
+ #conf.proxy_host = "localhost"
16
+ end
17
+
18
+ namespace :proxy do
19
+ desc "Run proxy tests"
20
+ task :test do
21
+ ProxyTest.run!
22
+ end
23
+ end
24
+ ```
@@ -0,0 +1,23 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rspec/core/rake_task'
4
+
5
+ desc "Run basic tests"
6
+ Rake::TestTask.new("test") { |t|
7
+ t.pattern = 'test/**/*_test.rb'
8
+ t.verbose = true
9
+ t.warning = true
10
+ }
11
+
12
+ desc "Start the singletonqueue server"
13
+ task :serve do
14
+ require './test/helpers/backend'
15
+ q = SingletonQueue.new
16
+ loop do
17
+ q.enq("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n")
18
+ end
19
+ end
20
+
21
+ RSpec::Core::RakeTask.new(:spec) do |t|
22
+ t.pattern = "spec/**/*_spec.rb"
23
+ end
@@ -0,0 +1,21 @@
1
+ require File.expand_path("../../test_helper", __FILE__)
2
+ class HelloWorldTest < ProxyTest::TestCase
3
+ def test_it_says_hello_world
4
+ # Returns the real objects from the transaction
5
+ res, req = expect :get, "/hello" do |response, request|
6
+ # Modify the request inline
7
+ request["X-Someotherheader"] = "thing"
8
+
9
+ # Modify the response inline
10
+ response.body = "body goes here"
11
+ response["X-Someheader"] = "value"
12
+ end
13
+
14
+ # Assertions about response go here
15
+ #
16
+ assert_equal res.body, "body goes here"
17
+ assert_equal res["X-SOMEHEADER"], "value"
18
+ assert_equal req.env["REQUEST_METHOD"], "GET"
19
+ assert_equal req.env["X-SOMEOTHERHEADER"], "thing"
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ require File.expand_path("../../test_helper", __FILE__)
2
+ class ImaTeapotTest < ProxyTest::TestCase
3
+ def test_it_is_a_teapot
4
+ res, req = expect :get, "/teapot" do |response, request|
5
+ response.status = 418
6
+ response.body = "I'm a teapot, yo"
7
+ end
8
+
9
+ assert_equal res.body, "I'm a teapot, yo"
10
+ assert_equal res.code.to_i, 418
11
+ end
12
+ end
13
+
@@ -0,0 +1,10 @@
1
+ require 'test/unit'
2
+ require 'net/http'
3
+
4
+ module ProxyTest
5
+ end
6
+
7
+ require File.expand_path("../proxytest/testcase", __FILE__)
8
+ require File.expand_path("../proxytest/configurator", __FILE__)
9
+ require File.expand_path("../proxytest/helpers/backend", __FILE__)
10
+ require File.expand_path("../proxytest/helpers/response", __FILE__)
@@ -0,0 +1,24 @@
1
+ module ProxyTest; extend self
2
+ def config
3
+ ::ProxyTest::Config.seed_from_environment!
4
+ yield ::ProxyTest::Config
5
+ end
6
+ end
7
+
8
+ module ProxyTest
9
+ class Config; class << self
10
+ attr_accessor :backend_port, :backend_host
11
+ attr_accessor :proxy_port, :proxy_host
12
+ attr_accessor :pattern
13
+
14
+ def seed_from_environment!
15
+ # Doesn't touch any values that have already been set
16
+ @pattern ||= "proxytest/**/*_test.rb"
17
+
18
+ @backend_port ||= ENV["BACKEND_PORT"]
19
+ @backend_host ||= ENV["BACKEND_HOST"] || "localhost"
20
+ @proxy_port ||= ENV["PROXY_PORT"]
21
+ @proxy_host ||= ENV["PROXY_HOST"] || "localhost"
22
+ end
23
+ end; end
24
+ end
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ #
4
+ require 'thread'
5
+ require 'socket'
6
+ require File.expand_path("../thin_http_parser", __FILE__)
7
+
8
+ module ProxyTest
9
+ class SingletonQueue
10
+
11
+ def self.new(*args)
12
+ # Guarantees that only a single instance will exist
13
+ @@instance ||= super(*args)
14
+ end
15
+
16
+ def self.get
17
+ # Helper to make what's going on more obvious
18
+ @@instance
19
+ end
20
+
21
+ ######
22
+
23
+ def initialize(opts={})
24
+ @in_q = Queue.new
25
+ @out_q = Queue.new
26
+ opts[:port] ||= ::ProxyTest::Config.backend_port
27
+ spawn_reader(opts[:port])
28
+ end
29
+ attr_reader :in_q, :out_q
30
+
31
+ def enq(data)
32
+ in_q.enq(data)
33
+ end
34
+
35
+ def deq
36
+ out_q.deq
37
+ end
38
+
39
+ def join!
40
+ @thread.join
41
+ end
42
+
43
+ private
44
+
45
+ def spawn_reader(port)
46
+ @thread = Thread.new(in_q, out_q) do |en_q, de_q|
47
+ serv = TCPServer.new port
48
+ loop do
49
+ # Create a parser object
50
+ parser = ::ProxyTest::RequestParser.new
51
+
52
+ # Pop a connection off the stack
53
+ client = serv.accept
54
+
55
+ # retrieve the headers
56
+ lines = []
57
+ while (line = client.gets) != "\r\n"
58
+ # XXX Hack around missing CONTENT_LENGTH in parser.env
59
+ if parser.env['CONTENT_LENGTH'].nil? && line =~ /^Content-Length: (\d+)/
60
+ parser.env['CONTENT_LENGTH'] = $1.to_i
61
+ elsif line =~ /(\S+): (.*)/
62
+ parser.env[$1.upcase] = $2.chomp
63
+ end
64
+ lines << line
65
+ end
66
+
67
+
68
+ parser.parse(lines.join("\r\n"))
69
+
70
+ if parser.content_length > 0
71
+ parser.parse(client.gets(parser.content_length))
72
+ end
73
+
74
+ # Send the request object back to the caller
75
+ de_q.enq(parser)
76
+
77
+ # Fetch the response object from the caller
78
+ response = en_q.deq
79
+ client.write(response.to_s)
80
+
81
+ client.close
82
+ end
83
+ end
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,53 @@
1
+ module ProxyTest
2
+ STATUSES = {
3
+ 200 => "OK",
4
+ 418 => "I'm a teapot"
5
+ }
6
+
7
+ class Response
8
+ attr_accessor :body
9
+ attr_reader :headers
10
+
11
+ def initialize
12
+ body = ""
13
+ # Default set of headers?
14
+ @headers = {}
15
+ end
16
+
17
+ def to_http
18
+ h1 = <<-HTTP
19
+ HTTP/1.0 #{status} #{STATUSES[status]}
20
+ Content-Type: text/html
21
+ Content-Length: #{body.length}
22
+ HTTP
23
+ headers.each do |k, v|
24
+ h1 += "#{k}: #{v}\r\n"
25
+ end
26
+
27
+ h1 += <<-HTTP
28
+
29
+ #{body}
30
+ HTTP
31
+ return h1
32
+ end
33
+
34
+ # Hack to make it look like a Net::HTTP entity
35
+ def [](v)
36
+ headers[v]
37
+ end
38
+
39
+ def []=(k, v)
40
+ headers[k] = v
41
+ end
42
+
43
+ # Status wrapper
44
+ def status
45
+ @status ||= 200
46
+ end
47
+
48
+ def status=(st)
49
+ @status = st
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,93 @@
1
+ # FILE: thin_http_parser.rb
2
+ # http://www.franzens.org/2011/10/writing-minimalistic-web-server-using.html
3
+ # Copyright 2011 Nils Franzén
4
+
5
+ module ProxyTest
6
+ # Uncomment this line if 'thin_parser' is not found
7
+ require 'thin'
8
+ require 'thin_parser'
9
+ require 'stringio'
10
+
11
+ # Freeze some HTTP header names & values
12
+ HTTP_VERSION = 'HTTP_VERSION'.freeze
13
+ HTTP_1_0 = 'HTTP/1.0'.freeze
14
+ CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze
15
+ CONNECTION = 'HTTP_CONNECTION'.freeze
16
+ KEEP_ALIVE_REGEXP = /\bkeep-alive\b/i.freeze
17
+ CLOSE_REGEXP = /\bclose\b/i.freeze
18
+
19
+ # Freeze some Rack header names
20
+ RACK_INPUT = 'rack.input'.freeze
21
+
22
+ # A request sent by the client to the server.
23
+ class RequestParser
24
+ INITIAL_BODY = ''
25
+
26
+ # Force external_encoding of request's body to ASCII_8BIT
27
+ INITIAL_BODY.encode!(Encoding::ASCII_8BIT) if INITIAL_BODY.respond_to?(:encode!)
28
+
29
+ # CGI-like request environment variables
30
+ attr_reader :env
31
+
32
+ # Unparsed data of the request
33
+ attr_reader :data
34
+
35
+ # Request body
36
+ attr_reader :body
37
+
38
+ def initialize
39
+ @parser = Thin::HttpParser.new
40
+ @data = ''
41
+ @nparsed = 0
42
+ @body = StringIO.new(INITIAL_BODY.dup)
43
+ @env = {
44
+ RACK_INPUT => @body,
45
+ }
46
+ end
47
+
48
+ # Parse a chunk of data into the request environment
49
+ # Returns +true+ if the parsing is complete.
50
+ def parse(data)
51
+ if @parser.finished? # Header finished, can only be some more body
52
+ body << data
53
+ else # Parse more header using the super parser
54
+ @data << data
55
+ @nparsed = @parser.execute(@env, @data, @nparsed)
56
+ end
57
+
58
+ if finished? # Check if header and body are complete
59
+ @data = nil
60
+ @body.rewind
61
+ true # Request is fully parsed
62
+ else
63
+ false # Not finished, need more data
64
+ end
65
+ end
66
+
67
+ # +true+ if headers and body are finished parsing
68
+ def finished?
69
+ @parser.finished? && @body.size >= content_length
70
+ end
71
+
72
+ # Expected size of the body
73
+ def content_length
74
+ @env[CONTENT_LENGTH].to_i
75
+ end
76
+
77
+ # Returns +true+ if the client expect the connection to be persistent.
78
+ def persistent?
79
+ # Clients and servers SHOULD NOT assume that a persistent connection
80
+ # is maintained for HTTP versions less than 1.1 unless it is explicitly
81
+ # signaled. (http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html)
82
+ if @env[HTTP_VERSION] == HTTP_1_0
83
+ @env[CONNECTION] =~ KEEP_ALIVE_REGEXP
84
+
85
+ # HTTP/1.1 client intends to maintain a persistent connection unless
86
+ # a Connection header including the connection-token "close" was sent
87
+ # in the request
88
+ else
89
+ @env[CONNECTION].nil? || @env[CONNECTION] !~ CLOSE_REGEXP
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,52 @@
1
+
2
+ class ProxyTest::TestCase < Test::Unit::TestCase
3
+ def q
4
+ ::ProxyTest::SingletonQueue.get
5
+ end
6
+
7
+ def expect(method, path, &block)
8
+ # Create stub objects for the response and the request
9
+ url = URI.parse("http://#{::ProxyTest::Config.proxy_host}:#{::ProxyTest::Config.proxy_port}")
10
+ mock_req = requester(method).new(path)
11
+ mock_res = ::ProxyTest::Response.new
12
+
13
+ yield(mock_res, mock_req)
14
+
15
+ # Pass the response into the http server
16
+ q.enq(mock_res.to_http)
17
+
18
+ # TODO Configurable port
19
+ real_res = Net::HTTP.start(url.host,url.port) do |http|
20
+ http.request(mock_req)
21
+ end
22
+
23
+ real_req = q.deq
24
+
25
+ [real_res, real_req]
26
+ end
27
+
28
+ def requester(sym)
29
+ case sym
30
+ when :get
31
+ Net::HTTP::Get
32
+ when :post
33
+ Net::HTTP::Post
34
+ when :put
35
+ Net::HTTP::Put
36
+ when :head
37
+ Net::HTTP::Head
38
+ else
39
+ raise "Unsupported HTTP verb"
40
+ end
41
+ end
42
+
43
+
44
+ def get(path, hdr, &block)
45
+ @paths << [path, hdr, block]
46
+ end
47
+
48
+ def paths
49
+ @paths ||= Array.new
50
+ end
51
+
52
+ end
@@ -0,0 +1,24 @@
1
+ # vim: ft=ruby
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "proxytest"
5
+ s.version = "0.0.0"
6
+ s.authors = ["Rich Healey"]
7
+ s.email = ["richo@99designs.com"]
8
+ s.homepage = "http://github.com/richo/proxytest"
9
+ s.summary = "A test framework for testing proxy logic"
10
+ s.description = s.summary
11
+
12
+ s.add_dependency "thin"
13
+
14
+ s.add_development_dependency "rake"
15
+ #s.add_development_dependency "mocha"
16
+ s.add_development_dependency "rspec"
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.require_paths = ["lib"]
22
+ end
23
+
24
+
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+
3
+ bundle install
4
+ bundle exec rake spec
@@ -0,0 +1,65 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe ProxyTest::Config do
4
+
5
+ it "Should not have any values until initialized" do
6
+ ::ProxyTest::Config.backend_port.should be_nil
7
+ ::ProxyTest::Config.backend_host.should be_nil
8
+ ::ProxyTest::Config.proxy_port.should be_nil
9
+ ::ProxyTest::Config.proxy_host.should be_nil
10
+
11
+ ::ProxyTest::Config.pattern.should be_nil
12
+ end
13
+
14
+ it "Should inherit sane defaults for host config" do
15
+ ::ProxyTest::config do |conf|
16
+ end
17
+
18
+ ::ProxyTest::Config.backend_host.should == "localhost"
19
+ ::ProxyTest::Config.proxy_host.should == "localhost"
20
+ end
21
+
22
+ it "Should inherit sane default for pattern" do
23
+ ::ProxyTest::config do |conf|
24
+ end
25
+
26
+ ::ProxyTest::Config.pattern.should == "proxytest/**/*_test.rb"
27
+ end
28
+
29
+ it "Should store hostnames given" do
30
+ ::ProxyTest::config do |conf|
31
+ conf.backend_host = "richo-mbp"
32
+ conf.proxy_host = "richo-mbp"
33
+ end
34
+
35
+ ::ProxyTest::Config.backend_host.should == "richo-mbp"
36
+ ::ProxyTest::Config.proxy_host.should == "richo-mbp"
37
+ end
38
+
39
+ it "Should store ports given" do
40
+ ::ProxyTest::config do |conf|
41
+ conf.backend_port = 2257
42
+ conf.proxy_port = 2256
43
+ end
44
+
45
+ ::ProxyTest::Config.backend_port.should == 2257
46
+ ::ProxyTest::Config.proxy_port.should == 2256
47
+ end
48
+
49
+ it "Should not clobber config on subsequent passes" do
50
+ ::ProxyTest::config do |conf|
51
+ conf.backend_port = 2257
52
+ conf.proxy_port = 2256
53
+ end
54
+ ::ProxyTest::config do |conf|
55
+ conf.backend_host = "richo-mbp"
56
+ conf.proxy_host = "richo-mbp"
57
+ end
58
+
59
+ ::ProxyTest::Config.backend_host.should == "richo-mbp"
60
+ ::ProxyTest::Config.proxy_host.should == "richo-mbp"
61
+ ::ProxyTest::Config.backend_port.should == 2257
62
+ ::ProxyTest::Config.proxy_port.should == 2256
63
+ end
64
+
65
+ end
@@ -0,0 +1 @@
1
+ require 'proxytest'
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: proxytest
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Rich Healey
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: thin
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: A test framework for testing proxy logic
63
+ email:
64
+ - richo@99designs.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - Gemfile
70
+ - Gemfile.lock
71
+ - LICENSE
72
+ - README.md
73
+ - Rakefile
74
+ - examples/hello_world_test.rb
75
+ - examples/teapot_test.rb
76
+ - lib/proxytest.rb
77
+ - lib/proxytest/configurator.rb
78
+ - lib/proxytest/helpers/backend.rb
79
+ - lib/proxytest/helpers/response.rb
80
+ - lib/proxytest/helpers/thin_http_parser.rb
81
+ - lib/proxytest/testcase.rb
82
+ - proxytest.gemspec
83
+ - script/cibuild
84
+ - spec/proxytest/config_spec.rb
85
+ - spec/spec_helper.rb
86
+ homepage: http://github.com/richo/proxytest
87
+ licenses: []
88
+ post_install_message:
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubyforge_project:
106
+ rubygems_version: 1.8.24
107
+ signing_key:
108
+ specification_version: 3
109
+ summary: A test framework for testing proxy logic
110
+ test_files:
111
+ - spec/proxytest/config_spec.rb
112
+ - spec/spec_helper.rb