actionpack 4.2.8 → 5.2.4.2

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 +5 -5
  2. data/CHANGELOG.md +285 -444
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -7
  5. data/lib/abstract_controller.rb +12 -5
  6. data/lib/abstract_controller/asset_paths.rb +2 -0
  7. data/lib/abstract_controller/base.rb +45 -49
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/{action_controller → abstract_controller}/caching/fragments.rb +78 -15
  10. data/lib/abstract_controller/callbacks.rb +47 -31
  11. data/lib/abstract_controller/collector.rb +8 -11
  12. data/lib/abstract_controller/error.rb +6 -0
  13. data/lib/abstract_controller/helpers.rb +25 -25
  14. data/lib/abstract_controller/logger.rb +2 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +4 -2
  16. data/lib/abstract_controller/rendering.rb +42 -41
  17. data/lib/abstract_controller/translation.rb +10 -7
  18. data/lib/abstract_controller/url_for.rb +2 -0
  19. data/lib/action_controller.rb +29 -21
  20. data/lib/action_controller/api.rb +149 -0
  21. data/lib/action_controller/api/api_rendering.rb +16 -0
  22. data/lib/action_controller/base.rb +27 -19
  23. data/lib/action_controller/caching.rb +14 -57
  24. data/lib/action_controller/form_builder.rb +50 -0
  25. data/lib/action_controller/log_subscriber.rb +10 -15
  26. data/lib/action_controller/metal.rb +98 -83
  27. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  28. data/lib/action_controller/metal/conditional_get.rb +118 -44
  29. data/lib/action_controller/metal/content_security_policy.rb +52 -0
  30. data/lib/action_controller/metal/cookies.rb +3 -3
  31. data/lib/action_controller/metal/data_streaming.rb +27 -46
  32. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  33. data/lib/action_controller/metal/etag_with_template_digest.rb +20 -13
  34. data/lib/action_controller/metal/exceptions.rb +8 -14
  35. data/lib/action_controller/metal/flash.rb +4 -3
  36. data/lib/action_controller/metal/force_ssl.rb +23 -21
  37. data/lib/action_controller/metal/head.rb +21 -19
  38. data/lib/action_controller/metal/helpers.rb +24 -14
  39. data/lib/action_controller/metal/http_authentication.rb +64 -57
  40. data/lib/action_controller/metal/implicit_render.rb +62 -8
  41. data/lib/action_controller/metal/instrumentation.rb +19 -21
  42. data/lib/action_controller/metal/live.rb +90 -106
  43. data/lib/action_controller/metal/mime_responds.rb +33 -46
  44. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  45. data/lib/action_controller/metal/params_wrapper.rb +61 -53
  46. data/lib/action_controller/metal/redirecting.rb +49 -28
  47. data/lib/action_controller/metal/renderers.rb +87 -44
  48. data/lib/action_controller/metal/rendering.rb +72 -50
  49. data/lib/action_controller/metal/request_forgery_protection.rb +203 -92
  50. data/lib/action_controller/metal/rescue.rb +9 -16
  51. data/lib/action_controller/metal/streaming.rb +12 -10
  52. data/lib/action_controller/metal/strong_parameters.rb +582 -165
  53. data/lib/action_controller/metal/testing.rb +2 -17
  54. data/lib/action_controller/metal/url_for.rb +19 -10
  55. data/lib/action_controller/railtie.rb +28 -10
  56. data/lib/action_controller/railties/helpers.rb +2 -0
  57. data/lib/action_controller/renderer.rb +117 -0
  58. data/lib/action_controller/template_assertions.rb +11 -0
  59. data/lib/action_controller/test_case.rb +280 -411
  60. data/lib/action_dispatch.rb +27 -19
  61. data/lib/action_dispatch/http/cache.rb +93 -47
  62. data/lib/action_dispatch/http/content_security_policy.rb +272 -0
  63. data/lib/action_dispatch/http/filter_parameters.rb +26 -20
  64. data/lib/action_dispatch/http/filter_redirect.rb +10 -11
  65. data/lib/action_dispatch/http/headers.rb +55 -22
  66. data/lib/action_dispatch/http/mime_negotiation.rb +60 -41
  67. data/lib/action_dispatch/http/mime_type.rb +134 -121
  68. data/lib/action_dispatch/http/mime_types.rb +20 -6
  69. data/lib/action_dispatch/http/parameter_filter.rb +25 -11
  70. data/lib/action_dispatch/http/parameters.rb +98 -39
  71. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  72. data/lib/action_dispatch/http/request.rb +200 -118
  73. data/lib/action_dispatch/http/response.rb +225 -110
  74. data/lib/action_dispatch/http/upload.rb +12 -6
  75. data/lib/action_dispatch/http/url.rb +110 -28
  76. data/lib/action_dispatch/journey.rb +7 -5
  77. data/lib/action_dispatch/journey/formatter.rb +55 -32
  78. data/lib/action_dispatch/journey/gtg/builder.rb +7 -5
  79. data/lib/action_dispatch/journey/gtg/simulator.rb +3 -9
  80. data/lib/action_dispatch/journey/gtg/transition_table.rb +17 -16
  81. data/lib/action_dispatch/journey/nfa/builder.rb +5 -3
  82. data/lib/action_dispatch/journey/nfa/dot.rb +13 -13
  83. data/lib/action_dispatch/journey/nfa/simulator.rb +3 -1
  84. data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -48
  85. data/lib/action_dispatch/journey/nodes/node.rb +18 -6
  86. data/lib/action_dispatch/journey/parser.rb +23 -22
  87. data/lib/action_dispatch/journey/parser.y +3 -2
  88. data/lib/action_dispatch/journey/parser_extras.rb +12 -4
  89. data/lib/action_dispatch/journey/path/pattern.rb +50 -44
  90. data/lib/action_dispatch/journey/route.rb +106 -28
  91. data/lib/action_dispatch/journey/router.rb +35 -23
  92. data/lib/action_dispatch/journey/router/utils.rb +20 -11
  93. data/lib/action_dispatch/journey/routes.rb +18 -16
  94. data/lib/action_dispatch/journey/scanner.rb +18 -15
  95. data/lib/action_dispatch/journey/visitors.rb +99 -52
  96. data/lib/action_dispatch/middleware/callbacks.rb +1 -2
  97. data/lib/action_dispatch/middleware/cookies.rb +304 -193
  98. data/lib/action_dispatch/middleware/debug_exceptions.rb +152 -57
  99. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  100. data/lib/action_dispatch/middleware/exception_wrapper.rb +68 -69
  101. data/lib/action_dispatch/middleware/executor.rb +21 -0
  102. data/lib/action_dispatch/middleware/flash.rb +78 -54
  103. data/lib/action_dispatch/middleware/public_exceptions.rb +27 -25
  104. data/lib/action_dispatch/middleware/reloader.rb +5 -91
  105. data/lib/action_dispatch/middleware/remote_ip.rb +41 -31
  106. data/lib/action_dispatch/middleware/request_id.rb +17 -9
  107. data/lib/action_dispatch/middleware/session/abstract_store.rb +41 -25
  108. data/lib/action_dispatch/middleware/session/cache_store.rb +24 -14
  109. data/lib/action_dispatch/middleware/session/cookie_store.rb +72 -67
  110. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -2
  111. data/lib/action_dispatch/middleware/show_exceptions.rb +26 -22
  112. data/lib/action_dispatch/middleware/ssl.rb +114 -36
  113. data/lib/action_dispatch/middleware/stack.rb +31 -44
  114. data/lib/action_dispatch/middleware/static.rb +57 -50
  115. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
  116. data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
  117. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +1 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
  122. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  123. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  124. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +64 -64
  125. data/lib/action_dispatch/railtie.rb +19 -11
  126. data/lib/action_dispatch/request/session.rb +106 -59
  127. data/lib/action_dispatch/request/utils.rb +67 -24
  128. data/lib/action_dispatch/routing.rb +17 -18
  129. data/lib/action_dispatch/routing/endpoint.rb +9 -2
  130. data/lib/action_dispatch/routing/inspector.rb +58 -67
  131. data/lib/action_dispatch/routing/mapper.rb +734 -447
  132. data/lib/action_dispatch/routing/polymorphic_routes.rb +161 -139
  133. data/lib/action_dispatch/routing/redirection.rb +36 -26
  134. data/lib/action_dispatch/routing/route_set.rb +321 -291
  135. data/lib/action_dispatch/routing/routes_proxy.rb +32 -5
  136. data/lib/action_dispatch/routing/url_for.rb +65 -25
  137. data/lib/action_dispatch/system_test_case.rb +147 -0
  138. data/lib/action_dispatch/system_testing/browser.rb +49 -0
  139. data/lib/action_dispatch/system_testing/driver.rb +59 -0
  140. data/lib/action_dispatch/system_testing/server.rb +31 -0
  141. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
  142. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
  143. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
  144. data/lib/action_dispatch/testing/assertion_response.rb +47 -0
  145. data/lib/action_dispatch/testing/assertions.rb +6 -4
  146. data/lib/action_dispatch/testing/assertions/response.rb +45 -20
  147. data/lib/action_dispatch/testing/assertions/routing.rb +30 -26
  148. data/lib/action_dispatch/testing/integration.rb +347 -209
  149. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  150. data/lib/action_dispatch/testing/test_process.rb +28 -22
  151. data/lib/action_dispatch/testing/test_request.rb +27 -34
  152. data/lib/action_dispatch/testing/test_response.rb +35 -7
  153. data/lib/action_pack.rb +4 -2
  154. data/lib/action_pack/gem_version.rb +5 -3
  155. data/lib/action_pack/version.rb +3 -1
  156. metadata +56 -39
  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,34 @@ 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
