actionpack 4.2.11.1 → 5.2.6

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 (166) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +328 -458
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -7
  5. data/lib/abstract_controller/asset_paths.rb +2 -0
  6. data/lib/abstract_controller/base.rb +45 -49
  7. data/lib/{action_controller → abstract_controller}/caching/fragments.rb +78 -15
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/callbacks.rb +47 -31
  10. data/lib/abstract_controller/collector.rb +8 -11
  11. data/lib/abstract_controller/error.rb +6 -0
  12. data/lib/abstract_controller/helpers.rb +25 -25
  13. data/lib/abstract_controller/logger.rb +2 -0
  14. data/lib/abstract_controller/railties/routes_helpers.rb +4 -2
  15. data/lib/abstract_controller/rendering.rb +42 -41
  16. data/lib/abstract_controller/translation.rb +10 -7
  17. data/lib/abstract_controller/url_for.rb +2 -0
  18. data/lib/abstract_controller.rb +12 -5
  19. data/lib/action_controller/api/api_rendering.rb +16 -0
  20. data/lib/action_controller/api.rb +149 -0
  21. data/lib/action_controller/base.rb +27 -19
  22. data/lib/action_controller/caching.rb +14 -57
  23. data/lib/action_controller/form_builder.rb +50 -0
  24. data/lib/action_controller/log_subscriber.rb +10 -15
  25. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  26. data/lib/action_controller/metal/conditional_get.rb +118 -44
  27. data/lib/action_controller/metal/content_security_policy.rb +52 -0
  28. data/lib/action_controller/metal/cookies.rb +3 -3
  29. data/lib/action_controller/metal/data_streaming.rb +27 -46
  30. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  31. data/lib/action_controller/metal/etag_with_template_digest.rb +20 -13
  32. data/lib/action_controller/metal/exceptions.rb +8 -14
  33. data/lib/action_controller/metal/flash.rb +4 -3
  34. data/lib/action_controller/metal/force_ssl.rb +23 -21
  35. data/lib/action_controller/metal/head.rb +21 -19
  36. data/lib/action_controller/metal/helpers.rb +24 -14
  37. data/lib/action_controller/metal/http_authentication.rb +65 -58
  38. data/lib/action_controller/metal/implicit_render.rb +62 -8
  39. data/lib/action_controller/metal/instrumentation.rb +19 -21
  40. data/lib/action_controller/metal/live.rb +90 -106
  41. data/lib/action_controller/metal/mime_responds.rb +33 -46
  42. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  43. data/lib/action_controller/metal/params_wrapper.rb +61 -53
  44. data/lib/action_controller/metal/redirecting.rb +49 -28
  45. data/lib/action_controller/metal/renderers.rb +87 -44
  46. data/lib/action_controller/metal/rendering.rb +72 -50
  47. data/lib/action_controller/metal/request_forgery_protection.rb +284 -97
  48. data/lib/action_controller/metal/rescue.rb +9 -16
  49. data/lib/action_controller/metal/streaming.rb +12 -10
  50. data/lib/action_controller/metal/strong_parameters.rb +583 -164
  51. data/lib/action_controller/metal/testing.rb +2 -17
  52. data/lib/action_controller/metal/url_for.rb +19 -10
  53. data/lib/action_controller/metal.rb +98 -83
  54. data/lib/action_controller/railtie.rb +28 -10
  55. data/lib/action_controller/railties/helpers.rb +2 -0
  56. data/lib/action_controller/renderer.rb +117 -0
  57. data/lib/action_controller/template_assertions.rb +11 -0
  58. data/lib/action_controller/test_case.rb +282 -413
  59. data/lib/action_controller.rb +29 -21
  60. data/lib/action_dispatch/http/cache.rb +93 -47
  61. data/lib/action_dispatch/http/content_security_policy.rb +272 -0
  62. data/lib/action_dispatch/http/filter_parameters.rb +26 -20
  63. data/lib/action_dispatch/http/filter_redirect.rb +10 -11
  64. data/lib/action_dispatch/http/headers.rb +55 -22
  65. data/lib/action_dispatch/http/mime_negotiation.rb +56 -41
  66. data/lib/action_dispatch/http/mime_type.rb +134 -121
  67. data/lib/action_dispatch/http/mime_types.rb +20 -6
  68. data/lib/action_dispatch/http/parameter_filter.rb +25 -11
  69. data/lib/action_dispatch/http/parameters.rb +98 -39
  70. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  71. data/lib/action_dispatch/http/request.rb +200 -118
  72. data/lib/action_dispatch/http/response.rb +225 -110
  73. data/lib/action_dispatch/http/upload.rb +12 -6
  74. data/lib/action_dispatch/http/url.rb +110 -28
  75. data/lib/action_dispatch/journey/formatter.rb +55 -32
  76. data/lib/action_dispatch/journey/gtg/builder.rb +7 -5
  77. data/lib/action_dispatch/journey/gtg/simulator.rb +3 -9
  78. data/lib/action_dispatch/journey/gtg/transition_table.rb +17 -16
  79. data/lib/action_dispatch/journey/nfa/builder.rb +5 -3
  80. data/lib/action_dispatch/journey/nfa/dot.rb +13 -13
  81. data/lib/action_dispatch/journey/nfa/simulator.rb +3 -1
  82. data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -48
  83. data/lib/action_dispatch/journey/nodes/node.rb +18 -6
  84. data/lib/action_dispatch/journey/parser.rb +23 -22
  85. data/lib/action_dispatch/journey/parser.y +3 -2
  86. data/lib/action_dispatch/journey/parser_extras.rb +12 -4
  87. data/lib/action_dispatch/journey/path/pattern.rb +50 -44
  88. data/lib/action_dispatch/journey/route.rb +106 -28
  89. data/lib/action_dispatch/journey/router/utils.rb +20 -11
  90. data/lib/action_dispatch/journey/router.rb +35 -23
  91. data/lib/action_dispatch/journey/routes.rb +18 -16
  92. data/lib/action_dispatch/journey/scanner.rb +18 -15
  93. data/lib/action_dispatch/journey/visitors.rb +99 -52
  94. data/lib/action_dispatch/journey.rb +7 -5
  95. data/lib/action_dispatch/middleware/callbacks.rb +1 -2
  96. data/lib/action_dispatch/middleware/cookies.rb +304 -193
  97. data/lib/action_dispatch/middleware/debug_exceptions.rb +152 -57
  98. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  99. data/lib/action_dispatch/middleware/exception_wrapper.rb +68 -69
  100. data/lib/action_dispatch/middleware/executor.rb +21 -0
  101. data/lib/action_dispatch/middleware/flash.rb +78 -54
  102. data/lib/action_dispatch/middleware/public_exceptions.rb +27 -25
  103. data/lib/action_dispatch/middleware/reloader.rb +5 -91
  104. data/lib/action_dispatch/middleware/remote_ip.rb +41 -31
  105. data/lib/action_dispatch/middleware/request_id.rb +17 -9
  106. data/lib/action_dispatch/middleware/session/abstract_store.rb +41 -25
  107. data/lib/action_dispatch/middleware/session/cache_store.rb +24 -14
  108. data/lib/action_dispatch/middleware/session/cookie_store.rb +72 -67
  109. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -2
  110. data/lib/action_dispatch/middleware/show_exceptions.rb +26 -22
  111. data/lib/action_dispatch/middleware/ssl.rb +114 -36
  112. data/lib/action_dispatch/middleware/stack.rb +31 -44
  113. data/lib/action_dispatch/middleware/static.rb +57 -50
  114. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
  115. data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
  116. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  117. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +1 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
  121. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  122. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  123. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +64 -64
  124. data/lib/action_dispatch/railtie.rb +19 -11
  125. data/lib/action_dispatch/request/session.rb +106 -59
  126. data/lib/action_dispatch/request/utils.rb +67 -24
  127. data/lib/action_dispatch/routing/endpoint.rb +9 -2
  128. data/lib/action_dispatch/routing/inspector.rb +58 -67
  129. data/lib/action_dispatch/routing/mapper.rb +733 -447
  130. data/lib/action_dispatch/routing/polymorphic_routes.rb +166 -140
  131. data/lib/action_dispatch/routing/redirection.rb +36 -26
  132. data/lib/action_dispatch/routing/route_set.rb +321 -291
  133. data/lib/action_dispatch/routing/routes_proxy.rb +32 -5
  134. data/lib/action_dispatch/routing/url_for.rb +65 -25
  135. data/lib/action_dispatch/routing.rb +17 -18
  136. data/lib/action_dispatch/system_test_case.rb +147 -0
  137. data/lib/action_dispatch/system_testing/browser.rb +49 -0
  138. data/lib/action_dispatch/system_testing/driver.rb +59 -0
  139. data/lib/action_dispatch/system_testing/server.rb +31 -0
  140. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
  141. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
  142. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
  143. data/lib/action_dispatch/testing/assertion_response.rb +47 -0
  144. data/lib/action_dispatch/testing/assertions/response.rb +45 -20
  145. data/lib/action_dispatch/testing/assertions/routing.rb +30 -26
  146. data/lib/action_dispatch/testing/assertions.rb +6 -4
  147. data/lib/action_dispatch/testing/integration.rb +348 -209
  148. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  149. data/lib/action_dispatch/testing/test_process.rb +28 -22
  150. data/lib/action_dispatch/testing/test_request.rb +27 -34
  151. data/lib/action_dispatch/testing/test_response.rb +35 -7
  152. data/lib/action_dispatch.rb +27 -19
  153. data/lib/action_pack/gem_version.rb +5 -3
  154. data/lib/action_pack/version.rb +3 -1
  155. data/lib/action_pack.rb +4 -2
  156. metadata +56 -38
  157. data/lib/action_controller/metal/hide_actions.rb +0 -40
  158. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  159. data/lib/action_controller/middleware.rb +0 -39
  160. data/lib/action_controller/model_naming.rb +0 -12
  161. data/lib/action_dispatch/journey/backwards.rb +0 -5
  162. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  163. data/lib/action_dispatch/middleware/params_parser.rb +0 -60
  164. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  165. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  166. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,81 +1,53 @@
