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