408
404
  end
409
405
  end
410
406
 
@@ -427,8 +423,8 @@ module ActionDispatch
427
423
  # assert_equal 200, status
428
424
  #
429
425
  # # post the login and follow through to the home page
430
- # post "/login", username: people(:jamis).username,
431
- # password: people(:jamis).password
426
+ # post "/login", params: { username: people(:jamis).username,
427
+ # password: people(:jamis).password }
432
428
  # follow_redirect!
433
429
  # assert_equal 200, status
434
430
  # assert_equal "/home", path
@@ -467,7 +463,7 @@ module ActionDispatch
467
463
  # end
468
464
  #
469
465
  # def speak(room, message)
470
- # xml_http_request "/say/#{room.id}", message: message
466
+ # post "/say/#{room.id}", xhr: true, params: { message: message }
471
467
  # assert(...)
472
468
  # ...
473
469
  # end
@@ -477,38 +473,180 @@ module ActionDispatch
477
473
  # open_session do |sess|
478
474
  # sess.extend(CustomAssertions)
479
475
  # who = people(who)
480
- # sess.post "/login", username: who.username,
481
- # password: who.password
476
+ # sess.post "/login", params: { username: who.username,
477
+ # password: who.password }
482
478
  # assert(...)
483
479
  # end
484
480
  # end
