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.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +495 -257
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -4
  5. data/lib/abstract_controller/base.rb +20 -11
  6. data/lib/abstract_controller/caching/fragments.rb +2 -0
  7. data/lib/abstract_controller/callbacks.rb +31 -6
  8. data/lib/abstract_controller/deprecator.rb +7 -0
  9. data/lib/abstract_controller/helpers.rb +75 -28
  10. data/lib/abstract_controller/railties/routes_helpers.rb +1 -16
  11. data/lib/abstract_controller/rendering.rb +12 -14
  12. data/lib/abstract_controller/translation.rb +11 -6
  13. data/lib/abstract_controller/url_for.rb +2 -0
  14. data/lib/abstract_controller.rb +6 -0
  15. data/lib/action_controller/api.rb +6 -4
  16. data/lib/action_controller/base.rb +3 -17
  17. data/lib/action_controller/caching.rb +2 -0
  18. data/lib/action_controller/deprecator.rb +7 -0
  19. data/lib/action_controller/form_builder.rb +2 -0
  20. data/lib/action_controller/log_subscriber.rb +16 -4
  21. data/lib/action_controller/metal/basic_implicit_render.rb +3 -1
  22. data/lib/action_controller/metal/conditional_get.rb +121 -123
  23. data/lib/action_controller/metal/content_security_policy.rb +5 -5
  24. data/lib/action_controller/metal/data_streaming.rb +20 -18
  25. data/lib/action_controller/metal/default_headers.rb +2 -0
  26. data/lib/action_controller/metal/etag_with_flash.rb +3 -1
  27. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
  28. data/lib/action_controller/metal/exceptions.rb +8 -0
  29. data/lib/action_controller/metal/head.rb +9 -7
  30. data/lib/action_controller/metal/helpers.rb +3 -14
  31. data/lib/action_controller/metal/http_authentication.rb +15 -9
  32. data/lib/action_controller/metal/implicit_render.rb +5 -3
  33. data/lib/action_controller/metal/instrumentation.rb +8 -1
  34. data/lib/action_controller/metal/live.rb +25 -1
  35. data/lib/action_controller/metal/mime_responds.rb +2 -2
  36. data/lib/action_controller/metal/params_wrapper.rb +4 -2
  37. data/lib/action_controller/metal/permissions_policy.rb +2 -2
  38. data/lib/action_controller/metal/redirecting.rb +29 -8
  39. data/lib/action_controller/metal/renderers.rb +4 -4
  40. data/lib/action_controller/metal/rendering.rb +114 -9
  41. data/lib/action_controller/metal/request_forgery_protection.rb +144 -53
  42. data/lib/action_controller/metal/rescue.rb +6 -3
  43. data/lib/action_controller/metal/streaming.rb +71 -31
  44. data/lib/action_controller/metal/strong_parameters.rb +200 -103
  45. data/lib/action_controller/metal/url_for.rb +9 -4
  46. data/lib/action_controller/metal.rb +79 -21
  47. data/lib/action_controller/railtie.rb +24 -10
  48. data/lib/action_controller/renderer.rb +99 -85
  49. data/lib/action_controller/test_case.rb +18 -8
  50. data/lib/action_controller.rb +13 -3
  51. data/lib/action_dispatch/constants.rb +32 -0
  52. data/lib/action_dispatch/deprecator.rb +7 -0
  53. data/lib/action_dispatch/http/cache.rb +9 -11
  54. data/lib/action_dispatch/http/content_security_policy.rb +35 -13
  55. data/lib/action_dispatch/http/filter_parameters.rb +23 -32
  56. data/lib/action_dispatch/http/headers.rb +3 -1
  57. data/lib/action_dispatch/http/mime_negotiation.rb +22 -22
  58. data/lib/action_dispatch/http/mime_type.rb +37 -11
  59. data/lib/action_dispatch/http/mime_types.rb +3 -1
  60. data/lib/action_dispatch/http/parameters.rb +1 -1
  61. data/lib/action_dispatch/http/permissions_policy.rb +38 -23
  62. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  63. data/lib/action_dispatch/http/request.rb +85 -32
  64. data/lib/action_dispatch/http/response.rb +80 -63
  65. data/lib/action_dispatch/http/upload.rb +15 -2
  66. data/lib/action_dispatch/journey/formatter.rb +8 -2
  67. data/lib/action_dispatch/journey/path/pattern.rb +14 -14
  68. data/lib/action_dispatch/journey/route.rb +3 -2
  69. data/lib/action_dispatch/journey/router.rb +9 -8
  70. data/lib/action_dispatch/journey/routes.rb +2 -2
  71. data/lib/action_dispatch/log_subscriber.rb +23 -0
  72. data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -6
  73. data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
  74. data/lib/action_dispatch/middleware/callbacks.rb +2 -0
  75. data/lib/action_dispatch/middleware/cookies.rb +108 -117
  76. data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -25
  77. data/lib/action_dispatch/middleware/debug_locks.rb +4 -1
  78. data/lib/action_dispatch/middleware/debug_view.rb +7 -2
  79. data/lib/action_dispatch/middleware/exception_wrapper.rb +186 -27
  80. data/lib/action_dispatch/middleware/executor.rb +7 -1
  81. data/lib/action_dispatch/middleware/flash.rb +7 -0
  82. data/lib/action_dispatch/middleware/host_authorization.rb +18 -8
  83. data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
  84. data/lib/action_dispatch/middleware/reloader.rb +7 -5
  85. data/lib/action_dispatch/middleware/remote_ip.rb +21 -20
  86. data/lib/action_dispatch/middleware/request_id.rb +4 -2
  87. data/lib/action_dispatch/middleware/server_timing.rb +4 -4
  88. data/lib/action_dispatch/middleware/session/abstract_store.rb +5 -0
  89. data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
  90. data/lib/action_dispatch/middleware/session/cookie_store.rb +11 -5
  91. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
  92. data/lib/action_dispatch/middleware/show_exceptions.rb +39 -22
  93. data/lib/action_dispatch/middleware/ssl.rb +18 -6
  94. data/lib/action_dispatch/middleware/stack.rb +7 -2
  95. data/lib/action_dispatch/middleware/static.rb +14 -10
  96. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  97. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
  98. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  99. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -3
  100. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -3
  101. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
  102. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  103. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
  104. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
  105. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
  106. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  107. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
  108. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  109. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  110. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  111. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -41
  112. data/lib/action_dispatch/railtie.rb +13 -4
  113. data/lib/action_dispatch/request/session.rb +16 -6
  114. data/lib/action_dispatch/request/utils.rb +8 -3
  115. data/lib/action_dispatch/routing/inspector.rb +54 -6
  116. data/lib/action_dispatch/routing/mapper.rb +97 -26
  117. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
  118. data/lib/action_dispatch/routing/redirection.rb +15 -6
  119. data/lib/action_dispatch/routing/route_set.rb +53 -23
  120. data/lib/action_dispatch/routing/routes_proxy.rb +10 -15
  121. data/lib/action_dispatch/routing/url_for.rb +26 -22
  122. data/lib/action_dispatch/routing.rb +7 -7
  123. data/lib/action_dispatch/system_test_case.rb +3 -3
  124. data/lib/action_dispatch/system_testing/browser.rb +25 -19
  125. data/lib/action_dispatch/system_testing/driver.rb +15 -23
  126. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +27 -16
  127. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  128. data/lib/action_dispatch/testing/assertions/response.rb +14 -7
  129. data/lib/action_dispatch/testing/assertions/routing.rb +67 -28
  130. data/lib/action_dispatch/testing/assertions.rb +3 -1
  131. data/lib/action_dispatch/testing/integration.rb +27 -17
  132. data/lib/action_dispatch/testing/request_encoder.rb +4 -1
  133. data/lib/action_dispatch/testing/test_process.rb +4 -3
  134. data/lib/action_dispatch/testing/test_request.rb +1 -1
  135. data/lib/action_dispatch/testing/test_response.rb +23 -9
  136. data/lib/action_dispatch.rb +41 -4
  137. data/lib/action_pack/gem_version.rb +4 -4
  138. data/lib/action_pack/version.rb +1 -1
  139. data/lib/action_pack.rb +1 -1
  140. 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, <tt>ActionDispatch::SystemTestCase</tt> is driven by the
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 <tt>ActionDispatch::SystemTestCase</tt> is a shim between Capybara
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, :options
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
- initialize_options
26
- yield options if block_given? && options
35
+ yield options if block_given?
27
36
  end