1
- require 'stringio'
2
- require 'uri'
3
- require 'active_support/core_ext/kernel/singleton_class'
4
- require 'active_support/core_ext/object/try'
5
- require 'rack/test'
6
- require 'minitest'
1
+ # frozen_string_literal: true
2
+
3
+ require "stringio"
4
+ require "uri"
5
+ require "active_support/core_ext/kernel/singleton_class"
6
+ require "active_support/core_ext/object/try"
7
+ require "rack/test"
8
+ require "minitest"
9
+
10
+ require "action_dispatch/testing/request_encoder"
7
11
 
8
12
  module ActionDispatch
9
13
  module Integration #:nodoc:
10
14
  module RequestHelpers
11
- # Performs a GET request with the given parameters.
12
- #
13
- # - +path+: The URI (as a String) on which you want to perform a GET
14
- # request.
15
- # - +parameters+: The HTTP parameters that you want to pass. This may
16
- # be +nil+,
17
- # a Hash, or a String that is appropriately encoded
18
- # (<tt>application/x-www-form-urlencoded</tt> or
19
- # <tt>multipart/form-data</tt>).
20
- # - +headers_or_env+: Additional headers to pass, as a Hash. The headers will be
21
- # merged into the Rack env hash.
22
- #
23
- # This method returns a Response object, which one can use to
24
- # inspect the details of the response. Furthermore, if this method was
25
- # called from an ActionDispatch::IntegrationTest object, then that
26
- # object's <tt>@response</tt> instance variable will point to the same
27
- # response object.
28
- #
29
- # You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with
30
- # +#post+, +#patch+, +#put+, +#delete+, and +#head+.
31
- def get(path, parameters = nil, headers_or_env = nil)
32
- process :get, path, parameters, headers_or_env
15
+ # Performs a GET request with the given parameters. See ActionDispatch::Integration::Session#process
16
+ # for more details.
17
+ def get(path, **args)
18
+ process(:get, path, **args)
33
19
  end
