actionpack 4.2.11.1 → 6.0.3

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