28
37
 
29
- # driver_path can be configured as a proc. The webdrivers gem uses this
30
- # proc to update web drivers. Running this proc early allows us to only
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::Service.driver_path&.call
43
+ resolve_driver_path(::Selenium::WebDriver::Chrome)
37
44
  when :firefox
38
- ::Selenium::WebDriver::Firefox::Service.driver_path&.call
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, :poltergeist, :webkit, :cuprite, :rack_test].include?(@driver_type)
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(capabilities: @browser.options).compact
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 set the +RAILS_SYSTEM_TESTING_SCREENSHOT_HTML+ environment variable to
22
- # save the HTML from the page that is being screenshotted so you can investigate the
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 set the +RAILS_SYSTEM_TESTING_SCREENSHOT+ environment variable to
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 save_html?
35
+ save_html if showing_html
36
36
  save_image
37
- puts display_image
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
- take_screenshot if failed? && supports_screenshot? && Capybara::Session.instance_created?
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 save_html?
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.tr("/\\", "--")
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 display_image
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 save_html?
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"
@@ -36,7 +36,7 @@ module ActionDispatch
36
36
 
37
37
  private
38
38
  def code_from_name(name)
39
- GENERIC_RESPONSE_CODES[name] || Rack::Utils::SYMBOL_TO_STATUS_CODE[name]
39
+ GENERIC_RESPONSE_CODES[name] || Rack::Utils.status_code(name)
40
40
  end
