actionpack 4.2.11.3 → 5.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +379 -462
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -3
- data/lib/abstract_controller.rb +0 -2
- data/lib/abstract_controller/base.rb +17 -32
- data/lib/abstract_controller/callbacks.rb +52 -19
- data/lib/abstract_controller/collector.rb +4 -9
- data/lib/abstract_controller/helpers.rb +2 -2
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -2
- data/lib/abstract_controller/rendering.rb +27 -22
- data/lib/abstract_controller/translation.rb +8 -7
- data/lib/action_controller.rb +4 -3
- data/lib/action_controller/api.rb +146 -0
- data/lib/action_controller/base.rb +6 -10
- data/lib/action_controller/caching.rb +1 -3
- data/lib/action_controller/caching/fragments.rb +48 -3
- data/lib/action_controller/form_builder.rb +48 -0
- data/lib/action_controller/log_subscriber.rb +1 -10
- data/lib/action_controller/metal.rb +89 -62
- data/lib/action_controller/metal/basic_implicit_render.rb +11 -0
- data/lib/action_controller/metal/conditional_get.rb +65 -24
- data/lib/action_controller/metal/cookies.rb +0 -2
- data/lib/action_controller/metal/data_streaming.rb +2 -22
- data/lib/action_controller/metal/etag_with_template_digest.rb +1 -1
- data/lib/action_controller/metal/exceptions.rb +11 -6
- data/lib/action_controller/metal/force_ssl.rb +6 -6
- data/lib/action_controller/metal/head.rb +14 -7
- data/lib/action_controller/metal/helpers.rb +9 -5
- data/lib/action_controller/metal/http_authentication.rb +37 -38
- data/lib/action_controller/metal/implicit_render.rb +23 -6
- data/lib/action_controller/metal/instrumentation.rb +0 -1
- data/lib/action_controller/metal/live.rb +17 -55
- data/lib/action_controller/metal/mime_responds.rb +17 -37
- data/lib/action_controller/metal/params_wrapper.rb +8 -8
- data/lib/action_controller/metal/redirecting.rb +32 -9
- data/lib/action_controller/metal/renderers.rb +10 -8
- data/lib/action_controller/metal/rendering.rb +38 -6
- data/lib/action_controller/metal/request_forgery_protection.rb +67 -35
- data/lib/action_controller/metal/rescue.rb +2 -4
- data/lib/action_controller/metal/streaming.rb +4 -4
- data/lib/action_controller/metal/strong_parameters.rb +231 -78
- data/lib/action_controller/metal/testing.rb +1 -12
- data/lib/action_controller/metal/url_for.rb +12 -5
- data/lib/action_controller/renderer.rb +111 -0
- data/lib/action_controller/template_assertions.rb +9 -0
- data/lib/action_controller/test_case.rb +267 -363
- data/lib/action_dispatch.rb +2 -1
- data/lib/action_dispatch/http/cache.rb +23 -26
- data/lib/action_dispatch/http/filter_parameters.rb +6 -8
- data/lib/action_dispatch/http/filter_redirect.rb +7 -8
- data/lib/action_dispatch/http/headers.rb +28 -11
- data/lib/action_dispatch/http/mime_negotiation.rb +40 -26
- data/lib/action_dispatch/http/mime_type.rb +92 -61
- data/lib/action_dispatch/http/mime_types.rb +1 -4
- data/lib/action_dispatch/http/parameter_filter.rb +18 -8
- data/lib/action_dispatch/http/parameters.rb +45 -41
- data/lib/action_dispatch/http/request.rb +146 -82
- data/lib/action_dispatch/http/response.rb +180 -99
- data/lib/action_dispatch/http/url.rb +117 -8
- data/lib/action_dispatch/journey/formatter.rb +34 -28
- data/lib/action_dispatch/journey/gtg/transition_table.rb +1 -1
- data/lib/action_dispatch/journey/nfa/dot.rb +0 -2
- data/lib/action_dispatch/journey/nfa/transition_table.rb +1 -46
- data/lib/action_dispatch/journey/nodes/node.rb +14 -4
- data/lib/action_dispatch/journey/parser_extras.rb +4 -0
- data/lib/action_dispatch/journey/path/pattern.rb +37 -41
- data/lib/action_dispatch/journey/route.rb +71 -17
- data/lib/action_dispatch/journey/router.rb +5 -6
- data/lib/action_dispatch/journey/router/utils.rb +5 -5
- data/lib/action_dispatch/journey/routes.rb +14 -15
- data/lib/action_dispatch/journey/visitors.rb +86 -43
- data/lib/action_dispatch/middleware/cookies.rb +184 -135
- data/lib/action_dispatch/middleware/debug_exceptions.rb +115 -45
- data/lib/action_dispatch/middleware/exception_wrapper.rb +21 -20
- data/lib/action_dispatch/middleware/flash.rb +61 -45
- data/lib/action_dispatch/middleware/load_interlock.rb +21 -0
- data/lib/action_dispatch/middleware/params_parser.rb +30 -46
- data/lib/action_dispatch/middleware/public_exceptions.rb +2 -2
- data/lib/action_dispatch/middleware/reloader.rb +2 -4
- data/lib/action_dispatch/middleware/remote_ip.rb +29 -19
- data/lib/action_dispatch/middleware/request_id.rb +11 -6
- data/lib/action_dispatch/middleware/session/abstract_store.rb +23 -11
- data/lib/action_dispatch/middleware/session/cache_store.rb +9 -6
- data/lib/action_dispatch/middleware/session/cookie_store.rb +29 -23
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +4 -0
- data/lib/action_dispatch/middleware/show_exceptions.rb +11 -9
- data/lib/action_dispatch/middleware/ssl.rb +93 -36
- data/lib/action_dispatch/middleware/stack.rb +43 -48
- data/lib/action_dispatch/middleware/static.rb +52 -40
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
- data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -63
- data/lib/action_dispatch/railtie.rb +0 -2
- data/lib/action_dispatch/request/session.rb +66 -34
- data/lib/action_dispatch/request/utils.rb +51 -19
- data/lib/action_dispatch/routing.rb +3 -8
- data/lib/action_dispatch/routing/inspector.rb +6 -30
- data/lib/action_dispatch/routing/mapper.rb +447 -322
- data/lib/action_dispatch/routing/polymorphic_routes.rb +8 -14
- data/lib/action_dispatch/routing/redirection.rb +3 -3
- data/lib/action_dispatch/routing/route_set.rb +124 -227
- data/lib/action_dispatch/routing/url_for.rb +27 -10
- data/lib/action_dispatch/testing/assertions.rb +1 -1
- data/lib/action_dispatch/testing/assertions/response.rb +27 -9
- data/lib/action_dispatch/testing/assertions/routing.rb +9 -9
- data/lib/action_dispatch/testing/integration.rb +237 -76
- data/lib/action_dispatch/testing/test_process.rb +5 -5
- data/lib/action_dispatch/testing/test_request.rb +12 -21
- data/lib/action_dispatch/testing/test_response.rb +1 -4
- data/lib/action_pack.rb +1 -1
- data/lib/action_pack/gem_version.rb +4 -4
- metadata +26 -25
- data/lib/action_controller/metal/hide_actions.rb +0 -40
- data/lib/action_controller/metal/rack_delegation.rb +0 -32
- data/lib/action_controller/middleware.rb +0 -39
- data/lib/action_controller/model_naming.rb +0 -12
- data/lib/action_dispatch/journey/router/strexp.rb +0 -27
- data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
- data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
- data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,7 +1,7 @@
|
|
1
1
|
module ActionDispatch
|
2
2
|
module Routing
|
3
3
|
# In <tt>config/routes.rb</tt> you define URL-to-controller mappings, but the reverse
|
4
|
-
# is also possible:
|
4
|
+
# is also possible: a URL can be generated from one of your routing definitions.
|
5
5
|
# URL generation functionality is centralized in this module.
|
6
6
|
#
|
7
7
|
# See ActionDispatch::Routing for general information about routing and routes.rb.
|
@@ -52,9 +52,11 @@ module ActionDispatch
|
|
52
52
|
# argument.
|
53
53
|
#
|
54
54
|
# For convenience reasons, mailers provide a shortcut for ActionController::UrlFor#url_for.
|
55
|
-
# So within mailers, you only have to type
|
56
|
-
# in full. However, mailers don't have hostname information, and
|
57
|
-
#
|
55
|
+
# So within mailers, you only have to type +url_for+ instead of 'ActionController::UrlFor#url_for'
|
56
|
+
# in full. However, mailers don't have hostname information, and you still have to provide
|
57
|
+
# the +:host+ argument or set the default host that will be used in all mailers using the
|
58
|
+
# configuration option +config.action_mailer.default_url_options+. For more information on
|
59
|
+
# url_for in mailers read the ActionMailer#Base documentation.
|
58
60
|
#
|
59
61
|
#
|
60
62
|
# == URL generation for named routes
|
@@ -147,6 +149,20 @@ module ActionDispatch
|
|
147
149
|
# # => 'http://somehost.org/myapp/tasks/testing'
|
148
150
|
# url_for controller: 'tasks', action: 'testing', host: 'somehost.org', script_name: "/myapp", only_path: true
|
149
151
|
# # => '/myapp/tasks/testing'
|
152
|
+
#
|
153
|
+
# Missing routes keys may be filled in from the current request's parameters
|
154
|
+
# (e.g. +:controller+, +:action+, +:id+ and any other parameters that are
|
155
|
+
# placed in the path). Given that the current action has been reached
|
156
|
+
# through `GET /users/1`:
|
157
|
+
#
|
158
|
+
# url_for(only_path: true) # => '/users/1'
|
159
|
+
# url_for(only_path: true, action: 'edit') # => '/users/1/edit'
|
160
|
+
# url_for(only_path: true, action: 'edit', id: 2) # => '/users/2/edit'
|
161
|
+
#
|
162
|
+
# Notice that no +:id+ parameter was provided to the first +url_for+ call
|
163
|
+
# and the helper used the one from the route's path. Any path parameter
|
164
|
+
# implicitly used by +url_for+ can always be overwritten like shown on the
|
165
|
+
# last +url_for+ calls.
|
150
166
|
def url_for(options = nil)
|
151
167
|
case options
|
152
168
|
when nil
|
@@ -155,6 +171,13 @@ module ActionDispatch
|
|
155
171
|
route_name = options.delete :use_route
|
156
172
|
_routes.url_for(options.symbolize_keys.reverse_merge!(url_options),
|
157
173
|
route_name)
|
174
|
+
when ActionController::Parameters
|
175
|
+
unless options.permitted?
|
176
|
+
raise ArgumentError.new("Generating an URL from non sanitized request parameters is insecure!")
|
177
|
+
end
|
178
|
+
route_name = options.delete :use_route
|
179
|
+
_routes.url_for(options.to_h.symbolize_keys.
|
180
|
+
reverse_merge!(url_options), route_name)
|
158
181
|
when String
|
159
182
|
options
|
160
183
|
when Symbol
|
@@ -185,12 +208,6 @@ module ActionDispatch
|
|
185
208
|
def _routes_context
|
186
209
|
self
|
187
210
|
end
|
188
|
-
|
189
|
-
private
|
190
|
-
|
191
|
-
def _generate_paths_by_default
|
192
|
-
true
|
193
|
-
end
|
194
211
|
end
|
195
212
|
end
|
196
213
|
end
|
@@ -12,7 +12,7 @@ module ActionDispatch
|
|
12
12
|
include Rails::Dom::Testing::Assertions
|
13
13
|
|
14
14
|
def html_document
|
15
|
-
@html_document ||= if @response.content_type.to_s =~ /xml
|
15
|
+
@html_document ||= if @response.content_type.to_s =~ /xml\z/
|
16
16
|
Nokogiri::XML::Document.parse(@response.body)
|
17
17
|
else
|
18
18
|
Nokogiri::HTML::Document.parse(@response.body)
|
@@ -3,6 +3,13 @@ module ActionDispatch
|
|
3
3
|
module Assertions
|
4
4
|
# A small suite of assertions that test responses from \Rails applications.
|
5
5
|
module ResponseAssertions
|
6
|
+
RESPONSE_PREDICATES = { # :nodoc:
|
7
|
+
success: :successful?,
|
8
|
+
missing: :not_found?,
|
9
|
+
redirect: :redirection?,
|
10
|
+
error: :server_error?,
|
11
|
+
}
|
12
|
+
|
6
13
|
# Asserts that the response is one of the following types:
|
7
14
|
#
|
8
15
|
# * <tt>:success</tt> - Status code was in the 200-299 range
|
@@ -14,17 +21,17 @@ module ActionDispatch
|
|
14
21
|
# or its symbolic equivalent <tt>assert_response(:not_implemented)</tt>.
|
15
22
|
# See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list.
|
16
23
|
#
|
17
|
-
# #
|
24
|
+
# # Asserts that the response was a redirection
|
18
25
|
# assert_response :redirect
|
19
26
|
#
|
20
|
-
# #
|
27
|
+
# # Asserts that the response code was status code 401 (unauthorized)
|
21
28
|
# assert_response 401
|
22
29
|
def assert_response(type, message = nil)
|
23
|
-
message ||=
|
30
|
+
message ||= generate_response_message(type)
|
24
31
|
|
25
32
|
if Symbol === type
|
26
33
|
if [:success, :missing, :redirect, :error].include?(type)
|
27
|
-
assert @response.send(
|
34
|
+
assert @response.send(RESPONSE_PREDICATES[type]), message
|
28
35
|
else
|
29
36
|
code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type]
|
30
37
|
if code.nil?
|
@@ -37,20 +44,20 @@ module ActionDispatch
|
|
37
44
|
end
|
38
45
|
end
|
39
46
|
|
40
|
-
#
|
47
|
+
# Asserts that the redirection options passed in match those of the redirect called in the latest action.
|
41
48
|
# This match can be partial, such that <tt>assert_redirected_to(controller: "weblog")</tt> will also
|
42
49
|
# match the redirection of <tt>redirect_to(controller: "weblog", action: "show")</tt> and so on.
|
43
50
|
#
|
44
|
-
# #
|
51
|
+
# # Asserts that the redirection was to the "index" action on the WeblogController
|
45
52
|
# assert_redirected_to controller: "weblog", action: "index"
|
46
53
|
#
|
47
|
-
# #
|
54
|
+
# # Asserts that the redirection was to the named route login_url
|
48
55
|
# assert_redirected_to login_url
|
49
56
|
#
|
50
|
-
# #
|
57
|
+
# # Asserts that the redirection was to the url for @customer
|
51
58
|
# assert_redirected_to @customer
|
52
59
|
#
|
53
|
-
# #
|
60
|
+
# # Asserts that the redirection matches the regular expression
|
54
61
|
# assert_redirected_to %r(\Ahttp://example.org)
|
55
62
|
def assert_redirected_to(options = {}, message=nil)
|
56
63
|
assert_response(:redirect, message)
|
@@ -77,6 +84,17 @@ module ActionDispatch
|
|
77
84
|
handle._compute_redirect_to_location(@request, fragment)
|
78
85
|
end
|
79
86
|
end
|
87
|
+
|
88
|
+
def generate_response_message(type, code = @response.response_code)
|
89
|
+
"Expected response to be a <#{type}>, but was a <#{code}>"
|
90
|
+
.concat location_if_redirected
|
91
|
+
end
|
92
|
+
|
93
|
+
def location_if_redirected
|
94
|
+
return '' unless @response.redirection? && @response.location.present?
|
95
|
+
location = normalize_argument_to_redirection(@response.location)
|
96
|
+
" redirect to <#{location}>"
|
97
|
+
end
|
80
98
|
end
|
81
99
|
end
|
82
100
|
end
|
@@ -14,14 +14,14 @@ module ActionDispatch
|
|
14
14
|
# requiring a specific HTTP method. The hash should contain a :path with the incoming request path
|
15
15
|
# and a :method containing the required HTTP verb.
|
16
16
|
#
|
17
|
-
# #
|
17
|
+
# # Asserts that POSTing to /items will call the create action on ItemsController
|
18
18
|
# assert_recognizes({controller: 'items', action: 'create'}, {path: 'items', method: :post})
|
19
19
|
#
|
20
20
|
# You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used
|
21
|
-
# to assert that values in the query string
|
21
|
+
# to assert that values in the query string will end up in the params hash correctly. To test query strings you must use the
|
22
22
|
# extras argument, appending the query string on the path directly will not work. For example:
|
23
23
|
#
|
24
|
-
# #
|
24
|
+
# # Asserts that a path of '/items/list/1?view=print' returns the correct options
|
25
25
|
# assert_recognizes({controller: 'items', action: 'list', id: '1', view: 'print'}, 'items/list/1', { view: "print" })
|
26
26
|
#
|
27
27
|
# The +message+ parameter allows you to pass in an error message that is displayed upon failure.
|
@@ -86,8 +86,8 @@ module ActionDispatch
|
|
86
86
|
end
|
87
87
|
# Load routes.rb if it hasn't been loaded.
|
88
88
|
|
89
|
-
generated_path,
|
90
|
-
found_extras = options.reject { |k, _| !
|
89
|
+
generated_path, query_string_keys = @routes.generate_extras(options, defaults)
|
90
|
+
found_extras = options.reject { |k, _| ! query_string_keys.include? k }
|
91
91
|
|
92
92
|
msg = message || sprintf("found extras <%s>, not <%s>", found_extras, extras)
|
93
93
|
assert_equal(extras, found_extras, msg)
|
@@ -104,13 +104,13 @@ module ActionDispatch
|
|
104
104
|
# The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The
|
105
105
|
# +message+ parameter allows you to specify a custom error message to display upon failure.
|
106
106
|
#
|
107
|
-
# #
|
107
|
+
# # Asserts a basic route: a controller with the default action (index)
|
108
108
|
# assert_routing '/home', controller: 'home', action: 'index'
|
109
109
|
#
|
110
110
|
# # Test a route generated with a specific controller, action, and parameter (id)
|
111
111
|
# assert_routing '/entries/show/23', controller: 'entries', action: 'show', id: 23
|
112
112
|
#
|
113
|
-
# #
|
113
|
+
# # Asserts a basic route (controller + default action), with an error message if it fails
|
114
114
|
# assert_routing '/store', { controller: 'store', action: 'index' }, {}, {}, 'Route for store index not generated properly'
|
115
115
|
#
|
116
116
|
# # Tests a route, providing a defaults hash
|
@@ -150,7 +150,7 @@ module ActionDispatch
|
|
150
150
|
old_controller, @controller = @controller, @controller.clone
|
151
151
|
_routes = @routes
|
152
152
|
|
153
|
-
@controller.singleton_class.
|
153
|
+
@controller.singleton_class.include(_routes.url_helpers)
|
154
154
|
@controller.view_context_class = Class.new(@controller.view_context_class) do
|
155
155
|
include _routes.url_helpers
|
156
156
|
end
|
@@ -183,7 +183,7 @@ module ActionDispatch
|
|
183
183
|
end
|
184
184
|
|
185
185
|
# Assume given controller
|
186
|
-
request = ActionController::TestRequest.
|
186
|
+
request = ActionController::TestRequest.create
|
187
187
|
|
188
188
|
if path =~ %r{://}
|
189
189
|
fail_on(URI::InvalidURIError, msg) do
|
@@ -2,6 +2,7 @@ require 'stringio'
|
|
2
2
|
require 'uri'
|
3
3
|
require 'active_support/core_ext/kernel/singleton_class'
|
4
4
|
require 'active_support/core_ext/object/try'
|
5
|
+
require 'active_support/core_ext/string/strip'
|
5
6
|
require 'rack/test'
|
6
7
|
require 'minitest'
|
7
8
|
|
@@ -12,12 +13,14 @@ module ActionDispatch
|
|
12
13
|
#
|
13
14
|
# - +path+: The URI (as a String) on which you want to perform a GET
|
14
15
|
# request.
|
15
|
-
# - +
|
16
|
+
# - +params+: The HTTP parameters that you want to pass. This may
|
16
17
|
# be +nil+,
|
17
18
|
# a Hash, or a String that is appropriately encoded
|
18
19
|
# (<tt>application/x-www-form-urlencoded</tt> or
|
19
20
|
# <tt>multipart/form-data</tt>).
|
20
|
-
# - +
|
21
|
+
# - +headers+: Additional headers to pass, as a Hash. The headers will be
|
22
|
+
# merged into the Rack env hash.
|
23
|
+
# - +env+: Additional env to pass, as a Hash. The headers will be
|
21
24
|
# merged into the Rack env hash.
|
22
25
|
#
|
23
26
|
# This method returns a Response object, which one can use to
|
@@ -28,38 +31,43 @@ module ActionDispatch
|
|
28
31
|
#
|
29
32
|
# You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with
|
30
33
|
# +#post+, +#patch+, +#put+, +#delete+, and +#head+.
|
31
|
-
|
32
|
-
|
34
|
+
#
|
35
|
+
# Example:
|
36
|
+
#
|
37
|
+
# get '/feed', params: { since: 201501011400 }
|
38
|
+
# post '/profile', headers: { "X-Test-Header" => "testvalue" }
|
39
|
+
def get(path, *args)
|
40
|
+
process_with_kwargs(:get, path, *args)
|
33
41
|
end
|
34
42
|
|
35
43
|
# Performs a POST request with the given parameters. See +#get+ for more
|
36
44
|
# details.
|
37
|
-
def post(path,
|
38
|
-
|
45
|
+
def post(path, *args)
|
46
|
+
process_with_kwargs(:post, path, *args)
|
39
47
|
end
|
40
48
|
|
41
49
|
# Performs a PATCH request with the given parameters. See +#get+ for more
|
42
50
|
# details.
|
43
|
-
def patch(path,
|
44
|
-
|
51
|
+
def patch(path, *args)
|
52
|
+
process_with_kwargs(:patch, path, *args)
|
45
53
|
end
|
46
54
|
|
47
55
|
# Performs a PUT request with the given parameters. See +#get+ for more
|
48
56
|
# details.
|
49
|
-
def put(path,
|
50
|
-
|
57
|
+
def put(path, *args)
|
58
|
+
process_with_kwargs(:put, path, *args)
|
51
59
|
end
|
52
60
|
|
53
61
|
# Performs a DELETE request with the given parameters. See +#get+ for
|
54
62
|
# more details.
|
55
|
-
def delete(path,
|
56
|
-
|
63
|
+
def delete(path, *args)
|
64
|
+
process_with_kwargs(:delete, path, *args)
|
57
65
|
end
|
58
66
|
|
59
67
|
# Performs a HEAD request with the given parameters. See +#get+ for more
|
60
68
|
# details.
|
61
|
-
def head(path,
|
62
|
-
|
69
|
+
def head(path, *args)
|
70
|
+
process_with_kwargs(:head, path, *args)
|
63
71
|
end
|
64
72
|
|
65
73
|
# Performs an XMLHttpRequest request with the given parameters, mirroring
|
@@ -68,11 +76,29 @@ module ActionDispatch
|
|
68
76
|
# The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
|
69
77
|
# +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
|
70
78
|
# string; the headers are a hash.
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
79
|
+
#
|
80
|
+
# Example:
|
81
|
+
#
|
82
|
+
# xhr :get, '/feed', params: { since: 201501011400 }
|
83
|
+
def xml_http_request(request_method, path, *args)
|
84
|
+
if kwarg_request?(args)
|
85
|
+
params, headers, env = args.first.values_at(:params, :headers, :env)
|
86
|
+
else
|
87
|
+
params = args[0]
|
88
|
+
headers = args[1]
|
89
|
+
env = {}
|
90
|
+
|
91
|
+
if params.present? || headers.present?
|
92
|
+
non_kwarg_request_warning
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
|
97
|
+
xhr and xml_http_request methods are deprecated in favor of
|
98
|
+
`get "/posts", xhr: true` and `post "/posts/1", xhr: true`
|
99
|
+
MSG
|
100
|
+
|
101
|
+
process(request_method, path, params: params, headers: headers, xhr: true)
|
76
102
|
end
|
77
103
|
alias xhr :xml_http_request
|
78
104
|
|
@@ -89,40 +115,52 @@ module ActionDispatch
|
|
89
115
|
# redirect. Note that the redirects are followed until the response is
|
90
116
|
# not a redirect--this means you may run into an infinite loop if your
|
91
117
|
# redirect loops back to itself.
|
92
|
-
|
93
|
-
|
118
|
+
#
|
119
|
+
# Example:
|
120
|
+
#
|
121
|
+
# request_via_redirect :post, '/welcome',
|
122
|
+
# params: { ref_id: 14 },
|
123
|
+
# headers: { "X-Test-Header" => "testvalue" }
|
124
|
+
def request_via_redirect(http_method, path, *args)
|
125
|
+
process_with_kwargs(http_method, path, *args)
|
126
|
+
|
94
127
|
follow_redirect! while redirect?
|
95
128
|
status
|
96
129
|
end
|
97
130
|
|
98
131
|
# Performs a GET request, following any subsequent redirect.
|
99
132
|
# See +request_via_redirect+ for more information.
|
100
|
-
def get_via_redirect(path,
|
101
|
-
|
133
|
+
def get_via_redirect(path, *args)
|
134
|
+
ActiveSupport::Deprecation.warn('`get_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.')
|
135
|
+
request_via_redirect(:get, path, *args)
|
102
136
|
end
|
103
137
|
|
104
138
|
# Performs a POST request, following any subsequent redirect.
|
105
139
|
# See +request_via_redirect+ for more information.
|
106
|
-
def post_via_redirect(path,
|
107
|
-
|
140
|
+
def post_via_redirect(path, *args)
|
141
|
+
ActiveSupport::Deprecation.warn('`post_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.')
|
142
|
+
request_via_redirect(:post, path, *args)
|
108
143
|
end
|
109
144
|
|
110
145
|
# Performs a PATCH request, following any subsequent redirect.
|
111
146
|
# See +request_via_redirect+ for more information.
|
112
|
-
def patch_via_redirect(path,
|
113
|
-
|
147
|
+
def patch_via_redirect(path, *args)
|
148
|
+
ActiveSupport::Deprecation.warn('`patch_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.')
|
149
|
+
request_via_redirect(:patch, path, *args)
|
114
150
|
end
|
115
151
|
|
116
152
|
# Performs a PUT request, following any subsequent redirect.
|
117
153
|
# See +request_via_redirect+ for more information.
|
118
|
-
def put_via_redirect(path,
|
119
|
-
|
154
|
+
def put_via_redirect(path, *args)
|
155
|
+
ActiveSupport::Deprecation.warn('`put_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.')
|
156
|
+
request_via_redirect(:put, path, *args)
|
120
157
|
end
|
121
158
|
|
122
159
|
# Performs a DELETE request, following any subsequent redirect.
|
123
160
|
# See +request_via_redirect+ for more information.
|
124
|
-
def delete_via_redirect(path,
|
125
|
-
|
161
|
+
def delete_via_redirect(path, *args)
|
162
|
+
ActiveSupport::Deprecation.warn('`delete_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.')
|
163
|
+
request_via_redirect(:delete, path, *args)
|
126
164
|
end
|
127
165
|
end
|
128
166
|
|
@@ -185,15 +223,6 @@ module ActionDispatch
|
|
185
223
|
super()
|
186
224
|
@app = app
|
187
225
|
|
188
|
-
# If the app is a Rails app, make url_helpers available on the session
|
189
|
-
# This makes app.url_for and app.foo_path available in the console
|
190
|
-
if app.respond_to?(:routes)
|
191
|
-
singleton_class.class_eval do
|
192
|
-
include app.routes.url_helpers
|
193
|
-
include app.routes.mounted_helpers
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
226
|
reset!
|
198
227
|
end
|
199
228
|
|
@@ -261,20 +290,54 @@ module ActionDispatch
|
|
261
290
|
@_mock_session ||= Rack::MockSession.new(@app, host)
|
262
291
|
end
|
263
292
|
|
293
|
+
def process_with_kwargs(http_method, path, *args)
|
294
|
+
if kwarg_request?(args)
|
295
|
+
process(http_method, path, *args)
|
296
|
+
else
|
297
|
+
non_kwarg_request_warning if args.any?
|
298
|
+
process(http_method, path, { params: args[0], headers: args[1] })
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
REQUEST_KWARGS = %i(params headers env xhr)
|
303
|
+
def kwarg_request?(args)
|
304
|
+
args[0].respond_to?(:keys) && args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) }
|
305
|
+
end
|
306
|
+
|
307
|
+
def non_kwarg_request_warning
|
308
|
+
ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
|
309
|
+
ActionDispatch::IntegrationTest HTTP request methods will accept only
|
310
|
+
the following keyword arguments in future Rails versions:
|
311
|
+
#{REQUEST_KWARGS.join(', ')}
|
312
|
+
|
313
|
+
Examples:
|
314
|
+
|
315
|
+
get '/profile',
|
316
|
+
params: { id: 1 },
|
317
|
+
headers: { 'X-Extra-Header' => '123' },
|
318
|
+
env: { 'action_dispatch.custom' => 'custom' },
|
319
|
+
xhr: true
|
320
|
+
MSG
|
321
|
+
end
|
322
|
+
|
264
323
|
# Performs the actual request.
|
265
|
-
def process(method, path,
|
324
|
+
def process(method, path, params: nil, headers: nil, env: nil, xhr: false)
|
266
325
|
if path =~ %r{://}
|
267
326
|
location = URI.parse(path)
|
268
327
|
https! URI::HTTPS === location if location.scheme
|
269
|
-
|
328
|
+
if url_host = location.host
|
329
|
+
default = Rack::Request::DEFAULT_PORTS[location.scheme]
|
330
|
+
url_host += ":#{location.port}" if default != location.port
|
331
|
+
host! url_host
|
332
|
+
end
|
270
333
|
path = location.query ? "#{location.path}?#{location.query}" : location.path
|
271
334
|
end
|
272
335
|
|
273
336
|
hostname, port = host.split(':')
|
274
337
|
|
275
|
-
|
338
|
+
request_env = {
|
276
339
|
:method => method,
|
277
|
-
:params =>
|
340
|
+
:params => params,
|
278
341
|
|
279
342
|
"SERVER_NAME" => hostname,
|
280
343
|
"SERVER_PORT" => port || (https? ? "443" : "80"),
|
@@ -287,26 +350,38 @@ module ActionDispatch
|
|
287
350
|
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
|
288
351
|
"HTTP_ACCEPT" => accept
|
289
352
|
}
|
290
|
-
|
291
|
-
|
353
|
+
|
354
|
+
if xhr
|
355
|
+
headers ||= {}
|
356
|
+
headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
|
357
|
+
headers['HTTP_ACCEPT'] ||= [Mime[:js], Mime[:html], Mime[:xml], 'text/xml', '*/*'].join(', ')
|
358
|
+
end
|
359
|
+
|
360
|
+
# this modifies the passed request_env directly
|
361
|
+
if headers.present?
|
362
|
+
Http::Headers.from_hash(request_env).merge!(headers)
|
363
|
+
end
|
364
|
+
if env.present?
|
365
|
+
Http::Headers.from_hash(request_env).merge!(env)
|
366
|
+
end
|
292
367
|
|
293
368
|
session = Rack::Test::Session.new(_mock_session)
|
294
369
|
|
295
370
|
# NOTE: rack-test v0.5 doesn't build a default uri correctly
|
296
371
|
# Make sure requested path is always a full uri
|
297
|
-
session.request(build_full_uri(path,
|
372
|
+
session.request(build_full_uri(path, request_env), request_env)
|
298
373
|
|
299
374
|
@request_count += 1
|
300
375
|
@request = ActionDispatch::Request.new(session.last_request.env)
|
301
376
|
response = _mock_session.last_response
|
302
377
|
@response = ActionDispatch::TestResponse.from_response(response)
|
378
|
+
@response.request = @request
|
303
379
|
@html_document = nil
|
304
|
-
@html_scanner_document = nil
|
305
380
|
@url_options = nil
|
306
381
|
|
307
|
-
@controller =
|
382
|
+
@controller = @request.controller_instance
|
308
383
|
|
309
|
-
|
384
|
+
response.status
|
310
385
|
end
|
311
386
|
|
312
387
|
def build_full_uri(path, env)
|
@@ -317,14 +392,36 @@ module ActionDispatch
|
|
317
392
|
module Runner
|
318
393
|
include ActionDispatch::Assertions
|
319
394
|
|
320
|
-
|
321
|
-
|
395
|
+
APP_SESSIONS = {}
|
396
|
+
|
397
|
+
attr_reader :app
|
398
|
+
|
399
|
+
def before_setup # :nodoc:
|
400
|
+
@app = nil
|
401
|
+
@integration_session = nil
|
402
|
+
super
|
403
|
+
end
|
404
|
+
|
405
|
+
def integration_session
|
406
|
+
@integration_session ||= create_session(app)
|
322
407
|
end
|
323
408
|
|
324
409
|
# Reset the current session. This is useful for testing multiple sessions
|
325
410
|
# in a single test case.
|
326
411
|
def reset!
|
327
|
-
@integration_session =
|
412
|
+
@integration_session = create_session(app)
|
413
|
+
end
|
414
|
+
|
415
|
+
def create_session(app)
|
416
|
+
klass = APP_SESSIONS[app] ||= Class.new(Integration::Session) {
|
417
|
+
# If the app is a Rails app, make url_helpers available on the session
|
418
|
+
# This makes app.url_for and app.foo_path available in the console
|
419
|
+
if app.respond_to?(:routes)
|
420
|
+
include app.routes.url_helpers
|
421
|
+
include app.routes.mounted_helpers
|
422
|
+
end
|
423
|
+
}
|
424
|
+
klass.new(app)
|
328
425
|
end
|
329
426
|
|
330
427
|
def remove! # :nodoc:
|
@@ -334,13 +431,9 @@ module ActionDispatch
|
|
334
431
|
%w(get post patch put head delete cookies assigns
|
335
432
|
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
|
336
433
|
define_method(method) do |*args|
|
337
|
-
reset! unless integration_session
|
338
|
-
|
339
434
|
# reset the html_document variable, except for cookies/assigns calls
|
340
435
|
unless method == 'cookies' || method == 'assigns'
|
341
436
|
@html_document = nil
|
342
|
-
@html_scanner_document = nil
|
343
|
-
reset_template_assertion
|
344
437
|
end
|
345
438
|
|
346
439
|
integration_session.__send__(method, *args).tap do
|
@@ -361,7 +454,6 @@ module ActionDispatch
|
|
361
454
|
# simultaneously.
|
362
455
|
def open_session
|
363
456
|
dup.tap do |session|
|
364
|
-
session.reset!
|
365
457
|
yield session if block_given?
|
366
458
|
end
|
367
459
|
end
|
@@ -369,19 +461,16 @@ module ActionDispatch
|
|
369
461
|
# Copy the instance variables from the current session instance into the
|
370
462
|
# test instance.
|
371
463
|
def copy_session_variables! #:nodoc:
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
end
|
464
|
+
@controller = @integration_session.controller
|
465
|
+
@response = @integration_session.response
|
466
|
+
@request = @integration_session.request
|
376
467
|
end
|
377
468
|
|
378
469
|
def default_url_options
|
379
|
-
reset! unless integration_session
|
380
470
|
integration_session.default_url_options
|
381
471
|
end
|
382
472
|
|
383
473
|
def default_url_options=(options)
|
384
|
-
reset! unless integration_session
|
385
474
|
integration_session.default_url_options = options
|
386
475
|
end
|
387
476
|
|
@@ -391,7 +480,6 @@ module ActionDispatch
|
|
391
480
|
|
392
481
|
# Delegate unhandled messages to the current session instance.
|
393
482
|
def method_missing(sym, *args, &block)
|
394
|
-
reset! unless integration_session
|
395
483
|
if integration_session.respond_to?(sym)
|
396
484
|
integration_session.__send__(sym, *args, &block).tap do
|
397
485
|
copy_session_variables!
|
@@ -400,11 +488,6 @@ module ActionDispatch
|
|
400
488
|
super
|
401
489
|
end
|
402
490
|
end
|
403
|
-
|
404
|
-
private
|
405
|
-
def integration_session
|
406
|
-
@integration_session ||= nil
|
407
|
-
end
|
408
491
|
end
|
409
492
|
end
|
410
493
|
|
@@ -427,8 +510,8 @@ module ActionDispatch
|
|
427
510
|
# assert_equal 200, status
|
428
511
|
#
|
429
512
|
# # post the login and follow through to the home page
|
430
|
-
# post "/login", username: people(:jamis).username,
|
431
|
-
# password: people(:jamis).password
|
513
|
+
# post "/login", params: { username: people(:jamis).username,
|
514
|
+
# password: people(:jamis).password }
|
432
515
|
# follow_redirect!
|
433
516
|
# assert_equal 200, status
|
434
517
|
# assert_equal "/home", path
|
@@ -467,7 +550,7 @@ module ActionDispatch
|
|
467
550
|
# end
|
468
551
|
#
|
469
552
|
# def speak(room, message)
|
470
|
-
#
|
553
|
+
# post "/say/#{room.id}", xhr: true, params: { message: message }
|
471
554
|
# assert(...)
|
472
555
|
# ...
|
473
556
|
# end
|
@@ -477,12 +560,91 @@ module ActionDispatch
|
|
477
560
|
# open_session do |sess|
|
478
561
|
# sess.extend(CustomAssertions)
|
479
562
|
# who = people(who)
|
480
|
-
# sess.post "/login", username: who.username,
|
481
|
-
# password: who.password
|
563
|
+
# sess.post "/login", params: { username: who.username,
|
564
|
+
# password: who.password }
|
482
565
|
# assert(...)
|
483
566
|
# end
|
484
567
|
# end
|
485
568
|
# end
|
569
|
+
#
|
570
|
+
# Another longer example would be:
|
571
|
+
#
|
572
|
+
# A simple integration test that exercises multiple controllers:
|
573
|
+
#
|
574
|
+
# require 'test_helper'
|
575
|
+
#
|
576
|
+
# class UserFlowsTest < ActionDispatch::IntegrationTest
|
577
|
+
# test "login and browse site" do
|
578
|
+
# # login via https
|
579
|
+
# https!
|
580
|
+
# get "/login"
|
581
|
+
# assert_response :success
|
582
|
+
#
|
583
|
+
# post "/login", params: { username: users(:david).username, password: users(:david).password }
|
584
|
+
# follow_redirect!
|
585
|
+
# assert_equal '/welcome', path
|
586
|
+
# assert_equal 'Welcome david!', flash[:notice]
|
587
|
+
#
|
588
|
+
# https!(false)
|
589
|
+
# get "/articles/all"
|
590
|
+
# assert_response :success
|
591
|
+
# assert_select 'h1', 'Articles'
|
592
|
+
# end
|
593
|
+
# end
|
594
|
+
#
|
595
|
+
# As you can see the integration test involves multiple controllers and
|
596
|
+
# exercises the entire stack from database to dispatcher. In addition you can
|
597
|
+
# have multiple session instances open simultaneously in a test and extend
|
598
|
+
# those instances with assertion methods to create a very powerful testing
|
599
|
+
# DSL (domain-specific language) just for your application.
|
600
|
+
#
|
601
|
+
# Here's an example of multiple sessions and custom DSL in an integration test
|
602
|
+
#
|
603
|
+
# require 'test_helper'
|
604
|
+
#
|
605
|
+
# class UserFlowsTest < ActionDispatch::IntegrationTest
|
606
|
+
# test "login and browse site" do
|
607
|
+
# # User david logs in
|
608
|
+
# david = login(:david)
|
609
|
+
# # User guest logs in
|
610
|
+
# guest = login(:guest)
|
611
|
+
#
|
612
|
+
# # Both are now available in different sessions
|
613
|
+
# assert_equal 'Welcome david!', david.flash[:notice]
|
614
|
+
# assert_equal 'Welcome guest!', guest.flash[:notice]
|
615
|
+
#
|
616
|
+
# # User david can browse site
|
617
|
+
# david.browses_site
|
618
|
+
# # User guest can browse site as well
|
619
|
+
# guest.browses_site
|
620
|
+
#
|
621
|
+
# # Continue with other assertions
|
622
|
+
# end
|
623
|
+
#
|
624
|
+
# private
|
625
|
+
#
|
626
|
+
# module CustomDsl
|
627
|
+
# def browses_site
|
628
|
+
# get "/products/all"
|
629
|
+
# assert_response :success
|
630
|
+
# assert_select 'h1', 'Products'
|
631
|
+
# end
|
632
|
+
# end
|
633
|
+
#
|
634
|
+
# def login(user)
|
635
|
+
# open_session do |sess|
|
636
|
+
# sess.extend(CustomDsl)
|
637
|
+
# u = users(user)
|
638
|
+
# sess.https!
|
639
|
+
# sess.post "/login", params: { username: u.username, password: u.password }
|
640
|
+
# assert_equal '/welcome', sess.path
|
641
|
+
# sess.https!(false)
|
642
|
+
# end
|
643
|
+
# end
|
644
|
+
# end
|
645
|
+
#
|
646
|
+
# Consult the Rails Testing Guide for more.
|
647
|
+
|
486
648
|
class IntegrationTest < ActiveSupport::TestCase
|
487
649
|
include Integration::Runner
|
488
650
|
include ActionController::TemplateAssertions
|
@@ -503,7 +665,6 @@ module ActionDispatch
|
|
503
665
|
end
|
504
666
|
|
505
667
|
def url_options
|
506
|
-
reset! unless integration_session
|
507
668
|
integration_session.url_options
|
508
669
|
end
|
509
670
|
|