485
481
  # end
486
- class IntegrationTest < ActiveSupport::TestCase
487
- include Integration::Runner
488
- include ActionController::TemplateAssertions
489
- include ActionDispatch::Routing::UrlFor
482
+ #
483
+ # Another longer example would be:
484
+ #
485
+ # A simple integration test that exercises multiple controllers:
486
+ #
487
+ # require 'test_helper'
488
+ #
489
+ # class UserFlowsTest < ActionDispatch::IntegrationTest
490
+ # test "login and browse site" do
491
+ # # login via https
492
+ # https!
493
+ # get "/login"
494
+ # assert_response :success
495
+ #
496
+ # post "/login", params: { username: users(:david).username, password: users(:david).password }
497
+ # follow_redirect!
498
+ # assert_equal '/welcome', path
499
+ # assert_equal 'Welcome david!', flash[:notice]
500
+ #
501
+ # https!(false)
502
+ # get "/articles/all"
503
+ # assert_response :success
504
+ # assert_select 'h1', 'Articles'
505
+ # end
506
+ # end
507
+ #
508
+ # As you can see the integration test involves multiple controllers and
509
+ # exercises the entire stack from database to dispatcher. In addition you can
510
+ # have multiple session instances open simultaneously in a test and extend
511
+ # those instances with assertion methods to create a very powerful testing
512
+ # DSL (domain-specific language) just for your application.
513
+ #
514
+ # Here's an example of multiple sessions and custom DSL in an integration test
515
+ #
516
+ # require 'test_helper'
517
+ #
518
+ # class UserFlowsTest < ActionDispatch::IntegrationTest
519
+ # test "login and browse site" do
520
+ # # User david logs in
521
+ # david = login(:david)
522
+ # # User guest logs in
523
+ # guest = login(:guest)
524
+ #
525
+ # # Both are now available in different sessions
526
+ # assert_equal 'Welcome david!', david.flash[:notice]
527
+ # assert_equal 'Welcome guest!', guest.flash[:notice]
528
+ #
529
+ # # User david can browse site
530
+ # david.browses_site
531
+ # # User guest can browse site as well
532
+ # guest.browses_site
533
+ #
534
+ # # Continue with other assertions
535
+ # end
536
+ #
537
+ # private
538
+ #
539
+ # module CustomDsl
540
+ # def browses_site
541
+ # get "/products/all"
542
+ # assert_response :success
543
+ # assert_select 'h1', 'Products'
544
+ # end
545
+ # end
546
+ #
547
+ # def login(user)
548
+ # open_session do |sess|
549
+ # sess.extend(CustomDsl)
550
+ # u = users(user)
551
+ # sess.https!
552
+ # sess.post "/login", params: { username: u.username, password: u.password }
553
+ # assert_equal '/welcome', sess.path
554
+ # sess.https!(false)
555
+ # end
556
+ # end
557
+ # end
558
+ #
559
+ # See the {request helpers documentation}[rdoc-ref:ActionDispatch::Integration::RequestHelpers] for help on how to
560
+ # use +get+, etc.
561
+ #
562
+ # === Changing the request encoding
563
+ #
564
+ # You can also test your JSON API easily by setting what the request should
565
+ # be encoded as:
566
+ #
567
+ # require "test_helper"
568
+ #
569
+ # class ApiTest < ActionDispatch::IntegrationTest
570
+ # test "creates articles" do
571
+ # assert_difference -> { Article.count } do
572
+ # post articles_path, params: { article: { title: "Ahoy!" } }, as: :json
573
+ # end
574
+ #
575
+ # assert_response :success
576
+ # assert_equal({ id: Article.last.id, title: "Ahoy!" }, response.parsed_body)
577
+ # end
578
+ # end
579
+ #
580
+ # The +as+ option passes an "application/json" Accept header (thereby setting
581
+ # the request format to JSON unless overridden), sets the content type to
582
+ # "application/json" and encodes the parameters as JSON.
583
+ #
584
+ # Calling +parsed_body+ on the response parses the response body based on the
585
+ # last response MIME type.
586
+ #
587
+ # Out of the box, only <tt>:json</tt> is supported. But for any custom MIME
588
+ # types you've registered, you can add your own encoders with:
589
+ #
590
+ # ActionDispatch::IntegrationTest.register_encoder :wibble,
591
+ # param_encoder: -> params { params.to_wibble },
592
+ # response_parser: -> body { body }
593
+ #
594
+ # Where +param_encoder+ defines how the params should be encoded and
595
+ # +response_parser+ defines how the response body should be parsed through
596
+ # +parsed_body+.
597
+ #
598
+ # Consult the Rails Testing Guide for more.
490
599
 
