rspec-rails 3.0.2 → 7.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.document +1 -1
  4. data/.yardopts +3 -1
  5. data/Capybara.md +6 -55
  6. data/Changelog.md +805 -47
  7. data/{License.txt → LICENSE.md} +5 -3
  8. data/README.md +278 -444
  9. data/lib/generators/rspec/channel/channel_generator.rb +12 -0
  10. data/lib/generators/rspec/{observer/templates/observer_spec.rb → channel/templates/channel_spec.rb.erb} +1 -1
  11. data/lib/generators/rspec/controller/controller_generator.rb +24 -7
  12. data/lib/generators/rspec/controller/templates/controller_spec.rb +3 -3
  13. data/lib/generators/rspec/controller/templates/request_spec.rb +19 -0
  14. data/lib/generators/rspec/controller/templates/routing_spec.rb +13 -0
  15. data/lib/generators/rspec/controller/templates/view_spec.rb +1 -1
  16. data/lib/generators/rspec/feature/feature_generator.rb +15 -2
  17. data/lib/generators/rspec/feature/templates/feature_singular_spec.rb +5 -0
  18. data/lib/generators/rspec/feature/templates/feature_spec.rb +1 -1
  19. data/lib/generators/rspec/generator/generator_generator.rb +24 -0
  20. data/lib/generators/rspec/generator/templates/generator_spec.rb +5 -0
  21. data/lib/generators/rspec/helper/helper_generator.rb +2 -2
  22. data/lib/generators/rspec/helper/templates/helper_spec.rb +1 -1
  23. data/lib/generators/rspec/install/install_generator.rb +41 -7
  24. data/lib/generators/rspec/install/templates/spec/rails_helper.rb +63 -22
  25. data/lib/generators/rspec/job/job_generator.rb +13 -0
  26. data/lib/generators/rspec/job/templates/job_spec.rb.erb +7 -0
  27. data/lib/generators/rspec/mailbox/mailbox_generator.rb +14 -0
  28. data/lib/generators/rspec/mailbox/templates/mailbox_spec.rb.erb +7 -0
  29. data/lib/generators/rspec/mailer/mailer_generator.rb +12 -3
  30. data/lib/generators/rspec/mailer/templates/mailer_spec.rb +2 -2
  31. data/lib/generators/rspec/mailer/templates/preview.rb +13 -0
  32. data/lib/generators/rspec/model/model_generator.rb +20 -6
  33. data/lib/generators/rspec/model/templates/fixtures.yml +1 -1
  34. data/lib/generators/rspec/model/templates/model_spec.rb +1 -1
  35. data/lib/generators/rspec/request/request_generator.rb +17 -0
  36. data/lib/generators/rspec/request/templates/request_spec.rb +10 -0
  37. data/lib/generators/rspec/scaffold/scaffold_generator.rb +90 -113
  38. data/lib/generators/rspec/scaffold/templates/api_controller_spec.rb +129 -0
  39. data/lib/generators/rspec/scaffold/templates/api_request_spec.rb +131 -0
  40. data/lib/generators/rspec/scaffold/templates/controller_spec.rb +46 -64
  41. data/lib/generators/rspec/scaffold/templates/edit_spec.rb +11 -7
  42. data/lib/generators/rspec/scaffold/templates/index_spec.rb +4 -3
  43. data/lib/generators/rspec/scaffold/templates/new_spec.rb +4 -4
  44. data/lib/generators/rspec/scaffold/templates/request_spec.rb +138 -0
  45. data/lib/generators/rspec/scaffold/templates/routing_spec.rb +18 -11
  46. data/lib/generators/rspec/scaffold/templates/show_spec.rb +3 -3
  47. data/lib/generators/rspec/system/system_generator.rb +24 -0
  48. data/lib/generators/rspec/system/templates/system_spec.rb +9 -0
  49. data/lib/generators/rspec/view/templates/view_spec.rb +1 -1
  50. data/lib/generators/rspec/view/view_generator.rb +4 -4
  51. data/lib/generators/rspec.rb +30 -11
  52. data/lib/rspec/rails/active_record.rb +25 -0
  53. data/lib/rspec/rails/adapters.rb +46 -29
  54. data/lib/rspec/rails/configuration.rb +165 -41
  55. data/lib/rspec/rails/example/channel_example_group.rb +93 -0
  56. data/lib/rspec/rails/example/controller_example_group.rb +185 -149
  57. data/lib/rspec/rails/example/feature_example_group.rb +43 -23
  58. data/lib/rspec/rails/example/helper_example_group.rb +28 -25
  59. data/lib/rspec/rails/example/job_example_group.rb +23 -0
  60. data/lib/rspec/rails/example/mailbox_example_group.rb +80 -0
  61. data/lib/rspec/rails/example/mailer_example_group.rb +27 -22
  62. data/lib/rspec/rails/example/model_example_group.rb +9 -6
  63. data/lib/rspec/rails/example/rails_example_group.rb +9 -2
  64. data/lib/rspec/rails/example/request_example_group.rb +21 -17
  65. data/lib/rspec/rails/example/routing_example_group.rb +47 -39
  66. data/lib/rspec/rails/example/system_example_group.rb +180 -0
  67. data/lib/rspec/rails/example/view_example_group.rb +179 -134
  68. data/lib/rspec/rails/example.rb +4 -0
  69. data/lib/rspec/rails/extensions/active_record/proxy.rb +5 -11
  70. data/lib/rspec/rails/feature_check.rb +51 -0
  71. data/lib/rspec/rails/file_fixture_support.rb +18 -0
  72. data/lib/rspec/rails/fixture_file_upload_support.rb +45 -0
  73. data/lib/rspec/rails/fixture_support.rb +70 -14
  74. data/lib/rspec/rails/matchers/action_cable/have_broadcasted_to.rb +180 -0
  75. data/lib/rspec/rails/matchers/action_cable/have_streams.rb +58 -0
  76. data/lib/rspec/rails/matchers/action_cable.rb +70 -0
  77. data/lib/rspec/rails/matchers/action_mailbox.rb +73 -0
  78. data/lib/rspec/rails/matchers/active_job.rb +526 -0
  79. data/lib/rspec/rails/matchers/base_matcher.rb +179 -0
  80. data/lib/rspec/rails/matchers/be_a_new.rb +70 -64
  81. data/lib/rspec/rails/matchers/be_new_record.rb +25 -20
  82. data/lib/rspec/rails/matchers/be_valid.rb +39 -34
  83. data/lib/rspec/rails/matchers/have_enqueued_mail.rb +259 -0
  84. data/lib/rspec/rails/matchers/have_http_status.rb +359 -333
  85. data/lib/rspec/rails/matchers/have_rendered.rb +55 -32
  86. data/lib/rspec/rails/matchers/redirect_to.rb +30 -27
  87. data/lib/rspec/rails/matchers/relation_match_array.rb +1 -1
  88. data/lib/rspec/rails/matchers/routing_matchers.rb +107 -101
  89. data/lib/rspec/rails/matchers/send_email.rb +122 -0
  90. data/lib/rspec/rails/matchers.rb +21 -12
  91. data/lib/rspec/rails/tasks/rspec.rake +9 -17
  92. data/lib/rspec/rails/vendor/capybara.rb +10 -11
  93. data/lib/rspec/rails/version.rb +1 -1
  94. data/lib/rspec/rails/view_assigns.rb +1 -20
  95. data/lib/rspec/rails/view_path_builder.rb +29 -0
  96. data/lib/rspec/rails/view_rendering.rb +89 -27
  97. data/lib/rspec/rails/view_spec_methods.rb +56 -0
  98. data/lib/rspec/rails.rb +9 -1
  99. data/lib/rspec-rails.rb +83 -3
  100. data.tar.gz.sig +0 -0
  101. metadata +108 -78
  102. metadata.gz.sig +3 -2
  103. data/lib/generators/rspec/integration/integration_generator.rb +0 -17
  104. data/lib/generators/rspec/integration/templates/request_spec.rb +0 -10
  105. data/lib/generators/rspec/observer/observer_generator.rb +0 -13