34
20
 
35
- # Performs a POST request with the given parameters. See +#get+ for more
36
- # details.
37
- def post(path, parameters = nil, headers_or_env = nil)
38
- process :post, path, parameters, headers_or_env
21
+ # Performs a POST request with the given parameters. See ActionDispatch::Integration::Session#process
22
+ # for more details.
23
+ def post(path, **args)
24
+ process(:post, path, **args)
39
25
  end
40
26
 
41
- # Performs a PATCH request with the given parameters. See +#get+ for more
42
- # details.
43
- def patch(path, parameters = nil, headers_or_env = nil)
44
- process :patch, path, parameters, headers_or_env
27
+ # Performs a PATCH request with the given parameters. See ActionDispatch::Integration::Session#process
28
+ # for more details.
29
+ def patch(path, **args)
30
+ process(:patch, path, **args)
45
31
  end
46
32
 
47
- # Performs a PUT request with the given parameters. See +#get+ for more
48
- # details.
49
- def put(path, parameters = nil, headers_or_env = nil)
50
- process :put, path, parameters, headers_or_env
33
+ # Performs a PUT request with the given parameters. See ActionDispatch::Integration::Session#process
34
+ # for more details.
35
+ def put(path, **args)
36
+ process(:put, path, **args)
51
37
  end