41
41
 
42
42
  def name_from_code(code)
@@ -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.keys.include?(type)
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
- def assert_redirected_to(options = {}, message = nil)
54
- assert_response(:redirect, message)
55
- return true if options === @response.location
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(options)
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.values.include?("#{code_or_name}?".to_sym)
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 %r{://}.match?(expected_path)
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, @routes = @routes, ActionDispatch::Routing::RouteSet.new
155
- if defined?(@controller) && @controller
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
- @routes = old_routes
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 %r{://}.match?(path)
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
- Nokogiri::HTML::Document.parse(@response.body)
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 "minitest"
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
- def follow_redirect!(**args)
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
- public_send(method, response.location, **args)
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
- # IntegrationTest#open_session, rather than instantiating
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 +#get+, +#post+, or other standard
210
- # HTTP methods in integration tests. +#process+ is only required when using a
211
- # request method that doesn't have a method defined in the integration tests.
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 %r{://}.match?(path)
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 tests
444
- # using the get/post methods:
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 +parsed_body+ on the response parses the response body based on the
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
- # +parsed_body+.
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 :json, response_parser: -> body { JSON.parse(body) }
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: fixture_file_upload('david.png', 'image/png') }
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: fixture_file_upload('david.png', 'image/png', :binary) }
19
- def fixture_file_upload(path, mime_type = nil, binary = false)
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
@@ -32,7 +32,7 @@ module ActionDispatch
32
32
  end
33
33
 
34
34
  def port=(number)
35
- set_header("SERVER_PORT", number.to_i)
35
+ set_header("SERVER_PORT", number)
36
36
  end
37
37
 
38
38
  def request_uri=(uri)