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.

Files changed (125) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +379 -462
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -3
  5. data/lib/abstract_controller.rb +0 -2
  6. data/lib/abstract_controller/base.rb +17 -32
  7. data/lib/abstract_controller/callbacks.rb +52 -19
  8. data/lib/abstract_controller/collector.rb +4 -9
  9. data/lib/abstract_controller/helpers.rb +2 -2
  10. data/lib/abstract_controller/railties/routes_helpers.rb +2 -2
  11. data/lib/abstract_controller/rendering.rb +27 -22
  12. data/lib/abstract_controller/translation.rb +8 -7
  13. data/lib/action_controller.rb +4 -3
  14. data/lib/action_controller/api.rb +146 -0
  15. data/lib/action_controller/base.rb +6 -10
  16. data/lib/action_controller/caching.rb +1 -3
  17. data/lib/action_controller/caching/fragments.rb +48 -3
  18. data/lib/action_controller/form_builder.rb +48 -0
  19. data/lib/action_controller/log_subscriber.rb +1 -10
  20. data/lib/action_controller/metal.rb +89 -62
  21. data/lib/action_controller/metal/basic_implicit_render.rb +11 -0
  22. data/lib/action_controller/metal/conditional_get.rb +65 -24
  23. data/lib/action_controller/metal/cookies.rb +0 -2
  24. data/lib/action_controller/metal/data_streaming.rb +2 -22
  25. data/lib/action_controller/metal/etag_with_template_digest.rb +1 -1
  26. data/lib/action_controller/metal/exceptions.rb +11 -6
  27. data/lib/action_controller/metal/force_ssl.rb +6 -6
  28. data/lib/action_controller/metal/head.rb +14 -7
  29. data/lib/action_controller/metal/helpers.rb +9 -5
  30. data/lib/action_controller/metal/http_authentication.rb +37 -38
  31. data/lib/action_controller/metal/implicit_render.rb +23 -6
  32. data/lib/action_controller/metal/instrumentation.rb +0 -1
  33. data/lib/action_controller/metal/live.rb +17 -55
  34. data/lib/action_controller/metal/mime_responds.rb +17 -37
  35. data/lib/action_controller/metal/params_wrapper.rb +8 -8
  36. data/lib/action_controller/metal/redirecting.rb +32 -9
  37. data/lib/action_controller/metal/renderers.rb +10 -8
  38. data/lib/action_controller/metal/rendering.rb +38 -6
  39. data/lib/action_controller/metal/request_forgery_protection.rb +67 -35
  40. data/lib/action_controller/metal/rescue.rb +2 -4
  41. data/lib/action_controller/metal/streaming.rb +4 -4
  42. data/lib/action_controller/metal/strong_parameters.rb +231 -78
  43. data/lib/action_controller/metal/testing.rb +1 -12
  44. data/lib/action_controller/metal/url_for.rb +12 -5
  45. data/lib/action_controller/renderer.rb +111 -0
  46. data/lib/action_controller/template_assertions.rb +9 -0
  47. data/lib/action_controller/test_case.rb +267 -363
  48. data/lib/action_dispatch.rb +2 -1
  49. data/lib/action_dispatch/http/cache.rb +23 -26
  50. data/lib/action_dispatch/http/filter_parameters.rb +6 -8
  51. data/lib/action_dispatch/http/filter_redirect.rb +7 -8
  52. data/lib/action_dispatch/http/headers.rb +28 -11
  53. data/lib/action_dispatch/http/mime_negotiation.rb +40 -26
  54. data/lib/action_dispatch/http/mime_type.rb +92 -61
  55. data/lib/action_dispatch/http/mime_types.rb +1 -4
  56. data/lib/action_dispatch/http/parameter_filter.rb +18 -8
  57. data/lib/action_dispatch/http/parameters.rb +45 -41
  58. data/lib/action_dispatch/http/request.rb +146 -82
  59. data/lib/action_dispatch/http/response.rb +180 -99
  60. data/lib/action_dispatch/http/url.rb +117 -8
  61. data/lib/action_dispatch/journey/formatter.rb +34 -28
  62. data/lib/action_dispatch/journey/gtg/transition_table.rb +1 -1
  63. data/lib/action_dispatch/journey/nfa/dot.rb +0 -2
  64. data/lib/action_dispatch/journey/nfa/transition_table.rb +1 -46
  65. data/lib/action_dispatch/journey/nodes/node.rb +14 -4
  66. data/lib/action_dispatch/journey/parser_extras.rb +4 -0
  67. data/lib/action_dispatch/journey/path/pattern.rb +37 -41
  68. data/lib/action_dispatch/journey/route.rb +71 -17
  69. data/lib/action_dispatch/journey/router.rb +5 -6
  70. data/lib/action_dispatch/journey/router/utils.rb +5 -5
  71. data/lib/action_dispatch/journey/routes.rb +14 -15
  72. data/lib/action_dispatch/journey/visitors.rb +86 -43
  73. data/lib/action_dispatch/middleware/cookies.rb +184 -135
  74. data/lib/action_dispatch/middleware/debug_exceptions.rb +115 -45
  75. data/lib/action_dispatch/middleware/exception_wrapper.rb +21 -20
  76. data/lib/action_dispatch/middleware/flash.rb +61 -45
  77. data/lib/action_dispatch/middleware/load_interlock.rb +21 -0
  78. data/lib/action_dispatch/middleware/params_parser.rb +30 -46
  79. data/lib/action_dispatch/middleware/public_exceptions.rb +2 -2
  80. data/lib/action_dispatch/middleware/reloader.rb +2 -4
  81. data/lib/action_dispatch/middleware/remote_ip.rb +29 -19
  82. data/lib/action_dispatch/middleware/request_id.rb +11 -6
  83. data/lib/action_dispatch/middleware/session/abstract_store.rb +23 -11
  84. data/lib/action_dispatch/middleware/session/cache_store.rb +9 -6
  85. data/lib/action_dispatch/middleware/session/cookie_store.rb +29 -23
  86. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +4 -0
  87. data/lib/action_dispatch/middleware/show_exceptions.rb +11 -9
  88. data/lib/action_dispatch/middleware/ssl.rb +93 -36
  89. data/lib/action_dispatch/middleware/stack.rb +43 -48
  90. data/lib/action_dispatch/middleware/static.rb +52 -40
  91. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
  92. data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
  93. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  94. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
  95. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  96. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  97. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -63
  98. data/lib/action_dispatch/railtie.rb +0 -2
  99. data/lib/action_dispatch/request/session.rb +66 -34
  100. data/lib/action_dispatch/request/utils.rb +51 -19
  101. data/lib/action_dispatch/routing.rb +3 -8
  102. data/lib/action_dispatch/routing/inspector.rb +6 -30
  103. data/lib/action_dispatch/routing/mapper.rb +447 -322
  104. data/lib/action_dispatch/routing/polymorphic_routes.rb +8 -14
  105. data/lib/action_dispatch/routing/redirection.rb +3 -3
  106. data/lib/action_dispatch/routing/route_set.rb +124 -227
  107. data/lib/action_dispatch/routing/url_for.rb +27 -10
  108. data/lib/action_dispatch/testing/assertions.rb +1 -1
  109. data/lib/action_dispatch/testing/assertions/response.rb +27 -9
  110. data/lib/action_dispatch/testing/assertions/routing.rb +9 -9
  111. data/lib/action_dispatch/testing/integration.rb +237 -76
  112. data/lib/action_dispatch/testing/test_process.rb +5 -5
  113. data/lib/action_dispatch/testing/test_request.rb +12 -21
  114. data/lib/action_dispatch/testing/test_response.rb +1 -4
  115. data/lib/action_pack.rb +1 -1
  116. data/lib/action_pack/gem_version.rb +4 -4
  117. metadata +26 -25
  118. data/lib/action_controller/metal/hide_actions.rb +0 -40
  119. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  120. data/lib/action_controller/middleware.rb +0 -39
  121. data/lib/action_controller/model_naming.rb +0 -12
  122. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  123. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  124. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  125. 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: an URL can be generated from one of your routing definitions.
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 'url_for' instead of 'ActionController::UrlFor#url_for'
56
- # in full. However, mailers don't have hostname information, and that's why you'll still
57
- # have to specify the <tt>:host</tt> argument when generating URLs in mailers.
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
- # # assert that the response was a redirection
24
+ # # Asserts that the response was a redirection
18
25
  # assert_response :redirect
