actionpack 5.2.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (170) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +429 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +57 -0
  5. data/lib/abstract_controller.rb +27 -0
  6. data/lib/abstract_controller/asset_paths.rb +12 -0
  7. data/lib/abstract_controller/base.rb +265 -0
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/caching/fragments.rb +166 -0
  10. data/lib/abstract_controller/callbacks.rb +212 -0
  11. data/lib/abstract_controller/collector.rb +43 -0
  12. data/lib/abstract_controller/error.rb +6 -0
  13. data/lib/abstract_controller/helpers.rb +194 -0
  14. data/lib/abstract_controller/logger.rb +14 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +20 -0
  16. data/lib/abstract_controller/rendering.rb +127 -0
  17. data/lib/abstract_controller/translation.rb +31 -0
  18. data/lib/abstract_controller/url_for.rb +35 -0
  19. data/lib/action_controller.rb +66 -0
  20. data/lib/action_controller/api.rb +149 -0
  21. data/lib/action_controller/api/api_rendering.rb +16 -0
  22. data/lib/action_controller/base.rb +276 -0
  23. data/lib/action_controller/caching.rb +46 -0
  24. data/lib/action_controller/form_builder.rb +50 -0
  25. data/lib/action_controller/log_subscriber.rb +78 -0
  26. data/lib/action_controller/metal.rb +256 -0
  27. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  28. data/lib/action_controller/metal/conditional_get.rb +274 -0
  29. data/lib/action_controller/metal/content_security_policy.rb +52 -0
  30. data/lib/action_controller/metal/cookies.rb +16 -0
  31. data/lib/action_controller/metal/data_streaming.rb +152 -0
  32. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  33. data/lib/action_controller/metal/etag_with_template_digest.rb +57 -0
  34. data/lib/action_controller/metal/exceptions.rb +53 -0
  35. data/lib/action_controller/metal/flash.rb +61 -0
  36. data/lib/action_controller/metal/force_ssl.rb +99 -0
  37. data/lib/action_controller/metal/head.rb +60 -0
  38. data/lib/action_controller/metal/helpers.rb +123 -0
  39. data/lib/action_controller/metal/http_authentication.rb +519 -0
  40. data/lib/action_controller/metal/implicit_render.rb +73 -0
  41. data/lib/action_controller/metal/instrumentation.rb +107 -0
  42. data/lib/action_controller/metal/live.rb +312 -0
  43. data/lib/action_controller/metal/mime_responds.rb +313 -0
  44. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  45. data/lib/action_controller/metal/params_wrapper.rb +293 -0
  46. data/lib/action_controller/metal/redirecting.rb +133 -0
  47. data/lib/action_controller/metal/renderers.rb +181 -0
  48. data/lib/action_controller/metal/rendering.rb +122 -0
  49. data/lib/action_controller/metal/request_forgery_protection.rb +445 -0
  50. data/lib/action_controller/metal/rescue.rb +28 -0
  51. data/lib/action_controller/metal/streaming.rb +223 -0
  52. data/lib/action_controller/metal/strong_parameters.rb +1086 -0
  53. data/lib/action_controller/metal/testing.rb +16 -0
  54. data/lib/action_controller/metal/url_for.rb +58 -0
  55. data/lib/action_controller/railtie.rb +89 -0
  56. data/lib/action_controller/railties/helpers.rb +24 -0
  57. data/lib/action_controller/renderer.rb +117 -0
  58. data/lib/action_controller/template_assertions.rb +11 -0
  59. data/lib/action_controller/test_case.rb +629 -0
  60. data/lib/action_dispatch.rb +112 -0
  61. data/lib/action_dispatch/http/cache.rb +222 -0
  62. data/lib/action_dispatch/http/content_security_policy.rb +272 -0
  63. data/lib/action_dispatch/http/filter_parameters.rb +84 -0
  64. data/lib/action_dispatch/http/filter_redirect.rb +37 -0
  65. data/lib/action_dispatch/http/headers.rb +132 -0
  66. data/lib/action_dispatch/http/mime_negotiation.rb +175 -0
  67. data/lib/action_dispatch/http/mime_type.rb +342 -0
  68. data/lib/action_dispatch/http/mime_types.rb +50 -0
  69. data/lib/action_dispatch/http/parameter_filter.rb +86 -0
  70. data/lib/action_dispatch/http/parameters.rb +126 -0
  71. data/lib/action_dispatch/http/rack_cache.rb +63 -0
  72. data/lib/action_dispatch/http/request.rb +430 -0
  73. data/lib/action_dispatch/http/response.rb +519 -0
  74. data/lib/action_dispatch/http/upload.rb +84 -0
  75. data/lib/action_dispatch/http/url.rb +350 -0
  76. data/lib/action_dispatch/journey.rb +7 -0
  77. data/lib/action_dispatch/journey/formatter.rb +189 -0
  78. data/lib/action_dispatch/journey/gtg/builder.rb +164 -0
  79. data/lib/action_dispatch/journey/gtg/simulator.rb +41 -0
  80. data/lib/action_dispatch/journey/gtg/transition_table.rb +158 -0
  81. data/lib/action_dispatch/journey/nfa/builder.rb +78 -0
  82. data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
  83. data/lib/action_dispatch/journey/nfa/simulator.rb +49 -0
  84. data/lib/action_dispatch/journey/nfa/transition_table.rb +120 -0
  85. data/lib/action_dispatch/journey/nodes/node.rb +140 -0
  86. data/lib/action_dispatch/journey/parser.rb +199 -0
  87. data/lib/action_dispatch/journey/parser.y +50 -0
  88. data/lib/action_dispatch/journey/parser_extras.rb +31 -0
  89. data/lib/action_dispatch/journey/path/pattern.rb +198 -0
  90. data/lib/action_dispatch/journey/route.rb +203 -0
  91. data/lib/action_dispatch/journey/router.rb +156 -0
  92. data/lib/action_dispatch/journey/router/utils.rb +102 -0
  93. data/lib/action_dispatch/journey/routes.rb +82 -0
  94. data/lib/action_dispatch/journey/scanner.rb +64 -0
  95. data/lib/action_dispatch/journey/visitors.rb +268 -0
  96. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  97. data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
  98. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  99. data/lib/action_dispatch/middleware/callbacks.rb +36 -0
  100. data/lib/action_dispatch/middleware/cookies.rb +685 -0
  101. data/lib/action_dispatch/middleware/debug_exceptions.rb +205 -0
  102. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  103. data/lib/action_dispatch/middleware/exception_wrapper.rb +147 -0
  104. data/lib/action_dispatch/middleware/executor.rb +21 -0
  105. data/lib/action_dispatch/middleware/flash.rb +300 -0
  106. data/lib/action_dispatch/middleware/public_exceptions.rb +57 -0
  107. data/lib/action_dispatch/middleware/reloader.rb +12 -0
  108. data/lib/action_dispatch/middleware/remote_ip.rb +183 -0
  109. data/lib/action_dispatch/middleware/request_id.rb +43 -0
  110. data/lib/action_dispatch/middleware/session/abstract_store.rb +92 -0
  111. data/lib/action_dispatch/middleware/session/cache_store.rb +54 -0
  112. data/lib/action_dispatch/middleware/session/cookie_store.rb +118 -0
  113. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +28 -0
  114. data/lib/action_dispatch/middleware/show_exceptions.rb +62 -0
  115. data/lib/action_dispatch/middleware/ssl.rb +150 -0
  116. data/lib/action_dispatch/middleware/stack.rb +116 -0
  117. data/lib/action_dispatch/middleware/static.rb +130 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +22 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +27 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  122. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +52 -0
  123. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  124. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +16 -0
  125. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  126. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +161 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  136. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  137. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
  138. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +200 -0
  139. data/lib/action_dispatch/railtie.rb +55 -0
  140. data/lib/action_dispatch/request/session.rb +234 -0
  141. data/lib/action_dispatch/request/utils.rb +78 -0
  142. data/lib/action_dispatch/routing.rb +260 -0
  143. data/lib/action_dispatch/routing/endpoint.rb +17 -0
  144. data/lib/action_dispatch/routing/inspector.rb +225 -0
  145. data/lib/action_dispatch/routing/mapper.rb +2267 -0
  146. data/lib/action_dispatch/routing/polymorphic_routes.rb +352 -0
  147. data/lib/action_dispatch/routing/redirection.rb +201 -0
  148. data/lib/action_dispatch/routing/route_set.rb +890 -0
  149. data/lib/action_dispatch/routing/routes_proxy.rb +69 -0
  150. data/lib/action_dispatch/routing/url_for.rb +236 -0
  151. data/lib/action_dispatch/system_test_case.rb +147 -0
  152. data/lib/action_dispatch/system_testing/browser.rb +49 -0
  153. data/lib/action_dispatch/system_testing/driver.rb +59 -0
  154. data/lib/action_dispatch/system_testing/server.rb +31 -0
  155. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
  156. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
  157. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
  158. data/lib/action_dispatch/testing/assertion_response.rb +47 -0
  159. data/lib/action_dispatch/testing/assertions.rb +24 -0
  160. data/lib/action_dispatch/testing/assertions/response.rb +107 -0
  161. data/lib/action_dispatch/testing/assertions/routing.rb +222 -0
  162. data/lib/action_dispatch/testing/integration.rb +652 -0
  163. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  164. data/lib/action_dispatch/testing/test_process.rb +50 -0
  165. data/lib/action_dispatch/testing/test_request.rb +71 -0
  166. data/lib/action_dispatch/testing/test_response.rb +53 -0
  167. data/lib/action_pack.rb +26 -0
  168. data/lib/action_pack/gem_version.rb +17 -0
  169. data/lib/action_pack/version.rb +10 -0
  170. metadata +318 -0
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionController
4
+ module Testing
5
+ extend ActiveSupport::Concern
6
+
7
+ # Behavior specific to functional tests
8
+ module Functional # :nodoc:
9
+ def recycle!
10
+ @_url_options = nil
11
+ self.formats = nil
12
+ self.params = nil
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionController
4
+ # Includes +url_for+ into the host class. The class has to provide a +RouteSet+ by implementing
5
+ # the <tt>_routes</tt> method. Otherwise, an exception will be raised.
6
+ #
7
+ # In addition to <tt>AbstractController::UrlFor</tt>, this module accesses the HTTP layer to define
8
+ # URL options like the +host+. In order to do so, this module requires the host class
9
+ # to implement +env+ which needs to be Rack-compatible and +request+
10
+ # which is either an instance of +ActionDispatch::Request+ or an object
11
+ # that responds to the +host+, +optional_port+, +protocol+ and
12
+ # +symbolized_path_parameter+ methods.
13
+ #
14
+ # class RootUrl
15
+ # include ActionController::UrlFor
16
+ # include Rails.application.routes.url_helpers
17
+ #
18
+ # delegate :env, :request, to: :controller
19
+ #
20
+ # def initialize(controller)
21
+ # @controller = controller
22
+ # @url = root_path # named route from the application.
23
+ # end
24
+ # end
25
+ module UrlFor
26
+ extend ActiveSupport::Concern
27
+
28
+ include AbstractController::UrlFor
29
+
30
+ def url_options
31
+ @_url_options ||= {
32
+ host: request.host,
33
+ port: request.optional_port,
34
+ protocol: request.protocol,
35
+ _recall: request.path_parameters
36
+ }.merge!(super).freeze
37
+
38
+ if (same_origin = _routes.equal?(request.routes)) ||
39
+ (script_name = request.engine_script_name(_routes)) ||
40
+ (original_script_name = request.original_script_name)
41
+
42
+ options = @_url_options.dup
43
+ if original_script_name
44
+ options[:original_script_name] = original_script_name
45
+ else
46
+ if same_origin
47
+ options[:script_name] = request.script_name.empty? ? "".freeze : request.script_name.dup
48
+ else
49
+ options[:script_name] = script_name
50
+ end
51
+ end
52
+ options.freeze
53
+ else
54
+ @_url_options
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails"
4
+ require "action_controller"
5
+ require "action_dispatch/railtie"
6
+ require "abstract_controller/railties/routes_helpers"
7
+ require "action_controller/railties/helpers"
8
+ require "action_view/railtie"
9
+
10
+ module ActionController
11
+ class Railtie < Rails::Railtie #:nodoc:
12
+ config.action_controller = ActiveSupport::OrderedOptions.new
13
+
14
+ config.eager_load_namespaces << ActionController
15
+
16
+ initializer "action_controller.assets_config", group: :all do |app|
17
+ app.config.action_controller.assets_dir ||= app.config.paths["public"].first
18
+ end
19
+
20
+ initializer "action_controller.set_helpers_path" do |app|
21
+ ActionController::Helpers.helpers_path = app.helpers_paths
22
+ end
23
+
24
+ initializer "action_controller.parameters_config" do |app|
25
+ options = app.config.action_controller
26
+
27
+ ActiveSupport.on_load(:action_controller, run_once: true) do
28
+ ActionController::Parameters.permit_all_parameters = options.delete(:permit_all_parameters) { false }
29
+ if app.config.action_controller[:always_permitted_parameters]
30
+ ActionController::Parameters.always_permitted_parameters =
31
+ app.config.action_controller.delete(:always_permitted_parameters)
32
+ end
33
+ ActionController::Parameters.action_on_unpermitted_parameters = options.delete(:action_on_unpermitted_parameters) do
34
+ (Rails.env.test? || Rails.env.development?) ? :log : false
35
+ end
36
+ end
37
+ end
38
+
39
+ initializer "action_controller.set_configs" do |app|
40
+ paths = app.config.paths
41
+ options = app.config.action_controller
42
+
43
+ options.logger ||= Rails.logger
44
+ options.cache_store ||= Rails.cache
45
+
46
+ options.javascripts_dir ||= paths["public/javascripts"].first
47
+ options.stylesheets_dir ||= paths["public/stylesheets"].first
48
+
49
+ # Ensure readers methods get compiled.
50
+ options.asset_host ||= app.config.asset_host
51
+ options.relative_url_root ||= app.config.relative_url_root
52
+
53
+ ActiveSupport.on_load(:action_controller) do
54
+ include app.routes.mounted_helpers
55
+ extend ::AbstractController::Railties::RoutesHelpers.with(app.routes)
56
+ extend ::ActionController::Railties::Helpers
57
+
58
+ options.each do |k, v|
59
+ k = "#{k}="
60
+ if respond_to?(k)
61
+ send(k, v)
62
+ elsif !Base.respond_to?(k)
63
+ raise "Invalid option key: #{k}"
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ initializer "action_controller.compile_config_methods" do
70
+ ActiveSupport.on_load(:action_controller) do
71
+ config.compile_methods! if config.respond_to?(:compile_methods!)
72
+ end
73
+ end
74
+
75
+ initializer "action_controller.request_forgery_protection" do |app|
76
+ ActiveSupport.on_load(:action_controller_base) do
77
+ if app.config.action_controller.default_protect_from_forgery
78
+ protect_from_forgery with: :exception
79
+ end
80
+ end
81
+ end
82
+
83
+ initializer "action_controller.eager_load_actions" do
84
+ ActiveSupport.on_load(:after_initialize) do
85
+ ActionController::Metal.descendants.each(&:action_methods) if config.eager_load
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionController
4
+ module Railties
5
+ module Helpers
6
+ def inherited(klass)
7
+ super
8
+ return unless klass.respond_to?(:helpers_path=)
9
+
10
+ if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_helpers_paths) }
11
+ paths = namespace.railtie_helpers_paths
12
+ else
13
+ paths = ActionController::Helpers.helpers_path
14
+ end
15
+
16
+ klass.helpers_path = paths
17
+
18
+ if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers
19
+ klass.helper :all
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/keys"
4
+
5
+ module ActionController
6
+ # ActionController::Renderer allows you to render arbitrary templates
7
+ # without requirement of being in controller actions.
8
+ #
9
+ # You get a concrete renderer class by invoking ActionController::Base#renderer.
10
+ # For example:
11
+ #
12
+ # ApplicationController.renderer
13
+ #
14
+ # It allows you to call method #render directly.
15
+ #
16
+ # ApplicationController.renderer.render template: '...'
17
+ #
18
+ # You can use this shortcut in a controller, instead of the previous example:
19
+ #
20
+ # ApplicationController.render template: '...'
21
+ #
22
+ # #render allows you to use the same options that you can use when rendering in a controller.
23
+ # For example:
24
+ #
25
+ # FooController.render :action, locals: { ... }, assigns: { ... }
26
+ #
27
+ # The template will be rendered in a Rack environment which is accessible through
28
+ # ActionController::Renderer#env. You can set it up in two ways:
29
+ #
30
+ # * by changing renderer defaults, like
31
+ #
32
+ # ApplicationController.renderer.defaults # => hash with default Rack environment
33
+ #
34
+ # * by initializing an instance of renderer by passing it a custom environment.
35
+ #
36
+ # ApplicationController.renderer.new(method: 'post', https: true)
37
+ #
38
+ class Renderer
39
+ attr_reader :defaults, :controller
40
+
41
+ DEFAULTS = {
42
+ http_host: "example.org",
43
+ https: false,
44
+ method: "get",
45
+ script_name: "",
46
+ input: ""
47
+ }.freeze
48
+
49
+ # Create a new renderer instance for a specific controller class.
50
+ def self.for(controller, env = {}, defaults = DEFAULTS.dup)
51
+ new(controller, env, defaults)
52
+ end
53
+
54
+ # Create a new renderer for the same controller but with a new env.
55
+ def new(env = {})
56
+ self.class.new controller, env, defaults
57
+ end
58
+
59
+ # Create a new renderer for the same controller but with new defaults.
60
+ def with_defaults(defaults)
61
+ self.class.new controller, @env, self.defaults.merge(defaults)
62
+ end
63
+
64
+ # Accepts a custom Rack environment to render templates in.
65
+ # It will be merged with the default Rack environment defined by
66
+ # +ActionController::Renderer::DEFAULTS+.
67
+ def initialize(controller, env, defaults)
68
+ @controller = controller
69
+ @defaults = defaults
70
+ @env = normalize_keys defaults.merge(env)
71
+ end
72
+
73
+ # Render templates with any options from ActionController::Base#render_to_string.
74
+ def render(*args)
75
+ raise "missing controller" unless controller
76
+
77
+ request = ActionDispatch::Request.new @env
78
+ request.routes = controller._routes
79
+
80
+ instance = controller.new
81
+ instance.set_request! request
82
+ instance.set_response! controller.make_response!(request)
83
+ instance.render_to_string(*args)
84
+ end
85
+
86
+ private
87
+ def normalize_keys(env)
88
+ new_env = {}
89
+ env.each_pair { |k, v| new_env[rack_key_for(k)] = rack_value_for(k, v) }
90
+ new_env["rack.url_scheme"] = new_env["HTTPS"] == "on" ? "https" : "http"
91
+ new_env
92
+ end
93
+
94
+ RACK_KEY_TRANSLATION = {
95
+ http_host: "HTTP_HOST",
96
+ https: "HTTPS",
97
+ method: "REQUEST_METHOD",
98
+ script_name: "SCRIPT_NAME",
99
+ input: "rack.input"
100
+ }
101
+
102
+ IDENTITY = ->(_) { _ }
103
+
104
+ RACK_VALUE_TRANSLATION = {
105
+ https: ->(v) { v ? "on" : "off" },
106
+ method: ->(v) { v.upcase },
107
+ }
108
+
109
+ def rack_key_for(key)
110
+ RACK_KEY_TRANSLATION.fetch(key, key.to_s)
111
+ end
112
+
113
+ def rack_value_for(key, value)
114
+ RACK_VALUE_TRANSLATION.fetch(key, IDENTITY).call value
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionController
4
+ module TemplateAssertions
5
+ def assert_template(options = {}, message = nil)
6
+ raise NoMethodError,
7
+ "assert_template has been extracted to a gem. To continue using it,
8
+ add `gem 'rails-controller-testing'` to your Gemfile."
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,629 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/session/abstract/id"
4
+ require "active_support/core_ext/hash/conversions"
5
+ require "active_support/core_ext/object/to_query"
6
+ require "active_support/core_ext/module/anonymous"
7
+ require "active_support/core_ext/module/redefine_method"
8
+ require "active_support/core_ext/hash/keys"
9
+ require "active_support/testing/constant_lookup"
10
+ require "action_controller/template_assertions"
11
+ require "rails-dom-testing"
12
+
13
+ module ActionController
14
+ class Metal
15
+ include Testing::Functional
16
+ end
17
+
18
+ module Live
19
+ # Disable controller / rendering threads in tests. User tests can access
20
+ # the database on the main thread, so they could open a txn, then the
21
+ # controller thread will open a new connection and try to access data
22
+ # that's only visible to the main thread's txn. This is the problem in #23483.
23
+ silence_redefinition_of_method :new_controller_thread
24
+ def new_controller_thread # :nodoc:
25
+ yield
26
+ end
27
+ end
28
+
29
+ # ActionController::TestCase will be deprecated and moved to a gem in Rails 5.1.
30
+ # Please use ActionDispatch::IntegrationTest going forward.
31
+ class TestRequest < ActionDispatch::TestRequest #:nodoc:
32
+ DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
33
+ DEFAULT_ENV.delete "PATH_INFO"
34
+
35
+ def self.new_session
36
+ TestSession.new
37
+ end
38
+
39
+ attr_reader :controller_class
40
+
41
+ # Create a new test request with default `env` values.
42
+ def self.create(controller_class)
43
+ env = {}
44
+ env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
45
+ env["rack.request.cookie_hash"] = {}.with_indifferent_access
46
+ new(default_env.merge(env), new_session, controller_class)
47
+ end
48
+
49
+ def self.default_env
50
+ DEFAULT_ENV
51
+ end
52
+ private_class_method :default_env
53
+
54
+ def initialize(env, session, controller_class)
55
+ super(env)
56
+
57
+ self.session = session
58
+ self.session_options = TestSession::DEFAULT_OPTIONS.dup
59
+ @controller_class = controller_class
60
+ @custom_param_parsers = {
61
+ xml: lambda { |raw_post| Hash.from_xml(raw_post)["hash"] }
62
+ }
63
+ end
64
+
65
+ def query_string=(string)
66
+ set_header Rack::QUERY_STRING, string
67
+ end
68
+
69
+ def content_type=(type)
70
+ set_header "CONTENT_TYPE", type
71
+ end
72
+
73
+ def assign_parameters(routes, controller_path, action, parameters, generated_path, query_string_keys)
74
+ non_path_parameters = {}
75
+ path_parameters = {}
76
+
77
+ parameters.each do |key, value|
78
+ if query_string_keys.include?(key)
79
+ non_path_parameters[key] = value
80
+ else
81
+ if value.is_a?(Array)
82
+ value = value.map(&:to_param)
83
+ else
84
+ value = value.to_param
85
+ end
86
+
87
+ path_parameters[key] = value
88
+ end
89
+ end
90
+
91
+ if get?
92
+ if query_string.blank?
93
+ self.query_string = non_path_parameters.to_query
94
+ end
95
+ else
96
+ if ENCODER.should_multipart?(non_path_parameters)
97
+ self.content_type = ENCODER.content_type
98
+ data = ENCODER.build_multipart non_path_parameters
99
+ else
100
+ fetch_header("CONTENT_TYPE") do |k|
101
+ set_header k, "application/x-www-form-urlencoded"
102
+ end
103
+
104
+ case content_mime_type.to_sym
105
+ when nil
106
+ raise "Unknown Content-Type: #{content_type}"
107
+ when :json
108
+ data = ActiveSupport::JSON.encode(non_path_parameters)
109
+ when :xml
110
+ data = non_path_parameters.to_xml
111
+ when :url_encoded_form
112
+ data = non_path_parameters.to_query
113
+ else
114
+ @custom_param_parsers[content_mime_type.symbol] = ->(_) { non_path_parameters }
115
+ data = non_path_parameters.to_query
116
+ end
117
+ end
118
+
119
+ data_stream = StringIO.new(data)
120
+ set_header "CONTENT_LENGTH", data_stream.length.to_s
121
+ set_header "rack.input", data_stream
122
+ end
123
+
124
+ fetch_header("PATH_INFO") do |k|
125
+ set_header k, generated_path
126
+ end
127
+ path_parameters[:controller] = controller_path
128
+ path_parameters[:action] = action
129
+
130
+ self.path_parameters = path_parameters
131
+ end
132
+
133
+ ENCODER = Class.new do
134
+ include Rack::Test::Utils
135
+
136
+ def should_multipart?(params)
137
+ # FIXME: lifted from Rack-Test. We should push this separation upstream.
138
+ multipart = false
139
+ query = lambda { |value|
140
+ case value
141
+ when Array
142
+ value.each(&query)
143
+ when Hash
144
+ value.values.each(&query)
145
+ when Rack::Test::UploadedFile
146
+ multipart = true
147
+ end
148
+ }
149
+ params.values.each(&query)
150
+ multipart
151
+ end
152
+
153
+ public :build_multipart
154
+
155
+ def content_type
156
+ "multipart/form-data; boundary=#{Rack::Test::MULTIPART_BOUNDARY}"
157
+ end
158
+ end.new
159
+
160
+ private
161
+
162
+ def params_parsers
163
+ super.merge @custom_param_parsers
164
+ end
165
+ end
166
+
167
+ class LiveTestResponse < Live::Response
168
+ # Was the response successful?
169
+ alias_method :success?, :successful?
170
+
171
+ # Was the URL not found?
172
+ alias_method :missing?, :not_found?
173
+
174
+ # Was there a server-side error?
175
+ alias_method :error?, :server_error?
176
+ end
177
+
178
+ # Methods #destroy and #load! are overridden to avoid calling methods on the
179
+ # @store object, which does not exist for the TestSession class.
180
+ class TestSession < Rack::Session::Abstract::SessionHash #:nodoc:
181
+ DEFAULT_OPTIONS = Rack::Session::Abstract::Persisted::DEFAULT_OPTIONS
182
+
183
+ def initialize(session = {})
184
+ super(nil, nil)
185
+ @id = SecureRandom.hex(16)
186
+ @data = stringify_keys(session)
187
+ @loaded = true
188
+ end
189
+
190
+ def exists?
191
+ true
192
+ end
193
+
194
+ def keys
195
+ @data.keys
196
+ end
197
+
198
+ def values
199
+ @data.values
200
+ end
201
+
202
+ def destroy
203
+ clear
204
+ end
205
+
206
+ def fetch(key, *args, &block)
207
+ @data.fetch(key.to_s, *args, &block)
208
+ end
209
+
210
+ private
211
+
212
+ def load!
213
+ @id
214
+ end
215
+ end
216
+
217
+ # Superclass for ActionController functional tests. Functional tests allow you to
218
+ # test a single controller action per test method.
219
+ #
220
+ # == Use integration style controller tests over functional style controller tests.
221
+ #
222
+ # Rails discourages the use of functional tests in favor of integration tests
223
+ # (use ActionDispatch::IntegrationTest).
224
+ #
225
+ # New Rails applications no longer generate functional style controller tests and they should
226
+ # only be used for backward compatibility. Integration style controller tests perform actual
227
+ # requests, whereas functional style controller tests merely simulate a request. Besides,
228
+ # integration tests are as fast as functional tests and provide lot of helpers such as +as+,
229
+ # +parsed_body+ for effective testing of controller actions including even API endpoints.
230
+ #
231
+ # == Basic example
232
+ #
233
+ # Functional tests are written as follows:
234
+ # 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+ or +head+ method to simulate
235
+ # an HTTP request.
236
+ # 2. Then, one asserts whether the current state is as expected. "State" can be anything:
237
+ # the controller's HTTP response, the database contents, etc.
238
+ #
239
+ # For example:
240
+ #
241
+ # class BooksControllerTest < ActionController::TestCase
242
+ # def test_create
243
+ # # Simulate a POST response with the given HTTP parameters.
244
+ # post(:create, params: { book: { title: "Love Hina" }})
245
+ #
246
+ # # Asserts that the controller tried to redirect us to
247
+ # # the created book's URI.
248
+ # assert_response :found
249
+ #
250
+ # # Asserts that the controller really put the book in the database.
251
+ # assert_not_nil Book.find_by(title: "Love Hina")
252
+ # end
253
+ # end
254
+ #
255
+ # You can also send a real document in the simulated HTTP request.
256
+ #
257
+ # def test_create
258
+ # json = {book: { title: "Love Hina" }}.to_json
259
+ # post :create, body: json
260
+ # end
261
+ #
262
+ # == Special instance variables
263
+ #
264
+ # ActionController::TestCase will also automatically provide the following instance
265
+ # variables for use in the tests:
266
+ #
267
+ # <b>@controller</b>::
268
+ # The controller instance that will be tested.
269
+ # <b>@request</b>::
270
+ # An ActionController::TestRequest, representing the current HTTP
271
+ # request. You can modify this object before sending the HTTP request. For example,
272
+ # you might want to set some session properties before sending a GET request.
273
+ # <b>@response</b>::
274
+ # An ActionDispatch::TestResponse object, representing the response
275
+ # of the last HTTP response. In the above example, <tt>@response</tt> becomes valid
276
+ # after calling +post+. If the various assert methods are not sufficient, then you
277
+ # may use this object to inspect the HTTP response in detail.
278
+ #
279
+ # (Earlier versions of \Rails required each functional test to subclass
280
+ # Test::Unit::TestCase and define @controller, @request, @response in +setup+.)
281
+ #
282
+ # == Controller is automatically inferred
283
+ #
284
+ # ActionController::TestCase will automatically infer the controller under test
285
+ # from the test class name. If the controller cannot be inferred from the test
286
+ # class name, you can explicitly set it with +tests+.
287
+ #
288
+ # class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase
289
+ # tests WidgetController
290
+ # end
291
+ #
292
+ # == \Testing controller internals
293
+ #
294
+ # In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
295
+ # can be used against. These collections are:
296
+ #
297
+ # * session: Objects being saved in the session.
298
+ # * flash: The flash objects currently in the session.
299
+ # * cookies: \Cookies being sent to the user on this request.
300
+ #
301
+ # These collections can be used just like any other hash:
302
+ #
303
+ # assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
304
+ # assert flash.empty? # makes sure that there's nothing in the flash
305
+ #
306
+ # On top of the collections, you have the complete URL that a given action redirected to available in <tt>redirect_to_url</tt>.
307
+ #
308
+ # For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
309
+ # action call which can then be asserted against.
310
+ #
311
+ # == Manipulating session and cookie variables
312
+ #
313
+ # Sometimes you need to set up the session and cookie variables for a test.
314
+ # To do this just assign a value to the session or cookie collection:
315
+ #
316
+ # session[:key] = "value"
317
+ # cookies[:key] = "value"
318
+ #
319
+ # To clear the cookies for a test just clear the cookie collection:
320
+ #
321
+ # cookies.clear
322
+ #
323
+ # == \Testing named routes
324
+ #
325
+ # If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case.
326
+ #
327
+ # assert_redirected_to page_url(title: 'foo')
328
+ class TestCase < ActiveSupport::TestCase
329
+ module Behavior
330
+ extend ActiveSupport::Concern
331
+ include ActionDispatch::TestProcess
332
+ include ActiveSupport::Testing::ConstantLookup
333
+ include Rails::Dom::Testing::Assertions
334
+
335
+ attr_reader :response, :request
336
+
337
+ module ClassMethods
338
+ # Sets the controller class name. Useful if the name can't be inferred from test class.
339
+ # Normalizes +controller_class+ before using.
340
+ #
341
+ # tests WidgetController
342
+ # tests :widget
343
+ # tests 'widget'
344
+ def tests(controller_class)
345
+ case controller_class
346
+ when String, Symbol
347
+ self.controller_class = "#{controller_class.to_s.camelize}Controller".constantize
348
+ when Class
349
+ self.controller_class = controller_class
350
+ else
351
+ raise ArgumentError, "controller class must be a String, Symbol, or Class"
352
+ end
353
+ end
354
+
355
+ def controller_class=(new_class)
356
+ self._controller_class = new_class
357
+ end
358
+
359
+ def controller_class
360
+ if current_controller_class = _controller_class
361
+ current_controller_class
362
+ else
363
+ self.controller_class = determine_default_controller_class(name)
364
+ end
365
+ end
366
+
367
+ def determine_default_controller_class(name)
368
+ determine_constant_from_test_name(name) do |constant|
369
+ Class === constant && constant < ActionController::Metal
370
+ end
371
+ end
372
+ end
373
+
374
+ # Simulate a GET request with the given parameters.
375
+ #
376
+ # - +action+: The controller action to call.
377
+ # - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+.
378
+ # - +body+: The request body with a string that is appropriately encoded
379
+ # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
380
+ # - +session+: A hash of parameters to store in the session. This may be +nil+.
381
+ # - +flash+: A hash of parameters to store in the flash. This may be +nil+.
382
+ #
383
+ # You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with
384
+ # +post+, +patch+, +put+, +delete+, and +head+.
385
+ # Example sending parameters, session and setting a flash message:
386
+ #
387
+ # get :show,
388
+ # params: { id: 7 },
389
+ # session: { user_id: 1 },
390
+ # flash: { notice: 'This is flash message' }
391
+ #
392
+ # Note that the request method is not verified. The different methods are
393
+ # available to make the tests more expressive.
394
+ def get(action, **args)
395
+ res = process(action, method: "GET", **args)
396
+ cookies.update res.cookies
397
+ res
398
+ end
399
+
400
+ # Simulate a POST request with the given parameters and set/volley the response.
401
+ # See +get+ for more details.
402
+ def post(action, **args)
403
+ process(action, method: "POST", **args)
404
+ end
405
+
406
+ # Simulate a PATCH request with the given parameters and set/volley the response.
407
+ # See +get+ for more details.
408
+ def patch(action, **args)
409
+ process(action, method: "PATCH", **args)
410
+ end
411
+
412
+ # Simulate a PUT request with the given parameters and set/volley the response.
413
+ # See +get+ for more details.
414
+ def put(action, **args)
415
+ process(action, method: "PUT", **args)
416
+ end
417
+
418
+ # Simulate a DELETE request with the given parameters and set/volley the response.
419
+ # See +get+ for more details.
420
+ def delete(action, **args)
421
+ process(action, method: "DELETE", **args)
422
+ end
423
+
424
+ # Simulate a HEAD request with the given parameters and set/volley the response.
425
+ # See +get+ for more details.
426
+ def head(action, **args)
427
+ process(action, method: "HEAD", **args)
428
+ end
429
+
430
+ # Simulate an HTTP request to +action+ by specifying request method,
431
+ # parameters and set/volley the response.
432
+ #
433
+ # - +action+: The controller action to call.
434
+ # - +method+: Request method used to send the HTTP request. Possible values
435
+ # are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+. Can be a symbol.
436
+ # - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+.
437
+ # - +body+: The request body with a string that is appropriately encoded
438
+ # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
439
+ # - +session+: A hash of parameters to store in the session. This may be +nil+.
440
+ # - +flash+: A hash of parameters to store in the flash. This may be +nil+.
441
+ # - +format+: Request format. Defaults to +nil+. Can be string or symbol.
442
+ # - +as+: Content type. Defaults to +nil+. Must be a symbol that corresponds
443
+ # to a mime type.
444
+ #
445
+ # Example calling +create+ action and sending two params:
446
+ #
447
+ # process :create,
448
+ # method: 'POST',
449
+ # params: {
450
+ # user: { name: 'Gaurish Sharma', email: 'user@example.com' }
451
+ # },
452
+ # session: { user_id: 1 },
453
+ # flash: { notice: 'This is flash message' }
454
+ #
455
+ # To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+ and +HEAD+ requests
456
+ # prefer using #get, #post, #patch, #put, #delete and #head methods
457
+ # respectively which will make tests more expressive.
458
+ #
459
+ # Note that the request method is not verified.
460
+ def process(action, method: "GET", params: nil, session: nil, body: nil, flash: {}, format: nil, xhr: false, as: nil)
461
+ check_required_ivars
462
+
463
+ http_method = method.to_s.upcase
464
+
465
+ @html_document = nil
466
+
467
+ cookies.update(@request.cookies)
468
+ cookies.update_cookies_from_jar
469
+ @request.set_header "HTTP_COOKIE", cookies.to_header
470
+ @request.delete_header "action_dispatch.cookies"
471
+
472
+ @request = TestRequest.new scrub_env!(@request.env), @request.session, @controller.class
473
+ @response = build_response @response_klass
474
+ @response.request = @request
475
+ @controller.recycle!
476
+
477
+ if body
478
+ @request.set_header "RAW_POST_DATA", body
479
+ end
480
+
481
+ @request.set_header "REQUEST_METHOD", http_method
482
+
483
+ if as
484
+ @request.content_type = Mime[as].to_s
485
+ format ||= as
486
+ end
487
+
488
+ parameters = (params || {}).symbolize_keys
489
+
490
+ if format
491
+ parameters[:format] = format
492
+ end
493
+
494
+ generated_extras = @routes.generate_extras(parameters.merge(controller: controller_class_name, action: action.to_s))
495
+ generated_path = generated_path(generated_extras)
496
+ query_string_keys = query_parameter_names(generated_extras)
497
+
498
+ @request.assign_parameters(@routes, controller_class_name, action.to_s, parameters, generated_path, query_string_keys)
499
+
500
+ @request.session.update(session) if session
501
+ @request.flash.update(flash || {})
502
+
503
+ if xhr
504
+ @request.set_header "HTTP_X_REQUESTED_WITH", "XMLHttpRequest"
505
+ @request.fetch_header("HTTP_ACCEPT") do |k|
506
+ @request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ")
507
+ end
508
+ end
509
+
510
+ @request.fetch_header("SCRIPT_NAME") do |k|
511
+ @request.set_header k, @controller.config.relative_url_root
512
+ end
513
+
514
+ begin
515
+ @controller.recycle!
516
+ @controller.dispatch(action, @request, @response)
517
+ ensure
518
+ @request = @controller.request
519
+ @response = @controller.response
520
+
521
+ if @request.have_cookie_jar?
522
+ unless @request.cookie_jar.committed?
523
+ @request.cookie_jar.write(@response)
524
+ cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
525
+ end
526
+ end
527
+ @response.prepare!
528
+
529
+ if flash_value = @request.flash.to_session_value
530
+ @request.session["flash"] = flash_value
531
+ else
532
+ @request.session.delete("flash")
533
+ end
534
+
535
+ if xhr
536
+ @request.delete_header "HTTP_X_REQUESTED_WITH"
537
+ @request.delete_header "HTTP_ACCEPT"
538
+ end
539
+ @request.query_string = ""
540
+
541
+ @response.sent!
542
+ end
543
+
544
+ @response
545
+ end
546
+
547
+ def controller_class_name
548
+ @controller.class.anonymous? ? "anonymous" : @controller.class.controller_path
549
+ end
550
+
551
+ def generated_path(generated_extras)
552
+ generated_extras[0]
553
+ end
554
+
555
+ def query_parameter_names(generated_extras)
556
+ generated_extras[1] + [:controller, :action]
557
+ end
558
+
559
+ def setup_controller_request_and_response
560
+ @controller = nil unless defined? @controller
561
+
562
+ @response_klass = ActionDispatch::TestResponse
563
+
564
+ if klass = self.class.controller_class
565
+ if klass < ActionController::Live
566
+ @response_klass = LiveTestResponse
567
+ end
568
+ unless @controller
569
+ begin
570
+ @controller = klass.new
571
+ rescue
572
+ warn "could not construct controller #{klass}" if $VERBOSE
573
+ end
574
+ end
575
+ end
576
+
577
+ @request = TestRequest.create(@controller.class)
578
+ @response = build_response @response_klass
579
+ @response.request = @request
580
+
581
+ if @controller
582
+ @controller.request = @request
583
+ @controller.params = {}
584
+ end
585
+ end
586
+
587
+ def build_response(klass)
588
+ klass.create
589
+ end
590
+
591
+ included do
592
+ include ActionController::TemplateAssertions
593
+ include ActionDispatch::Assertions
594
+ class_attribute :_controller_class
595
+ setup :setup_controller_request_and_response
596
+ ActiveSupport.run_load_hooks(:action_controller_test_case, self)
597
+ end
598
+
599
+ private
600
+
601
+ def scrub_env!(env)
602
+ env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
603
+ env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
604
+ env.delete "action_dispatch.request.query_parameters"
605
+ env.delete "action_dispatch.request.request_parameters"
606
+ env["rack.input"] = StringIO.new
607
+ env.delete "CONTENT_LENGTH"
608
+ env.delete "RAW_POST_DATA"
609
+ env
610
+ end
611
+
612
+ def document_root_element
613
+ html_document.root
614
+ end
615
+
616
+ def check_required_ivars
617
+ # Sanity check for required instance variables so we can give an
618
+ # understandable error message.
619
+ [:@routes, :@controller, :@request, :@response].each do |iv_name|
620
+ if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil?
621
+ raise "#{iv_name} is nil: make sure you set it in your test's setup method."
622
+ end
623
+ end
624
+ end
625
+ end
626
+
627
+ include Behavior
628
+ end
629
+ end