actionpack 5.2.8.1 → 6.0.6

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.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +270 -347
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/abstract_controller/base.rb +4 -3
  6. data/lib/abstract_controller/caching/fragments.rb +6 -22
  7. data/lib/abstract_controller/caching.rb +1 -1
  8. data/lib/abstract_controller/callbacks.rb +12 -0
  9. data/lib/abstract_controller/collector.rb +1 -2
  10. data/lib/abstract_controller/helpers.rb +7 -6
  11. data/lib/abstract_controller/railties/routes_helpers.rb +1 -1
  12. data/lib/abstract_controller/translation.rb +4 -4
  13. data/lib/action_controller/api.rb +2 -1
  14. data/lib/action_controller/base.rb +2 -7
  15. data/lib/action_controller/caching.rb +1 -2
  16. data/lib/action_controller/log_subscriber.rb +8 -5
  17. data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
  18. data/lib/action_controller/metal/conditional_get.rb +9 -3
  19. data/lib/action_controller/metal/content_security_policy.rb +0 -1
  20. data/lib/action_controller/metal/data_streaming.rb +5 -6
  21. data/lib/action_controller/metal/default_headers.rb +17 -0
  22. data/lib/action_controller/metal/etag_with_template_digest.rb +1 -1
  23. data/lib/action_controller/metal/exceptions.rb +23 -2
  24. data/lib/action_controller/metal/flash.rb +5 -5
  25. data/lib/action_controller/metal/force_ssl.rb +15 -56
  26. data/lib/action_controller/metal/head.rb +1 -1
  27. data/lib/action_controller/metal/helpers.rb +3 -4
  28. data/lib/action_controller/metal/http_authentication.rb +20 -21
  29. data/lib/action_controller/metal/implicit_render.rb +4 -14
  30. data/lib/action_controller/metal/instrumentation.rb +3 -6
  31. data/lib/action_controller/metal/live.rb +29 -31
  32. data/lib/action_controller/metal/mime_responds.rb +13 -2
  33. data/lib/action_controller/metal/params_wrapper.rb +18 -14
  34. data/lib/action_controller/metal/redirecting.rb +5 -5
  35. data/lib/action_controller/metal/renderers.rb +4 -4
  36. data/lib/action_controller/metal/rendering.rb +2 -3
  37. data/lib/action_controller/metal/request_forgery_protection.rb +25 -48
  38. data/lib/action_controller/metal/streaming.rb +0 -1
  39. data/lib/action_controller/metal/strong_parameters.rb +65 -44
  40. data/lib/action_controller/metal/url_for.rb +1 -1
  41. data/lib/action_controller/metal.rb +8 -6
  42. data/lib/action_controller/railties/helpers.rb +1 -1
  43. data/lib/action_controller/renderer.rb +17 -3
  44. data/lib/action_controller/template_assertions.rb +1 -1
  45. data/lib/action_controller/test_case.rb +7 -8
  46. data/lib/action_controller.rb +5 -1
  47. data/lib/action_dispatch/http/cache.rb +14 -11
  48. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  49. data/lib/action_dispatch/http/content_security_policy.rb +28 -17
  50. data/lib/action_dispatch/http/filter_parameters.rb +8 -7
  51. data/lib/action_dispatch/http/filter_redirect.rb +1 -2
  52. data/lib/action_dispatch/http/headers.rb +1 -2
  53. data/lib/action_dispatch/http/mime_negotiation.rb +13 -6
  54. data/lib/action_dispatch/http/mime_type.rb +14 -8
  55. data/lib/action_dispatch/http/parameter_filter.rb +5 -79
  56. data/lib/action_dispatch/http/parameters.rb +15 -6
  57. data/lib/action_dispatch/http/request.rb +21 -14
  58. data/lib/action_dispatch/http/response.rb +40 -21
  59. data/lib/action_dispatch/http/upload.rb +9 -1
  60. data/lib/action_dispatch/http/url.rb +81 -82
  61. data/lib/action_dispatch/journey/formatter.rb +2 -3
  62. data/lib/action_dispatch/journey/gtg/builder.rb +0 -1
  63. data/lib/action_dispatch/journey/gtg/transition_table.rb +0 -1
  64. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -2
  65. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -1
  66. data/lib/action_dispatch/journey/nodes/node.rb +9 -8
  67. data/lib/action_dispatch/journey/path/pattern.rb +6 -3
  68. data/lib/action_dispatch/journey/route.rb +5 -4
  69. data/lib/action_dispatch/journey/router/utils.rb +10 -10
  70. data/lib/action_dispatch/journey/router.rb +0 -4
  71. data/lib/action_dispatch/journey/routes.rb +0 -2
  72. data/lib/action_dispatch/journey/scanner.rb +10 -4
  73. data/lib/action_dispatch/journey/visitors.rb +1 -4
  74. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  75. data/lib/action_dispatch/middleware/callbacks.rb +2 -4
  76. data/lib/action_dispatch/middleware/cookies.rb +62 -78
  77. data/lib/action_dispatch/middleware/debug_exceptions.rb +45 -61
  78. data/lib/action_dispatch/middleware/debug_locks.rb +5 -5
  79. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  80. data/lib/action_dispatch/middleware/exception_wrapper.rb +49 -16
  81. data/lib/action_dispatch/middleware/flash.rb +1 -1
  82. data/lib/action_dispatch/middleware/host_authorization.rb +121 -0
  83. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
  84. data/lib/action_dispatch/middleware/remote_ip.rb +9 -12
  85. data/lib/action_dispatch/middleware/request_id.rb +2 -2
  86. data/lib/action_dispatch/middleware/session/abstract_store.rb +0 -1
  87. data/lib/action_dispatch/middleware/session/cookie_store.rb +1 -7
  88. data/lib/action_dispatch/middleware/show_exceptions.rb +1 -2
  89. data/lib/action_dispatch/middleware/ssl.rb +8 -8
  90. data/lib/action_dispatch/middleware/stack.rb +38 -2
  91. data/lib/action_dispatch/middleware/static.rb +6 -7
  92. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  93. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  94. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +3 -1
  95. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  96. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
  97. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
  98. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  99. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  100. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +26 -4
  101. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  102. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +7 -4
  103. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +5 -2
  104. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +4 -0
  105. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  106. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  107. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
  108. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
  109. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +2 -2
  110. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +3 -0
  111. data/lib/action_dispatch/railtie.rb +7 -2
  112. data/lib/action_dispatch/request/session.rb +9 -2
  113. data/lib/action_dispatch/routing/inspector.rb +97 -50
  114. data/lib/action_dispatch/routing/mapper.rb +63 -42
  115. data/lib/action_dispatch/routing/polymorphic_routes.rb +3 -6
  116. data/lib/action_dispatch/routing/route_set.rb +25 -31
  117. data/lib/action_dispatch/routing/url_for.rb +2 -2
  118. data/lib/action_dispatch/routing.rb +21 -20
  119. data/lib/action_dispatch/system_test_case.rb +44 -6
  120. data/lib/action_dispatch/system_testing/browser.rb +38 -7
  121. data/lib/action_dispatch/system_testing/driver.rb +11 -2
  122. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +6 -5
  123. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +7 -6
  124. data/lib/action_dispatch/testing/assertion_response.rb +0 -1
  125. data/lib/action_dispatch/testing/assertions/response.rb +2 -3
  126. data/lib/action_dispatch/testing/assertions/routing.rb +15 -3
  127. data/lib/action_dispatch/testing/assertions.rb +1 -1
  128. data/lib/action_dispatch/testing/integration.rb +33 -12
  129. data/lib/action_dispatch/testing/request_encoder.rb +2 -2
  130. data/lib/action_dispatch/testing/test_process.rb +2 -2
  131. data/lib/action_dispatch/testing/test_response.rb +4 -32
  132. data/lib/action_dispatch.rb +7 -2
  133. data/lib/action_pack/gem_version.rb +4 -4
  134. data/lib/action_pack.rb +1 -1
  135. metadata +29 -15
  136. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -120,8 +120,7 @@ module ActionDispatch