@@ -1,355 +1,381 @@
1
1
  # The following code inspired and modified from Rails' `assert_response`:
2
2
  #
3
- # https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/testing/assertions/response.rb#L22-L38
3
+ # https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/testing/assertions/response.rb#L22-L38
4
4
  #
5
5
  # Thank you to all the Rails devs who did the heavy lifting on this!
6
6
 
7
- module RSpec::Rails::Matchers
8
- # Namespace for various implementations of `have_http_status`.
9
- #
10
- # @api private
11
- module HaveHttpStatus
12
- # Instantiates an instance of the proper matcher based on the provided
13
- # `target`.
14
- #
15
- # @param target [Object] expected http status or code
16
- # @return response matcher instance
17
- def self.matcher_for_status(target)
18
- if GenericStatus.valid_statuses.include?(target)
19
- GenericStatus.new(target)
20
- elsif Symbol === target
21
- SymbolicStatus.new(target)
22
- else
23
- NumericCode.new(target)
24
- end
25
- end
26
-
27
- # @api private
28
- # Conversion function to coerce the provided object into an
29
- # `ActionDispatch::TestResponse`.
30
- #
31
- # @param obj [Object] object to convert to a response
32
- # @return [ActionDispatch::TestResponse]
33
- def as_test_response(obj)
34
- if ::ActionDispatch::Response === obj
35
- ::ActionDispatch::TestResponse.from_response(obj)
36
- elsif ::ActionDispatch::TestResponse === obj
37
- obj
38
- elsif obj.respond_to?(:status_code) && obj.respond_to?(:response_headers)
39
- # Acts As Capybara Session
40
- # Hack to support `Capybara::Session` without having to load Capybara or
41
- # catch `NameError`s for the undefined constants
42
- ::ActionDispatch::TestResponse.new.tap{ |resp|
43
- resp.status = obj.status_code
44
- resp.headers = obj.response_headers
45
- resp.body = obj.body
46
- }
47
- else
48
- raise TypeError, "Invalid response type: #{obj}"
49
- end
50
- end
51
- module_function :as_test_response
52
-
53
- # @return [String, nil] a formatted failure message if `@invalid_response`
54
- # is present, `nil` otherwise
55
- def invalid_response_type_message
56
- if @invalid_response
57
- "expected a response object, but an instance of " +
58
- "#{@invalid_response.class} was received"
59
- end
60
- end
61
-
62
- # @api private
63
- # Provides an implementation for `have_http_status` matching against
64
- # numeric http status codes.
65
- #
66
- # Not intended to be instantiated directly.
67
- #
68
- # @example
69
- # expect(response).to have_http_status(404)
70
- #
71
- # @see RSpec::Rails::Matchers.have_http_status
72
- class NumericCode < RSpec::Matchers::BuiltIn::BaseMatcher
73
- include HaveHttpStatus
74
-
75
- def initialize(code)
76
- @expected = code.to_i
77
- @actual = nil
78
- @invalid_response = nil
79
- end
80
-
81
- # @param [Object] response object providing an http code to match
82
- # @return [Boolean] `true` if the numeric code matched the `response` code
83
- def matches?(response)
84
- test_response = as_test_response(response)
85
- @actual = test_response.response_code
86
- expected == @actual
87
- rescue TypeError => _ignored
88
- @invalid_response = response
89
- false
90
- end
91
-
92
- # @return [String]
93
- def description
94
- "respond with numeric status code #{expected}"
95
- end
96
-
97
- # @return [String] explaining why the match failed
98
- def failure_message
99
- invalid_response_type_message ||
100
- "expected the response to have status code #{expected.inspect}" +
101
- " but it was #{actual.inspect}"
102
- end
103
-
104
- # @return [String] explaining why the match failed
105
- def failure_message_when_negated
106
- invalid_response_type_message ||
107
- "expected the response not to have status code #{expected.inspect}" +
108
- " but it did"
109
- end
110
- end
111
-
112
- # @api private
113
- # Provides an implementation for `have_http_status` matching against
114
- # Rack symbol http status codes.
115
- #
116
- # Not intended to be instantiated directly.
117
- #
118
- # @example
119
- # expect(response).to have_http_status(:created)
120
- #
121
- # @see RSpec::Rails::Matchers.have_http_status
122
- # @see https://github.com/rack/rack/blob/master/lib/rack/utils.rb `Rack::Utils::SYMBOL_TO_STATUS_CODE`
123
- class SymbolicStatus < RSpec::Matchers::BuiltIn::BaseMatcher
124
- include HaveHttpStatus
125
-
126
- def initialize(status)
127
- @expected_status = status
128
- @actual = nil
129
- @invalid_response = nil
130
- unless set_expected_code!
131
- raise ArgumentError, "Invalid HTTP status: #{status.inspect}"
7
+ module RSpec
8
+ module Rails
9
+ module Matchers
10
+ # Namespace for various implementations of `have_http_status`.
11
+ #
12
+ # @api private
13
+ module HaveHttpStatus
14
+ # Instantiates an instance of the proper matcher based on the provided
15
+ # `target`.
16
+ #
17
+ # @param target [Object] expected http status or code
18
+ # @return response matcher instance
19
+ def self.matcher_for_status(target)
20
+ if GenericStatus.valid_statuses.include?(target)
21
+ GenericStatus.new(target)
22
+ elsif Symbol === target
23
+ SymbolicStatus.new(target)
24
+ else
25
+ NumericCode.new(target)
26
+ end
132
27
  end
