omg-actionpack 8.0.0.alpha1

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