actionpack 7.0.4 → 7.1.5.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +495 -257
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -4
- data/lib/abstract_controller/base.rb +20 -11
- data/lib/abstract_controller/caching/fragments.rb +2 -0
- data/lib/abstract_controller/callbacks.rb +31 -6
- data/lib/abstract_controller/deprecator.rb +7 -0
- data/lib/abstract_controller/helpers.rb +75 -28
- data/lib/abstract_controller/railties/routes_helpers.rb +1 -16
- data/lib/abstract_controller/rendering.rb +12 -14
- data/lib/abstract_controller/translation.rb +11 -6
- data/lib/abstract_controller/url_for.rb +2 -0
- data/lib/abstract_controller.rb +6 -0
- data/lib/action_controller/api.rb +6 -4
- data/lib/action_controller/base.rb +3 -17
- data/lib/action_controller/caching.rb +2 -0
- data/lib/action_controller/deprecator.rb +7 -0
- data/lib/action_controller/form_builder.rb +2 -0
- data/lib/action_controller/log_subscriber.rb +16 -4
- data/lib/action_controller/metal/basic_implicit_render.rb +3 -1
- data/lib/action_controller/metal/conditional_get.rb +121 -123
- data/lib/action_controller/metal/content_security_policy.rb +5 -5
- data/lib/action_controller/metal/data_streaming.rb +20 -18
- 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 +8 -0
- data/lib/action_controller/metal/head.rb +9 -7
- data/lib/action_controller/metal/helpers.rb +3 -14
- data/lib/action_controller/metal/http_authentication.rb +15 -9
- data/lib/action_controller/metal/implicit_render.rb +5 -3
- data/lib/action_controller/metal/instrumentation.rb +8 -1
- data/lib/action_controller/metal/live.rb +25 -1
- data/lib/action_controller/metal/mime_responds.rb +2 -2
- data/lib/action_controller/metal/params_wrapper.rb +4 -2
- data/lib/action_controller/metal/permissions_policy.rb +2 -2
- data/lib/action_controller/metal/redirecting.rb +29 -8
- data/lib/action_controller/metal/renderers.rb +4 -4
- data/lib/action_controller/metal/rendering.rb +114 -9
- data/lib/action_controller/metal/request_forgery_protection.rb +144 -53
- data/lib/action_controller/metal/rescue.rb +6 -3
- data/lib/action_controller/metal/streaming.rb +71 -31
- data/lib/action_controller/metal/strong_parameters.rb +200 -103
- data/lib/action_controller/metal/url_for.rb +9 -4
- data/lib/action_controller/metal.rb +79 -21
- data/lib/action_controller/railtie.rb +24 -10
- data/lib/action_controller/renderer.rb +99 -85
- data/lib/action_controller/test_case.rb +18 -8
- data/lib/action_controller.rb +13 -3
- data/lib/action_dispatch/constants.rb +32 -0
- data/lib/action_dispatch/deprecator.rb +7 -0
- data/lib/action_dispatch/http/cache.rb +9 -11
- data/lib/action_dispatch/http/content_security_policy.rb +35 -13
- data/lib/action_dispatch/http/filter_parameters.rb +23 -32
- data/lib/action_dispatch/http/headers.rb +3 -1
- data/lib/action_dispatch/http/mime_negotiation.rb +22 -22
- data/lib/action_dispatch/http/mime_type.rb +37 -11
- data/lib/action_dispatch/http/mime_types.rb +3 -1
- data/lib/action_dispatch/http/parameters.rb +1 -1
- data/lib/action_dispatch/http/permissions_policy.rb +38 -23
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +85 -32
- data/lib/action_dispatch/http/response.rb +80 -63
- data/lib/action_dispatch/http/upload.rb +15 -2
- data/lib/action_dispatch/journey/formatter.rb +8 -2
- data/lib/action_dispatch/journey/path/pattern.rb +14 -14
- data/lib/action_dispatch/journey/route.rb +3 -2
- data/lib/action_dispatch/journey/router.rb +9 -8
- data/lib/action_dispatch/journey/routes.rb +2 -2
- data/lib/action_dispatch/log_subscriber.rb +23 -0
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -6
- 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 +108 -117
- data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -25
- data/lib/action_dispatch/middleware/debug_locks.rb +4 -1
- data/lib/action_dispatch/middleware/debug_view.rb +7 -2
- data/lib/action_dispatch/middleware/exception_wrapper.rb +186 -27
- data/lib/action_dispatch/middleware/executor.rb +7 -1
- data/lib/action_dispatch/middleware/flash.rb +7 -0
- data/lib/action_dispatch/middleware/host_authorization.rb +18 -8
- 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 +21 -20
- data/lib/action_dispatch/middleware/request_id.rb +4 -2
- data/lib/action_dispatch/middleware/server_timing.rb +4 -4
- data/lib/action_dispatch/middleware/session/abstract_store.rb +5 -0
- data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
- data/lib/action_dispatch/middleware/session/cookie_store.rb +11 -5
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
- data/lib/action_dispatch/middleware/show_exceptions.rb +39 -22
- data/lib/action_dispatch/middleware/ssl.rb +18 -6
- data/lib/action_dispatch/middleware/stack.rb +7 -2
- data/lib/action_dispatch/middleware/static.rb +14 -10
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -3
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -3
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
- 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 +59 -41
- data/lib/action_dispatch/railtie.rb +13 -4
- data/lib/action_dispatch/request/session.rb +16 -6
- data/lib/action_dispatch/request/utils.rb +8 -3
- data/lib/action_dispatch/routing/inspector.rb +54 -6
- data/lib/action_dispatch/routing/mapper.rb +97 -26
- data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
- data/lib/action_dispatch/routing/redirection.rb +15 -6
- data/lib/action_dispatch/routing/route_set.rb +53 -23
- data/lib/action_dispatch/routing/routes_proxy.rb +10 -15
- data/lib/action_dispatch/routing/url_for.rb +26 -22
- data/lib/action_dispatch/routing.rb +7 -7
- data/lib/action_dispatch/system_test_case.rb +3 -3
- data/lib/action_dispatch/system_testing/browser.rb +25 -19
- data/lib/action_dispatch/system_testing/driver.rb +15 -23
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +27 -16
- 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 +67 -28
- data/lib/action_dispatch/testing/assertions.rb +3 -1
- data/lib/action_dispatch/testing/integration.rb +27 -17
- data/lib/action_dispatch/testing/request_encoder.rb +4 -1
- data/lib/action_dispatch/testing/test_process.rb +4 -3
- data/lib/action_dispatch/testing/test_request.rb +1 -1
- data/lib/action_dispatch/testing/test_response.rb +23 -9
- data/lib/action_dispatch.rb +41 -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 +68 -32
@@ -51,7 +51,7 @@ module ActionDispatch
|
|
51
51
|
# driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
|
52
52
|
# end
|
53
53
|
#
|
54
|
-
# By default,
|
54
|
+
# By default, +ActionDispatch::SystemTestCase+ is driven by the
|
55
55
|
# Selenium driver, with the Chrome browser, and a browser size of 1400x1400.
|
56
56
|
#
|
57
57
|
# Changing the driver configuration options is easy. Let's say you want to use
|
@@ -106,8 +106,8 @@ module ActionDispatch
|
|
106
106
|
# end
|
107
107
|
# end
|
108
108
|
#
|
109
|
-
# Because
|
110
|
-
# and Rails, any driver that is supported by Capybara is supported by system
|
109
|
+
# Because +ActionDispatch::SystemTestCase+ is a shim between Capybara
|
110
|
+
# and \Rails, any driver that is supported by Capybara is supported by system
|
111
111
|
# tests as long as you include the required gems and files.
|
112
112
|
class SystemTestCase < ActiveSupport::TestCase
|
113
113
|
include Capybara::DSL
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module ActionDispatch
|
4
4
|
module SystemTesting
|
5
5
|
class Browser # :nodoc:
|
6
|
-
attr_reader :name
|
6
|
+
attr_reader :name
|
7
7
|
|
8
8
|
def initialize(name)
|
9
9
|
@name = name
|
@@ -21,35 +21,32 @@ module ActionDispatch
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
+
def options
|
25
|
+
@options ||=
|
26
|
+
case type
|
27
|
+
when :chrome
|
28
|
+
::Selenium::WebDriver::Chrome::Options.new
|
29
|
+
when :firefox
|
30
|
+
::Selenium::WebDriver::Firefox::Options.new
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
24
34
|
def configure
|
25
|
-
|
26
|
-
yield options if block_given? && options
|
35
|
+
yield options if block_given?
|
27
36
|
end
|
28
37
|
|
29
|
-
# driver_path
|
30
|
-
#
|
31
|
-
# update the webdriver once and avoid race conditions when using
|
32
|
-
# parallel tests.
|
38
|
+
# driver_path is lazily initialized by default. Eagerly set it to
|
39
|
+
# avoid race conditions when using parallel tests.
|
33
40
|
def preload
|
34
41
|
case type
|
35
42
|
when :chrome
|
36
|
-
::Selenium::WebDriver::Chrome
|
43
|
+
resolve_driver_path(::Selenium::WebDriver::Chrome)
|
37
44
|
when :firefox
|
38
|
-
::Selenium::WebDriver::Firefox
|
45
|
+
resolve_driver_path(::Selenium::WebDriver::Firefox)
|
39
46
|
end
|
40
47
|
end
|
41
48
|
|
42
49
|
private
|
43
|
-
def initialize_options
|
44
|
-
@options ||=
|
45
|
-
case type
|
46
|
-
when :chrome
|
47
|
-
::Selenium::WebDriver::Chrome::Options.new
|
48
|
-
when :firefox
|
49
|
-
::Selenium::WebDriver::Firefox::Options.new
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
50
|
def set_default_options
|
54
51
|
case name
|
55
52
|
when :headless_chrome
|
@@ -71,6 +68,15 @@ module ActionDispatch
|
|
71
68
|
capabilities.add_argument("-headless")
|
72
69
|
end
|
73
70
|
end
|
71
|
+
|
72
|
+
def resolve_driver_path(namespace)
|
73
|
+
# The path method has been deprecated in 4.20.0
|
74
|
+
if Gem::Version.new(::Selenium::WebDriver::VERSION) >= Gem::Version.new("4.20.0")
|
75
|
+
namespace::Service.driver_path = ::Selenium::WebDriver::DriverFinder.new(options, namespace::Service.new).driver_path
|
76
|
+
else
|
77
|
+
namespace::Service.driver_path = ::Selenium::WebDriver::DriverFinder.path(options, namespace::Service)
|
78
|
+
end
|
79
|
+
end
|
74
80
|
end
|
75
81
|
end
|
76
82
|
end
|
@@ -12,19 +12,11 @@ module ActionDispatch
|
|
12
12
|
@name = @options.delete(:name) || driver_type
|
13
13
|
@capabilities = capabilities
|
14
14
|
|
15
|
-
if [:poltergeist, :webkit].include?(driver_type)
|
16
|
-
ActiveSupport::Deprecation.warn <<~MSG.squish
|
17
|
-
Poltergeist and capybara-webkit are not maintained already.
|
18
|
-
Driver registration of :poltergeist or :webkit is deprecated and will be removed in Rails 7.1.
|
19
|
-
You can still use :selenium, and also :cuprite is available for alternative to Poltergeist.
|
20
|
-
MSG
|
21
|
-
end
|
22
|
-
|
23
15
|
if driver_type == :selenium
|
24
16
|
gem "selenium-webdriver", ">= 4.0.0"
|
25
17
|
require "selenium/webdriver"
|
26
18
|
@browser = Browser.new(options[:using])
|
27
|
-
@browser.preload
|
19
|
+
@browser.preload unless @options[:browser] == :remote
|
28
20
|
else
|
29
21
|
@browser = nil
|
30
22
|
end
|
@@ -38,7 +30,7 @@ module ActionDispatch
|
|
38
30
|
|
39
31
|
private
|
40
32
|
def registerable?
|
41
|
-
[:selenium, :
|
33
|
+
[:selenium, :cuprite, :rack_test, :playwright].include?(@driver_type)
|
42
34
|
end
|
43
35
|
|
44
36
|
def register
|
@@ -47,16 +39,15 @@ module ActionDispatch
|
|
47
39
|
Capybara.register_driver name do |app|
|
48
40
|
case @driver_type
|
49
41
|
when :selenium then register_selenium(app)
|
50
|
-
when :poltergeist then register_poltergeist(app)
|
51
|
-
when :webkit then register_webkit(app)
|
52
42
|
when :cuprite then register_cuprite(app)
|
53
43
|
when :rack_test then register_rack_test(app)
|
44
|
+
when :playwright then register_playwright(app)
|
54
45
|
end
|
55
46
|
end
|
56
47
|
end
|
57
48
|
|
58
49
|
def browser_options
|
59
|
-
@options.merge(
|
50
|
+
@options.merge(options: @browser.options).compact
|
60
51
|
end
|
61
52
|
|
62
53
|
def register_selenium(app)
|
@@ -65,16 +56,6 @@ module ActionDispatch
|
|
65
56
|
end
|
66
57
|
end
|
67
58
|
|
68
|
-
def register_poltergeist(app)
|
69
|
-
Capybara::Poltergeist::Driver.new(app, @options.merge(window_size: @screen_size))
|
70
|
-
end
|
71
|
-
|
72
|
-
def register_webkit(app)
|
73
|
-
Capybara::Webkit::Driver.new(app, Capybara::Webkit::Configuration.to_hash.merge(@options)).tap do |driver|
|
74
|
-
driver.resize_window_to(driver.current_window_handle, *@screen_size)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
59
|
def register_cuprite(app)
|
79
60
|
Capybara::Cuprite::Driver.new(app, @options.merge(window_size: @screen_size))
|
80
61
|
end
|
@@ -83,6 +64,17 @@ module ActionDispatch
|
|
83
64
|
Capybara::RackTest::Driver.new(app, respect_data_method: true, **@options)
|
84
65
|
end
|
85
66
|
|
67
|
+
def register_playwright(app)
|
68
|
+
screen = { width: @screen_size[0], height: @screen_size[1] } if @screen_size
|
69
|
+
options = {
|
70
|
+
screen: screen,
|
71
|
+
viewport: screen,
|
72
|
+
**@options
|
73
|
+
}.compact
|
74
|
+
|
75
|
+
Capybara::Playwright::Driver.new(app, **options)
|
76
|
+
end
|
77
|
+
|
86
78
|
def setup
|
87
79
|
Capybara.current_driver = name
|
88
80
|
end
|
@@ -13,28 +13,28 @@ 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 screenshot will be displayed in your console, if supported.
|
17
|
-
#
|
18
16
|
# The default screenshots directory is +tmp/screenshots+ but you can set a different
|
19
17
|
# one with +Capybara.save_path+
|
20
18
|
#
|
21
|
-
# You can
|
22
|
-
# save the HTML from the page that is being screenshotted
|
23
|
-
# 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
|
24
22
|
#
|
25
|
-
# You can
|
26
|
-
# 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:
|
27
25
|
# * [+simple+ (default)] Only displays the screenshot path.
|
28
26
|
# This is the default value.
|
29
27
|
# * [+inline+] Display the screenshot in the terminal using the
|
30
28
|
# iTerm image protocol (https://iterm2.com/documentation-images.html).
|
31
29
|
# * [+artifact+] Display the screenshot in the terminal, using the terminal
|
32
30
|
# artifact format (https://buildkite.github.io/terminal-to-html/inline-images/).
|
33
|
-
def take_screenshot
|
31
|
+
def take_screenshot(html: false, screenshot: nil)
|
32
|
+
showing_html = html || html_from_env?
|
33
|
+
|
34
34
|
increment_unique
|
35
|
-
save_html if
|
35
|
+
save_html if showing_html
|
36
36
|
save_image
|
37
|
-
|
37
|
+
show display_image(html: showing_html, screenshot_output: screenshot)
|
38
38
|
end
|
39
39
|
|
40
40
|
# Takes a screenshot of the current page in the browser if the test
|
@@ -42,13 +42,16 @@ module ActionDispatch
|
|
42
42
|
#
|
43
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
|
@@ -87,6 +90,10 @@ module ActionDispatch
|
|
87
90
|
"#{absolute_path}.png"
|
88
91
|
end
|
89
92
|
|
93
|
+
def relative_image_path
|
94
|
+
"#{absolute_path.relative_path_from(Rails.root)}.png"
|
95
|
+
end
|
96
|
+
|
90
97
|
def absolute_html_path
|
91
98
|
"#{absolute_path}.html"
|
92
99
|
end
|
@@ -109,11 +116,15 @@ module ActionDispatch
|
|
109
116
|
output_type
|
110
117
|
end
|
111
118
|
|
112
|
-
def
|
119
|
+
def show(img)
|
120
|
+
puts img
|
121
|
+
end
|
122
|
+
|
123
|
+
def display_image(html:, screenshot_output:)
|
113
124
|
message = +"[Screenshot Image]: #{image_path}\n"
|
114
|
-
message << +"[Screenshot HTML]: #{html_path}\n" if
|
125
|
+
message << +"[Screenshot HTML]: #{html_path}\n" if html
|
115
126
|
|
116
|
-
case output_type
|
127
|
+
case screenshot_output || output_type
|
117
128
|
when "artifact"
|
118
129
|
message << "\e]1338;url=artifact://#{absolute_image_path}\a\n"
|
119
130
|
when "inline"
|
@@ -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
|
@@ -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
|
@@ -190,6 +198,37 @@ module ActionDispatch
|
|
190
198
|
ruby2_keywords(:method_missing)
|
191
199
|
|
192
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
|
+
|
193
232
|
# Recognizes the route for a given path.
|
194
233
|
def recognized_request_for(path, extras = {}, msg)
|
195
234
|
if path.is_a?(Hash)
|
@@ -202,7 +241,7 @@ module ActionDispatch
|
|
202
241
|
controller = @controller if defined?(@controller)
|
203
242
|
request = ActionController::TestRequest.create controller&.class
|
204
243
|
|
205
|
-
if
|
244
|
+
if path.include?("://")
|
206
245
|
fail_on(URI::InvalidURIError, msg) do
|
207
246
|
uri = URI.parse(path)
|
208
247
|
request.env["rack.url_scheme"] = uri.scheme || "http"
|
@@ -6,6 +6,8 @@ require "action_dispatch/testing/assertions/routing"
|
|
6
6
|
|
7
7
|
module ActionDispatch
|
8
8
|
module Assertions
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
9
11
|
include ResponseAssertions
|
10
12
|
include RoutingAssertions
|
11
13
|
include Rails::Dom::Testing::Assertions
|
@@ -14,7 +16,7 @@ module ActionDispatch
|
|
14
16
|
@html_document ||= if @response.media_type&.end_with?("xml")
|
15
17
|
Nokogiri::XML::Document.parse(@response.body)
|
16
18
|
else
|
17
|
-
|
19
|
+
Rails::Dom::Testing.html_document.parse(@response.body)
|
18
20
|
end
|
19
21
|
end
|
20
22
|
end
|
@@ -3,7 +3,7 @@
|
|
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
|
|
@@ -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
|
@@ -206,9 +211,10 @@ module ActionDispatch
|
|
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
|
|
@@ -440,8 +449,9 @@ module ActionDispatch
|
|
440
449
|
# more completely than either unit or functional tests do, exercising the
|
441
450
|
# entire stack, from the dispatcher to the database.
|
442
451
|
#
|
443
|
-
# At its simplest, you simply extend <tt>IntegrationTest</tt> and write your
|
444
|
-
# 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:
|
445
455
|
#
|
446
456
|
# require "test_helper"
|
447
457
|
#
|
@@ -612,7 +622,7 @@ module ActionDispatch
|
|
612
622
|
# the request format to JSON unless overridden), sets the content type to
|
613
623
|
# "application/json" and encodes the parameters as JSON.
|
614
624
|
#
|
615
|
-
# Calling
|
625
|
+
# Calling TestResponse#parsed_body on the response parses the response body based on the
|
616
626
|
# last response MIME type.
|
617
627
|
#
|
618
628
|
# Out of the box, only <tt>:json</tt> is supported. But for any custom MIME
|
@@ -624,9 +634,9 @@ module ActionDispatch
|
|
624
634
|
#
|
625
635
|
# Where +param_encoder+ defines how the params should be encoded and
|
626
636
|
# +response_parser+ defines how the response body should be parsed through
|
627
|
-
#
|
637
|
+
# TestResponse#parsed_body.
|
628
638
|
#
|
629
|
-
# Consult the Rails Testing Guide for more.
|
639
|
+
# Consult the {Rails Testing Guide}[https://guides.rubyonrails.org/testing.html] for more.
|
630
640
|
|
631
641
|
class IntegrationTest < ActiveSupport::TestCase
|
632
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,21 +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
|
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
20
|
if self.class.file_fixture_path && !File.exist?(path)
|
21
21
|
path = file_fixture(path)
|
22
22
|
end
|
23
23
|
|
24
24
|
Rack::Test::UploadedFile.new(path, mime_type, binary)
|
25
25
|
end
|
26
|
+
alias_method :fixture_file_upload, :file_fixture_upload
|
26
27
|
end
|
27
28
|
|
28
29
|
include FixtureFile
|