133
- end
134
-
135
- # @param [Object] response object providing an http code to match
136
- # @return [Boolean] `true` if Rack's associated numeric HTTP code matched
137
- # the `response` code
138
- def matches?(response)
139
- test_response = as_test_response(response)
140
- @actual = test_response.response_code
141
- expected == @actual
142
- rescue TypeError => _ignored
143
- @invalid_response = response
144
- false
145
- end
146
-
147
- # @return [String]
148
- def description
149
- "respond with status code #{pp_expected}"
150
- end
151
-
152
- # @return [String] explaining why the match failed
153
- def failure_message
154
- invalid_response_type_message ||
155
- "expected the response to have status code #{pp_expected} but it" +
156
- " was #{pp_actual}"
157
- end
158
-
159
- # @return [String] explaining why the match failed
160
- def failure_message_when_negated
161
- invalid_response_type_message ||
162
- "expected the response not to have status code #{pp_expected} " +
163
- "but it did"
164
- end
165
28
 
166
- # The initialized expected status symbol
167
- attr_reader :expected_status
168
- private :expected_status
169
-
170
- private
171
-
172
- # @return [Symbol] representing the actual http numeric code
173
- def actual_status
174
- if actual
175
- @actual_status ||= compute_status_from(actual)
29
+ # @api private
30
+ # Conversion function to coerce the provided object into an
31
+ # `ActionDispatch::TestResponse`.
32
+ #
33
+ # @param obj [Object] object to convert to a response
34
+ # @return [ActionDispatch::TestResponse]
35
+ def as_test_response(obj)
36
+ if ::ActionDispatch::Response === obj || ::Rack::MockResponse === obj
37
+ ::ActionDispatch::TestResponse.from_response(obj)
38
+ elsif ::ActionDispatch::TestResponse === obj
39
+ obj
40
+ elsif obj.respond_to?(:status_code) && obj.respond_to?(:response_headers)
41
+ # Acts As Capybara Session
42
+ # Hack to support `Capybara::Session` without having to load
43
+ # Capybara or catch `NameError`s for the undefined constants
44
+ obj = ActionDispatch::Response.new.tap do |resp|
45
+ resp.status = obj.status_code
46
+ resp.headers.clear
47
+ resp.headers.merge!(obj.response_headers)
48
+ resp.body = obj.body
49
+ resp.request = ActionDispatch::Request.new({})
50
+ end
51
+ ::ActionDispatch::TestResponse.from_response(obj)
52
+ else
53
+ raise TypeError, "Invalid response type: #{obj}"
54
+ end
176
55
  end