120
120
  opts
121
121
  end
122
122
 
123
- # Returns the path component of a URL for the given record. It uses
124
- # <tt>polymorphic_url</tt> with <tt>routing_type: :path</tt>.
123
+ # Returns the path component of a URL for the given record.
125
124
  def polymorphic_path(record_or_hash_or_array, options = {})
126
125
  if Hash === record_or_hash_or_array
127
126
  options = record_or_hash_or_array.merge(options)
@@ -157,7 +156,6 @@ module ActionDispatch
157
156
  end
158
157
 
159
158
  private
160
-
161
159
  def polymorphic_url_for_action(action, record_or_hash, options)
162
160
  polymorphic_url(record_or_hash, options.merge(action: action))
163
161
  end
@@ -182,8 +180,8 @@ module ActionDispatch
182
180
  CACHE[type].fetch(action) { build action, type }
183
181
  end
184
182
 
185
- def self.url; CACHE["url".freeze][nil]; end
186
- def self.path; CACHE["path".freeze][nil]; end
183
+ def self.url; CACHE["url"][nil]; end
184
+ def self.path; CACHE["path"][nil]; end
187
185
 
188
186
  def self.build(action, type)
189
187
  prefix = action ? "#{action}_" : ""
@@ -328,7 +326,6 @@ module ActionDispatch
328
326
  end
