omg-actionpack 8.0.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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