177
- end
56
+ module_function :as_test_response
178
57
 
179
- # Reverse lookup of the Rack status code symbol based on the numeric http
180
- # code
181
- #
182
- # @param code [Fixnum] http status code to look up
183
- # @return [Symbol] representing the http numeric code
184
- def compute_status_from(code)
185
- status, _ = Rack::Utils::SYMBOL_TO_STATUS_CODE.find{ |_, c| c == code }
186
- status
187
- end
188
-
189
- # @return [String] pretty format the actual response status
190
- def pp_actual
191
- pp_status(actual_status, actual)
192
- end
58
+ # @return [String, nil] a formatted failure message if
59
+ # `@invalid_response` is present, `nil` otherwise
60
+ def invalid_response_type_message
61
+ return unless @invalid_response
193
62
 
194
- # @return [String] pretty format the expected status and associated code
195
- def pp_expected
196
- pp_status(expected_status, expected)
197
- end
198
-
199
- # @return [String] pretty format the actual response status
200
- def pp_status(status, code)
201
- if status
202
- "#{status.inspect} (#{code})"
203
- else
204
- code.to_s
63
+ "expected a response object, but an instance of " \
64
+ "#{@invalid_response.class} was received"
205
65
  end
206
- end
207
-
208
- # Sets `expected` to the numeric http code based on the Rack
209
- # `expected_status` status
210
- #
211
- # @see Rack::Utils::SYMBOL_TO_STATUS_CODE
212
- # @return [nil] if an associated code could not be found
213
- def set_expected_code!
214
- @expected ||= Rack::Utils::SYMBOL_TO_STATUS_CODE[expected_status]
215
- end
216
- end
217
-
218
- # @api private
219
- # Provides an implementation for `have_http_status` matching against
220
- # `ActionDispatch::TestResponse` http status category queries.
221
- #
222
- # Not intended to be instantiated directly.
223
- #
224
- # @example
225
- # expect(response).to have_http_status(:success)
226
- # expect(response).to have_http_status(:error)
227
- # expect(response).to have_http_status(:missing)
228
- # expect(response).to have_http_status(:redirect)
229
- #
230
- # @see RSpec::Rails::Matchers.have_http_status
231
- # @see ActionDispatch::TestResponse
232
- class GenericStatus < RSpec::Matchers::BuiltIn::BaseMatcher
233
- include HaveHttpStatus
234
-
235
- # @return [Array<Symbol>] of status codes which represent a HTTP status
236
- # code "group"
237
- # @see https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
238
- def self.valid_statuses
239
- [:error, :success, :missing, :redirect]
240
- end
241
66
 
