rack_toolkit 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 017242be2ce01950ad46eafb4c9aea3855b57dbe
4
+ data.tar.gz: 36138ff9218e813e4734074c118bd1606b3fd635
5
+ SHA512:
6
+ metadata.gz: c2974c29604008d2446a65b7e39d6bf1d0bd344e5b26645587445db141ea3ba83cba87b3e3d84f1b1423103fd84dbc6e322363135e236855cd74ed0520ff18e5
7
+ data.tar.gz: a9d9ef14206965cf3d6c54940ac51c69e85a1535f86f47d107449f5422dfe416e0332f23d6ebe08d8f8b6a4ba76a1625448f37b64dea7197ccd32e5fcc4ac039
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ -I lib
2
+ --format documentation
3
+ --color
4
+ --order random
data/.rspec-fast ADDED
@@ -0,0 +1,5 @@
1
+ -I lib
2
+ --format documentation
3
+ --color
4
+ --tag ~external
5
+ --order random
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ - jruby-9.1.2.0
6
+ before_install: gem install bundler -v 1.12.5
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rack_toolkit.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Rodrigo Rosenfeld Rosas
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.
data/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # RackToolkit [![Build Status](https://travis-ci.org/rosenfeld/rack_toolkit.svg?branch=master)](https://travis-ci.org/rosenfeld/rack_toolkit)
2
+
3
+ RackToolkit will launch a Puma server in an available random port (unless one is specified) and
4
+ will serve any Rack application, which can be changed dynamically. It's mostly useful for testing
5
+ Rack apps, specially when an application is a mixin of several small Rack apps. It also provides
6
+ a DSL to perform get and post requests and allows very fast integration tests with automatic
7
+ cookies based session management (using http-cookies's CookieJar).
8
+
9
+ It also supports "virtual hosts" so that you can use domain names and they would be forwarded
10
+ to the Rack app if the domain is listed in the `virtual_hosts` option. It can also simulate
11
+ https as if the request was coming from an HTTP proxy, like nginx, transparently if you use
12
+ "https://..." in the requests using the DSL.
13
+
14
+ The DSL can be also used to perform requests to other Internet domains.
15
+
16
+ ## Installation
17
+
18
+ Add this line to your application's Gemfile:
19
+
20
+ ```ruby
21
+ gem 'rack_toolkit'
22
+ ```
23
+
24
+ And then execute:
25
+
26
+ $ bundle
27
+
28
+ Or install it yourself as:
29
+
30
+ $ gem install rack_toolkit
31
+
32
+ ## Usage
33
+
34
+ Create a new server (the initializer supports many options, but they can be also set later):
35
+
36
+ ```ruby
37
+ require 'rack_toolkit'
38
+ server = RackToolkit::Server.new start: true # or start it manually with server.start
39
+ # you can stop it with server.stop
40
+ ```
41
+
42
+ Set a Rack app application and perform a request:
43
+
44
+ ```ruby
45
+ server.app = ->(env){ [ 200, {}, 'success!' ] }
46
+ server.get '/'
47
+ server.last_response.code == '200'
48
+ server.last_response.body == 'success!'
49
+ ```
50
+
51
+ `last_response` is a [Net::HTTPResponse](http://ruby-doc.org/stdlib/libdoc/net/http/rdoc/Net/HTTPResponse.html).
52
+
53
+ Any cookies set by the application are stored in a cookie jar (`server.cookie_jar`).
54
+ Use `server.reset_session!` to clear the session cookies jar and the referer information.
55
+
56
+ The `default_domain` is automatically appended to the `virtual_hosts` option. Here's how to use
57
+ them:
58
+
59
+ ```ruby
60
+ server.virtual_hosts << 'my-domain.com' << 'www.my-domain.com'
61
+ server.default_domain = 'test-app.com' # it's appended to virtual_hosts
62
+ server.base_uri.to_s # 'http://test-app.com'
63
+ server.post 'https://test-app.com/signin', follow_redirect: false # default is true
64
+ server.env['HTTP_X_FORWARDED_PROTO'] == 'https'
65
+ server.env['HTTP_HOST'] == 'test-app.com'
66
+ server.last_response.status_code == 302 # assuming an app performing a redirect
67
+ server.follow_redirect! # not necessary if follow_redirect: false is not specified
68
+ server.last_response.ok? == true
69
+
70
+ server.post '/signin', params: {user: 'guest', pass: 'secret'}
71
+
72
+ server.post_data '/json_input', '{"json": "input"}'
73
+ ```
74
+
75
+ Usually you'd start the server before the whole suite and replace the `app` as you test
76
+ different apps. Starting Puma is really fast (less than 5ms usually) so if you prefer
77
+ you can start it on each test file. Also, RackToolkit was designed so that you can span
78
+ multiple servers running different apps for example if you want them to communicate to each
79
+ other for testing SSO for example.
80
+
81
+ Take a look at this project's test suite to see an example on how it can be configured and how
82
+ it works.
83
+
84
+ ## Development
85
+
86
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec`
87
+ to run the tests. You can also run `bin/console` for an interactive prompt that will allow
88
+ you to experiment.
89
+
90
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a
91
+ new version, update the version number in `version.rb`, and then run `bundle exec rake release`,
92
+ which will create a git tag for the version, push git commits and tags, and push the `.gem`
93
+ file to [rubygems.org](https://rubygems.org).
94
+
95
+ ## Contributing
96
+
97
+ Bug reports and pull requests are welcome [on GitHub](https://github.com/rosenfeld/rack_toolkit).
98
+
99
+
100
+ ## License
101
+
102
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
103
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "rack_toolkit"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ require 'net/http'
3
+
4
+ module RackToolkit
5
+ class Server
6
+ module ResponseEnhancer
7
+ def status_code
8
+ @status_code ||= code.to_i
9
+ end
10
+
11
+ def ok?
12
+ code == '200'
13
+ end
14
+
15
+ def redirect?
16
+ Net::HTTPRedirection === self
17
+ end
18
+
19
+ def error?
20
+ !(ok? || redirect?)
21
+ end
22
+
23
+ def headers
24
+ @headers ||= to_hash
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puma'
4
+ require 'net/http'
5
+ require 'uri'
6
+ require 'socket'
7
+ require 'set'
8
+ require 'http-cookie'
9
+ require_relative 'response_enhancer'
10
+
11
+ module RackToolkit
12
+ class Server
13
+ attr_accessor :app, :default_env, :referer, :default_headers
14
+ attr_reader :bind_host, :host, :server, :default_domain, :last_response, :http, :cookie_jar,
15
+ :env, :env_from_server, :rack_env, :virtual_hosts
16
+
17
+ def initialize(app: nil, port: nil, bind_host: '127.0.0.1', virtual_hosts: [], host: nil,
18
+ dynamic_app: nil, default_env: {}, default_domain: nil, default_headers: {},
19
+ start: false)
20
+ @app, @port, @bind_host, @default_env, @default_domain, @default_headers =
21
+ app, port, bind_host, default_env, default_domain, default_headers
22
+ @host = host || bind_host
23
+ @virtual_hosts = Set.new virtual_hosts
24
+ @virtual_hosts << default_domain if default_domain
25
+ @virtual_hosts << host
26
+ @dynamic_app = dynamic_app || default_dynamic_app
27
+ @request_env = {}
28
+ @referer = nil
29
+ @cookie_jar = HTTP::CookieJar.new
30
+ self.start if start
31
+ end
32
+
33
+ def start
34
+ @server = Puma::Server.new(@dynamic_app)
35
+ @server.add_tcp_listener @host, port
36
+ @server_thread = @server.run
37
+ @http = Net::HTTP.start @host, port
38
+ end
39
+
40
+ def stop
41
+ @http.finish rescue nil # ignore errors on finish
42
+ @server.stop true
43
+ end
44
+
45
+ def default_domain=(default_domain)
46
+ @default_domain = default_domain
47
+ @virtual_hosts << default_domain if default_domain
48
+ end
49
+
50
+ def base_uri
51
+ @base_uri ||= URI("http://#{host}:#{port}")
52
+ end
53
+
54
+ def reset_session!
55
+ @cookie_jar.clear
56
+ self.referer = nil
57
+ end
58
+
59
+ # response = get('/'); body = response.body; headers = response.to_hash
60
+ # response['set-cookie'] # => string
61
+ # response.get_fields('set-cookie') # => array # same as response.to_hash['set-cookie']
62
+ def get(url_or_path, params: nil, headers: {}, env_override: {}, follow_redirect: true,
63
+ redirect_limit: 5)
64
+ wrap_response(url_or_path, headers, env_override, params, follow_redirect,
65
+ redirect_limit) do |uri, h, http|
66
+ http.get(uri.path.empty? ? '/' : uri.path, h)
67
+ end
68
+ end
69
+
70
+ def post(url_or_path, params: nil, query_params: nil, headers: {}, env_override: {},
71
+ follow_redirect: true, redirect_limit: 5)
72
+ wrap_response(url_or_path, headers, env_override, params, follow_redirect,
73
+ redirect_limit) do |uri, h, http|
74
+ req = Net::HTTP::Post.new uri, h
75
+ req.form_data = params if params
76
+ http.request req
77
+ end
78
+ end
79
+
80
+ def post_data(url_or_path, data, query_params: nil, headers: {},
81
+ env_override: {}, follow_redirect: true, redirect_limit: 5)
82
+ wrap_response(url_or_path, headers, env_override, query_params, follow_redirect,
83
+ redirect_limit) do |uri, h, http|
84
+ http.post(uri, data, h)
85
+ end
86
+ end
87
+
88
+ def follow_redirect!(limit: 5, env_override: {})
89
+ get @last_response['location'], redirect_limit: limit - 1, env_override: {}
90
+ end
91
+
92
+ def port
93
+ @port ||= find_free_tcp_port
94
+ end
95
+
96
+ private
97
+
98
+ def find_free_tcp_port
99
+ server = TCPServer.new(bind_host, 0)
100
+ server.addr[1]
101
+ ensure
102
+ server.close
103
+ end
104
+
105
+ NO_DEFINED_APP_RESPONSE =[ 500, {}, [ 'No app was defined for server' ] ].freeze
106
+ def default_dynamic_app
107
+ ->(env) do
108
+ return NO_DEFINED_APP_RESPONSE unless app
109
+ @env_from_server = env.clone
110
+ @env = (@rack_env = env.merge(default_env).merge(@request_env)).clone
111
+ app.call @rack_env
112
+ end
113
+ end
114
+
115
+ def wrap_response(url_or_path, headers, env_override, params, follow_redirect, redirect_limit)
116
+ uri = normalize_uri(url_or_path, params)
117
+ h = prepare_headers headers, env_override, uri
118
+ response = uri.host == host ? yield(uri, h, @http) :
119
+ Net::HTTP.start(uri.host, uri.port){|http| yield uri, h, http }
120
+ @last_response = response.extend ResponseEnhancer
121
+ store_cookies uri
122
+ self.referer = @original_uri.to_s
123
+ @request_env = {}
124
+ handle_redirect redirect_limit, env_override if follow_redirect
125
+ @last_response
126
+ end
127
+
128
+ def normalize_uri(uri, params)
129
+ uri = URI(uri)
130
+ uri.host ||= default_domain || host
131
+ uri.scheme ||= 'http'
132
+ if params and uri.query
133
+ raise InvalidArguments, "Do not send query params if url already contains them"
134
+ end
135
+ uri.query = URI.encode_www_form params if params
136
+ @original_uri = URI(uri.to_s)
137
+ if @virtual_hosts.include?(uri.host)
138
+ uri.host = host
139
+ uri.scheme = 'http'
140
+ end
141
+ URI(uri.to_s)
142
+ end
143
+
144
+ def prepare_headers(headers, env_override, uri)
145
+ uri = @original_uri
146
+ @request_env = @request_env.merge(env_override).delete_if{|k, v| v.nil? }
147
+ h = default_headers.merge('Cookie' => HTTP::Cookie.cookie_value(@cookie_jar.cookies uri))
148
+ if referer && (origin_uri = URI referer) &&
149
+ (origin_uri.scheme == 'http' || uri.scheme == 'https')
150
+ origin_uri = URI(referer)
151
+ origin_uri.path = ''
152
+ h['Origin'] = origin_uri.to_s
153
+ h['Referer'] = referer
154
+ end
155
+ h['X-Forwarded-Proto'] = 'https' if uri.scheme == 'https'
156
+ h['Host'] ||= uri.host
157
+ h['Host'] ||= default_domain if default_domain
158
+ h.merge(headers).delete_if{|k, v| v.nil? }
159
+ end
160
+
161
+ InfiniteRedirect = Class.new RuntimeError
162
+ def handle_redirect(redirect_limit, env_override)
163
+ return unless Net::HTTPRedirection === @last_response
164
+ raise InfiniteRedirect, "Redirect loop" unless redirect_limit > 0
165
+ @last_response = follow_redirect!(limit: redirect_limit, env_override: env_override)
166
+ end
167
+
168
+ def store_cookies(uri)
169
+ return unless cookies = @last_response.get_fields('set-cookie')
170
+ cookies.each do |cookie|
171
+ @cookie_jar.parse cookie, uri
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RackToolkit
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'rack_toolkit/version'
4
+ require_relative 'rack_toolkit/server'
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ lib = File.expand_path('lib')
5
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+ require_relative 'lib/rack_toolkit/version'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'rack_toolkit'
10
+ spec.version = RackToolkit::VERSION
11
+ spec.authors = ['Rodrigo Rosenfeld Rosas']
12
+ spec.email = ['rr.rosas@gmail.com']
13
+
14
+ spec.summary = %q{A dynamic Rack server and helper methods to help testing Rack apps.}
15
+ spec.description =
16
+ %q{This gem makes it easier to start a Puma server that will bind to a dynamic free port
17
+ by default and provide helper methods like get and post, managing sessions automatically
18
+ and using keep-alive to make the requests faster. Usually the server would be run when the
19
+ test suite starts.}
20
+ spec.homepage = 'https://github.com/rosenfeld/rack_toolkit'
21
+ spec.license = 'MIT'
22
+
23
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^spec/}) }
24
+ spec.require_paths = ['lib']
25
+
26
+ spec.add_development_dependency 'bundler', '~> 1.12'
27
+ spec.add_development_dependency 'rake', '~> 10.0'
28
+ spec.add_development_dependency 'rspec', '~> 3.0'
29
+ spec.add_runtime_dependency 'puma', '~> 3.5'
30
+ spec.add_runtime_dependency 'http-cookie', '~> 1.0.2'
31
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack_toolkit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rodrigo Rosenfeld Rosas
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-07-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.12'
19
+ name: bundler
20
+ prerelease: false
21
+ type: :development
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.12'
27
+ - !ruby/object:Gem::Dependency
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '10.0'
33
+ name: rake
34
+ prerelease: false
35
+ type: :development
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.0'
47
+ name: rspec
48
+ prerelease: false
49
+ type: :development
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '3.5'
61
+ name: puma
62
+ prerelease: false
63
+ type: :runtime
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.5'
69
+ - !ruby/object:Gem::Dependency
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: 1.0.2
75
+ name: http-cookie
76
+ prerelease: false
77
+ type: :runtime
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.0.2
83
+ description: |-
84
+ This gem makes it easier to start a Puma server that will bind to a dynamic free port
85
+ by default and provide helper methods like get and post, managing sessions automatically
86
+ and using keep-alive to make the requests faster. Usually the server would be run when the
87
+ test suite starts.
88
+ email:
89
+ - rr.rosas@gmail.com
90
+ executables: []
91
+ extensions: []
92
+ extra_rdoc_files: []
93
+ files:
94
+ - ".gitignore"
95
+ - ".rspec"
96
+ - ".rspec-fast"
97
+ - ".travis.yml"
98
+ - Gemfile
99
+ - LICENSE.txt
100
+ - README.md
101
+ - Rakefile
102
+ - bin/console
103
+ - bin/setup
104
+ - lib/rack_toolkit.rb
105
+ - lib/rack_toolkit/response_enhancer.rb
106
+ - lib/rack_toolkit/server.rb
107
+ - lib/rack_toolkit/version.rb
108
+ - rack_toolkit.gemspec
109
+ homepage: https://github.com/rosenfeld/rack_toolkit
110
+ licenses:
111
+ - MIT
112
+ metadata: {}
113
+ post_install_message:
114
+ rdoc_options: []
115
+ require_paths:
116
+ - lib
117
+ required_ruby_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ requirements: []
128
+ rubyforge_project:
129
+ rubygems_version: 2.6.4
130
+ signing_key:
131
+ specification_version: 4
132
+ summary: A dynamic Rack server and helper methods to help testing Rack apps.
133
+ test_files: []