proxytest 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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