rack_toolkit 0.1.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.
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: []