242
- def initialize(type)
243
- unless self.class.valid_statuses.include?(type)
244
- raise ArgumentError, "Invalid generic HTTP status: #{type.inspect}"
67
+ # @api private
68
+ # Provides an implementation for `have_http_status` matching against
69
+ # numeric http status codes.
70
+ #
71
+ # Not intended to be instantiated directly.
72
+ #
73
+ # @example
74
+ # expect(response).to have_http_status(404)
75
+ #
76
+ # @see RSpec::Rails::Matchers#have_http_status
77
+ class NumericCode < RSpec::Rails::Matchers::BaseMatcher
78
+ include HaveHttpStatus
79
+
80
+ def initialize(code)
81
+ @expected = code.to_i
82
+ @actual = nil
83
+ @invalid_response = nil
84
+ end
85
+
86
+ # @param [Object] response object providing an http code to match
87
+ # @return [Boolean] `true` if the numeric code matched the `response` code
88
+ def matches?(response)
89
+ test_response = as_test_response(response)
90
+ @actual = test_response.response_code.to_i
91
+ expected == @actual
92
+ rescue TypeError => _ignored
93
+ @invalid_response = response
94
+ false
95
+ end
96
+
97
+ # @return [String]
98
+ def description
99
+ "respond with numeric status code #{expected}"
100
+ end
101
+
102
+ # @return [String] explaining why the match failed
103
+ def failure_message
104
+ invalid_response_type_message ||
105
+ "expected the response to have status code #{expected.inspect}" \
106
+ " but it was #{actual.inspect}"
107
+ end
108
+
109
+ # @return [String] explaining why the match failed
110
+ def failure_message_when_negated
111
+ invalid_response_type_message ||
112
+ "expected the response not to have status code " \
113
+ "#{expected.inspect} but it did"
114
+ end
245
115
  end
246
- @expected = type
247
- @actual = nil
248
- @invalid_response = nil
249
- end
250
-
251
- # @return [Boolean] `true` if Rack's associated numeric HTTP code matched
252
- # the `response` code
253
- def matches?(response)
254
- test_response = as_test_response(response)
255
- @actual = test_response.response_code
256
- test_response.send("#{expected}?")
257
- rescue TypeError => _ignored
258
- @invalid_response = response
259
- false
260
- end
261
-
262
- # @return [String]
263
- def description
264
- "respond with #{type_message}"
265
- end
266
-
267
- # @return [String] explaining why the match failed
268
- def failure_message
269
- invalid_response_type_message ||
270
- "expected the response to have #{type_message} but it was #{actual}"
271
- end
272
-
273
- # @return [String] explaining why the match failed
274
- def failure_message_when_negated
275
- invalid_response_type_message ||
276
- "expected the response not to have #{type_message} but it was #{actual}"
277
- end
278
-
279
- private
280
116
 