491
- @@app = nil
600
+ class IntegrationTest < ActiveSupport::TestCase
601
+ include TestProcess::FixtureFile
492
602
 
493
- def self.app
494
- @@app || ActionDispatch.test_app
603
+ module UrlOptions
604
+ extend ActiveSupport::Concern
605
+ def url_options
606
+ integration_session.url_options
607
+ end
495
608
  end
496
609
 
497
- def self.app=(app)
498
- @@app = app
499
- end
610
+ module Behavior
611
+ extend ActiveSupport::Concern
500
612
 
501
- def app
502
- super || self.class.app
503
- end
613
+ include Integration::Runner
614
+ include ActionController::TemplateAssertions
504
615
 
505
- def url_options
506
- reset! unless integration_session
507
- integration_session.url_options
508
- end
616
+ included do
617
+ include ActionDispatch::Routing::UrlFor
618
+ include UrlOptions # don't let UrlFor override the url_options method
619
+ ActiveSupport.run_load_hooks(:action_dispatch_integration_test, self)
620
+ @@app = nil
621
+ end
509
622
 
510
- def document_root_element
511
- html_document.root
623
+ module ClassMethods
624
+ def app
625
+ if defined?(@@app) && @@app
626
+ @@app
627
+ else
628
+ ActionDispatch.test_app
629
+ end
630
+ end
631
+
632
+ def app=(app)
633
+ @@app = app
634
+ end
635
+
636
+ def register_encoder(*args)
637
+ RequestEncoder.register_encoder(*args)
638
+ end
639
+ end
640
+
641
+ def app
642
+ super || self.class.app
643
+ end
644
+
645
+ def document_root_element
646
+ html_document.root
647
+ end
512
648
  end
649
+
650
+ include Behavior
513
651
  end
514
652
  end