actionpack 6.1.7.5 → 7.1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +355 -435
- data/MIT-LICENSE +2 -1
- data/README.rdoc +6 -7
- data/lib/abstract_controller/asset_paths.rb +1 -1
- data/lib/abstract_controller/base.rb +33 -37
- data/lib/abstract_controller/caching/fragments.rb +4 -2
- data/lib/abstract_controller/caching.rb +1 -1
- data/lib/abstract_controller/callbacks.rb +50 -11
- data/lib/abstract_controller/collector.rb +2 -2
- data/lib/abstract_controller/deprecator.rb +7 -0
- data/lib/abstract_controller/error.rb +1 -1
- data/lib/abstract_controller/helpers.rb +78 -30
- data/lib/abstract_controller/logger.rb +1 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +3 -16
- data/lib/abstract_controller/rendering.rb +12 -14
- data/lib/abstract_controller/translation.rb +26 -7
- data/lib/abstract_controller/url_for.rb +6 -6
- data/lib/abstract_controller.rb +6 -0
- data/lib/action_controller/api.rb +12 -10
- data/lib/action_controller/base.rb +8 -21
- data/lib/action_controller/caching.rb +2 -0
- data/lib/action_controller/deprecator.rb +7 -0
- data/lib/action_controller/form_builder.rb +4 -2
- data/lib/action_controller/log_subscriber.rb +20 -7
- data/lib/action_controller/metal/basic_implicit_render.rb +3 -1
- data/lib/action_controller/metal/conditional_get.rb +137 -102
- data/lib/action_controller/metal/content_security_policy.rb +37 -3
- data/lib/action_controller/metal/cookies.rb +1 -1
- data/lib/action_controller/metal/data_streaming.rb +25 -31
- data/lib/action_controller/metal/default_headers.rb +2 -0
- data/lib/action_controller/metal/etag_with_flash.rb +3 -1
- data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
- data/lib/action_controller/metal/exceptions.rb +27 -30
- data/lib/action_controller/metal/flash.rb +6 -2
- data/lib/action_controller/metal/head.rb +9 -7
- data/lib/action_controller/metal/helpers.rb +5 -16
- data/lib/action_controller/metal/http_authentication.rb +78 -42
- data/lib/action_controller/metal/implicit_render.rb +5 -3
- data/lib/action_controller/metal/instrumentation.rb +62 -50
- data/lib/action_controller/metal/live.rb +67 -2
- data/lib/action_controller/metal/mime_responds.rb +5 -5
- data/lib/action_controller/metal/params_wrapper.rb +24 -13
- data/lib/action_controller/metal/permissions_policy.rb +20 -29
- data/lib/action_controller/metal/redirecting.rb +96 -23
- data/lib/action_controller/metal/renderers.rb +14 -15
- data/lib/action_controller/metal/rendering.rb +121 -16
- data/lib/action_controller/metal/request_forgery_protection.rb +208 -68
- data/lib/action_controller/metal/rescue.rb +7 -4
- data/lib/action_controller/metal/streaming.rb +74 -36
- data/lib/action_controller/metal/strong_parameters.rb +254 -151
- data/lib/action_controller/metal/testing.rb +9 -2
- data/lib/action_controller/metal/url_for.rb +10 -5
- data/lib/action_controller/metal.rb +89 -34
- data/lib/action_controller/railtie.rb +66 -9
- data/lib/action_controller/renderer.rb +99 -85
- data/lib/action_controller/test_case.rb +42 -11
- data/lib/action_controller.rb +10 -6
- data/lib/action_dispatch/constants.rb +32 -0
- data/lib/action_dispatch/deprecator.rb +7 -0
- data/lib/action_dispatch/http/cache.rb +21 -16
- data/lib/action_dispatch/http/content_security_policy.rb +122 -44
- data/lib/action_dispatch/http/filter_parameters.rb +14 -23
- data/lib/action_dispatch/http/headers.rb +3 -1
- data/lib/action_dispatch/http/mime_negotiation.rb +25 -15
- data/lib/action_dispatch/http/mime_type.rb +43 -22
- data/lib/action_dispatch/http/mime_types.rb +3 -1
- data/lib/action_dispatch/http/parameters.rb +6 -6
- data/lib/action_dispatch/http/permissions_policy.rb +57 -19
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +75 -51
- data/lib/action_dispatch/http/response.rb +81 -77
- data/lib/action_dispatch/http/upload.rb +15 -2
- data/lib/action_dispatch/http/url.rb +11 -19
- data/lib/action_dispatch/journey/formatter.rb +8 -2
- data/lib/action_dispatch/journey/gtg/builder.rb +11 -12
- data/lib/action_dispatch/journey/gtg/simulator.rb +10 -4
- data/lib/action_dispatch/journey/gtg/transition_table.rb +77 -21
- data/lib/action_dispatch/journey/nodes/node.rb +70 -5
- data/lib/action_dispatch/journey/path/pattern.rb +36 -27
- data/lib/action_dispatch/journey/route.rb +8 -14
- data/lib/action_dispatch/journey/router/utils.rb +2 -2
- data/lib/action_dispatch/journey/router.rb +10 -9
- data/lib/action_dispatch/journey/routes.rb +5 -5
- data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
- data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
- data/lib/action_dispatch/log_subscriber.rb +23 -0
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -7
- data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
- data/lib/action_dispatch/middleware/callbacks.rb +2 -0
- data/lib/action_dispatch/middleware/cookies.rb +97 -107
- data/lib/action_dispatch/middleware/debug_exceptions.rb +31 -28
- data/lib/action_dispatch/middleware/debug_locks.rb +7 -4
- data/lib/action_dispatch/middleware/debug_view.rb +7 -2
- data/lib/action_dispatch/middleware/exception_wrapper.rb +190 -27
- data/lib/action_dispatch/middleware/executor.rb +3 -0
- data/lib/action_dispatch/middleware/flash.rb +24 -18
- data/lib/action_dispatch/middleware/host_authorization.rb +19 -20
- data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
- data/lib/action_dispatch/middleware/reloader.rb +7 -5
- data/lib/action_dispatch/middleware/remote_ip.rb +32 -19
- data/lib/action_dispatch/middleware/request_id.rb +5 -3
- data/lib/action_dispatch/middleware/server_timing.rb +76 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +6 -1
- data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
- data/lib/action_dispatch/middleware/session/cookie_store.rb +19 -13
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
- data/lib/action_dispatch/middleware/show_exceptions.rb +30 -25
- data/lib/action_dispatch/middleware/ssl.rb +18 -6
- data/lib/action_dispatch/middleware/stack.rb +34 -11
- data/lib/action_dispatch/middleware/static.rb +16 -16
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +5 -5
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -11
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +10 -5
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -3
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +9 -9
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +45 -18
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -15
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +6 -6
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +7 -7
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +64 -55
- data/lib/action_dispatch/railtie.rb +20 -4
- data/lib/action_dispatch/request/session.rb +59 -19
- data/lib/action_dispatch/request/utils.rb +8 -3
- data/lib/action_dispatch/routing/inspector.rb +55 -7
- data/lib/action_dispatch/routing/mapper.rb +117 -107
- data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
- data/lib/action_dispatch/routing/redirection.rb +20 -8
- data/lib/action_dispatch/routing/route_set.rb +67 -27
- data/lib/action_dispatch/routing/routes_proxy.rb +11 -16
- data/lib/action_dispatch/routing/url_for.rb +29 -26
- data/lib/action_dispatch/routing.rb +12 -13
- data/lib/action_dispatch/system_test_case.rb +8 -8
- data/lib/action_dispatch/system_testing/browser.rb +20 -29
- data/lib/action_dispatch/system_testing/driver.rb +34 -18
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +35 -20
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +0 -8
- data/lib/action_dispatch/testing/assertion_response.rb +1 -1
- data/lib/action_dispatch/testing/assertions/response.rb +14 -7
- data/lib/action_dispatch/testing/assertions/routing.rb +70 -30
- data/lib/action_dispatch/testing/assertions.rb +3 -4
- data/lib/action_dispatch/testing/integration.rb +33 -25
- data/lib/action_dispatch/testing/request_encoder.rb +4 -1
- data/lib/action_dispatch/testing/test_process.rb +5 -30
- data/lib/action_dispatch/testing/test_request.rb +1 -1
- data/lib/action_dispatch/testing/test_response.rb +34 -2
- data/lib/action_dispatch.rb +38 -4
- data/lib/action_pack/gem_version.rb +4 -4
- data/lib/action_pack/version.rb +1 -1
- data/lib/action_pack.rb +1 -1
- metadata +67 -30
@@ -13,42 +13,45 @@ module ActionDispatch
|
|
13
13
|
# to investigate changes at different points during your test. These will be
|
14
14
|
# named with a sequential prefix (or 'failed' for failing tests)
|
15
15
|
#
|
16
|
-
# The
|
16
|
+
# The default screenshots directory is +tmp/screenshots+ but you can set a different
|
17
|
+
# one with +Capybara.save_path+
|
17
18
|
#
|
18
|
-
# You can
|
19
|
-
# save the HTML from the page that is being
|
20
|
-
# elements on the page at the time of the screenshot
|
19
|
+
# You can use the +html+ argument or set the +RAILS_SYSTEM_TESTING_SCREENSHOT_HTML+
|
20
|
+
# environment variable to save the HTML from the page that is being screenshotted
|
21
|
+
# so you can investigate the elements on the page at the time of the screenshot
|
21
22
|
#
|
22
|
-
# You can
|
23
|
-
# control the output. Possible values are:
|
23
|
+
# You can use the +screenshot+ argument or set the +RAILS_SYSTEM_TESTING_SCREENSHOT+
|
24
|
+
# environment variable to control the output. Possible values are:
|
24
25
|
# * [+simple+ (default)] Only displays the screenshot path.
|
25
26
|
# This is the default value.
|
26
27
|
# * [+inline+] Display the screenshot in the terminal using the
|
27
28
|
# iTerm image protocol (https://iterm2.com/documentation-images.html).
|
28
29
|
# * [+artifact+] Display the screenshot in the terminal, using the terminal
|
29
30
|
# artifact format (https://buildkite.github.io/terminal-to-html/inline-images/).
|
30
|
-
def take_screenshot
|
31
|
+
def take_screenshot(html: false, screenshot: nil)
|
32
|
+
showing_html = html || html_from_env?
|
33
|
+
|
31
34
|
increment_unique
|
32
|
-
save_html if
|
35
|
+
save_html if showing_html
|
33
36
|
save_image
|
34
|
-
|
37
|
+
show display_image(html: showing_html, screenshot_output: screenshot)
|
35
38
|
end
|
36
39
|
|
37
40
|
# Takes a screenshot of the current page in the browser if the test
|
38
41
|
# failed.
|
39
42
|
#
|
40
|
-
# +take_failed_screenshot+ is
|
41
|
-
# that is generated with the application. To take screenshots when a test
|
42
|
-
# fails add +take_failed_screenshot+ to the teardown block before clearing
|
43
|
-
# sessions.
|
43
|
+
# +take_failed_screenshot+ is called during system test teardown.
|
44
44
|
def take_failed_screenshot
|
45
|
-
|
45
|
+
return unless failed? && supports_screenshot? && Capybara::Session.instance_created?
|
46
|
+
|
47
|
+
take_screenshot
|
48
|
+
metadata[:failure_screenshot_path] = relative_image_path if Minitest::Runnable.method_defined?(:metadata)
|
46
49
|
end
|
47
50
|
|
48
51
|
private
|
49
52
|
attr_accessor :_screenshot_counter
|
50
53
|
|
51
|
-
def
|
54
|
+
def html_from_env?
|
52
55
|
ENV["RAILS_SYSTEM_TESTING_SCREENSHOT_HTML"] == "1"
|
53
56
|
end
|
54
57
|
|
@@ -62,7 +65,7 @@ module ActionDispatch
|
|
62
65
|
end
|
63
66
|
|
64
67
|
def image_name
|
65
|
-
sanitized_method_name = method_name.
|
68
|
+
sanitized_method_name = method_name.gsub(/[^\w]+/, "-")
|
66
69
|
name = "#{unique}_#{sanitized_method_name}"
|
67
70
|
name[0...225]
|
68
71
|
end
|
@@ -76,13 +79,21 @@ module ActionDispatch
|
|
76
79
|
end
|
77
80
|
|
78
81
|
def absolute_path
|
79
|
-
Rails.root.join(
|
82
|
+
Rails.root.join(screenshots_dir, image_name)
|
83
|
+
end
|
84
|
+
|
85
|
+
def screenshots_dir
|
86
|
+
Capybara.save_path.presence || "tmp/screenshots"
|
80
87
|
end
|
81
88
|
|
82
89
|
def absolute_image_path
|
83
90
|
"#{absolute_path}.png"
|
84
91
|
end
|
85
92
|
|
93
|
+
def relative_image_path
|
94
|
+
"#{absolute_path.relative_path_from(Rails.root)}.png"
|
95
|
+
end
|
96
|
+
|
86
97
|
def absolute_html_path
|
87
98
|
"#{absolute_path}.html"
|
88
99
|
end
|
@@ -105,11 +116,15 @@ module ActionDispatch
|
|
105
116
|
output_type
|
106
117
|
end
|
107
118
|
|
108
|
-
def
|
119
|
+
def show(img)
|
120
|
+
puts img
|
121
|
+
end
|
122
|
+
|
123
|
+
def display_image(html:, screenshot_output:)
|
109
124
|
message = +"[Screenshot Image]: #{image_path}\n"
|
110
|
-
message << +"[Screenshot HTML]: #{html_path}\n" if
|
125
|
+
message << +"[Screenshot HTML]: #{html_path}\n" if html
|
111
126
|
|
112
|
-
case output_type
|
127
|
+
case screenshot_output || output_type
|
113
128
|
when "artifact"
|
114
129
|
message << "\e]1338;url=artifact://#{absolute_image_path}\a\n"
|
115
130
|
when "inline"
|
@@ -4,14 +4,6 @@ module ActionDispatch
|
|
4
4
|
module SystemTesting
|
5
5
|
module TestHelpers
|
6
6
|
module SetupAndTeardown # :nodoc:
|
7
|
-
def host!(host)
|
8
|
-
ActiveSupport::Deprecation.warn \
|
9
|
-
"ActionDispatch::SystemTestCase#host! is deprecated with no replacement. " \
|
10
|
-
"Set Capybara.app_host directly or rely on Capybara's default host."
|
11
|
-
|
12
|
-
Capybara.app_host = host
|
13
|
-
end
|
14
|
-
|
15
7
|
def before_teardown
|
16
8
|
take_failed_screenshot
|
17
9
|
ensure
|
@@ -20,7 +20,7 @@ module ActionDispatch
|
|
20
20
|
#
|
21
21
|
# You can also pass an explicit status number like <tt>assert_response(501)</tt>
|
22
22
|
# or its symbolic equivalent <tt>assert_response(:not_implemented)</tt>.
|
23
|
-
# See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list.
|
23
|
+
# See +Rack::Utils::SYMBOL_TO_STATUS_CODE+ for a full list.
|
24
24
|
#
|
25
25
|
# # Asserts that the response was a redirection
|
26
26
|
# assert_response :redirect
|
@@ -30,7 +30,7 @@ module ActionDispatch
|
|
30
30
|
def assert_response(type, message = nil)
|
31
31
|
message ||= generate_response_message(type)
|
32
32
|
|
33
|
-
if RESPONSE_PREDICATES.
|
33
|
+
if RESPONSE_PREDICATES.key?(type)
|
34
34
|
assert @response.public_send(RESPONSE_PREDICATES[type]), message
|
35
35
|
else
|
36
36
|
assert_equal AssertionResponse.new(type).code, @response.response_code, message
|
@@ -50,12 +50,19 @@ module ActionDispatch
|
|
50
50
|
#
|
51
51
|
# # Asserts that the redirection matches the regular expression
|
52
52
|
# assert_redirected_to %r(\Ahttp://example.org)
|
53
|
-
|
54
|
-
|
55
|
-
|
53
|
+
#
|
54
|
+
# # Asserts that the redirection has the HTTP status code 301 (Moved
|
55
|
+
# # Permanently).
|
56
|
+
# assert_redirected_to "/some/path", status: :moved_permanently
|
57
|
+
def assert_redirected_to(url_options = {}, options = {}, message = nil)
|
58
|
+
options, message = {}, options unless options.is_a?(Hash)
|
59
|
+
|
60
|
+
status = options[:status] || :redirect
|
61
|
+
assert_response(status, message)
|
62
|
+
return true if url_options === @response.location
|
56
63
|
|
57
64
|
redirect_is = normalize_argument_to_redirection(@response.location)
|
58
|
-
redirect_expected = normalize_argument_to_redirection(
|
65
|
+
redirect_expected = normalize_argument_to_redirection(url_options)
|
59
66
|
|
60
67
|
message ||= "Expected response to be a redirect to <#{redirect_expected}> but was a redirect to <#{redirect_is}>"
|
61
68
|
assert_operator redirect_expected, :===, redirect_is, message
|
@@ -93,7 +100,7 @@ module ActionDispatch
|
|
93
100
|
end
|
94
101
|
|
95
102
|
def code_with_name(code_or_name)
|
96
|
-
if RESPONSE_PREDICATES.
|
103
|
+
if RESPONSE_PREDICATES.value?("#{code_or_name}?".to_sym)
|
97
104
|
code_or_name = RESPONSE_PREDICATES.invert["#{code_or_name}?".to_sym]
|
98
105
|
end
|
99
106
|
|
@@ -9,6 +9,36 @@ module ActionDispatch
|
|
9
9
|
module Assertions
|
10
10
|
# Suite of assertions to test routes generated by \Rails and the handling of requests made to them.
|
11
11
|
module RoutingAssertions
|
12
|
+
extend ActiveSupport::Concern
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
# A helper to make it easier to test different route configurations.
|
16
|
+
# This method temporarily replaces @routes with a new RouteSet instance
|
17
|
+
# before each test.
|
18
|
+
#
|
19
|
+
# The new instance is yielded to the passed block. Typically the block
|
20
|
+
# will create some routes using <tt>set.draw { match ... }</tt>:
|
21
|
+
#
|
22
|
+
# with_routing do |set|
|
23
|
+
# set.draw do
|
24
|
+
# resources :users
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
def with_routing(&block)
|
29
|
+
old_routes, old_controller = nil
|
30
|
+
|
31
|
+
setup do
|
32
|
+
old_routes, old_controller = @routes, @controller
|
33
|
+
create_routes(&block)
|
34
|
+
end
|
35
|
+
|
36
|
+
teardown do
|
37
|
+
reset_routes(old_routes, old_controller)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
12
42
|
def setup # :nodoc:
|
13
43
|
@routes ||= nil
|
14
44
|
super
|
@@ -18,8 +48,8 @@ module ActionDispatch
|
|
18
48
|
# match +path+. Basically, it asserts that \Rails recognizes the route given by +expected_options+.
|
19
49
|
#
|
20
50
|
# Pass a hash in the second argument (+path+) to specify the request method. This is useful for routes
|
21
|
-
# requiring a specific HTTP method. The hash should contain a
|
22
|
-
# and a
|
51
|
+
# requiring a specific HTTP method. The hash should contain a +:path+ with the incoming request path
|
52
|
+
# and a +:method+ containing the required HTTP verb.
|
23
53
|
#
|
24
54
|
# # Asserts that POSTing to /items will call the create action on ItemsController
|
25
55
|
# assert_recognizes({controller: 'items', action: 'create'}, {path: 'items', method: :post})
|
@@ -83,7 +113,7 @@ module ActionDispatch
|
|
83
113
|
# # Asserts that the generated route gives us our custom route
|
84
114
|
# assert_generates "changesets/12", { controller: 'scm', action: 'show_diff', revision: "12" }
|
85
115
|
def assert_generates(expected_path, options, defaults = {}, extras = {}, message = nil)
|
86
|
-
if
|
116
|
+
if expected_path.include?("://")
|
87
117
|
fail_on(URI::InvalidURIError, message) do
|
88
118
|
uri = URI.parse(expected_path)
|
89
119
|
expected_path = uri.path.to_s.empty? ? "/" : uri.path
|
@@ -150,33 +180,11 @@ module ActionDispatch
|
|
150
180
|
# assert_equal "/users", users_path
|
151
181
|
# end
|
152
182
|
#
|
153
|
-
def with_routing
|
154
|
-
old_routes,
|
155
|
-
|
156
|
-
old_controller, @controller = @controller, @controller.clone
|
157
|
-
_routes = @routes
|
158
|
-
|
159
|
-
@controller.singleton_class.include(_routes.url_helpers)
|
160
|
-
|
161
|
-
if @controller.respond_to? :view_context_class
|
162
|
-
view_context_class = Class.new(@controller.view_context_class) do
|
163
|
-
include _routes.url_helpers
|
164
|
-
end
|
165
|
-
|
166
|
-
custom_view_context = Module.new {
|
167
|
-
define_method(:view_context_class) do
|
168
|
-
view_context_class
|
169
|
-
end
|
170
|
-
}
|
171
|
-
@controller.extend(custom_view_context)
|
172
|
-
end
|
173
|
-
end
|
174
|
-
yield @routes
|
183
|
+
def with_routing(&block)
|
184
|
+
old_routes, old_controller = @routes, @controller
|
185
|
+
create_routes(&block)
|
175
186
|
ensure
|
176
|
-
|
177
|
-
if defined?(@controller) && @controller
|
178
|
-
@controller = old_controller
|
179
|
-
end
|
187
|
+
reset_routes(old_routes, old_controller)
|
180
188
|
end
|
181
189
|
|
182
190
|
# ROUTES TODO: These assertions should really work in an integration context
|
@@ -187,8 +195,40 @@ module ActionDispatch
|
|
187
195
|
super
|
188
196
|
end
|
189
197
|
end
|
198
|
+
ruby2_keywords(:method_missing)
|
190
199
|
|
191
200
|
private
|
201
|
+
def create_routes
|
202
|
+
@routes = ActionDispatch::Routing::RouteSet.new
|
203
|
+
if defined?(@controller) && @controller
|
204
|
+
@controller = @controller.clone
|
205
|
+
_routes = @routes
|
206
|
+
|
207
|
+
@controller.singleton_class.include(_routes.url_helpers)
|
208
|
+
|
209
|
+
if @controller.respond_to? :view_context_class
|
210
|
+
view_context_class = Class.new(@controller.view_context_class) do
|
211
|
+
include _routes.url_helpers
|
212
|
+
end
|
213
|
+
|
214
|
+
custom_view_context = Module.new {
|
215
|
+
define_method(:view_context_class) do
|
216
|
+
view_context_class
|
217
|
+
end
|
218
|
+
}
|
219
|
+
@controller.extend(custom_view_context)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
yield @routes
|
223
|
+
end
|
224
|
+
|
225
|
+
def reset_routes(old_routes, old_controller)
|
226
|
+
@routes = old_routes
|
227
|
+
if defined?(@controller) && @controller
|
228
|
+
@controller = old_controller
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
192
232
|
# Recognizes the route for a given path.
|
193
233
|
def recognized_request_for(path, extras = {}, msg)
|
194
234
|
if path.is_a?(Hash)
|
@@ -201,7 +241,7 @@ module ActionDispatch
|
|
201
241
|
controller = @controller if defined?(@controller)
|
202
242
|
request = ActionController::TestRequest.create controller&.class
|
203
243
|
|
204
|
-
if
|
244
|
+
if path.include?("://")
|
205
245
|
fail_on(URI::InvalidURIError, msg) do
|
206
246
|
uri = URI.parse(path)
|
207
247
|
request.env["rack.url_scheme"] = uri.scheme || "http"
|
@@ -1,12 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "rails-dom-testing"
|
4
|
+
require "action_dispatch/testing/assertions/response"
|
5
|
+
require "action_dispatch/testing/assertions/routing"
|
4
6
|
|
5
7
|
module ActionDispatch
|
6
8
|
module Assertions
|
7
|
-
autoload :ResponseAssertions, "action_dispatch/testing/assertions/response"
|
8
|
-
autoload :RoutingAssertions, "action_dispatch/testing/assertions/routing"
|
9
|
-
|
10
9
|
extend ActiveSupport::Concern
|
11
10
|
|
12
11
|
include ResponseAssertions
|
@@ -17,7 +16,7 @@ module ActionDispatch
|
|
17
16
|
@html_document ||= if @response.media_type&.end_with?("xml")
|
18
17
|
Nokogiri::XML::Document.parse(@response.body)
|
19
18
|
else
|
20
|
-
|
19
|
+
Rails::Dom::Testing.html_document.parse(@response.body)
|
21
20
|
end
|
22
21
|
end
|
23
22
|
end
|
@@ -3,12 +3,12 @@
|
|
3
3
|
require "stringio"
|
4
4
|
require "uri"
|
5
5
|
require "rack/test"
|
6
|
-
require "
|
6
|
+
require "active_support/test_case"
|
7
7
|
|
8
8
|
require "action_dispatch/testing/request_encoder"
|
9
9
|
|
10
10
|
module ActionDispatch
|
11
|
-
module Integration
|
11
|
+
module Integration # :nodoc:
|
12
12
|
module RequestHelpers
|
13
13
|
# Performs a GET request with the given parameters. See ActionDispatch::Integration::Session#process
|
14
14
|
# for more details.
|
@@ -58,7 +58,9 @@ module ActionDispatch
|
|
58
58
|
# the same HTTP verb will be used when redirecting, otherwise a GET request
|
59
59
|
# will be performed. Any arguments are passed to the
|
60
60
|
# underlying request.
|
61
|
-
|
61
|
+
#
|
62
|
+
# The HTTP_REFERER header will be set to the previous url.
|
63
|
+
def follow_redirect!(headers: {}, **args)
|
62
64
|
raise "not a redirect! #{status} #{status_message}" unless redirect?
|
63
65
|
|
64
66
|
method =
|
@@ -68,7 +70,11 @@ module ActionDispatch
|
|
68
70
|
:get
|
69
71
|
end
|
70
72
|
|
71
|
-
|
73
|
+
if [ :HTTP_REFERER, "HTTP_REFERER" ].none? { |key| headers.key? key }
|
74
|
+
headers["HTTP_REFERER"] = request.url
|
75
|
+
end
|
76
|
+
|
77
|
+
public_send(method, response.location, headers: headers, **args)
|
72
78
|
status
|
73
79
|
end
|
74
80
|
end
|
@@ -78,9 +84,8 @@ module ActionDispatch
|
|
78
84
|
# multiple sessions and run them side-by-side, you can also mimic (to some
|
79
85
|
# limited extent) multiple simultaneous users interacting with your system.
|
80
86
|
#
|
81
|
-
# Typically, you will instantiate a new session using
|
82
|
-
#
|
83
|
-
# Integration::Session directly.
|
87
|
+
# Typically, you will instantiate a new session using Runner#open_session,
|
88
|
+
# rather than instantiating a \Session directly.
|
84
89
|
class Session
|
85
90
|
DEFAULT_HOST = "www.example.com"
|
86
91
|
|
@@ -122,7 +127,7 @@ module ActionDispatch
|
|
122
127
|
|
123
128
|
include ActionDispatch::Routing::UrlFor
|
124
129
|
|
125
|
-
# Create and initialize a new Session instance.
|
130
|
+
# Create and initialize a new \Session instance.
|
126
131
|
def initialize(app)
|
127
132
|
super()
|
128
133
|
@app = app
|
@@ -199,16 +204,17 @@ module ActionDispatch
|
|
199
204
|
# merged into the Rack env hash.
|
200
205
|
# - +env+: Additional env to pass, as a Hash. The headers will be
|
201
206
|
# merged into the Rack env hash.
|
202
|
-
# - +xhr+: Set to +true+ if you want to make
|
207
|
+
# - +xhr+: Set to +true+ if you want to make an Ajax request.
|
203
208
|
# Adds request headers characteristic of XMLHttpRequest e.g. HTTP_X_REQUESTED_WITH.
|
204
209
|
# The headers will be merged into the Rack env hash.
|
205
210
|
# - +as+: Used for encoding the request with different content type.
|
206
211
|
# Supports +:json+ by default and will set the appropriate request headers.
|
207
212
|
# The headers will be merged into the Rack env hash.
|
208
213
|
#
|
209
|
-
# This method is rarely used directly. Use
|
210
|
-
# HTTP methods in integration
|
211
|
-
#
|
214
|
+
# This method is rarely used directly. Use RequestHelpers#get,
|
215
|
+
# RequestHelpers#post, or other standard HTTP methods in integration
|
216
|
+
# tests. +#process+ is only required when using a request method that
|
217
|
+
# doesn't have a method defined in the integration tests.
|
212
218
|
#
|
213
219
|
# This method returns the response status, after performing the request.
|
214
220
|
# Furthermore, if this method was called from an ActionDispatch::IntegrationTest object,
|
@@ -226,7 +232,7 @@ module ActionDispatch
|
|
226
232
|
method = :post
|
227
233
|
end
|
228
234
|
|
229
|
-
if
|
235
|
+
if path.include?("://")
|
230
236
|
path = build_expanded_path(path) do |location|
|
231
237
|
https! URI::HTTPS === location if location.scheme
|
232
238
|
|
@@ -252,10 +258,13 @@ module ActionDispatch
|
|
252
258
|
"REQUEST_URI" => path,
|
253
259
|
"HTTP_HOST" => host,
|
254
260
|
"REMOTE_ADDR" => remote_addr,
|
255
|
-
"CONTENT_TYPE" => request_encoder.content_type,
|
256
261
|
"HTTP_ACCEPT" => request_encoder.accept_header || accept
|
257
262
|
}
|
258
263
|
|
264
|
+
if request_encoder.content_type
|
265
|
+
request_env["CONTENT_TYPE"] = request_encoder.content_type
|
266
|
+
end
|
267
|
+
|
259
268
|
wrapped_headers = Http::Headers.from_hash({})
|
260
269
|
wrapped_headers.merge!(headers) if headers
|
261
270
|
|
@@ -363,13 +372,11 @@ module ActionDispatch
|
|
363
372
|
reset_html_document = "@html_document = nil"
|
364
373
|
end
|
365
374
|
|
366
|
-
definition = RUBY_VERSION >= "2.7" ? "..." : "*args"
|
367
|
-
|
368
375
|
module_eval <<~RUBY, __FILE__, __LINE__ + 1
|
369
|
-
def #{method}(
|
376
|
+
def #{method}(...)
|
370
377
|
#{reset_html_document}
|
371
378
|
|
372
|
-
result = integration_session.#{method}(
|
379
|
+
result = integration_session.#{method}(...)
|
373
380
|
copy_session_variables!
|
374
381
|
result
|
375
382
|
end
|
@@ -404,7 +411,7 @@ module ActionDispatch
|
|
404
411
|
|
405
412
|
# Copy the instance variables from the current session instance into the
|
406
413
|
# test instance.
|
407
|
-
def copy_session_variables!
|
414
|
+
def copy_session_variables! # :nodoc:
|
408
415
|
@controller = @integration_session.controller
|
409
416
|
@response = @integration_session.response
|
410
417
|
@request = @integration_session.request
|
@@ -433,7 +440,7 @@ module ActionDispatch
|
|
433
440
|
super
|
434
441
|
end
|
435
442
|
end
|
436
|
-
ruby2_keywords(:method_missing)
|
443
|
+
ruby2_keywords(:method_missing)
|
437
444
|
end
|
438
445
|
end
|
439
446
|
|
@@ -442,8 +449,9 @@ module ActionDispatch
|
|
442
449
|
# more completely than either unit or functional tests do, exercising the
|
443
450
|
# entire stack, from the dispatcher to the database.
|
444
451
|
#
|
445
|
-
# At its simplest, you simply extend <tt>IntegrationTest</tt> and write your
|
446
|
-
# using the get/
|
452
|
+
# At its simplest, you simply extend <tt>IntegrationTest</tt> and write your
|
453
|
+
# tests using the Integration::RequestHelpers#get and/or
|
454
|
+
# Integration::RequestHelpers#post methods:
|
447
455
|
#
|
448
456
|
# require "test_helper"
|
449
457
|
#
|
@@ -614,7 +622,7 @@ module ActionDispatch
|
|
614
622
|
# the request format to JSON unless overridden), sets the content type to
|
615
623
|
# "application/json" and encodes the parameters as JSON.
|
616
624
|
#
|
617
|
-
# Calling
|
625
|
+
# Calling TestResponse#parsed_body on the response parses the response body based on the
|
618
626
|
# last response MIME type.
|
619
627
|
#
|
620
628
|
# Out of the box, only <tt>:json</tt> is supported. But for any custom MIME
|
@@ -626,9 +634,9 @@ module ActionDispatch
|
|
626
634
|
#
|
627
635
|
# Where +param_encoder+ defines how the params should be encoded and
|
628
636
|
# +response_parser+ defines how the response body should be parsed through
|
629
|
-
#
|
637
|
+
# TestResponse#parsed_body.
|
630
638
|
#
|
631
|
-
# Consult the Rails Testing Guide for more.
|
639
|
+
# Consult the {Rails Testing Guide}[https://guides.rubyonrails.org/testing.html] for more.
|
632
640
|
|
633
641
|
class IntegrationTest < ActiveSupport::TestCase
|
634
642
|
include TestProcess::FixtureFile
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "nokogiri"
|
4
|
+
|
3
5
|
module ActionDispatch
|
4
6
|
class RequestEncoder # :nodoc:
|
5
7
|
class IdentityEncoder
|
@@ -50,6 +52,7 @@ module ActionDispatch
|
|
50
52
|
@encoders[mime_name] = new(mime_name, param_encoder, response_parser)
|
51
53
|
end
|
52
54
|
|
53
|
-
register_encoder :
|
55
|
+
register_encoder :html, response_parser: -> body { Rails::Dom::Testing.html_document.parse(body) }
|
56
|
+
register_encoder :json, response_parser: -> body { JSON.parse(body, object_class: ActiveSupport::HashWithIndifferentAccess) }
|
54
57
|
end
|
55
58
|
end
|
@@ -8,47 +8,22 @@ module ActionDispatch
|
|
8
8
|
module FixtureFile
|
9
9
|
# Shortcut for <tt>Rack::Test::UploadedFile.new(File.join(ActionDispatch::IntegrationTest.file_fixture_path, path), type)</tt>:
|
10
10
|
#
|
11
|
-
# post :change_avatar, params: { avatar:
|
11
|
+
# post :change_avatar, params: { avatar: file_fixture_upload('david.png', 'image/png') }
|
12
12
|
#
|
13
13
|
# Default fixture files location is <tt>test/fixtures/files</tt>.
|
14
14
|
#
|
15
15
|
# To upload binary files on Windows, pass <tt>:binary</tt> as the last parameter.
|
16
16
|
# This will not affect other platforms:
|
17
17
|
#
|
18
|
-
# post :change_avatar, params: { avatar:
|
19
|
-
def
|
20
|
-
if self.class.
|
21
|
-
!File.exist?(path)
|
22
|
-
original_path = path
|
23
|
-
path = Pathname.new(self.class.fixture_path).join(path)
|
24
|
-
|
25
|
-
if !self.class.file_fixture_path
|
26
|
-
ActiveSupport::Deprecation.warn(<<~EOM)
|
27
|
-
Passing a path to `fixture_file_upload` relative to `fixture_path` is deprecated.
|
28
|
-
In Rails 7.0, the path needs to be relative to `file_fixture_path` which you
|
29
|
-
haven't set yet. Set `file_fixture_path` to discard this warning.
|
30
|
-
EOM
|
31
|
-
elsif path.exist?
|
32
|
-
non_deprecated_path = Pathname(File.absolute_path(path)).relative_path_from(Pathname(File.absolute_path(self.class.file_fixture_path)))
|
33
|
-
|
34
|
-
if Pathname(original_path) != non_deprecated_path
|
35
|
-
ActiveSupport::Deprecation.warn(<<~EOM)
|
36
|
-
Passing a path to `fixture_file_upload` relative to `fixture_path` is deprecated.
|
37
|
-
In Rails 7.0, the path needs to be relative to `file_fixture_path`.
|
38
|
-
|
39
|
-
Please modify the call from
|
40
|
-
`fixture_file_upload("#{original_path}")` to `fixture_file_upload("#{non_deprecated_path}")`.
|
41
|
-
EOM
|
42
|
-
end
|
43
|
-
else
|
44
|
-
path = file_fixture(original_path)
|
45
|
-
end
|
46
|
-
elsif self.class.file_fixture_path && !File.exist?(path)
|
18
|
+
# post :change_avatar, params: { avatar: file_fixture_upload('david.png', 'image/png', :binary) }
|
19
|
+
def file_fixture_upload(path, mime_type = nil, binary = false)
|
20
|
+
if self.class.file_fixture_path && !File.exist?(path)
|
47
21
|
path = file_fixture(path)
|
48
22
|
end
|
49
23
|
|
50
24
|
Rack::Test::UploadedFile.new(path, mime_type, binary)
|
51
25
|
end
|
26
|
+
alias_method :fixture_file_upload, :file_fixture_upload
|
52
27
|
end
|
53
28
|
|
54
29
|
include FixtureFile
|
@@ -3,8 +3,8 @@
|
|
3
3
|
require "action_dispatch/testing/request_encoder"
|
4
4
|
|
5
5
|
module ActionDispatch
|
6
|
-
# Integration test methods such as
|
7
|
-
# and
|
6
|
+
# Integration test methods such as Integration::RequestHelpers#get
|
7
|
+
# and Integration::RequestHelpers#post return objects of class
|
8
8
|
# TestResponse, which represent the HTTP response results of the requested
|
9
9
|
# controller actions.
|
10
10
|
#
|
@@ -14,6 +14,38 @@ module ActionDispatch
|
|
14
14
|
new response.status, response.headers, response.body
|
15
15
|
end
|
16
16
|
|
17
|
+
# Returns a parsed body depending on the response MIME type. When a parser
|
18
|
+
# corresponding to the MIME type is not found, it returns the raw body.
|
19
|
+
#
|
20
|
+
# ==== Examples
|
21
|
+
# get "/posts"
|
22
|
+
# response.content_type # => "text/html; charset=utf-8"
|
23
|
+
# response.parsed_body.class # => Nokogiri::HTML5::Document
|
24
|
+
# response.parsed_body.to_html # => "<!DOCTYPE html>\n<html>\n..."
|
25
|
+
#
|
26
|
+
# assert_pattern { response.parsed_body.at("main") => { content: "Hello, world" } }
|
27
|
+
#
|
28
|
+
# response.parsed_body.at("main") => {name:, content:}
|
29
|
+
# assert_equal "main", name
|
30
|
+
# assert_equal "Some main content", content
|
31
|
+
#
|
32
|
+
# get "/posts.json"
|
33
|
+
# response.content_type # => "application/json; charset=utf-8"
|
34
|
+
# response.parsed_body.class # => Array
|
35
|
+
# response.parsed_body # => [{"id"=>42, "title"=>"Title"},...
|
36
|
+
#
|
37
|
+
# assert_pattern { response.parsed_body => [{ id: 42 }] }
|
38
|
+
#
|
39
|
+
# get "/posts/42.json"
|
40
|
+
# response.content_type # => "application/json; charset=utf-8"
|
41
|
+
# response.parsed_body.class # => ActiveSupport::HashWithIndifferentAccess
|
42
|
+
# response.parsed_body # => {"id"=>42, "title"=>"Title"}
|
43
|
+
#
|
44
|
+
# assert_pattern { response.parsed_body => [{ title: /title/i }] }
|
45
|
+
#
|
46
|
+
# response.parsed_body => {id:, title:}
|
47
|
+
# assert_equal 42, id
|
48
|
+
# assert_equal "Title", title
|
17
49
|
def parsed_body
|
18
50
|
@parsed_body ||= response_parser.call(body)
|
19
51
|
end
|