281
- # @return [String] formating the expected status and associated code(s)
282
- def type_message
283
- @type_message ||= (expected == :error ? "an error" : "a #{expected}") +
284
- " status code (#{type_codes})"
285
- end
286
-
287
- # @return [String] formatting the associated code(s) for the various
288
- # status code "groups"
289
- # @see https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
290
- # @see https://github.com/rack/rack/blob/master/lib/rack/response.rb `Rack::Response`
291
- def type_codes
292
- # At the time of this commit the most recent version of
293
- # `ActionDispatch::TestResponse` defines the following aliases:
117
+ # @api private
118
+ # Provides an implementation for `have_http_status` matching against
119
+ # Rack symbol http status codes.
120
+ #
121
+ # Not intended to be instantiated directly.
294
122
  #
295
- # alias_method :success?, :successful?
296
- # alias_method :missing?, :not_found?
297
- # alias_method :redirect?, :redirection?
298
- # alias_method :error?, :server_error?
123
+ # @example
124
+ # expect(response).to have_http_status(:created)
299
125
  #
300
- # It's parent `ActionDispatch::Response` includes
301
- # `Rack::Response::Helpers` which defines the aliased methods as:
126
+ # @see RSpec::Rails::Matchers#have_http_status
127
+ # @see https://github.com/rack/rack/blob/master/lib/rack/utils.rb `Rack::Utils::SYMBOL_TO_STATUS_CODE`
128
+ class SymbolicStatus < RSpec::Rails::Matchers::BaseMatcher
129
+ include HaveHttpStatus
130
+
131
+ def initialize(status)
132
+ @expected_status = status
133
+ @actual = nil
134
+ @invalid_response = nil
135
+ set_expected_code!
136
+ end
137
+
138
+ # @param [Object] response object providing an http code to match
139
+ # @return [Boolean] `true` if Rack's associated numeric HTTP code matched
140
+ # the `response` code
141
+ def matches?(response)
142
+ test_response = as_test_response(response)
143
+ @actual = test_response.response_code
144
+ expected == @actual
145
+ rescue TypeError => _ignored
146
+ @invalid_response = response
147
+ false
148
+ end
149
+
150
+ # @return [String]
151
+ def description
152
+ "respond with status code #{pp_expected}"
153
+ end
154
+
155
+ # @return [String] explaining why the match failed
156
+ def failure_message
157
+ invalid_response_type_message ||
158
+ "expected the response to have status code #{pp_expected} but it" \
159
+ " was #{pp_actual}"
160
+ end
161
+
162
+ # @return [String] explaining why the match failed
163
+ def failure_message_when_negated
164
+ invalid_response_type_message ||
165
+ "expected the response not to have status code #{pp_expected} " \
166
+ "but it did"
167
+ end
168
+
169
+ # The initialized expected status symbol
170
+ attr_reader :expected_status
171
+ private :expected_status
172
+
173
+ private
174
+
175
+ # @return [Symbol] representing the actual http numeric code
176
+ def actual_status
177
+ return unless actual
178
+
179
+ @actual_status ||= compute_status_from(actual)
180
+ end
181
+
182
+ # Reverse lookup of the Rack status code symbol based on the numeric
183
+ # http code
184
+ #
185
+ # @param code [Fixnum] http status code to look up
186
+ # @return [Symbol] representing the http numeric code
187
+ def compute_status_from(code)
188
+ status, _ = Rack::Utils::SYMBOL_TO_STATUS_CODE.find do |_, c|
189
+ c == code
190
+ end
191
+ status
192
+ end
193
+
194
+ # @return [String] pretty format the actual response status
195
+ def pp_actual
196
+ pp_status(actual_status, actual)
197
+ end
198
+
199
+ # @return [String] pretty format the expected status and associated code
200
+ def pp_expected
201
+ pp_status(expected_status, expected)
202
+ end
203
+
204
+ # @return [String] pretty format the actual response status
205
+ def pp_status(status, code)
206
+ if status
207
+ "#{status.inspect} (#{code})"
208
+ else
209
+ code.to_s
210
+ end
211
+ end
212
+
213
+ # Sets `expected` to the numeric http code based on the Rack
214
+ # `expected_status` status
215
+ #
216
+ # @see Rack::Utils::SYMBOL_TO_STATUS_CODE
217
+ # @raise [ArgumentError] if an associated code could not be found
218
+ def set_expected_code!
219
+ @expected ||= Rack::Utils.status_code(expected_status)
220
+ end
221
+ end
222
+
223
+ # @api private
224
+ # Provides an implementation for `have_http_status` matching against
225
+ # `ActionDispatch::TestResponse` http status category queries.
226
+ #
227
+ # Not intended to be instantiated directly.
302
228
  #
