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.

Files changed (131) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +553 -401
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -3
  5. data/lib/abstract_controller/base.rb +28 -38
  6. data/lib/{action_controller → abstract_controller}/caching/fragments.rb +51 -11
  7. data/lib/abstract_controller/caching.rb +62 -0
  8. data/lib/abstract_controller/callbacks.rb +52 -19
  9. data/lib/abstract_controller/collector.rb +4 -9
  10. data/lib/abstract_controller/error.rb +4 -0
  11. data/lib/abstract_controller/helpers.rb +4 -3
  12. data/lib/abstract_controller/railties/routes_helpers.rb +2 -2
  13. data/lib/abstract_controller/rendering.rb +28 -18
  14. data/lib/abstract_controller/translation.rb +8 -7
  15. data/lib/abstract_controller.rb +6 -2
  16. data/lib/action_controller/api/api_rendering.rb +14 -0
  17. data/lib/action_controller/api.rb +147 -0
  18. data/lib/action_controller/base.rb +10 -13
  19. data/lib/action_controller/caching.rb +13 -58
  20. data/lib/action_controller/form_builder.rb +48 -0
  21. data/lib/action_controller/log_subscriber.rb +3 -10
  22. data/lib/action_controller/metal/basic_implicit_render.rb +11 -0
  23. data/lib/action_controller/metal/conditional_get.rb +106 -34
  24. data/lib/action_controller/metal/cookies.rb +1 -3
  25. data/lib/action_controller/metal/data_streaming.rb +11 -32
  26. data/lib/action_controller/metal/etag_with_template_digest.rb +1 -1
  27. data/lib/action_controller/metal/exceptions.rb +11 -6
  28. data/lib/action_controller/metal/force_ssl.rb +10 -10
  29. data/lib/action_controller/metal/head.rb +14 -8
  30. data/lib/action_controller/metal/helpers.rb +15 -6
  31. data/lib/action_controller/metal/http_authentication.rb +44 -35
  32. data/lib/action_controller/metal/implicit_render.rb +61 -6
  33. data/lib/action_controller/metal/instrumentation.rb +5 -5
  34. data/lib/action_controller/metal/live.rb +66 -88
  35. data/lib/action_controller/metal/mime_responds.rb +27 -42
  36. data/lib/action_controller/metal/params_wrapper.rb +8 -8
  37. data/lib/action_controller/metal/redirecting.rb +32 -9
  38. data/lib/action_controller/metal/renderers.rb +85 -40
  39. data/lib/action_controller/metal/rendering.rb +38 -6
  40. data/lib/action_controller/metal/request_forgery_protection.rb +126 -48
  41. data/lib/action_controller/metal/rescue.rb +3 -12
  42. data/lib/action_controller/metal/streaming.rb +4 -4
  43. data/lib/action_controller/metal/strong_parameters.rb +293 -90
  44. data/lib/action_controller/metal/testing.rb +1 -12
  45. data/lib/action_controller/metal/url_for.rb +12 -5
  46. data/lib/action_controller/metal.rb +88 -63
  47. data/lib/action_controller/renderer.rb +111 -0
  48. data/lib/action_controller/template_assertions.rb +9 -0
  49. data/lib/action_controller/test_case.rb +288 -368
  50. data/lib/action_controller.rb +12 -9
  51. data/lib/action_dispatch/http/cache.rb +73 -34
  52. data/lib/action_dispatch/http/filter_parameters.rb +15 -11
  53. data/lib/action_dispatch/http/filter_redirect.rb +7 -8
  54. data/lib/action_dispatch/http/headers.rb +44 -13
  55. data/lib/action_dispatch/http/mime_negotiation.rb +41 -23
  56. data/lib/action_dispatch/http/mime_type.rb +126 -90
  57. data/lib/action_dispatch/http/mime_types.rb +3 -4
  58. data/lib/action_dispatch/http/parameter_filter.rb +18 -8
  59. data/lib/action_dispatch/http/parameters.rb +54 -41
  60. data/lib/action_dispatch/http/request.rb +149 -82
  61. data/lib/action_dispatch/http/response.rb +206 -102
  62. data/lib/action_dispatch/http/url.rb +117 -8
  63. data/lib/action_dispatch/journey/formatter.rb +39 -28
  64. data/lib/action_dispatch/journey/gtg/transition_table.rb +1 -1
  65. data/lib/action_dispatch/journey/nfa/dot.rb +0 -2
  66. data/lib/action_dispatch/journey/nfa/transition_table.rb +1 -46
  67. data/lib/action_dispatch/journey/nodes/node.rb +14 -4
  68. data/lib/action_dispatch/journey/parser_extras.rb +4 -0
  69. data/lib/action_dispatch/journey/path/pattern.rb +38 -42
  70. data/lib/action_dispatch/journey/route.rb +74 -19
  71. data/lib/action_dispatch/journey/router/utils.rb +5 -5
  72. data/lib/action_dispatch/journey/router.rb +5 -9
  73. data/lib/action_dispatch/journey/routes.rb +14 -15
  74. data/lib/action_dispatch/journey/visitors.rb +86 -43
  75. data/lib/action_dispatch/middleware/callbacks.rb +10 -1
  76. data/lib/action_dispatch/middleware/cookies.rb +189 -135
  77. data/lib/action_dispatch/middleware/debug_exceptions.rb +124 -49
  78. data/lib/action_dispatch/middleware/exception_wrapper.rb +21 -21
  79. data/lib/action_dispatch/middleware/executor.rb +19 -0
  80. data/lib/action_dispatch/middleware/flash.rb +66 -45
  81. data/lib/action_dispatch/middleware/params_parser.rb +32 -46
  82. data/lib/action_dispatch/middleware/public_exceptions.rb +2 -2
  83. data/lib/action_dispatch/middleware/reloader.rb +14 -58
  84. data/lib/action_dispatch/middleware/remote_ip.rb +29 -19
  85. data/lib/action_dispatch/middleware/request_id.rb +11 -6
  86. data/lib/action_dispatch/middleware/session/abstract_store.rb +23 -11
  87. data/lib/action_dispatch/middleware/session/cache_store.rb +9 -6
  88. data/lib/action_dispatch/middleware/session/cookie_store.rb +30 -24
  89. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +4 -0
  90. data/lib/action_dispatch/middleware/show_exceptions.rb +11 -9
  91. data/lib/action_dispatch/middleware/ssl.rb +115 -36
  92. data/lib/action_dispatch/middleware/stack.rb +44 -40
  93. data/lib/action_dispatch/middleware/static.rb +51 -35
  94. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
  95. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  96. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
  97. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  98. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  99. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -63
  100. data/lib/action_dispatch/railtie.rb +2 -2
  101. data/lib/action_dispatch/request/session.rb +69 -33
  102. data/lib/action_dispatch/request/utils.rb +51 -19
  103. data/lib/action_dispatch/routing/inspector.rb +32 -43
  104. data/lib/action_dispatch/routing/mapper.rb +491 -338
  105. data/lib/action_dispatch/routing/polymorphic_routes.rb +8 -14
  106. data/lib/action_dispatch/routing/redirection.rb +3 -3
  107. data/lib/action_dispatch/routing/route_set.rb +145 -238
  108. data/lib/action_dispatch/routing/url_for.rb +27 -10
  109. data/lib/action_dispatch/routing.rb +17 -13
  110. data/lib/action_dispatch/testing/assertion_response.rb +45 -0
  111. data/lib/action_dispatch/testing/assertions/response.rb +38 -20
  112. data/lib/action_dispatch/testing/assertions/routing.rb +11 -10
  113. data/lib/action_dispatch/testing/assertions.rb +1 -1
  114. data/lib/action_dispatch/testing/integration.rb +368 -97
  115. data/lib/action_dispatch/testing/test_process.rb +5 -6
  116. data/lib/action_dispatch/testing/test_request.rb +22 -31
  117. data/lib/action_dispatch/testing/test_response.rb +7 -4
  118. data/lib/action_dispatch.rb +3 -1
  119. data/lib/action_pack/gem_version.rb +3 -3
  120. data/lib/action_pack.rb +1 -1
  121. metadata +30 -34
  122. data/lib/action_controller/metal/hide_actions.rb +0 -40
  123. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  124. data/lib/action_controller/middleware.rb +0 -39
  125. data/lib/action_controller/model_naming.rb +0 -12
  126. data/lib/action_dispatch/journey/backwards.rb +0 -5
  127. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  128. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  129. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  130. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
  131. /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