19
26
  #
20
- # # assert that the response code was status code 401 (unauthorized)
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 ||= "Expected response to be a <#{type}>, but was <#{@response.response_code}>"
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("#{type}?"), message
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
- # Assert that the redirection options passed in match those of the redirect called in the latest action.
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
- # # assert that the redirection was to the "index" action on the WeblogController
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
- # # assert that the redirection was to the named route login_url
54
+ # # Asserts that the redirection was to the named route login_url
48
55
  # assert_redirected_to login_url
49
56
  #
50
- # # assert that the redirection was to the url for @customer
57
+ # # Asserts that the redirection was to the url for @customer
51
58
  # assert_redirected_to @customer
52
59
  #
53
- # # asserts that the redirection matches the regular expression
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
- # # assert that POSTing to /items will call the create action on ItemsController
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 string will end up in the params hash correctly. To test query strings you must use the
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
- # # assert that a path of '/items/list/1?view=print' returns the correct options
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, extra_keys = @routes.generate_extras(options, defaults)
90
- found_extras = options.reject { |k, _| ! extra_keys.include? 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
- # # Assert a basic route: a controller with the default action (index)
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
- # # Assert a basic route (controller + default action), with an error message if it fails
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.send(:include, _routes.url_helpers)
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.new
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
- # - +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,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
- 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
@@ -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
- 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,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
- 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
+ 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, parameters = nil, headers_or_env = nil)
101
- request_via_redirect(:get, path, parameters, headers_or_env)
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, parameters = nil, headers_or_env = nil)
107
- request_via_redirect(:post, path, parameters, headers_or_env)
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, parameters = nil, headers_or_env = nil)
113
- request_via_redirect(:patch, path, parameters, headers_or_env)
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, parameters = nil, headers_or_env = nil)
119
- request_via_redirect(:put, path, parameters, headers_or_env)
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, parameters = nil, headers_or_env = nil)
125
- request_via_redirect(:delete, path, parameters, headers_or_env)
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, parameters = nil, headers_or_env = nil)
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
- host! "#{location.host}:#{location.port}" if location.host
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
- env = {
338
+ request_env = {
276
339
  :method => method,
277
- :params => parameters,
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
- # this modifies the passed env directly
291
- Http::Headers.new(env).merge!(headers_or_env || {})
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, env), env)
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 = session.last_request.env['action_controller.instance']
382
+ @controller = @request.controller_instance
308
383
 
309
- return response.status
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
- def app
321
- @app ||= nil
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 = Integration::Session.new(app)
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
- return unless integration_session
373
- %w(controller response request).each do |var|
374
- instance_variable_set("@#{var}", @integration_session.__send__(var))
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
- # xml_http_request "/say/#{room.id}", message: message
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