52
38
 
53
- # Performs a DELETE request with the given parameters. See +#get+ for
54
- # more details.
55
- def delete(path, parameters = nil, headers_or_env = nil)
56
- process :delete, path, parameters, headers_or_env
39
+ # Performs a DELETE request with the given parameters. See ActionDispatch::Integration::Session#process
40
+ # for more details.
41
+ def delete(path, **args)
42
+ process(:delete, path, **args)
57
43
  end
58
44
 
59
- # Performs a HEAD request with the given parameters. See +#get+ for more
60
- # details.
61
- def head(path, parameters = nil, headers_or_env = nil)
62
- process :head, path, parameters, headers_or_env
45
+ # Performs a HEAD request with the given parameters. See ActionDispatch::Integration::Session#process
46
+ # for more details.
47
+ def head(path, *args)
48
+ process(:head, path, *args)
63
49
  end
64
50
 
65
- # Performs an XMLHttpRequest request with the given parameters, mirroring
66
- # a request from the Prototype library.
67
- #
68
- # The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
69
- # +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
70
- # 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)
76
- end
77
- alias xhr :xml_http_request
78
-
79
51
  # Follow a single redirect response. If the last response was not a
80
52
  # redirect, an exception will be raised. Otherwise, the redirect is
81
53
  # performed on the location header.
@@ -84,46 +56,6 @@ module ActionDispatch
84
56
  get(response.location)
85
57
  status
86
58
  end
87
-
88
- # Performs a request using the specified method, following any subsequent
89
- # redirect. Note that the redirects are followed until the response is
90
- # not a redirect--this means you may run into an infinite loop if your
91
- # 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)
94
- follow_redirect! while redirect?
95
- status
96
- end
97
-
98
- # Performs a GET request, following any subsequent redirect.
99
- # 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)
102
- end
103
-
104
- # Performs a POST request, following any subsequent redirect.
105
- # 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)
108
- end
109
-
110
- # Performs a PATCH request, following any subsequent redirect.
111
- # 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)
114
- end
115
-
116
- # Performs a PUT request, following any subsequent redirect.
117
- # 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)
120
- end
121
-
122
- # Performs a DELETE request, following any subsequent redirect.
123
- # 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)
126
- end
127
59
  end
128
60
 
129
61
  # An instance of this class represents a set of requests and responses
@@ -141,11 +73,11 @@ module ActionDispatch
141
73
  include TestProcess, RequestHelpers, Assertions
142
74
 
143
75
  %w( status status_message headers body redirect? ).each do |method|
144
- delegate method, :to => :response, :allow_nil => true
76
+ delegate method, to: :response, allow_nil: true
145
77
  end
146
78
 
147
79
  %w( path ).each do |method|
148
- delegate method, :to => :request, :allow_nil => true
80
+ delegate method, to: :request, allow_nil: true
149
81
  end
150
82
 
151
83
  # The hostname used in the last request.
@@ -185,15 +117,6 @@ module ActionDispatch
185
117
  super()
186
118
  @app = app
187
119
 
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
120
  reset!
198
121
  end
199
122
 
@@ -205,7 +128,7 @@ module ActionDispatch
205
128
  url_options.reverse_merge!(@app.routes.default_url_options)
206
129
  end
207
130
 
208
- url_options.reverse_merge!(:host => host, :protocol => https? ? "https" : "http")
131
+ url_options.reverse_merge!(host: host, protocol: https? ? "https" : "http")
209
132
  end
210
133
  end
211
134
 
@@ -223,8 +146,8 @@ module ActionDispatch
223
146
 
224
147
  self.host = DEFAULT_HOST
225
148
  self.remote_addr = "127.0.0.1"
226
- self.accept = "text/xml,application/xml,application/xhtml+xml," +
227
- "text/html;q=0.9,text/plain;q=0.8,image/png," +
149
+ self.accept = "text/xml,application/xml,application/xhtml+xml," \
150
+ "text/html;q=0.9,text/plain;q=0.8,image/png," \
228
151
  "*/*;q=0.5"
229
152
 
230
153
  unless defined? @named_routes_configured
@@ -251,96 +174,177 @@ module ActionDispatch
251
174
  @https
252
175
  end
253
176
 
254
- # Set the host name to use in the next request.
177
+ # Performs the actual request.
255
178
  #