- # - +parameters+: The HTTP parameters that you want to pass. This may
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
- # - +headers_or_env+: Additional headers to pass, as a Hash. The headers will be
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
- def get(path, parameters = nil, headers_or_env = nil)
32
- process :get, path, parameters, headers_or_env
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, parameters = nil, headers_or_env = nil)
38
- process :post, path, parameters, headers_or_env
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, parameters = nil, headers_or_env = nil)
44
- process :patch, path, parameters, headers_or_env
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, parameters = nil, headers_or_env = nil)
50
- process :put, path, parameters, headers_or_env
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, parameters = nil, headers_or_env = nil)
56
- process :delete, path, parameters, headers_or_env
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, parameters = nil, headers_or_env = nil)
62
- process :head, path, parameters, headers_or_env
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
- # a request from the Prototype library.
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
- def xml_http_request(request_method, path, parameters = nil, headers_or_env = nil)
72
- headers_or_env ||= {}
73
- headers_or_env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
74
- headers_or_env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
75
- process(request_method, path, parameters, headers_or_env)
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
- def request_via_redirect(http_method, path, parameters = nil, headers_or_env = nil)
93
- process(http_method, path, parameters, headers_or_env)
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, parameters = nil, headers_or_env = nil)
101
- request_via_redirect(:get, path, parameters, headers_or_env)
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, parameters = nil, headers_or_env = nil)
107
- request_via_redirect(:post, path, parameters, headers_or_env)
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, parameters = nil, headers_or_env = nil)
113
- request_via_redirect(:patch, path, parameters, headers_or_env)
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, parameters = nil, headers_or_env = nil)
119
- request_via_redirect(:put, path, parameters, headers_or_env)
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, parameters = nil, headers_or_env = nil)
125
- request_via_redirect(:delete, path, parameters, headers_or_env)
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, parameters = nil, headers_or_env = nil)
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
- host! "#{location.host}:#{location.port}" if location.host
270
- path = location.query ? "#{location.path}?#{location.query}" : location.path
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
- env = {
345
+ request_env = {
276
346
  :method => method,
277
- :params => parameters,
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" => "application/x-www-form-urlencoded",
357
+ "CONTENT_TYPE" => request_encoder.content_type,
288
358
  "HTTP_ACCEPT" => accept
289
359
  }
290
- # this modifies the passed env directly
291
- Http::Headers.new(env).merge!(headers_or_env || {})
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, env), env)
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 = session.last_request.env['action_controller.instance']
390
+ @controller = @request.controller_instance
308
391
 