329
327
 
330
328
  private
331
-
332
329
  def polymorphic_mapping(target, record)
333
330
  if record.respond_to?(:to_model)
334
331
  target._routes.polymorphic_mappings[record.to_model.model_name.name]
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "action_dispatch/journey"
4
4
  require "active_support/core_ext/object/to_query"
5
- require "active_support/core_ext/hash/slice"
6
5
  require "active_support/core_ext/module/redefine_method"
7
6
  require "active_support/core_ext/module/remove_method"
8
7
  require "active_support/core_ext/array/extract_options"
@@ -36,12 +35,11 @@ module ActionDispatch
36
35
  if @raise_on_name_error
37
36
  raise
38
37
  else
39
- return [404, { "X-Cascade" => "pass" }, []]
38
+ [404, { "X-Cascade" => "pass" }, []]
40
39
  end
41
40
  end
42
41
 
43
42
  private
44
-
45
43
  def controller(req)
46
44
  req.controller_class
47
45
  rescue NameError => e
@@ -60,7 +58,6 @@ module ActionDispatch
60
58
  end
61
59
 
62
60
  private
63
-
64
61
  def controller(_); @controller_class; end
65
62
  end
66
63
 
@@ -91,11 +88,11 @@ module ActionDispatch
91
88
 
92
89
  def clear!
93
90
  @path_helpers.each do |helper|
94
- @path_helpers_module.send :remove_method, helper
91
+ @path_helpers_module.remove_method helper
95
92
  end
96
93
 
97
94
  @url_helpers.each do |helper|
98
- @url_helpers_module.send :remove_method, helper
95
+ @url_helpers_module.remove_method helper
99
96
  end
100
97
 
101
98
  @routes.clear
@@ -109,8 +106,8 @@ module ActionDispatch
109
106
  url_name = :"#{name}_url"
110
107
 
111
108
  if routes.key? key
112
- @path_helpers_module.send :undef_method, path_name
113
- @url_helpers_module.send :undef_method, url_name
109
+ @path_helpers_module.undef_method path_name
110
+ @url_helpers_module.undef_method url_name
114
111
  end
115
112
  routes[key] = route
116
113
  define_url_helper @path_helpers_module, route, path_name, route.defaults, name, PATH
@@ -154,13 +151,13 @@ module ActionDispatch
154
151
  url_name = :"#{name}_url"
155
152
 
156
153
  @path_helpers_module.module_eval do
157
- define_method(path_name) do |*args|
154
+ redefine_method(path_name) do |*args|
158
155
  helper.call(self, args, true)
159
156
  end
160
157
  end
161
158
 
162
159
  @url_helpers_module.module_eval do
163
- define_method(url_name) do |*args|
160
+ redefine_method(url_name) do |*args|
164
161
  helper.call(self, args, false)
165
162
  end
166
163
  end
@@ -216,7 +213,6 @@ module ActionDispatch
216
213
  end
217
214
 
218
215
  private
219
-
220
216
  def optimized_helper(args)
221
217
  params = parameterize_args(args) do
222
218
  raise_generation_error(args)
@@ -246,7 +242,7 @@ module ActionDispatch
246
242
  missing_keys << missing_key
247
243
  }
248
244
  constraints = Hash[@route.requirements.merge(params).sort_by { |k, v| k.to_s }]
249
- message = "No route matches #{constraints.inspect}".dup
245
+ message = +"No route matches #{constraints.inspect}"
250
246
  message << ", missing required keys: #{missing_keys.sort.inspect}"
251
247
 
252
248
  raise ActionController::UrlGenerationError, message
@@ -318,23 +314,21 @@ module ActionDispatch
318
314
  #
319
315
  def define_url_helper(mod, route, name, opts, route_key, url_strategy)
320
316
  helper = UrlHelper.create(route, opts, route_key, url_strategy)