303
- # def successful?; status >= 200 && status < 300; end
304
- # def redirection?; status >= 300 && status < 400; end
305
- # def server_error?; status >= 500 && status < 600; end
306
- # def not_found?; status == 404; end
229
+ # @example
230
+ # expect(response).to have_http_status(:success)
231
+ # expect(response).to have_http_status(:error)
232
+ # expect(response).to have_http_status(:missing)
233
+ # expect(response).to have_http_status(:redirect)
307
234
  #
308
- # @see https://github.com/rails/rails/blob/ca200378/actionpack/lib/action_dispatch/testing/test_response.rb#L17-L27
309
- # @see https://github.com/rails/rails/blob/ca200378/actionpack/lib/action_dispatch/http/response.rb#L74
310
- # @see https://github.com/rack/rack/blob/ce4a3959/lib/rack/response.rb#L119-L122
311
- @type_codes ||= case expected
312
- when :error
313
- "5xx"
314
- when :success
315
- "2xx"
316
- when :missing
317
- "404"
318
- when :redirect
319
- "3xx"
320
- end
235
+ # @see RSpec::Rails::Matchers#have_http_status
236
+ # @see https://github.com/rails/rails/blob/7-2-stable/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
237
+ class GenericStatus < RSpec::Rails::Matchers::BaseMatcher
238
+ include HaveHttpStatus
239
+
240
+ # @return [Array<Symbol>] of status codes which represent a HTTP status
241
+ # code "group"
242
+ # @see https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
243
+ def self.valid_statuses
244
+ [
245
+ :error, :success, :missing,
246
+ :server_error, :successful, :not_found,
247
+ :redirect
248
+ ]
249
+ end
250
+
251
+ def initialize(type)
252
+ unless self.class.valid_statuses.include?(type)
253
+ raise ArgumentError, "Invalid generic HTTP status: #{type.inspect}"
254
+ end
255
+
256
+ @expected = type
257
+ @actual = nil
258
+ @invalid_response = nil
259
+ end
260
+
261
+ # @return [Boolean] `true` if Rack's associated numeric HTTP code matched
262
+ # the `response` code or the named response status
263
+ def matches?(response)
264
+ test_response = as_test_response(response)
265
+ @actual = test_response.response_code
266
+ check_expected_status(test_response, expected)
267
+ rescue TypeError => _ignored
268
+ @invalid_response = response
269
+ false
270
+ end
271
+
272
+ # @return [String]
273
+ def description
274
+ "respond with #{type_message}"
275
+ end
276
+
277
+ # @return [String] explaining why the match failed
278
+ def failure_message
279
+ invalid_response_type_message ||
280
+ "expected the response to have #{type_message} but it was #{actual}"
281
+ end
282
+
283
+ # @return [String] explaining why the match failed
284
+ def failure_message_when_negated
285
+ invalid_response_type_message ||
286
+ "expected the response not to have #{type_message} but it was #{actual}"
287
+ end
288
+
289
+ protected
290
+
291
+ RESPONSE_METHODS = {
292
+ success: 'successful',
293
+ error: 'server_error',
294
+ missing: 'not_found'
295
+ }.freeze
296
+
297
+ def check_expected_status(test_response, expected)
298
+ test_response.send(
299
+ "#{RESPONSE_METHODS.fetch(expected, expected)}?")
300
+ end
301
+
302
+ private
303
+
304
+ # @return [String] formatting the expected status and associated code(s)
305
+ def type_message
306
+ @type_message ||= (expected == :error ? "an error" : "a #{expected}") +
307
+ " status code (#{type_codes})"
308
+ end
309
+
310
+ # @return [String] formatting the associated code(s) for the various
311
+ # status code "groups"
312
+ # @see https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
313
+ # @see https://github.com/rack/rack/blob/master/lib/rack/response.rb `Rack::Response`
314
+ def type_codes
315
+ # At the time of this commit the most recent version of
316
+ # `ActionDispatch::TestResponse` defines the following aliases:
317
+ #
318
+ # alias_method :success?, :successful?
319
+ # alias_method :missing?, :not_found?
320
+ # alias_method :redirect?, :redirection?
321
+ # alias_method :error?, :server_error?
322
+ #
323
+ # It's parent `ActionDispatch::Response` includes
324
+ # `Rack::Response::Helpers` which defines the aliased methods as:
325
+ #
326
+ # def successful?; status >= 200 && status < 300; end
327
+ # def redirection?; status >= 300 && status < 400; end
328
+ # def server_error?; status >= 500 && status < 600; end
329
+ # def not_found?; status == 404; end
330
+ #
331
+ # @see https://github.com/rails/rails/blob/ca200378/actionpack/lib/action_dispatch/testing/test_response.rb#L17-L27
332
+ # @see https://github.com/rails/rails/blob/ca200378/actionpack/lib/action_dispatch/http/response.rb#L74
333
+ # @see https://github.com/rack/rack/blob/ce4a3959/lib/rack/response.rb#L119-L122
334
+ @type_codes ||= case expected
335
+ when :error, :server_error
336
+ "5xx"
337
+ when :success, :successful
338
+ "2xx"
339
+ when :missing, :not_found
340
+ "404"
341
+ when :redirect
342
+ "3xx"
343
+ end
344
+ end
345
+ end
321
346
  end
