omg-actionpack 8.0.0.alpha1

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 (187) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +129 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +57 -0
  5. data/lib/abstract_controller/asset_paths.rb +14 -0
  6. data/lib/abstract_controller/base.rb +299 -0
  7. data/lib/abstract_controller/caching/fragments.rb +149 -0
  8. data/lib/abstract_controller/caching.rb +68 -0
  9. data/lib/abstract_controller/callbacks.rb +265 -0
  10. data/lib/abstract_controller/collector.rb +44 -0
  11. data/lib/abstract_controller/deprecator.rb +9 -0
  12. data/lib/abstract_controller/error.rb +8 -0
  13. data/lib/abstract_controller/helpers.rb +243 -0
  14. data/lib/abstract_controller/logger.rb +16 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +25 -0
  16. data/lib/abstract_controller/rendering.rb +126 -0
  17. data/lib/abstract_controller/translation.rb +42 -0
  18. data/lib/abstract_controller/url_for.rb +37 -0
  19. data/lib/abstract_controller.rb +36 -0
  20. data/lib/action_controller/api/api_rendering.rb +18 -0
  21. data/lib/action_controller/api.rb +155 -0
  22. data/lib/action_controller/base.rb +332 -0
  23. data/lib/action_controller/caching.rb +49 -0
  24. data/lib/action_controller/deprecator.rb +9 -0
  25. data/lib/action_controller/form_builder.rb +55 -0
  26. data/lib/action_controller/log_subscriber.rb +96 -0
  27. data/lib/action_controller/metal/allow_browser.rb +123 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
  29. data/lib/action_controller/metal/conditional_get.rb +341 -0
  30. data/lib/action_controller/metal/content_security_policy.rb +86 -0
  31. data/lib/action_controller/metal/cookies.rb +20 -0
  32. data/lib/action_controller/metal/data_streaming.rb +154 -0
  33. data/lib/action_controller/metal/default_headers.rb +21 -0
  34. data/lib/action_controller/metal/etag_with_flash.rb +22 -0
  35. data/lib/action_controller/metal/etag_with_template_digest.rb +59 -0
  36. data/lib/action_controller/metal/exceptions.rb +106 -0
  37. data/lib/action_controller/metal/flash.rb +67 -0
  38. data/lib/action_controller/metal/head.rb +67 -0
  39. data/lib/action_controller/metal/helpers.rb +129 -0
  40. data/lib/action_controller/metal/http_authentication.rb +565 -0
  41. data/lib/action_controller/metal/implicit_render.rb +67 -0
  42. data/lib/action_controller/metal/instrumentation.rb +120 -0
  43. data/lib/action_controller/metal/live.rb +398 -0
  44. data/lib/action_controller/metal/logging.rb +22 -0
  45. data/lib/action_controller/metal/mime_responds.rb +337 -0
  46. data/lib/action_controller/metal/parameter_encoding.rb +84 -0
  47. data/lib/action_controller/metal/params_wrapper.rb +312 -0
  48. data/lib/action_controller/metal/permissions_policy.rb +38 -0
  49. data/lib/action_controller/metal/rate_limiting.rb +62 -0
  50. data/lib/action_controller/metal/redirecting.rb +251 -0
  51. data/lib/action_controller/metal/renderers.rb +181 -0
  52. data/lib/action_controller/metal/rendering.rb +260 -0
  53. data/lib/action_controller/metal/request_forgery_protection.rb +667 -0
  54. data/lib/action_controller/metal/rescue.rb +33 -0
  55. data/lib/action_controller/metal/streaming.rb +183 -0
  56. data/lib/action_controller/metal/strong_parameters.rb +1546 -0
  57. data/lib/action_controller/metal/testing.rb +25 -0
  58. data/lib/action_controller/metal/url_for.rb +65 -0
  59. data/lib/action_controller/metal.rb +339 -0
  60. data/lib/action_controller/railtie.rb +149 -0
  61. data/lib/action_controller/railties/helpers.rb +26 -0
  62. data/lib/action_controller/renderer.rb +161 -0
  63. data/lib/action_controller/template_assertions.rb +13 -0
  64. data/lib/action_controller/test_case.rb +691 -0
  65. data/lib/action_controller.rb +80 -0
  66. data/lib/action_dispatch/constants.rb +34 -0
  67. data/lib/action_dispatch/deprecator.rb +9 -0
  68. data/lib/action_dispatch/http/cache.rb +249 -0
  69. data/lib/action_dispatch/http/content_disposition.rb +47 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +365 -0
  71. data/lib/action_dispatch/http/filter_parameters.rb +80 -0
  72. data/lib/action_dispatch/http/filter_redirect.rb +50 -0
  73. data/lib/action_dispatch/http/headers.rb +134 -0
  74. data/lib/action_dispatch/http/mime_negotiation.rb +187 -0
  75. data/lib/action_dispatch/http/mime_type.rb +389 -0
  76. data/lib/action_dispatch/http/mime_types.rb +54 -0
  77. data/lib/action_dispatch/http/parameters.rb +119 -0
  78. data/lib/action_dispatch/http/permissions_policy.rb +189 -0
  79. data/lib/action_dispatch/http/rack_cache.rb +67 -0
  80. data/lib/action_dispatch/http/request.rb +498 -0
  81. data/lib/action_dispatch/http/response.rb +556 -0
  82. data/lib/action_dispatch/http/upload.rb +107 -0
  83. data/lib/action_dispatch/http/url.rb +344 -0
  84. data/lib/action_dispatch/journey/formatter.rb +226 -0
  85. data/lib/action_dispatch/journey/gtg/builder.rb +149 -0
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +50 -0
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +217 -0
  88. data/lib/action_dispatch/journey/nfa/dot.rb +27 -0
  89. data/lib/action_dispatch/journey/nodes/node.rb +208 -0
  90. data/lib/action_dispatch/journey/parser.rb +103 -0
  91. data/lib/action_dispatch/journey/path/pattern.rb +209 -0
  92. data/lib/action_dispatch/journey/route.rb +189 -0
  93. data/lib/action_dispatch/journey/router/utils.rb +105 -0
  94. data/lib/action_dispatch/journey/router.rb +151 -0
  95. data/lib/action_dispatch/journey/routes.rb +82 -0
  96. data/lib/action_dispatch/journey/scanner.rb +70 -0
  97. data/lib/action_dispatch/journey/visitors.rb +267 -0
  98. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  99. data/lib/action_dispatch/journey/visualizer/fsm.js +159 -0
  100. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  101. data/lib/action_dispatch/journey.rb +7 -0
  102. data/lib/action_dispatch/log_subscriber.rb +25 -0
  103. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  104. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  105. data/lib/action_dispatch/middleware/callbacks.rb +38 -0
  106. data/lib/action_dispatch/middleware/cookies.rb +719 -0
  107. data/lib/action_dispatch/middleware/debug_exceptions.rb +206 -0
  108. data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
  109. data/lib/action_dispatch/middleware/debug_view.rb +73 -0
  110. data/lib/action_dispatch/middleware/exception_wrapper.rb +350 -0
  111. data/lib/action_dispatch/middleware/executor.rb +32 -0
  112. data/lib/action_dispatch/middleware/flash.rb +318 -0
  113. data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
  114. data/lib/action_dispatch/middleware/public_exceptions.rb +64 -0
  115. data/lib/action_dispatch/middleware/reloader.rb +16 -0
  116. data/lib/action_dispatch/middleware/remote_ip.rb +199 -0
  117. data/lib/action_dispatch/middleware/request_id.rb +50 -0
  118. data/lib/action_dispatch/middleware/server_timing.rb +78 -0
  119. data/lib/action_dispatch/middleware/session/abstract_store.rb +112 -0
  120. data/lib/action_dispatch/middleware/session/cache_store.rb +66 -0
  121. data/lib/action_dispatch/middleware/session/cookie_store.rb +129 -0
  122. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +34 -0
  123. data/lib/action_dispatch/middleware/show_exceptions.rb +88 -0
  124. data/lib/action_dispatch/middleware/ssl.rb +180 -0
  125. data/lib/action_dispatch/middleware/stack.rb +194 -0
  126. data/lib/action_dispatch/middleware/static.rb +192 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +17 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +62 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  136. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
  137. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
  138. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +35 -0
  139. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  140. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  141. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
  142. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +284 -0
  143. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
  144. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  145. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  146. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  147. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  148. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  149. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  150. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  151. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  152. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  153. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +19 -0
  154. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +232 -0
  155. data/lib/action_dispatch/railtie.rb +77 -0
  156. data/lib/action_dispatch/request/session.rb +283 -0
  157. data/lib/action_dispatch/request/utils.rb +109 -0
  158. data/lib/action_dispatch/routing/endpoint.rb +19 -0
  159. data/lib/action_dispatch/routing/inspector.rb +323 -0
  160. data/lib/action_dispatch/routing/mapper.rb +2372 -0
  161. data/lib/action_dispatch/routing/polymorphic_routes.rb +363 -0
  162. data/lib/action_dispatch/routing/redirection.rb +218 -0
  163. data/lib/action_dispatch/routing/route_set.rb +958 -0
  164. data/lib/action_dispatch/routing/routes_proxy.rb +66 -0
  165. data/lib/action_dispatch/routing/url_for.rb +244 -0
  166. data/lib/action_dispatch/routing.rb +262 -0
  167. data/lib/action_dispatch/system_test_case.rb +206 -0
  168. data/lib/action_dispatch/system_testing/browser.rb +75 -0
  169. data/lib/action_dispatch/system_testing/driver.rb +85 -0
  170. data/lib/action_dispatch/system_testing/server.rb +33 -0
  171. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
  172. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
  173. data/lib/action_dispatch/testing/assertion_response.rb +48 -0
  174. data/lib/action_dispatch/testing/assertions/response.rb +114 -0
  175. data/lib/action_dispatch/testing/assertions/routing.rb +343 -0
  176. data/lib/action_dispatch/testing/assertions.rb +25 -0
  177. data/lib/action_dispatch/testing/integration.rb +694 -0
  178. data/lib/action_dispatch/testing/request_encoder.rb +60 -0
  179. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  180. data/lib/action_dispatch/testing/test_process.rb +57 -0
  181. data/lib/action_dispatch/testing/test_request.rb +73 -0
  182. data/lib/action_dispatch/testing/test_response.rb +58 -0
  183. data/lib/action_dispatch.rb +147 -0
  184. data/lib/action_pack/gem_version.rb +19 -0
  185. data/lib/action_pack/version.rb +12 -0
  186. data/lib/action_pack.rb +27 -0
  187. metadata +375 -0
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module ActionDispatch
6
+ module Assertions
7
+ # A small suite of assertions that test responses from Rails applications.
8
+ module ResponseAssertions
9
+ RESPONSE_PREDICATES = { # :nodoc:
10
+ success: :successful?,
11
+ missing: :not_found?,
12
+ redirect: :redirection?,
13
+ error: :server_error?,
14
+ }
15
+
16
+ # Asserts that the response is one of the following types:
17
+ #
18
+ # * `:success` - Status code was in the 200-299 range
19
+ # * `:redirect` - Status code was in the 300-399 range
20
+ # * `:missing` - Status code was 404
21
+ # * `:error` - Status code was in the 500-599 range
22
+ #
23
+ #
24
+ # You can also pass an explicit status number like `assert_response(501)` or its
25
+ # symbolic equivalent `assert_response(:not_implemented)`. See
26
+ # `Rack::Utils::SYMBOL_TO_STATUS_CODE` for a full list.
27
+ #
28
+ # # Asserts that the response was a redirection
29
+ # assert_response :redirect
30
+ #
31
+ # # Asserts that the response code was status code 401 (unauthorized)
32
+ # assert_response 401
33
+ def assert_response(type, message = nil)
34
+ message ||= generate_response_message(type)
35
+
36
+ if RESPONSE_PREDICATES.key?(type)
37
+ assert @response.public_send(RESPONSE_PREDICATES[type]), message
38
+ else
39
+ assert_equal AssertionResponse.new(type).code, @response.response_code, message
40
+ end
41
+ end
42
+
43
+ # Asserts that the response is a redirect to a URL matching the given options.
44
+ #
45
+ # # Asserts that the redirection was to the "index" action on the WeblogController
46
+ # assert_redirected_to controller: "weblog", action: "index"
47
+ #
48
+ # # Asserts that the redirection was to the named route login_url
49
+ # assert_redirected_to login_url
50
+ #
51
+ # # Asserts that the redirection was to the URL for @customer
52
+ # assert_redirected_to @customer
53
+ #
54
+ # # Asserts that the redirection matches the regular expression
55
+ # assert_redirected_to %r(\Ahttp://example.org)
56
+ #
57
+ # # Asserts that the redirection has the HTTP status code 301 (Moved
58
+ # # Permanently).
59
+ # assert_redirected_to "/some/path", status: :moved_permanently
60
+ def assert_redirected_to(url_options = {}, options = {}, message = nil)
61
+ options, message = {}, options unless options.is_a?(Hash)
62
+
63
+ status = options[:status] || :redirect
64
+ assert_response(status, message)
65
+ return true if url_options === @response.location
66
+
67
+ redirect_is = normalize_argument_to_redirection(@response.location)
68
+ redirect_expected = normalize_argument_to_redirection(url_options)
69
+
70
+ message ||= "Expected response to be a redirect to <#{redirect_expected}> but was a redirect to <#{redirect_is}>"
71
+ assert_operator redirect_expected, :===, redirect_is, message
72
+ end
73
+
74
+ private
75
+ # Proxy to to_param if the object will respond to it.
76
+ def parameterize(value)
77
+ value.respond_to?(:to_param) ? value.to_param : value
78
+ end
79
+
80
+ def normalize_argument_to_redirection(fragment)
81
+ if Regexp === fragment
82
+ fragment
83
+ else
84
+ handle = @controller || ActionController::Redirecting
85
+ handle._compute_redirect_to_location(@request, fragment)
86
+ end
87
+ end
88
+
89
+ def generate_response_message(expected, actual = @response.response_code)
90
+ (+"Expected response to be a <#{code_with_name(expected)}>,"\
91
+ " but was a <#{code_with_name(actual)}>").concat(location_if_redirected).concat(response_body_if_short)
92
+ end
93
+
94
+ def response_body_if_short
95
+ return "" if @response.body.size > 500
96
+ "\nResponse body: #{@response.body}"
97
+ end
98
+
99
+ def location_if_redirected
100
+ return "" unless @response.redirection? && @response.location.present?
101
+ location = normalize_argument_to_redirection(@response.location)
102
+ " redirect to <#{location}>"
103
+ end
104
+
105
+ def code_with_name(code_or_name)
106
+ if RESPONSE_PREDICATES.value?("#{code_or_name}?".to_sym)
107
+ code_or_name = RESPONSE_PREDICATES.invert["#{code_or_name}?".to_sym]
108
+ end
109
+
110
+ AssertionResponse.new(code_or_name).code_and_name
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,343 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "uri"
6
+ require "active_support/core_ext/hash/indifferent_access"
7
+ require "active_support/core_ext/string/access"
8
+ require "action_controller/metal/exceptions"
9
+
10
+ module ActionDispatch
11
+ module Assertions
12
+ # Suite of assertions to test routes generated by Rails and the handling of
13
+ # requests made to them.
14
+ module RoutingAssertions
15
+ extend ActiveSupport::Concern
16
+
17
+ module WithIntegrationRouting # :nodoc:
18
+ extend ActiveSupport::Concern
19
+
20
+ module ClassMethods
21
+ def with_routing(&block)
22
+ old_routes = nil
23
+ old_integration_session = nil
24
+
25
+ setup do
26
+ old_routes = app.routes
27
+ old_integration_session = integration_session
28
+ create_routes(&block)
29
+ end
30
+
31
+ teardown do
32
+ reset_routes(old_routes, old_integration_session)
33
+ end
34
+ end
35
+ end
36
+
37
+ def with_routing(&block)
38
+ old_routes = app.routes
39
+ old_integration_session = integration_session
40
+ create_routes(&block)
41
+ ensure
42
+ reset_routes(old_routes, old_integration_session)
43
+ end
44
+
45
+ private
46
+ def create_routes
47
+ app = self.app
48
+ routes = ActionDispatch::Routing::RouteSet.new
49
+ rack_app = app.config.middleware.build(routes)
50
+ https = integration_session.https?
51
+ host = integration_session.host
52
+
53
+ app.instance_variable_set(:@routes, routes)
54
+ app.instance_variable_set(:@app, rack_app)
55
+ @integration_session = Class.new(ActionDispatch::Integration::Session) do
56
+ include app.routes.url_helpers
57
+ include app.routes.mounted_helpers
58
+ end.new(app)
59
+ @integration_session.https! https
60
+ @integration_session.host! host
61
+ @routes = routes
62
+
63
+ yield routes
64
+ end
65
+
66
+ def reset_routes(old_routes, old_integration_session)
67
+ old_rack_app = app.config.middleware.build(old_routes)
68
+
69
+ app.instance_variable_set(:@routes, old_routes)
70
+ app.instance_variable_set(:@app, old_rack_app)
71
+ @integration_session = old_integration_session
72
+ @routes = old_routes
73
+ end
74
+ end
75
+
76
+ module ClassMethods
77
+ # A helper to make it easier to test different route configurations. This method
78
+ # temporarily replaces @routes with a new RouteSet instance before each test.
79
+ #
80
+ # The new instance is yielded to the passed block. Typically the block will
81
+ # create some routes using `set.draw { match ... }`:
82
+ #
83
+ # with_routing do |set|
84
+ # set.draw do
85
+ # resources :users
86
+ # end
87
+ # end
88
+ #
89
+ def with_routing(&block)
90
+ old_routes, old_controller = nil
91
+
92
+ setup do
93
+ old_routes, old_controller = @routes, @controller
94
+ create_routes(&block)
95
+ end
96
+
97
+ teardown do
98
+ reset_routes(old_routes, old_controller)
99
+ end
100
+ end
101
+ end
102
+
103
+ def setup # :nodoc:
104
+ @routes ||= nil
105
+ super
106
+ end
107
+
108
+ # A helper to make it easier to test different route configurations. This method
109
+ # temporarily replaces @routes with a new RouteSet instance.
110
+ #
111
+ # The new instance is yielded to the passed block. Typically the block will
112
+ # create some routes using `set.draw { match ... }`:
113
+ #
114
+ # with_routing do |set|
115
+ # set.draw do
116
+ # resources :users
117
+ # end
118
+ # assert_equal "/users", users_path
119
+ # end
120
+ #
121
+ def with_routing(&block)
122
+ old_routes, old_controller = @routes, @controller
123
+ create_routes(&block)
124
+ ensure
125
+ reset_routes(old_routes, old_controller)
126
+ end
127
+
128
+ # Asserts that the routing of the given `path` was handled correctly and that
129
+ # the parsed options (given in the `expected_options` hash) match `path`.
130
+ # Basically, it asserts that Rails recognizes the route given by
131
+ # `expected_options`.
132
+ #
133
+ # Pass a hash in the second argument (`path`) to specify the request method.
134
+ # This is useful for routes requiring a specific HTTP method. The hash should
135
+ # contain a `:path` with the incoming request path and a `:method` containing
136
+ # the required HTTP verb.
137
+ #
138
+ # # Asserts that POSTing to /items will call the create action on ItemsController
139
+ # assert_recognizes({controller: 'items', action: 'create'}, {path: 'items', method: :post})
140
+ #
141
+ # You can also pass in `extras` with a hash containing URL parameters that would
142
+ # normally be in the query string. This can be used to assert that values in the
143
+ # query string will end up in the params hash correctly. To test query strings
144
+ # you must use the extras argument because appending the query string on the
145
+ # path directly will not work. For example:
146
+ #
147
+ # # Asserts that a path of '/items/list/1?view=print' returns the correct options
148
+ # assert_recognizes({controller: 'items', action: 'list', id: '1', view: 'print'}, 'items/list/1', { view: "print" })
149
+ #
150
+ # The `message` parameter allows you to pass in an error message that is
151
+ # displayed upon failure.
152
+ #
153
+ # # Check the default route (i.e., the index action)
154
+ # assert_recognizes({controller: 'items', action: 'index'}, 'items')
155
+ #
156
+ # # Test a specific action
157
+ # assert_recognizes({controller: 'items', action: 'list'}, 'items/list')
158
+ #
159
+ # # Test an action with a parameter
160
+ # assert_recognizes({controller: 'items', action: 'destroy', id: '1'}, 'items/destroy/1')
161
+ #
162
+ # # Test a custom route
163
+ # assert_recognizes({controller: 'items', action: 'show', id: '1'}, 'view/item1')
164
+ def assert_recognizes(expected_options, path, extras = {}, msg = nil)
165
+ if path.is_a?(Hash) && path[:method].to_s == "all"
166
+ [:get, :post, :put, :delete].each do |method|
167
+ assert_recognizes(expected_options, path.merge(method: method), extras, msg)
168
+ end
169
+ else
170
+ request = recognized_request_for(path, extras, msg)
171
+
172
+ expected_options = expected_options.clone
173
+
174
+ expected_options.stringify_keys!
175
+
176
+ msg = message(msg, "") {
177
+ sprintf("The recognized options <%s> did not match <%s>, difference:",
178
+ request.path_parameters, expected_options)
179
+ }
180
+
181
+ assert_equal(expected_options, request.path_parameters, msg)
182
+ end
183
+ end
184
+
185
+ # Asserts that the provided options can be used to generate the provided path.
186
+ # This is the inverse of `assert_recognizes`. The `extras` parameter is used to
187
+ # tell the request the names and values of additional request parameters that
188
+ # would be in a query string. The `message` parameter allows you to specify a
189
+ # custom error message for assertion failures.
190
+ #
191
+ # The `defaults` parameter is unused.
192
+ #
193
+ # # Asserts that the default action is generated for a route with no action
194
+ # assert_generates "/items", controller: "items", action: "index"
195
+ #
196
+ # # Tests that the list action is properly routed
197
+ # assert_generates "/items/list", controller: "items", action: "list"
198
+ #
199
+ # # Tests the generation of a route with a parameter
200
+ # assert_generates "/items/list/1", { controller: "items", action: "list", id: "1" }
201
+ #
202
+ # # Asserts that the generated route gives us our custom route
203
+ # assert_generates "changesets/12", { controller: 'scm', action: 'show_diff', revision: "12" }
204
+ def assert_generates(expected_path, options, defaults = {}, extras = {}, message = nil)
205
+ if expected_path.include?("://")
206
+ fail_on(URI::InvalidURIError, message) do
207
+ uri = URI.parse(expected_path)
208
+ expected_path = uri.path.to_s.empty? ? "/" : uri.path
209
+ end
210
+ else
211
+ expected_path = "/#{expected_path}" unless expected_path.start_with?("/")
212
+ end
213
+
214
+ options = options.clone
215
+ generated_path, query_string_keys = @routes.generate_extras(options, defaults)
216
+ found_extras = options.reject { |k, _| ! query_string_keys.include? k }
217
+
218
+ msg = message || sprintf("found extras <%s>, not <%s>", found_extras, extras)
219
+ assert_equal(extras, found_extras, msg)
220
+
221
+ msg = message || sprintf("The generated path <%s> did not match <%s>", generated_path,
222
+ expected_path)
223
+ assert_equal(expected_path, generated_path, msg)
224
+ end
225
+
226
+ # Asserts that path and options match both ways; in other words, it verifies
227
+ # that `path` generates `options` and then that `options` generates `path`. This
228
+ # essentially combines `assert_recognizes` and `assert_generates` into one step.
229
+ #
230
+ # The `extras` hash allows you to specify options that would normally be
231
+ # provided as a query string to the action. The `message` parameter allows you
232
+ # to specify a custom error message to display upon failure.
233
+ #
234
+ # # Asserts a basic route: a controller with the default action (index)
235
+ # assert_routing '/home', controller: 'home', action: 'index'
236
+ #
237
+ # # Test a route generated with a specific controller, action, and parameter (id)
238
+ # assert_routing '/entries/show/23', controller: 'entries', action: 'show', id: 23
239
+ #
240
+ # # Asserts a basic route (controller + default action), with an error message if it fails
241
+ # assert_routing '/store', { controller: 'store', action: 'index' }, {}, {}, 'Route for store index not generated properly'
242
+ #
243
+ # # Tests a route, providing a defaults hash
244
+ # assert_routing 'controller/action/9', {id: "9", item: "square"}, {controller: "controller", action: "action"}, {}, {item: "square"}
245
+ #
246
+ # # Tests a route with an HTTP method
247
+ # assert_routing({ method: 'put', path: '/product/321' }, { controller: "product", action: "update", id: "321" })
248
+ def assert_routing(path, options, defaults = {}, extras = {}, message = nil)
249
+ assert_recognizes(options, path, extras, message)
250
+
251
+ controller, default_controller = options[:controller], defaults[:controller]
252
+ if controller && controller.include?(?/) && default_controller && default_controller.include?(?/)
253
+ options[:controller] = "/#{controller}"
254
+ end
255
+
256
+ generate_options = options.dup.delete_if { |k, _| defaults.key?(k) }
257
+ assert_generates(path.is_a?(Hash) ? path[:path] : path, generate_options, defaults, extras, message)
258
+ end
259
+
260
+ # ROUTES TODO: These assertions should really work in an integration context
261
+ def method_missing(selector, ...)
262
+ if @controller && @routes&.named_routes&.route_defined?(selector)
263
+ @controller.public_send(selector, ...)
264
+ else
265
+ super
266
+ end
267
+ end
268
+
269
+ private
270
+ def create_routes
271
+ @routes = ActionDispatch::Routing::RouteSet.new
272
+ if @controller
273
+ @controller = @controller.clone
274
+ _routes = @routes
275
+
276
+ @controller.singleton_class.include(_routes.url_helpers)
277
+
278
+ if @controller.respond_to? :view_context_class
279
+ view_context_class = Class.new(@controller.view_context_class) do
280
+ include _routes.url_helpers
281
+ end
282
+
283
+ custom_view_context = Module.new {
284
+ define_method(:view_context_class) do
285
+ view_context_class
286
+ end
287
+ }
288
+ @controller.extend(custom_view_context)
289
+ end
290
+ end
291
+ yield @routes
292
+ end
293
+
294
+ def reset_routes(old_routes, old_controller)
295
+ @routes = old_routes
296
+ if @controller
297
+ @controller = old_controller
298
+ end
299
+ end
300
+
301
+ # Recognizes the route for a given path.
302
+ def recognized_request_for(path, extras = {}, msg)
303
+ if path.is_a?(Hash)
304
+ method = path[:method]
305
+ path = path[:path]
306
+ else
307
+ method = :get
308
+ end
309
+
310
+ controller = @controller if defined?(@controller)
311
+ request = ActionController::TestRequest.create controller&.class
312
+
313
+ if path.include?("://")
314
+ fail_on(URI::InvalidURIError, msg) do
315
+ uri = URI.parse(path)
316
+ request.env["rack.url_scheme"] = uri.scheme || "http"
317
+ request.host = uri.host if uri.host
318
+ request.port = uri.port if uri.port
319
+ request.path = uri.path.to_s.empty? ? "/" : uri.path
320
+ end
321
+ else
322
+ path = "/#{path}" unless path.start_with?("/")
323
+ request.path = path
324
+ end
325
+
326
+ request.request_method = method if method
327
+
328
+ params = fail_on(ActionController::RoutingError, msg) do
329
+ @routes.recognize_path(path, method: method, extras: extras)
330
+ end
331
+ request.path_parameters = params.with_indifferent_access
332
+
333
+ request
334
+ end
335
+
336
+ def fail_on(exception_class, message)
337
+ yield
338
+ rescue exception_class => e
339
+ raise Minitest::Assertion, message || e.message
340
+ end
341
+ end
342
+ end
343
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "rails-dom-testing"
6
+ require "action_dispatch/testing/assertions/response"
7
+ require "action_dispatch/testing/assertions/routing"
8
+
9
+ module ActionDispatch
10
+ module Assertions
11
+ extend ActiveSupport::Concern
12
+
13
+ include ResponseAssertions
14
+ include RoutingAssertions
15
+ include Rails::Dom::Testing::Assertions
16
+
17
+ def html_document
18
+ @html_document ||= if @response.media_type&.end_with?("xml")
19
+ Nokogiri::XML::Document.parse(@response.body)
20
+ else
21
+ Rails::Dom::Testing.html_document.parse(@response.body)
22
+ end
23
+ end
24
+ end
25
+ end