256
- # session.host! "www.example.com"
257
- alias :host! :host=
258
-
259
- private
260
- def _mock_session
261
- @_mock_session ||= Rack::MockSession.new(@app, host)
179
+ # - +method+: The HTTP method (GET, POST, PATCH, PUT, DELETE, HEAD, OPTIONS)
180
+ # as a symbol.
181
+ # - +path+: The URI (as a String) on which you want to perform the
182
+ # request.
183
+ # - +params+: The HTTP parameters that you want to pass. This may
184
+ # be +nil+,
185
+ # a Hash, or a String that is appropriately encoded
186
+ # (<tt>application/x-www-form-urlencoded</tt> or
187
+ # <tt>multipart/form-data</tt>).
188
+ # - +headers+: Additional headers to pass, as a Hash. The headers will be
189
+ # merged into the Rack env hash.
190
+ # - +env+: Additional env to pass, as a Hash. The headers will be
191
+ # merged into the Rack env hash.
192
+ #
193
+ # This method is rarely used directly. Use +#get+, +#post+, or other standard
194
+ # HTTP methods in integration tests. +#process+ is only required when using a
195
+ # request method that doesn't have a method defined in the integration tests.
196
+ #
197
+ # This method returns the response status, after performing the request.
198
+ # Furthermore, if this method was called from an ActionDispatch::IntegrationTest object,
199
+ # then that object's <tt>@response</tt> instance variable will point to a Response object
200
+ # which one can use to inspect the details of the response.
201
+ #
202
+ # Example:
203
+ # process :get, '/author', params: { since: 201501011400 }
204
+ def process(method, path, params: nil, headers: nil, env: nil, xhr: false, as: nil)
205
+ request_encoder = RequestEncoder.encoder(as)
206
+ headers ||= {}
207
+
208
+ if method == :get && as == :json && params
209
+ headers["X-Http-Method-Override"] = "GET"
210
+ method = :post
262
211
  end
263
212
 