321
- mod.module_eval do
322
- define_method(name) do |*args|
323
- last = args.last
324
- options = \
325
- case last
326
- when Hash
327
- args.pop
328
- when ActionController::Parameters
329
- args.pop.to_h
330
- end
331
- helper.call self, args, options
332
- end
317
+ mod.define_method(name) do |*args|
318
+ last = args.last
319
+ options = \
320
+ case last
321
+ when Hash
322
+ args.pop
323
+ when ActionController::Parameters
324
+ args.pop.to_h
325
+ end
326
+ helper.call self, args, options
333
327
  end
334
328
  end
335
329
  end
336
330
 
337
- # strategy for building urls to send to the client
331
+ # strategy for building URLs to send to the client
338
332
  PATH = ->(options) { ActionDispatch::Http::URL.path_for(options) }
339
333
  UNKNOWN = ->(options) { ActionDispatch::Http::URL.url_for(options) }
340
334
 
@@ -378,7 +372,7 @@ module ActionDispatch
378
372
  @prepend = []
379
373
  @disable_clear_and_finalize = false
380
374
  @finalized = false
381
- @env_key = "ROUTES_#{object_id}_SCRIPT_NAME".freeze
375
+ @env_key = "ROUTES_#{object_id}_SCRIPT_NAME"
382
376
 
383
377
  @set = Journey::Routes.new
384
378
  @router = Journey::Router.new @set
@@ -585,7 +579,7 @@ module ActionDispatch
585
579
  "You may have defined two routes with the same name using the `:as` option, or " \
586
580
  "you may be overriding a route already defined by a resource with the same naming. " \
587
581
  "For the latter, you can restrict the routes created with `resources` as explained here: \n" \
588
- "http://guides.rubyonrails.org/routing.html#restricting-the-routes-created"
582
+ "https://guides.rubyonrails.org/routing.html#restricting-the-routes-created"
589
583
  end
590
584
 
591
585
  route = @set.add_route(name, mapping)
@@ -594,14 +588,14 @@ module ActionDispatch
594
588
  if route.segment_keys.include?(:controller)
595
589
  ActiveSupport::Deprecation.warn(<<-MSG.squish)
596
590
  Using a dynamic :controller segment in a route is deprecated and
597
- will be removed in Rails 6.0.
591
+ will be removed in Rails 6.1.
598
592
  MSG
599
593
  end
600
594
 
601
595
  if route.segment_keys.include?(:action)
602
596
  ActiveSupport::Deprecation.warn(<<-MSG.squish)
603
597
  Using a dynamic :action segment in a route is deprecated and
604
- will be removed in Rails 6.0.
598
+ will be removed in Rails 6.1.
605
599
  MSG
606
600
  end
607
601
 
@@ -730,7 +724,7 @@ module ActionDispatch
730
724
  # Remove leading slashes from controllers
731
725
  def normalize_controller!
732
726
  if controller
733
- if controller.start_with?("/".freeze)
727
+ if controller.start_with?("/")
734
728
  @options[:controller] = controller[1..-1]
735
729
  else
736
730
  @options[:controller] = controller
@@ -842,7 +836,7 @@ module ActionDispatch
842
836
 
843
837
  def recognize_path(path, environment = {})
844
838
  method = (environment[:method] || "GET").to_s.upcase