322
- end
323
- end
324
347
 
325
- # @api public
326
- # Passes if `response` has a matching HTTP status code.
327
- #
328
- # The following symbolic status codes are allowed:
329
- #
330
- # - `Rack::Utils::SYMBOL_TO_STATUS_CODE`
331
- # - One of the defined `ActionDispatch::TestResponse` aliases:
332
- # - `:error`
333
- # - `:missing`
334
- # - `:redirect`
335
- # - `:success`
336
- #
337
- # @example Accepts numeric and symbol statuses
338
- # expect(response).to have_http_status(404)
339
- # expect(response).to have_http_status(:created)
340
- # expect(response).to have_http_status(:success)
341
- # expect(response).to have_http_status(:error)
342
- # expect(response).to have_http_status(:missing)
343
- # expect(response).to have_http_status(:redirect)
344
- #
345
- # @example Works with standard `response` objects and Capybara's `page`
346
- # expect(response).to have_http_status(404)
347
- # expect(page).to have_http_status(:created)
348
- #
349
- # @see https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
350
- # @see https://github.com/rack/rack/blob/master/lib/rack/utils.rb `Rack::Utils::SYMBOL_TO_STATUS_CODE`
351
- def have_http_status(target)
352
- raise ArgumentError, "Invalid HTTP status: nil" unless target
353
- HaveHttpStatus.matcher_for_status(target)
348
+ # @api public
349
+ # Passes if `response` has a matching HTTP status code.
350
+ #
351
+ # The following symbolic status codes are allowed:
352
+ #
353
+ # - `Rack::Utils::SYMBOL_TO_STATUS_CODE`
354
+ # - One of the defined `ActionDispatch::TestResponse` aliases:
355
+ # - `:error`
356
+ # - `:missing`
357
+ # - `:redirect`
358
+ # - `:success`
359
+ #
360
+ # @example Accepts numeric and symbol statuses
361
+ # expect(response).to have_http_status(404)
362
+ # expect(response).to have_http_status(:created)
363
+ # expect(response).to have_http_status(:success)
364
+ # expect(response).to have_http_status(:error)
365
+ # expect(response).to have_http_status(:missing)
366
+ # expect(response).to have_http_status(:redirect)
367
+ #
368
+ # @example Works with standard `response` objects and Capybara's `page`
369
+ # expect(response).to have_http_status(404)
370
+ # expect(page).to have_http_status(:created)
371
+ #
372
+ # @see https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
373
+ # @see https://github.com/rack/rack/blob/master/lib/rack/utils.rb `Rack::Utils::SYMBOL_TO_STATUS_CODE`
374
+ def have_http_status(target)
375
+ raise ArgumentError, "Invalid HTTP status: nil" unless target
376
+
377
+ HaveHttpStatus.matcher_for_status(target)
378
+ end
379
+ end
354
380
  end
355
381
  end