264
- # Performs the actual request.
265
- def process(method, path, parameters = nil, headers_or_env = nil)
266
- if path =~ %r{://}
267
- location = URI.parse(path)
213
+ if path =~ %r{://}
214
+ path = build_expanded_path(path) do |location|
268
215
  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
216
+
217
+ if url_host = location.host
218
+ default = Rack::Request::DEFAULT_PORTS[location.scheme]
219
+ url_host += ":#{location.port}" if default != location.port
220
+ host! url_host
221
+ end
271
222
  end
223
+ end
224
+
225
+ hostname, port = host.split(":")
226
+
227
+ request_env = {
228
+ :method => method,
229
+ :params => request_encoder.encode_params(params),
230
+
231
+ "SERVER_NAME" => hostname,
232
+ "SERVER_PORT" => port || (https? ? "443" : "80"),
233
+ "HTTPS" => https? ? "on" : "off",
234
+ "rack.url_scheme" => https? ? "https" : "http",
272
235
 
273
- hostname, port = host.split(':')
236
+ "REQUEST_URI" => path,
237
+ "HTTP_HOST" => host,
238
+ "REMOTE_ADDR" => remote_addr,
239
+ "CONTENT_TYPE" => request_encoder.content_type,
240
+ "HTTP_ACCEPT" => request_encoder.accept_header || accept
241
+ }
274
242
 
275
- env = {
276
- :method => method,
277
- :params => parameters,
243
+ wrapped_headers = Http::Headers.from_hash({})
244
+ wrapped_headers.merge!(headers) if headers
245
+
246
+ if xhr
247
+ wrapped_headers["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest"
248
+ wrapped_headers["HTTP_ACCEPT"] ||= [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ")
249
+ end
250
+
251
+ # This modifies the passed request_env directly.
252
+ if wrapped_headers.present?
253
+ Http::Headers.from_hash(request_env).merge!(wrapped_headers)
254
+ end
255
+ if env.present?
256
+ Http::Headers.from_hash(request_env).merge!(env)
257
+ end
278
258
 
279
- "SERVER_NAME" => hostname,
280
- "SERVER_PORT" => port || (https? ? "443" : "80"),
281
- "HTTPS" => https? ? "on" : "off",
282
- "rack.url_scheme" => https? ? "https" : "http",
259
+ session = Rack::Test::Session.new(_mock_session)
283
260
 
284
- "REQUEST_URI" => path,
285
- "HTTP_HOST" => host,
286
- "REMOTE_ADDR" => remote_addr,
287
- "CONTENT_TYPE" => "application/x-www-form-urlencoded",
288
- "HTTP_ACCEPT" => accept
289
- }
290
- # this modifies the passed env directly
291
- Http::Headers.new(env).merge!(headers_or_env || {})
261
+ # NOTE: rack-test v0.5 doesn't build a default uri correctly
262
+ # Make sure requested path is always a full URI.
263
+ session.request(build_full_uri(path, request_env), request_env)
292
264
 
293
- session = Rack::Test::Session.new(_mock_session)
265
+ @request_count += 1
266
+ @request = ActionDispatch::Request.new(session.last_request.env)
267
+ response = _mock_session.last_response
268
+ @response = ActionDispatch::TestResponse.from_response(response)
269
+ @response.request = @request
270
+ @html_document = nil
271
+ @url_options = nil
294
272
 
295
- # NOTE: rack-test v0.5 doesn't build a default uri correctly
296
- # Make sure requested path is always a full uri
297
- session.request(build_full_uri(path, env), env)
273
+ @controller = @request.controller_instance
298
274
 
299
- @request_count += 1
300
- @request = ActionDispatch::Request.new(session.last_request.env)
301
- response = _mock_session.last_response
302
- @response = ActionDispatch::TestResponse.from_response(response)
303
- @html_document = nil
304
- @html_scanner_document = nil
305
- @url_options = nil
275
+ response.status
276
+ end
306
277
 
307
- @controller = session.last_request.env['action_controller.instance']
278
+ # Set the host name to use in the next request.
279
+ #
280
+ # session.host! "www.example.com"
281
+ alias :host! :host=
308
282
 
309
- return response.status
283
+ private
284
+ def _mock_session
285
+ @_mock_session ||= Rack::MockSession.new(@app, host)
310
286
  end
311
287
 
312
288
  def build_full_uri(path, env)
313
289
  "#{env['rack.url_scheme']}://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{path}"
314
290
  end
291
+
292
+ def build_expanded_path(path)
293
+ location = URI.parse(path)
294
+ yield location if block_given?
295
+ path = location.path
296
+ location.query ? "#{path}?#{location.query}" : path
297
+ end
315
298
  end
316
299
 
317
300
  module Runner
318
301
  include ActionDispatch::Assertions
319
302
 
320
- def app
321
- @app ||= nil
303
+ APP_SESSIONS = {}
304
+
305
+ attr_reader :app
306
+
307
+ def initialize(*args, &blk)
308
+ super(*args, &blk)
309
+ @integration_session = nil
310
+ end
311
+
312
+ def before_setup # :nodoc:
313
+ @app = nil
314
+ super
315
+ end
316
+
317
+ def integration_session
318
+ @integration_session ||= create_session(app)
322
319
  end
323
320
 
324
321
  # Reset the current session. This is useful for testing multiple sessions
325
322
  # in a single test case.
326
323
  def reset!
327
- @integration_session = Integration::Session.new(app)
324
+ @integration_session = create_session(app)
325
+ end
326
+
327
+ def create_session(app)
328
+ klass = APP_SESSIONS[app] ||= Class.new(Integration::Session) {
329
+ # If the app is a Rails app, make url_helpers available on the session.
330
+ # This makes app.url_for and app.foo_path available in the console.
331
+ if app.respond_to?(:routes)
332
+ include app.routes.url_helpers
333
+ include app.routes.mounted_helpers
334
+ end
335
+ }
336
+ klass.new(app)
328
337
  end
329
338
 
330
339
  def remove! # :nodoc:
331
340
  @integration_session = nil
332
341
  end
333
342
 
334
- %w(get post patch put head delete cookies assigns
335
- xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
343
+ %w(get post patch put head delete cookies assigns follow_redirect!).each do |method|
336
344
  define_method(method) do |*args|
337
- reset! unless integration_session
338
-
339
345
  # reset the html_document variable, except for cookies/assigns calls
340
- unless method == 'cookies' || method == 'assigns'
346
+ unless method == "cookies" || method == "assigns"
341
347
  @html_document = nil
342
- @html_scanner_document = nil
343
- reset_template_assertion
344
348
  end
345
349
 
346
350
  integration_session.__send__(method, *args).tap do
@@ -369,42 +373,35 @@ module ActionDispatch
369
373
  # Copy the instance variables from the current session instance into the
370
374
  # test instance.
371
375
  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
376
+ @controller = @integration_session.controller
377
+ @response = @integration_session.response
378
+ @request = @integration_session.request
376
379
  end
377
380
 
378
381
  def default_url_options
379
- reset! unless integration_session
380
382
  integration_session.default_url_options
381
383
  end
382
384
 
383
385
  def default_url_options=(options)
384
- reset! unless integration_session
385
386
  integration_session.default_url_options = options
386
387
  end
387
388
 
388
- def respond_to?(method, include_private = false)
389
- integration_session.respond_to?(method, include_private) || super
389
+ private
390
+ def respond_to_missing?(method, _)
391
+ integration_session.respond_to?(method) || super
390
392
  end
391
393
 
392
394
  # Delegate unhandled messages to the current session instance.
393
- def method_missing(sym, *args, &block)
394
- reset! unless integration_session
395
- if integration_session.respond_to?(sym)
396
- integration_session.__send__(sym, *args, &block).tap do
395
+ def method_missing(method, *args, &block)
396
+ if integration_session.respond_to?(method)
397
+ integration_session.public_send(method, *args, &block).tap do
397
398
  copy_session_variables!
398
399
  end
399
400
  else
400
401
  super
401
402
  end
402
403
  end
403
-
404
- private
405
- def integration_session
406
- @integration_session ||= nil
407
- end
404
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
408
405
  end
409
406
  end
410
407
 
@@ -427,8 +424,8 @@ module ActionDispatch
427
424
  # assert_equal 200, status
428
425
  #
429
426
  # # post the login and follow through to the home page
430
- # post "/login", username: people(:jamis).username,
431
- # password: people(:jamis).password
427
+ # post "/login", params: { username: people(:jamis).username,
428
+ # password: people(:jamis).password }
432
429
  # follow_redirect!
433
430
  # assert_equal 200, status
434
431
  # assert_equal "/home", path
@@ -467,7 +464,7 @@ module ActionDispatch
467
464
  # end
468
465
  #
469
466
  # def speak(room, message)
470
- # xml_http_request "/say/#{room.id}", message: message
467
+ # post "/say/#{room.id}", xhr: true, params: { message: message }
471
468
  # assert(...)
472
469
  # ...
473
470
  # end
@@ -477,38 +474,180 @@ module ActionDispatch
477
474
  # open_session do |sess|
478
475
  # sess.extend(CustomAssertions)
479
476
  # who = people(who)
480
- # sess.post "/login", username: who.username,
481
- # password: who.password
477
+ # sess.post "/login", params: { username: who.username,
478
+ # password: who.password }
482
479
  # assert(...)
483
480
  # end
484
481
  # end
485
482
  # end
486
- class IntegrationTest < ActiveSupport::TestCase
487
- include Integration::Runner
488
- include ActionController::TemplateAssertions
489
- include ActionDispatch::Routing::UrlFor
483
+ #
484
+ # Another longer example would be:
485
+ #
486
+ # A simple integration test that exercises multiple controllers:
487
+ #
488
+ # require 'test_helper'
489
+ #
490
+ # class UserFlowsTest < ActionDispatch::IntegrationTest
491
+ # test "login and browse site" do
492
+ # # login via https
493
+ # https!
494
+ # get "/login"
495
+ # assert_response :success
496
+ #
497
+ # post "/login", params: { username: users(:david).username, password: users(:david).password }
498
+ # follow_redirect!
499
+ # assert_equal '/welcome', path
500
+ # assert_equal 'Welcome david!', flash[:notice]
501
+ #
502
+ # https!(false)
503
+ # get "/articles/all"
504
+ # assert_response :success
505
+ # assert_select 'h1', 'Articles'
506
+ # end
507
+ # end
508
+ #
509
+ # As you can see the integration test involves multiple controllers and
510
+ # exercises the entire stack from database to dispatcher. In addition you can
511
+ # have multiple session instances open simultaneously in a test and extend
512
+ # those instances with assertion methods to create a very powerful testing
513
+ # DSL (domain-specific language) just for your application.
514
+ #
515
+ # Here's an example of multiple sessions and custom DSL in an integration test
516
+ #
517
+ # require 'test_helper'
518
+ #
519
+ # class UserFlowsTest < ActionDispatch::IntegrationTest
520
+ # test "login and browse site" do
521
+ # # User david logs in
522
+ # david = login(:david)
523
+ # # User guest logs in
524
+ # guest = login(:guest)
525
+ #
526
+ # # Both are now available in different sessions
527
+ # assert_equal 'Welcome david!', david.flash[:notice]
528
+ # assert_equal 'Welcome guest!', guest.flash[:notice]
529
+ #
530
+ # # User david can browse site
531
+ # david.browses_site
532
+ # # User guest can browse site as well
533
+ # guest.browses_site
534
+ #
535
+ # # Continue with other assertions
536
+ # end
537
+ #
538
+ # private
539
+ #
540
+ # module CustomDsl
541
+ # def browses_site
542
+ # get "/products/all"
543
+ # assert_response :success
544
+ # assert_select 'h1', 'Products'
545
+ # end
546
+ # end
547
+ #
548
+ # def login(user)
549
+ # open_session do |sess|
550
+ # sess.extend(CustomDsl)
551
+ # u = users(user)
552
+ # sess.https!
553
+ # sess.post "/login", params: { username: u.username, password: u.password }
554
+ # assert_equal '/welcome', sess.path
555
+ # sess.https!(false)
556
+ # end
557
+ # end
558
+ # end
559
+ #
560
+ # See the {request helpers documentation}[rdoc-ref:ActionDispatch::Integration::RequestHelpers] for help on how to
561
+ # use +get+, etc.
562
+ #
563
+ # === Changing the request encoding
564
+ #
565
+ # You can also test your JSON API easily by setting what the request should
566
+ # be encoded as:
567
+ #
568
+ # require "test_helper"
569
+ #
570
+ # class ApiTest < ActionDispatch::IntegrationTest
571
+ # test "creates articles" do
572
+ # assert_difference -> { Article.count } do
573
+ # post articles_path, params: { article: { title: "Ahoy!" } }, as: :json
574
+ # end
575
+ #
576
+ # assert_response :success
577
+ # assert_equal({ id: Article.last.id, title: "Ahoy!" }, response.parsed_body)
578
+ # end
579
+ # end
580
+ #
581
+ # The +as+ option passes an "application/json" Accept header (thereby setting
582
+ # the request format to JSON unless overridden), sets the content type to
583
+ # "application/json" and encodes the parameters as JSON.
584
+ #
585
+ # Calling +parsed_body+ on the response parses the response body based on the
586
+ # last response MIME type.
587
+ #
588
+ # Out of the box, only <tt>:json</tt> is supported. But for any custom MIME
589
+ # types you've registered, you can add your own encoders with:
590
+ #
591
+ # ActionDispatch::IntegrationTest.register_encoder :wibble,
592
+ # param_encoder: -> params { params.to_wibble },
593
+ # response_parser: -> body { body }
594
+ #
595
+ # Where +param_encoder+ defines how the params should be encoded and
596
+ # +response_parser+ defines how the response body should be parsed through
597
+ # +parsed_body+.
598
+ #
599
+ # Consult the Rails Testing Guide for more.
490
600
 
491
- @@app = nil
601
+ class IntegrationTest < ActiveSupport::TestCase
602
+ include TestProcess::FixtureFile
492
603
 
493
- def self.app
494
- @@app || ActionDispatch.test_app
604
+ module UrlOptions
605
+ extend ActiveSupport::Concern
606
+ def url_options
607
+ integration_session.url_options
608
+ end
495
609
  end
496
610
 
497
- def self.app=(app)
498
- @@app = app
499
- end
611
+ module Behavior
612
+ extend ActiveSupport::Concern
500
613
 
501
- def app
502
- super || self.class.app
503
- end
614
+ include Integration::Runner
615
+ include ActionController::TemplateAssertions
504
616
 
505
- def url_options
506
- reset! unless integration_session
507
- integration_session.url_options
508
- end
617
+ included do
618
+ include ActionDispatch::Routing::UrlFor
619
+ include UrlOptions # don't let UrlFor override the url_options method
620
+ ActiveSupport.run_load_hooks(:action_dispatch_integration_test, self)
621
+ @@app = nil
622
+ end
509
623
 
510
- def document_root_element
511
- html_document.root
624
+ module ClassMethods
625
+ def app
626
+ if defined?(@@app) && @@app
627
+ @@app
628
+ else
629
+ ActionDispatch.test_app
630
+ end
631
+ end
632
+
633
+ def app=(app)
634
+ @@app = app
635
+ end
636
+
637
+ def register_encoder(*args)
638
+ RequestEncoder.register_encoder(*args)
639
+ end
640
+ end
641
+
642
+ def app
643
+ super || self.class.app
644
+ end
645
+
646
+ def document_root_element
647
+ html_document.root
648
+ end
512
649
  end
650
+
651
+ include Behavior
513
652
  end
514
653
  end