845
- path = Journey::Router::Utils.normalize_path(path) unless path =~ %r{://}
839
+ path = Journey::Router::Utils.normalize_path(path) unless %r{://}.match?(path)
846
840
  extras = environment[:extras] || {}
847
841
 
848
842
  begin
@@ -107,6 +107,7 @@ module ActionDispatch
107
107
  @_routes = nil
108
108
  super
109
109
  end
110
+ ruby2_keywords(:initialize) if respond_to?(:ruby2_keywords, true)
110
111
 
111
112
  # Hook overridden in controller to add request information
112
113
  # with +default_url_options+. Application logic should not
@@ -133,6 +134,7 @@ module ActionDispatch
133
134
  # <tt>ActionDispatch::Http::URL.tld_length</tt>, which in turn defaults to 1.
134
135
  # * <tt>:port</tt> - Optionally specify the port to connect to.
135
136
  # * <tt>:anchor</tt> - An anchor name to be appended to the path.
137
+ # * <tt>:params</tt> - The query parameters to be appended to the path.
136
138
  # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
137
139
  # * <tt>:script_name</tt> - Specifies application path relative to domain root. If provided, prepends application path.
138
140
  #
@@ -214,13 +216,11 @@ module ActionDispatch
214
216
  end
215
217
 
216
218
  protected
217
-
218
219
  def optimize_routes_generation?
219
220
  _routes.optimize_routes_generation? && default_url_options.empty?
220
221
  end
221
222
 
222
223
  private
223
-
224
224
  def _with_routes(routes) # :doc:
225
225
  old_routes, @_routes = @_routes, routes
226
226
  yield
@@ -74,8 +74,8 @@ module ActionDispatch
74
74
  # For routes that don't fit the <tt>resources</tt> mold, you can use the HTTP helper
75
75
  # methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
76
76
  #
77
- # get 'post/:id' => 'posts#show'
78
- # post 'post/:id' => 'posts#create_comment'
77
+ # get 'post/:id', to: 'posts#show'
78
+ # post 'post/:id', to: 'posts#create_comment'
79
79
  #
80
80
  # Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
81
81
  # URL will route to the <tt>show</tt> action.
@@ -83,7 +83,7 @@ module ActionDispatch
83
83
  # If your route needs to respond to more than one HTTP method (or all methods) then using the
84
84
  # <tt>:via</tt> option on <tt>match</tt> is preferable.
85
85
  #
86
- # match 'post/:id' => 'posts#show', via: [:get, :post]
86
+ # match 'post/:id', to: 'posts#show', via: [:get, :post]
87
87
  #
88
88
  # == Named routes
89
89
  #
@@ -94,7 +94,7 @@ module ActionDispatch
94
94
  # Example:
95
95
  #
96
96
  # # In config/routes.rb
97
- # get '/login' => 'accounts#login', as: 'login'
97
+ # get '/login', to: 'accounts#login', as: 'login'
98
98
  #
99
99
  # # With render, redirect_to, tests, etc.
100
100
  # redirect_to login_url
@@ -120,9 +120,9 @@ module ActionDispatch
120
120
  #
121
121
  # # In config/routes.rb
122
122
  # controller :blog do
123
- # get 'blog/show' => :list
124
- # get 'blog/delete' => :delete
125
- # get 'blog/edit' => :edit
123
+ # get 'blog/show', to: :list
124
+ # get 'blog/delete', to: :delete
125
+ # get 'blog/edit', to: :edit
126
126
  # end
127
127
  #
128
128
  # # provides named routes for show, delete, and edit
@@ -132,7 +132,7 @@ module ActionDispatch
132
132
  #
133
133
  # Routes can generate pretty URLs. For example:
134
134
  #
135
- # get '/articles/:year/:month/:day' => 'articles#find_by_id', constraints: {
135
+ # get '/articles/:year/:month/:day', to: 'articles#find_by_id', constraints: {
136
136
  # year: /\d{4}/,
137
137
  # month: /\d{1,2}/,
138
138
  # day: /\d{1,2}/
@@ -147,7 +147,7 @@ module ActionDispatch
147
147
  # You can specify a regular expression to define a format for a parameter.
148
148
  #
149
149
  # controller 'geocode' do
150
- # get 'geocode/:postalcode' => :show, constraints: {
150
+ # get 'geocode/:postalcode', to: :show, constraints: {
151
151
  # postalcode: /\d{5}(-\d{4})?/
152
152
  # }
153
153
  # end
@@ -156,13 +156,13 @@ module ActionDispatch
156
156
  # expression modifiers:
157
157
  #
158
158
  # controller 'geocode' do
159
- # get 'geocode/:postalcode' => :show, constraints: {
159
+ # get 'geocode/:postalcode', to: :show, constraints: {
160
160
  # postalcode: /hx\d\d\s\d[a-z]{2}/i
161
161
  # }
162
162
  # end
163
163
  #
164
164
  # controller 'geocode' do
165
- # get 'geocode/:postalcode' => :show, constraints: {
165
+ # get 'geocode/:postalcode', to: :show, constraints: {
166
166
  # postalcode: /# Postalcode format
167
167
  # \d{5} #Prefix
168
168
  # (-\d{4})? #Suffix
@@ -178,13 +178,13 @@ module ActionDispatch
178
178
  #
179
179
  # You can redirect any path to another path using the redirect helper in your router:
180
180
  #
181
- # get "/stories" => redirect("/posts")
181
+ # get "/stories", to: redirect("/posts")
182
182
  #
183
183
  # == Unicode character routes
184
184
  #
185
185
  # You can specify unicode character routes in your router:
186
186
  #
187
- # get "こんにちは" => "welcome#index"
187
+ # get "こんにちは", to: "welcome#index"
188
188
  #
189
189
  # == Routing to Rack Applications
190
190
  #
@@ -192,7 +192,7 @@ module ActionDispatch
192
192
  # index action in the PostsController, you can specify any Rack application
193
193
  # as the endpoint for a matcher:
194
194
  #
195
- # get "/application.js" => Sprockets
195
+ # get "/application.js", to: Sprockets
196
196
  #
197
197
  # == Reloading routes
198
198
  #
@@ -210,8 +210,8 @@ module ActionDispatch
210
210
  # === +assert_routing+
211
211
  #
212
212
  # def test_movie_route_properly_splits
213
- # opts = {controller: "plugin", action: "checkout", id: "2"}
214
- # assert_routing "plugin/checkout/2", opts
213
+ # opts = {controller: "plugin", action: "checkout", id: "2"}
214
+ # assert_routing "plugin/checkout/2", opts
215
215
  # end
216
216
  #
217
217
  # +assert_routing+ lets you test whether or not the route properly resolves into options.
@@ -219,8 +219,8 @@ module ActionDispatch
219
219
  # === +assert_recognizes+
220
220
  #
221
221
  # def test_route_has_options
222
- # opts = {controller: "plugin", action: "show", id: "12"}
223
- # assert_recognizes opts, "/plugins/show/12"
222
+ # opts = {controller: "plugin", action: "show", id: "12"}
223
+ # assert_recognizes opts, "/plugins/show/12"
224
224
  # end
225
225
  #
226
226
  # Note the subtle difference between the two: +assert_routing+ tests that
@@ -243,8 +243,9 @@ module ActionDispatch
243
243
  #
244
244
  # rails routes
245
245
  #
246
- # Target specific controllers by prefixing the command with <tt>-c</tt> option.
247
- #
246
+ # Target a specific controller with <tt>-c</tt>, or grep routes
247
+ # using <tt>-g</tt>. Useful in conjunction with <tt>--expanded</tt>
248
+ # which displays routes vertically.
248
249
  module Routing
249
250
  extend ActiveSupport::Autoload
250
251
 
@@ -4,13 +4,13 @@ gem "capybara", ">= 2.15"
4
4
 
5
5
  require "capybara/dsl"
6
6
  require "capybara/minitest"
7
+ require "selenium/webdriver"
7
8
  require "action_controller"
8
9
  require "action_dispatch/system_testing/driver"
9
10
  require "action_dispatch/system_testing/browser"
10
11
  require "action_dispatch/system_testing/server"
11
12
  require "action_dispatch/system_testing/test_helpers/screenshot_helper"
12
13
  require "action_dispatch/system_testing/test_helpers/setup_and_teardown"
13
- require "action_dispatch/system_testing/test_helpers/undef_methods"
14
14
 
15
15
  module ActionDispatch
16
16
  # = System Testing
@@ -89,19 +89,49 @@ module ActionDispatch
89
89
  # { js_errors: true }
90
90
  # end
91
91
  #
92
+ # Some drivers require browser capabilities to be passed as a block instead
93
+ # of through the +options+ hash.
94
+ #
95
+ # As an example, if you want to add mobile emulation on chrome, you'll have to
96
+ # create an instance of selenium's +Chrome::Options+ object and add
97
+ # capabilities with a block.
98
+ #
99
+ # The block will be passed an instance of <tt><Driver>::Options</tt> where you can
100
+ # define the capabilities you want. Please refer to your driver documentation
101
+ # to learn about supported options.
102
+ #
103
+ # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
104
+ # driven_by :selenium, using: :chrome, screen_size: [1024, 768] do |driver_option|
105
+ # driver_option.add_emulation(device_name: 'iPhone 6')
106
+ # driver_option.add_extension('path/to/chrome_extension.crx')
107
+ # end
108
+ # end
109
+ #
92
110
  # Because <tt>ActionDispatch::SystemTestCase</tt> is a shim between Capybara
93
111
  # and Rails, any driver that is supported by Capybara is supported by system
94
112
  # tests as long as you include the required gems and files.
95
- class SystemTestCase < IntegrationTest
113
+ class SystemTestCase < ActiveSupport::TestCase
96
114
  include Capybara::DSL
97
115
  include Capybara::Minitest::Assertions
98
116
  include SystemTesting::TestHelpers::SetupAndTeardown
99
117
  include SystemTesting::TestHelpers::ScreenshotHelper
100
- include SystemTesting::TestHelpers::UndefMethods
101
118
 
102
119
  def initialize(*) # :nodoc:
103
120
  super
121
+ self.class.driven_by(:selenium) unless self.class.driver?
104
122
  self.class.driver.use
123
+ @proxy_route = if ActionDispatch.test_app
124
+ Class.new do
125
+ include ActionDispatch.test_app.routes.url_helpers
126
+ include ActionDispatch.test_app.routes.mounted_helpers
127
+
128
+ def url_options
129
+ default_url_options.merge(host: Capybara.app_host)
130
+ end
131
+ end.new
132
+ else
133
+ nil
134
+ end
105
135
  end
106
136
 
107
137
  def self.start_application # :nodoc:
@@ -134,11 +164,19 @@ module ActionDispatch
134
164
  # driven_by :selenium, using: :firefox
135
165
  #
136
166
  # driven_by :selenium, using: :headless_firefox
137
- def self.driven_by(driver, using: :chrome, screen_size: [1400, 1400], options: {})
138
- self.driver = SystemTesting::Driver.new(driver, using: using, screen_size: screen_size, options: options)
167
+ def self.driven_by(driver, using: :chrome, screen_size: [1400, 1400], options: {}, &capabilities)
168
+ driver_options = { using: using, screen_size: screen_size, options: options }
169
+
170
+ self.driver = SystemTesting::Driver.new(driver, **driver_options, &capabilities)
139
171
  end
140
172
 
141
- driven_by :selenium
173
+ def method_missing(method, *args, &block)
174
+ if @proxy_route.respond_to?(method)
175
+ @proxy_route.send(method, *args, &block)
176
+ else
177
+ super
178
+ end
179
+ end
142
180
 
143
181
  ActiveSupport.run_load_hooks(:action_dispatch_system_test_case, self)
144
182
  end
@@ -29,20 +29,51 @@ module ActionDispatch
29
29
  end
30
30
  end
31
31
 
32
+ def capabilities
33
+ @option ||=
34
+ case type
35
+ when :chrome
36
+ ::Selenium::WebDriver::Chrome::Options.new
37
+ when :firefox
38
+ ::Selenium::WebDriver::Firefox::Options.new
39
+ end
40
+ end
41
+
42
+ # driver_path can be configured as a proc. The webdrivers gem uses this
43
+ # proc to update web drivers. Running this proc early allows us to only
44
+ # update the webdriver once and avoid race conditions when using
45
+ # parallel tests.
46
+ def preload
47
+ case type
48
+ when :chrome
49
+ if ::Selenium::WebDriver::Service.respond_to? :driver_path=
50
+ ::Selenium::WebDriver::Chrome::Service.driver_path.try(:call)
51
+ else
52
+ # Selenium <= v3.141.0
53
+ ::Selenium::WebDriver::Chrome.driver_path
54
+ end
55
+ when :firefox
56
+ if ::Selenium::WebDriver::Service.respond_to? :driver_path=
57
+ ::Selenium::WebDriver::Firefox::Service.driver_path.try(:call)
58
+ else
59
+ # Selenium <= v3.141.0
60
+ ::Selenium::WebDriver::Firefox.driver_path
61
+ end
62
+ end
63
+ end
64
+
32
65
  private
33
66
  def headless_chrome_browser_options
34
- options = Selenium::WebDriver::Chrome::Options.new
35
- options.args << "--headless"
36
- options.args << "--disable-gpu" if Gem.win_platform?
67
+ capabilities.add_argument("--headless")
68
+ capabilities.add_argument("--disable-gpu") if Gem.win_platform?
37
69
 
38
- options
70
+ capabilities
39
71
  end
40
72
 
41
73
  def headless_firefox_browser_options
42
- options = Selenium::WebDriver::Firefox::Options.new
43
- options.args << "-headless"
74
+ capabilities.add_argument("-headless")
44
75
 
45
- options
76
+ capabilities
46
77
  end
47
78
  end
48
79
  end
@@ -3,11 +3,14 @@
3
3
  module ActionDispatch
4
4
  module SystemTesting
5
5
  class Driver # :nodoc:
6
- def initialize(name, **options)
6
+ def initialize(name, **options, &capabilities)
7
7
  @name = name
8
8
  @browser = Browser.new(options[:using])
9
9
  @screen_size = options[:screen_size]
10
10
  @options = options[:options]
11
+ @capabilities = capabilities
12
+
13
+ @browser.preload unless name == :rack_test
11
14
  end
12
15
 
13
16
  def use
@@ -22,6 +25,8 @@ module ActionDispatch
22
25
  end
23
26
 
24
27
  def register
28
+ define_browser_capabilities(@browser.capabilities)
29
+
25
30
  Capybara.register_driver @name do |app|
26
31
  case @name
27
32
  when :selenium then register_selenium(app)
@@ -31,12 +36,16 @@ module ActionDispatch
31
36
  end
32
37
  end
33
38
 
39
+ def define_browser_capabilities(capabilities)
40
+ @capabilities.call(capabilities) if @capabilities
41
+ end
42
+
34
43
  def browser_options
35
44
  @options.merge(options: @browser.options).compact
36
45
  end
37
46
 
38
47
  def register_selenium(app)
39
- Capybara::Selenium::Driver.new(app, { browser: @browser.type }.merge(browser_options)).tap do |driver|
48
+ Capybara::Selenium::Driver.new(app, **{ browser: @browser.type }.merge(browser_options)).tap do |driver|
40
49
  driver.browser.manage.window.size = Selenium::WebDriver::Dimension.new(*@screen_size)
41
50
  end
42
51
  end
@@ -20,7 +20,7 @@ module ActionDispatch
20
20
  # * [+inline+] Display the screenshot in the terminal using the
21
21
  # iTerm image protocol (https://iterm2.com/documentation-images.html).
22
22
  # * [+artifact+] Display the screenshot in the terminal, using the terminal
23
- # artifact format (https://buildkite.github.io/terminal/inline-images/).
23
+ # artifact format (https://buildkite.github.io/terminal-to-html/inline-images/).
24
24
  def take_screenshot
25
25
  save_image
26
26
  puts display_image
@@ -39,11 +39,12 @@ module ActionDispatch
39
39
 
40
40
  private
41
41
  def image_name
42
- failed? ? "failures_#{method_name}" : method_name
42
+ name = method_name[0...225]
43
+ failed? ? "failures_#{name}" : name
43
44
  end
44
45
 
45
46
  def image_path
46
- @image_path ||= absolute_image_path.relative_path_from(Pathname.pwd).to_s
47
+ @image_path ||= absolute_image_path.to_s
47
48
  end
48
49
 
49
50
  def absolute_image_path
@@ -65,7 +66,7 @@ module ActionDispatch
65
66
  end
66
67
 
67
68
  def display_image
68
- message = "[Screenshot]: #{image_path}\n".dup
69
+ message = +"[Screenshot]: #{image_path}\n"
69
70
 
70
71
  case output_type
71
72
  when "artifact"
@@ -80,7 +81,7 @@ module ActionDispatch
80
81
  end
81
82
 
82
83
  def inline_base64(path)
83
- Base64.encode64(path).gsub("\n", "")
84
+ Base64.strict_encode64(path)
84
85
  end
85
86
 
86
87
  def failed?
@@ -7,7 +7,6 @@ module ActionDispatch
7
7
  DEFAULT_HOST = "http://127.0.0.1"
8
8
 
9
9
  def host!(host)
10
- super
11
10
  Capybara.app_host = host
12
11
  end
13
12
 
@@ -16,12 +15,14 @@ module ActionDispatch
16
15
  super
17
16
  end
18
17
 
18
+ def before_teardown
19
+ take_failed_screenshot
20
+ ensure
21
+ super
22
+ end
23
+
19
24
  def after_teardown
20
- begin
21
- take_failed_screenshot
22
- ensure
23
- Capybara.reset_sessions!
24
- end
25
+ Capybara.reset_sessions!
25
26
  ensure
26
27
  super
27
28
  end
@@ -35,7 +35,6 @@ module ActionDispatch
35
35
  end
36
36
 
37
37
  private
38
-
39
38
  def code_from_name(name)
40
39
  GENERIC_RESPONSE_CODES[name] || Rack::Utils::SYMBOL_TO_STATUS_CODE[name]
41
40
  end
@@ -79,9 +79,8 @@ module ActionDispatch
79
79
  end
80
80
 
81
81
  def generate_response_message(expected, actual = @response.response_code)
82
- "Expected response to be a <#{code_with_name(expected)}>,"\
83
- " but was a <#{code_with_name(actual)}>"
84
- .dup.concat(location_if_redirected).concat(response_body_if_short)
82
+ (+"Expected response to be a <#{code_with_name(expected)}>,"\
83
+ " but was a <#{code_with_name(actual)}>").concat(location_if_redirected).concat(response_body_if_short)
85
84
  end
86
85
 
87
86
  def response_body_if_short