actionpack 4.2.10 → 5.0.0
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 +4 -4
- data/CHANGELOG.md +553 -401
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -3
- data/lib/abstract_controller/base.rb +28 -38
- data/lib/{action_controller → abstract_controller}/caching/fragments.rb +51 -11
- data/lib/abstract_controller/caching.rb +62 -0
- data/lib/abstract_controller/callbacks.rb +52 -19
- data/lib/abstract_controller/collector.rb +4 -9
- data/lib/abstract_controller/error.rb +4 -0
- data/lib/abstract_controller/helpers.rb +4 -3
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -2
- data/lib/abstract_controller/rendering.rb +28 -18
- data/lib/abstract_controller/translation.rb +8 -7
- data/lib/abstract_controller.rb +6 -2
- data/lib/action_controller/api/api_rendering.rb +14 -0
- data/lib/action_controller/api.rb +147 -0
- data/lib/action_controller/base.rb +10 -13
- data/lib/action_controller/caching.rb +13 -58
- data/lib/action_controller/form_builder.rb +48 -0
- data/lib/action_controller/log_subscriber.rb +3 -10
- data/lib/action_controller/metal/basic_implicit_render.rb +11 -0
- data/lib/action_controller/metal/conditional_get.rb +106 -34
- data/lib/action_controller/metal/cookies.rb +1 -3
- data/lib/action_controller/metal/data_streaming.rb +11 -32
- 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 +10 -10
- data/lib/action_controller/metal/head.rb +14 -8
- data/lib/action_controller/metal/helpers.rb +15 -6
- data/lib/action_controller/metal/http_authentication.rb +44 -35
- data/lib/action_controller/metal/implicit_render.rb +61 -6
- data/lib/action_controller/metal/instrumentation.rb +5 -5
- data/lib/action_controller/metal/live.rb +66 -88
- data/lib/action_controller/metal/mime_responds.rb +27 -42
- 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 +85 -40
- data/lib/action_controller/metal/rendering.rb +38 -6
- data/lib/action_controller/metal/request_forgery_protection.rb +126 -48
- data/lib/action_controller/metal/rescue.rb +3 -12
- data/lib/action_controller/metal/streaming.rb +4 -4
- data/lib/action_controller/metal/strong_parameters.rb +293 -90
- data/lib/action_controller/metal/testing.rb +1 -12
- data/lib/action_controller/metal/url_for.rb +12 -5
- data/lib/action_controller/metal.rb +88 -63
- data/lib/action_controller/renderer.rb +111 -0
- data/lib/action_controller/template_assertions.rb +9 -0
- data/lib/action_controller/test_case.rb +288 -368
- data/lib/action_controller.rb +12 -9
- data/lib/action_dispatch/http/cache.rb +73 -34
- data/lib/action_dispatch/http/filter_parameters.rb +15 -11
- data/lib/action_dispatch/http/filter_redirect.rb +7 -8
- data/lib/action_dispatch/http/headers.rb +44 -13
- data/lib/action_dispatch/http/mime_negotiation.rb +41 -23
- data/lib/action_dispatch/http/mime_type.rb +126 -90
- data/lib/action_dispatch/http/mime_types.rb +3 -4
- data/lib/action_dispatch/http/parameter_filter.rb +18 -8
- data/lib/action_dispatch/http/parameters.rb +54 -41
- data/lib/action_dispatch/http/request.rb +149 -82
- data/lib/action_dispatch/http/response.rb +206 -102
- data/lib/action_dispatch/http/url.rb +117 -8
- data/lib/action_dispatch/journey/formatter.rb +39 -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 +38 -42
- data/lib/action_dispatch/journey/route.rb +74 -19
- data/lib/action_dispatch/journey/router/utils.rb +5 -5
- data/lib/action_dispatch/journey/router.rb +5 -9
- data/lib/action_dispatch/journey/routes.rb +14 -15
- data/lib/action_dispatch/journey/visitors.rb +86 -43
- data/lib/action_dispatch/middleware/callbacks.rb +10 -1
- data/lib/action_dispatch/middleware/cookies.rb +189 -135
- data/lib/action_dispatch/middleware/debug_exceptions.rb +124 -49
- data/lib/action_dispatch/middleware/exception_wrapper.rb +21 -21
- data/lib/action_dispatch/middleware/executor.rb +19 -0
- data/lib/action_dispatch/middleware/flash.rb +66 -45
- data/lib/action_dispatch/middleware/params_parser.rb +32 -46
- data/lib/action_dispatch/middleware/public_exceptions.rb +2 -2
- data/lib/action_dispatch/middleware/reloader.rb +14 -58
- 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 +30 -24
- 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 +115 -36
- data/lib/action_dispatch/middleware/stack.rb +44 -40
- data/lib/action_dispatch/middleware/static.rb +51 -35
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
- 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 +2 -2
- data/lib/action_dispatch/request/session.rb +69 -33
- data/lib/action_dispatch/request/utils.rb +51 -19
- data/lib/action_dispatch/routing/inspector.rb +32 -43
- data/lib/action_dispatch/routing/mapper.rb +491 -338
- 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 +145 -238
- data/lib/action_dispatch/routing/url_for.rb +27 -10
- data/lib/action_dispatch/routing.rb +17 -13
- data/lib/action_dispatch/testing/assertion_response.rb +45 -0
- data/lib/action_dispatch/testing/assertions/response.rb +38 -20
- data/lib/action_dispatch/testing/assertions/routing.rb +11 -10
- data/lib/action_dispatch/testing/assertions.rb +1 -1
- data/lib/action_dispatch/testing/integration.rb +368 -97
- data/lib/action_dispatch/testing/test_process.rb +5 -6
- data/lib/action_dispatch/testing/test_request.rb +22 -31
- data/lib/action_dispatch/testing/test_response.rb +7 -4
- data/lib/action_dispatch.rb +3 -1
- data/lib/action_pack/gem_version.rb +3 -3
- data/lib/action_pack.rb +1 -1
- metadata +30 -34
- 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/backwards.rb +0 -5
- 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
- /data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
@@ -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,51 +31,74 @@ 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
|
66
|
-
#
|
74
|
+
# an AJAX request made from JavaScript.
|
67
75
|
#
|
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,53 @@ 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
|
+
ActiveSupport::Deprecation.warn('`request_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.')
|
126
|
+
process_with_kwargs(http_method, path, *args)
|
127
|
+
|
94
128
|
follow_redirect! while redirect?
|
95
129
|
status
|
96
130
|
end
|
97
131
|
|
98
132
|
# Performs a GET request, following any subsequent redirect.
|
99
133
|
# See +request_via_redirect+ for more information.
|
100
|
-
def get_via_redirect(path,
|
101
|
-
|
134
|
+
def get_via_redirect(path, *args)
|
135
|
+
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.')
|
136
|
+
request_via_redirect(:get, path, *args)
|
102
137
|
end
|
103
138
|
|
104
139
|
# Performs a POST request, following any subsequent redirect.
|
105
140
|
# See +request_via_redirect+ for more information.
|
106
|
-
def post_via_redirect(path,
|
107
|
-
|
141
|
+
def post_via_redirect(path, *args)
|
142
|
+
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.')
|
143
|
+
request_via_redirect(:post, path, *args)
|
108
144
|
end
|
109
145
|
|
110
146
|
# Performs a PATCH request, following any subsequent redirect.
|
111
147
|
# See +request_via_redirect+ for more information.
|
112
|
-
def patch_via_redirect(path,
|
113
|
-
|
148
|
+
def patch_via_redirect(path, *args)
|
149
|
+
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.')
|
150
|
+
request_via_redirect(:patch, path, *args)
|
114
151
|
end
|
115
152
|
|
116
153
|
# Performs a PUT request, following any subsequent redirect.
|
117
154
|
# See +request_via_redirect+ for more information.
|
118
|
-
def put_via_redirect(path,
|
119
|
-
|
155
|
+
def put_via_redirect(path, *args)
|
156
|
+
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.')
|
157
|
+
request_via_redirect(:put, path, *args)
|
120
158
|
end
|
121
159
|
|
122
160
|
# Performs a DELETE request, following any subsequent redirect.
|
123
161
|
# See +request_via_redirect+ for more information.
|
124
|
-
def delete_via_redirect(path,
|
125
|
-
|
162
|
+
def delete_via_redirect(path, *args)
|
163
|
+
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.')
|
164
|
+
request_via_redirect(:delete, path, *args)
|
126
165
|
end
|
127
166
|
end
|
128
167
|
|
@@ -185,15 +224,6 @@ module ActionDispatch
|
|
185
224
|
super()
|
186
225
|
@app = app
|
187
226
|
|
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
227
|
reset!
|
198
228
|
end
|
199
229
|
|
@@ -261,20 +291,60 @@ module ActionDispatch
|
|
261
291
|
@_mock_session ||= Rack::MockSession.new(@app, host)
|
262
292
|
end
|
263
293
|
|
294
|
+
def process_with_kwargs(http_method, path, *args)
|
295
|
+
if kwarg_request?(args)
|
296
|
+
process(http_method, path, *args)
|
297
|
+
else
|
298
|
+
non_kwarg_request_warning if args.any?
|
299
|
+
process(http_method, path, { params: args[0], headers: args[1] })
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
REQUEST_KWARGS = %i(params headers env xhr as)
|
304
|
+
def kwarg_request?(args)
|
305
|
+
args[0].respond_to?(:keys) && args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) }
|
306
|
+
end
|
307
|
+
|
308
|
+
def non_kwarg_request_warning
|
309
|
+
ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
|
310
|
+
ActionDispatch::IntegrationTest HTTP request methods will accept only
|
311
|
+
the following keyword arguments in future Rails versions:
|
312
|
+
#{REQUEST_KWARGS.join(', ')}
|
313
|
+
|
314
|
+
Examples:
|
315
|
+
|
316
|
+
get '/profile',
|
317
|
+
params: { id: 1 },
|
318
|
+
headers: { 'X-Extra-Header' => '123' },
|
319
|
+
env: { 'action_dispatch.custom' => 'custom' },
|
320
|
+
xhr: true,
|
321
|
+
as: :json
|
322
|
+
MSG
|
323
|
+
end
|
324
|
+
|
264
325
|
# Performs the actual request.
|
265
|
-
def process(method, path,
|
326
|
+
def process(method, path, params: nil, headers: nil, env: nil, xhr: false, as: nil)
|
327
|
+
request_encoder = RequestEncoder.encoder(as)
|
328
|
+
|
266
329
|
if path =~ %r{://}
|
267
330
|
location = URI.parse(path)
|
268
331
|
https! URI::HTTPS === location if location.scheme
|
269
|
-
|
270
|
-
|
332
|
+
if url_host = location.host
|
333
|
+
default = Rack::Request::DEFAULT_PORTS[location.scheme]
|
334
|
+
url_host += ":#{location.port}" if default != location.port
|
335
|
+
host! url_host
|
336
|
+
end
|
337
|
+
path = request_encoder.append_format_to location.path
|
338
|
+
path = location.query ? "#{path}?#{location.query}" : path
|
339
|
+
else
|
340
|
+
path = request_encoder.append_format_to path
|
271
341
|
end
|
272
342
|
|
273
343
|
hostname, port = host.split(':')
|
274
344
|
|
275
|
-
|
345
|
+
request_env = {
|
276
346
|
:method => method,
|
277
|
-
:params =>
|
347
|
+
:params => request_encoder.encode_params(params),
|
278
348
|
|
279
349
|
"SERVER_NAME" => hostname,
|
280
350
|
"SERVER_PORT" => port || (https? ? "443" : "80"),
|
@@ -284,47 +354,132 @@ module ActionDispatch
|
|
284
354
|
"REQUEST_URI" => path,
|
285
355
|
"HTTP_HOST" => host,
|
286
356
|
"REMOTE_ADDR" => remote_addr,
|
287
|
-
"CONTENT_TYPE" =>
|
357
|
+
"CONTENT_TYPE" => request_encoder.content_type,
|
288
358
|
"HTTP_ACCEPT" => accept
|
289
359
|
}
|
290
|
-
|
291
|
-
|
360
|
+
|
361
|
+
if xhr
|
362
|
+
headers ||= {}
|
363
|
+
headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
|
364
|
+
headers['HTTP_ACCEPT'] ||= [Mime[:js], Mime[:html], Mime[:xml], 'text/xml', '*/*'].join(', ')
|
365
|
+
end
|
366
|
+
|
367
|
+
# this modifies the passed request_env directly
|
368
|
+
if headers.present?
|
369
|
+
Http::Headers.from_hash(request_env).merge!(headers)
|
370
|
+
end
|
371
|
+
if env.present?
|
372
|
+
Http::Headers.from_hash(request_env).merge!(env)
|
373
|
+
end
|
292
374
|
|
293
375
|
session = Rack::Test::Session.new(_mock_session)
|
294
376
|
|
295
377
|
# NOTE: rack-test v0.5 doesn't build a default uri correctly
|
296
378
|
# Make sure requested path is always a full uri
|
297
|
-
session.request(build_full_uri(path,
|
379
|
+
session.request(build_full_uri(path, request_env), request_env)
|
298
380
|
|
299
381
|
@request_count += 1
|
300
382
|
@request = ActionDispatch::Request.new(session.last_request.env)
|
301
383
|
response = _mock_session.last_response
|
302
384
|
@response = ActionDispatch::TestResponse.from_response(response)
|
385
|
+
@response.request = @request
|
386
|
+
@response.response_parser = RequestEncoder.parser(@response.content_type)
|
303
387
|
@html_document = nil
|
304
|
-
@html_scanner_document = nil
|
305
388
|
@url_options = nil
|
306
389
|
|
307
|
-
@controller =
|
390
|
+
@controller = @request.controller_instance
|
308
391
|
|
309
|
-
|
392
|
+
response.status
|
310
393
|
end
|
311
394
|
|
312
395
|
def build_full_uri(path, env)
|
313
396
|
"#{env['rack.url_scheme']}://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{path}"
|
314
397
|
end
|
398
|
+
|
399
|
+
class RequestEncoder # :nodoc:
|
400
|
+
@encoders = {}
|
401
|
+
|
402
|
+
attr_reader :response_parser
|
403
|
+
|
404
|
+
def initialize(mime_name, param_encoder, response_parser, url_encoded_form = false)
|
405
|
+
@mime = Mime[mime_name]
|
406
|
+
|
407
|
+
unless @mime
|
408
|
+
raise ArgumentError, "Can't register a request encoder for " \
|
409
|
+
"unregistered MIME Type: #{mime_name}. See `Mime::Type.register`."
|
410
|
+
end
|
411
|
+
|
412
|
+
@url_encoded_form = url_encoded_form
|
413
|
+
@path_format = ".#{@mime.symbol}" unless @url_encoded_form
|
414
|
+
@response_parser = response_parser || -> body { body }
|
415
|
+
@param_encoder = param_encoder || :"to_#{@mime.symbol}".to_proc
|
416
|
+
end
|
417
|
+
|
418
|
+
def append_format_to(path)
|
419
|
+
path << @path_format unless @url_encoded_form
|
420
|
+
path
|
421
|
+
end
|
422
|
+
|
423
|
+
def content_type
|
424
|
+
@mime.to_s
|
425
|
+
end
|
426
|
+
|
427
|
+
def encode_params(params)
|
428
|
+
@param_encoder.call(params)
|
429
|
+
end
|
430
|
+
|
431
|
+
def self.parser(content_type)
|
432
|
+
mime = Mime::Type.lookup(content_type)
|
433
|
+
encoder(mime ? mime.ref : nil).response_parser
|
434
|
+
end
|
435
|
+
|
436
|
+
def self.encoder(name)
|
437
|
+
@encoders[name] || WWWFormEncoder
|
438
|
+
end
|
439
|
+
|
440
|
+
def self.register_encoder(mime_name, param_encoder: nil, response_parser: nil)
|
441
|
+
@encoders[mime_name] = new(mime_name, param_encoder, response_parser)
|
442
|
+
end
|
443
|
+
|
444
|
+
register_encoder :json, response_parser: -> body { JSON.parse(body) }
|
445
|
+
|
446
|
+
WWWFormEncoder = new(:url_encoded_form, -> params { params }, nil, true)
|
447
|
+
end
|
315
448
|
end
|
316
449
|
|
317
450
|
module Runner
|
318
451
|
include ActionDispatch::Assertions
|
319
452
|
|
320
|
-
|
321
|
-
|
453
|
+
APP_SESSIONS = {}
|
454
|
+
|
455
|
+
attr_reader :app
|
456
|
+
|
457
|
+
def before_setup # :nodoc:
|
458
|
+
@app = nil
|
459
|
+
@integration_session = nil
|
460
|
+
super
|
461
|
+
end
|
462
|
+
|
463
|
+
def integration_session
|
464
|
+
@integration_session ||= create_session(app)
|
322
465
|
end
|
323
466
|
|
324
467
|
# Reset the current session. This is useful for testing multiple sessions
|
325
468
|
# in a single test case.
|
326
469
|
def reset!
|
327
|
-
@integration_session =
|
470
|
+
@integration_session = create_session(app)
|
471
|
+
end
|
472
|
+
|
473
|
+
def create_session(app)
|
474
|
+
klass = APP_SESSIONS[app] ||= Class.new(Integration::Session) {
|
475
|
+
# If the app is a Rails app, make url_helpers available on the session
|
476
|
+
# This makes app.url_for and app.foo_path available in the console
|
477
|
+
if app.respond_to?(:routes)
|
478
|
+
include app.routes.url_helpers
|
479
|
+
include app.routes.mounted_helpers
|
480
|
+
end
|
481
|
+
}
|
482
|
+
klass.new(app)
|
328
483
|
end
|
329
484
|
|
330
485
|
def remove! # :nodoc:
|
@@ -334,13 +489,9 @@ module ActionDispatch
|
|
334
489
|
%w(get post patch put head delete cookies assigns
|
335
490
|
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
|
336
491
|
define_method(method) do |*args|
|
337
|
-
reset! unless integration_session
|
338
|
-
|
339
492
|
# reset the html_document variable, except for cookies/assigns calls
|
340
493
|
unless method == 'cookies' || method == 'assigns'
|
341
494
|
@html_document = nil
|
342
|
-
@html_scanner_document = nil
|
343
|
-
reset_template_assertion
|
344
495
|
end
|
345
496
|
|
346
497
|
integration_session.__send__(method, *args).tap do
|
@@ -361,7 +512,6 @@ module ActionDispatch
|
|
361
512
|
# simultaneously.
|
362
513
|
def open_session
|
363
514
|
dup.tap do |session|
|
364
|
-
session.reset!
|
365
515
|
yield session if block_given?
|
366
516
|
end
|
367
517
|
end
|
@@ -369,19 +519,16 @@ module ActionDispatch
|
|
369
519
|
# Copy the instance variables from the current session instance into the
|
370
520
|
# test instance.
|
371
521
|
def copy_session_variables! #:nodoc:
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
end
|
522
|
+
@controller = @integration_session.controller
|
523
|
+
@response = @integration_session.response
|
524
|
+
@request = @integration_session.request
|
376
525
|
end
|
377
526
|
|
378
527
|
def default_url_options
|
379
|
-
reset! unless integration_session
|
380
528
|
integration_session.default_url_options
|
381
529
|
end
|
382
530
|
|
383
531
|
def default_url_options=(options)
|
384
|
-
reset! unless integration_session
|
385
532
|
integration_session.default_url_options = options
|
386
533
|
end
|
387
534
|
|
@@ -391,7 +538,6 @@ module ActionDispatch
|
|
391
538
|
|
392
539
|
# Delegate unhandled messages to the current session instance.
|
393
540
|
def method_missing(sym, *args, &block)
|
394
|
-
reset! unless integration_session
|
395
541
|
if integration_session.respond_to?(sym)
|
396
542
|
integration_session.__send__(sym, *args, &block).tap do
|
397
543
|
copy_session_variables!
|
@@ -400,11 +546,6 @@ module ActionDispatch
|
|
400
546
|
super
|
401
547
|
end
|
402
548
|
end
|
403
|
-
|
404
|
-
private
|
405
|
-
def integration_session
|
406
|
-
@integration_session ||= nil
|
407
|
-
end
|
408
549
|
end
|
409
550
|
end
|
410
551
|
|
@@ -427,8 +568,8 @@ module ActionDispatch
|
|
427
568
|
# assert_equal 200, status
|
428
569
|
#
|
429
570
|
# # post the login and follow through to the home page
|
430
|
-
# post "/login", username: people(:jamis).username,
|
431
|
-
# password: people(:jamis).password
|
571
|
+
# post "/login", params: { username: people(:jamis).username,
|
572
|
+
# password: people(:jamis).password }
|
432
573
|
# follow_redirect!
|
433
574
|
# assert_equal 200, status
|
434
575
|
# assert_equal "/home", path
|
@@ -467,7 +608,7 @@ module ActionDispatch
|
|
467
608
|
# end
|
468
609
|
#
|
469
610
|
# def speak(room, message)
|
470
|
-
#
|
611
|
+
# post "/say/#{room.id}", xhr: true, params: { message: message }
|
471
612
|
# assert(...)
|
472
613
|
# ...
|
473
614
|
# end
|
@@ -477,38 +618,168 @@ module ActionDispatch
|
|
477
618
|
# open_session do |sess|
|
478
619
|
# sess.extend(CustomAssertions)
|
479
620
|
# who = people(who)
|
480
|
-
# sess.post "/login", username: who.username,
|
481
|
-
# password: who.password
|
621
|
+
# sess.post "/login", params: { username: who.username,
|
622
|
+
# password: who.password }
|
482
623
|
# assert(...)
|
483
624
|
# end
|
484
625
|
# end
|
485
626
|
# end
|
627
|
+
#
|
628
|
+
# Another longer example would be:
|
629
|
+
#
|
630
|
+
# A simple integration test that exercises multiple controllers:
|
631
|
+
#
|
632
|
+
# require 'test_helper'
|
633
|
+
#
|
634
|
+
# class UserFlowsTest < ActionDispatch::IntegrationTest
|
635
|
+
# test "login and browse site" do
|
636
|
+
# # login via https
|
637
|
+
# https!
|
638
|
+
# get "/login"
|
639
|
+
# assert_response :success
|
640
|
+
#
|
641
|
+
# post "/login", params: { username: users(:david).username, password: users(:david).password }
|
642
|
+
# follow_redirect!
|
643
|
+
# assert_equal '/welcome', path
|
644
|
+
# assert_equal 'Welcome david!', flash[:notice]
|
645
|
+
#
|
646
|
+
# https!(false)
|
647
|
+
# get "/articles/all"
|
648
|
+
# assert_response :success
|
649
|
+
# assert_select 'h1', 'Articles'
|
650
|
+
# end
|
651
|
+
# end
|
652
|
+
#
|
653
|
+
# As you can see the integration test involves multiple controllers and
|
654
|
+
# exercises the entire stack from database to dispatcher. In addition you can
|
655
|
+
# have multiple session instances open simultaneously in a test and extend
|
656
|
+
# those instances with assertion methods to create a very powerful testing
|
657
|
+
# DSL (domain-specific language) just for your application.
|
658
|
+
#
|
659
|
+
# Here's an example of multiple sessions and custom DSL in an integration test
|
660
|
+
#
|
661
|
+
# require 'test_helper'
|
662
|
+
#
|
663
|
+
# class UserFlowsTest < ActionDispatch::IntegrationTest
|
664
|
+
# test "login and browse site" do
|
665
|
+
# # User david logs in
|
666
|
+
# david = login(:david)
|
667
|
+
# # User guest logs in
|
668
|
+
# guest = login(:guest)
|
669
|
+
#
|
670
|
+
# # Both are now available in different sessions
|
671
|
+
# assert_equal 'Welcome david!', david.flash[:notice]
|
672
|
+
# assert_equal 'Welcome guest!', guest.flash[:notice]
|
673
|
+
#
|
674
|
+
# # User david can browse site
|
675
|
+
# david.browses_site
|
676
|
+
# # User guest can browse site as well
|
677
|
+
# guest.browses_site
|
678
|
+
#
|
679
|
+
# # Continue with other assertions
|
680
|
+
# end
|
681
|
+
#
|
682
|
+
# private
|
683
|
+
#
|
684
|
+
# module CustomDsl
|
685
|
+
# def browses_site
|
686
|
+
# get "/products/all"
|
687
|
+
# assert_response :success
|
688
|
+
# assert_select 'h1', 'Products'
|
689
|
+
# end
|
690
|
+
# end
|
691
|
+
#
|
692
|
+
# def login(user)
|
693
|
+
# open_session do |sess|
|
694
|
+
# sess.extend(CustomDsl)
|
695
|
+
# u = users(user)
|
696
|
+
# sess.https!
|
697
|
+
# sess.post "/login", params: { username: u.username, password: u.password }
|
698
|
+
# assert_equal '/welcome', sess.path
|
699
|
+
# sess.https!(false)
|
700
|
+
# end
|
701
|
+
# end
|
702
|
+
# end
|
703
|
+
#
|
704
|
+
# You can also test your JSON API easily by setting what the request should
|
705
|
+
# be encoded as:
|
706
|
+
#
|
707
|
+
# require 'test_helper'
|
708
|
+
#
|
709
|
+
# class ApiTest < ActionDispatch::IntegrationTest
|
710
|
+
# test 'creates articles' do
|
711
|
+
# assert_difference -> { Article.count } do
|
712
|
+
# post articles_path, params: { article: { title: 'Ahoy!' } }, as: :json
|
713
|
+
# end
|
714
|
+
#
|
715
|
+
# assert_response :success
|
716
|
+
# assert_equal({ id: Arcticle.last.id, title: 'Ahoy!' }, response.parsed_body)
|
717
|
+
# end
|
718
|
+
# end
|
719
|
+
#
|
720
|
+
# The `as` option sets the format to JSON, sets the content type to
|
721
|
+
# 'application/json' and encodes the parameters as JSON.
|
722
|
+
#
|
723
|
+
# Calling `parsed_body` on the response parses the response body as what
|
724
|
+
# the last request was encoded as. If the request wasn't encoded `as` something,
|
725
|
+
# it's the same as calling `body`.
|
726
|
+
#
|
727
|
+
# For any custom MIME Types you've registered, you can even add your own encoders with:
|
728
|
+
#
|
729
|
+
# ActionDispatch::IntegrationTest.register_encoder :wibble,
|
730
|
+
# param_encoder: -> params { params.to_wibble },
|
731
|
+
# response_parser: -> body { body }
|
732
|
+
#
|
733
|
+
# Where `param_encoder` defines how the params should be encoded and
|
734
|
+
# `response_parser` defines how the response body should be parsed through
|
735
|
+
# `parsed_body`.
|
736
|
+
#
|
737
|
+
# Consult the Rails Testing Guide for more.
|
738
|
+
|
486
739
|
class IntegrationTest < ActiveSupport::TestCase
|
487
|
-
|
488
|
-
|
489
|
-
|
740
|
+
module UrlOptions
|
741
|
+
extend ActiveSupport::Concern
|
742
|
+
def url_options
|
743
|
+
integration_session.url_options
|
744
|
+
end
|
745
|
+
end
|
490
746
|
|
491
|
-
|
747
|
+
module Behavior
|
748
|
+
extend ActiveSupport::Concern
|
492
749
|
|
493
|
-
|
494
|
-
|
495
|
-
end
|
750
|
+
include Integration::Runner
|
751
|
+
include ActionController::TemplateAssertions
|
496
752
|
|
497
|
-
|
498
|
-
|
499
|
-
|
753
|
+
included do
|
754
|
+
include ActionDispatch::Routing::UrlFor
|
755
|
+
include UrlOptions # don't let UrlFor override the url_options method
|
756
|
+
ActiveSupport.run_load_hooks(:action_dispatch_integration_test, self)
|
757
|
+
@@app = nil
|
758
|
+
end
|
500
759
|
|
501
|
-
|
502
|
-
|
503
|
-
|
760
|
+
module ClassMethods
|
761
|
+
def app
|
762
|
+
defined?(@@app) ? @@app : ActionDispatch.test_app
|
763
|
+
end
|
504
764
|
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
end
|
765
|
+
def app=(app)
|
766
|
+
@@app = app
|
767
|
+
end
|
509
768
|
|
510
|
-
|
511
|
-
|
769
|
+
def register_encoder(*args)
|
770
|
+
Integration::Session::RequestEncoder.register_encoder(*args)
|
771
|
+
end
|
772
|
+
end
|
773
|
+
|
774
|
+
def app
|
775
|
+
super || self.class.app
|
776
|
+
end
|
777
|
+
|
778
|
+
def document_root_element
|
779
|
+
html_document.root
|
780
|
+
end
|
512
781
|
end
|
782
|
+
|
783
|
+
include Behavior
|
513
784
|
end
|
514
785
|
end
|