309
- return response.status
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
- def app
321
- @app ||= nil
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 = Integration::Session.new(app)
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
- return unless integration_session
373
- %w(controller response request).each do |var|
374
- instance_variable_set("@#{var}", @integration_session.__send__(var))
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
- # xml_http_request "/say/#{room.id}", message: message
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
- include Integration::Runner
488
- include ActionController::TemplateAssertions
489
- include ActionDispatch::Routing::UrlFor
740
+ module UrlOptions
741
+ extend ActiveSupport::Concern
742
+ def url_options
743
+ integration_session.url_options
744
+ end
745
+ end
490
746
 
491
- @@app = nil
747
+ module Behavior
748
+ extend ActiveSupport::Concern
492
749
 
493
- def self.app
494
- @@app || ActionDispatch.test_app
495
- end
750
+ include Integration::Runner
751
+ include ActionController::TemplateAssertions
496
752
 
497
- def self.app=(app)
498
- @@app = app
499
- end
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
- def app
502
- super || self.class.app
503
- end
760
+ module ClassMethods
761
+ def app
762
+ defined?(@@app) ? @@app : ActionDispatch.test_app
763
+ end
504
764
 
505
- def url_options
506
- reset! unless integration_session
507
- integration_session.url_options
508
- end
765
+ def app=(app)
766
+ @@app = app
767
+ end
509
768
 
510
- def document_root_element
511
- html_document.root
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