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.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data/.document +1 -1
- data/.yardopts +3 -1
- data/Capybara.md +6 -55
- data/Changelog.md +805 -47
- data/{License.txt → LICENSE.md} +5 -3
- data/README.md +278 -444
- data/lib/generators/rspec/channel/channel_generator.rb +12 -0
- data/lib/generators/rspec/{observer/templates/observer_spec.rb → channel/templates/channel_spec.rb.erb} +1 -1
- data/lib/generators/rspec/controller/controller_generator.rb +24 -7
- data/lib/generators/rspec/controller/templates/controller_spec.rb +3 -3
- data/lib/generators/rspec/controller/templates/request_spec.rb +19 -0
- data/lib/generators/rspec/controller/templates/routing_spec.rb +13 -0
- data/lib/generators/rspec/controller/templates/view_spec.rb +1 -1
- data/lib/generators/rspec/feature/feature_generator.rb +15 -2
- data/lib/generators/rspec/feature/templates/feature_singular_spec.rb +5 -0
- data/lib/generators/rspec/feature/templates/feature_spec.rb +1 -1
- data/lib/generators/rspec/generator/generator_generator.rb +24 -0
- data/lib/generators/rspec/generator/templates/generator_spec.rb +5 -0
- data/lib/generators/rspec/helper/helper_generator.rb +2 -2
- data/lib/generators/rspec/helper/templates/helper_spec.rb +1 -1
- data/lib/generators/rspec/install/install_generator.rb +41 -7
- data/lib/generators/rspec/install/templates/spec/rails_helper.rb +63 -22
- data/lib/generators/rspec/job/job_generator.rb +13 -0
- data/lib/generators/rspec/job/templates/job_spec.rb.erb +7 -0
- data/lib/generators/rspec/mailbox/mailbox_generator.rb +14 -0
- data/lib/generators/rspec/mailbox/templates/mailbox_spec.rb.erb +7 -0
- data/lib/generators/rspec/mailer/mailer_generator.rb +12 -3
- data/lib/generators/rspec/mailer/templates/mailer_spec.rb +2 -2
- data/lib/generators/rspec/mailer/templates/preview.rb +13 -0
- data/lib/generators/rspec/model/model_generator.rb +20 -6
- data/lib/generators/rspec/model/templates/fixtures.yml +1 -1
- data/lib/generators/rspec/model/templates/model_spec.rb +1 -1
- data/lib/generators/rspec/request/request_generator.rb +17 -0
- data/lib/generators/rspec/request/templates/request_spec.rb +10 -0
- data/lib/generators/rspec/scaffold/scaffold_generator.rb +90 -113
- data/lib/generators/rspec/scaffold/templates/api_controller_spec.rb +129 -0
- data/lib/generators/rspec/scaffold/templates/api_request_spec.rb +131 -0
- data/lib/generators/rspec/scaffold/templates/controller_spec.rb +46 -64
- data/lib/generators/rspec/scaffold/templates/edit_spec.rb +11 -7
- data/lib/generators/rspec/scaffold/templates/index_spec.rb +4 -3
- data/lib/generators/rspec/scaffold/templates/new_spec.rb +4 -4
- data/lib/generators/rspec/scaffold/templates/request_spec.rb +138 -0
- data/lib/generators/rspec/scaffold/templates/routing_spec.rb +18 -11
- data/lib/generators/rspec/scaffold/templates/show_spec.rb +3 -3
- data/lib/generators/rspec/system/system_generator.rb +24 -0
- data/lib/generators/rspec/system/templates/system_spec.rb +9 -0
- data/lib/generators/rspec/view/templates/view_spec.rb +1 -1
- data/lib/generators/rspec/view/view_generator.rb +4 -4
- data/lib/generators/rspec.rb +30 -11
- data/lib/rspec/rails/active_record.rb +25 -0
- data/lib/rspec/rails/adapters.rb +46 -29
- data/lib/rspec/rails/configuration.rb +165 -41
- data/lib/rspec/rails/example/channel_example_group.rb +93 -0
- data/lib/rspec/rails/example/controller_example_group.rb +185 -149
- data/lib/rspec/rails/example/feature_example_group.rb +43 -23
- data/lib/rspec/rails/example/helper_example_group.rb +28 -25
- data/lib/rspec/rails/example/job_example_group.rb +23 -0
- data/lib/rspec/rails/example/mailbox_example_group.rb +80 -0
- data/lib/rspec/rails/example/mailer_example_group.rb +27 -22
- data/lib/rspec/rails/example/model_example_group.rb +9 -6
- data/lib/rspec/rails/example/rails_example_group.rb +9 -2
- data/lib/rspec/rails/example/request_example_group.rb +21 -17
- data/lib/rspec/rails/example/routing_example_group.rb +47 -39
- data/lib/rspec/rails/example/system_example_group.rb +180 -0
- data/lib/rspec/rails/example/view_example_group.rb +179 -134
- data/lib/rspec/rails/example.rb +4 -0
- data/lib/rspec/rails/extensions/active_record/proxy.rb +5 -11
- data/lib/rspec/rails/feature_check.rb +51 -0
- data/lib/rspec/rails/file_fixture_support.rb +18 -0
- data/lib/rspec/rails/fixture_file_upload_support.rb +45 -0
- data/lib/rspec/rails/fixture_support.rb +70 -14
- data/lib/rspec/rails/matchers/action_cable/have_broadcasted_to.rb +180 -0
- data/lib/rspec/rails/matchers/action_cable/have_streams.rb +58 -0
- data/lib/rspec/rails/matchers/action_cable.rb +70 -0
- data/lib/rspec/rails/matchers/action_mailbox.rb +73 -0
- data/lib/rspec/rails/matchers/active_job.rb +526 -0
- data/lib/rspec/rails/matchers/base_matcher.rb +179 -0
- data/lib/rspec/rails/matchers/be_a_new.rb +70 -64
- data/lib/rspec/rails/matchers/be_new_record.rb +25 -20
- data/lib/rspec/rails/matchers/be_valid.rb +39 -34
- data/lib/rspec/rails/matchers/have_enqueued_mail.rb +259 -0
- data/lib/rspec/rails/matchers/have_http_status.rb +359 -333
- data/lib/rspec/rails/matchers/have_rendered.rb +55 -32
- data/lib/rspec/rails/matchers/redirect_to.rb +30 -27
- data/lib/rspec/rails/matchers/relation_match_array.rb +1 -1
- data/lib/rspec/rails/matchers/routing_matchers.rb +107 -101
- data/lib/rspec/rails/matchers/send_email.rb +122 -0
- data/lib/rspec/rails/matchers.rb +21 -12
- data/lib/rspec/rails/tasks/rspec.rake +9 -17
- data/lib/rspec/rails/vendor/capybara.rb +10 -11
- data/lib/rspec/rails/version.rb +1 -1
- data/lib/rspec/rails/view_assigns.rb +1 -20
- data/lib/rspec/rails/view_path_builder.rb +29 -0
- data/lib/rspec/rails/view_rendering.rb +89 -27
- data/lib/rspec/rails/view_spec_methods.rb +56 -0
- data/lib/rspec/rails.rb +9 -1
- data/lib/rspec-rails.rb +83 -3
- data.tar.gz.sig +0 -0
- metadata +108 -78
- metadata.gz.sig +3 -2
- data/lib/generators/rspec/integration/integration_generator.rb +0 -17
- data/lib/generators/rspec/integration/templates/request_spec.rb +0 -10
- 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/
|
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
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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
|
-
|
56
|
+
module_function :as_test_response
|
178
57
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
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
|
-
|
195
|
-
|
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
|
-
|
243
|
-
|
244
|
-
|
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
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
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
|
-
#
|
296
|
-
#
|
297
|
-
# alias_method :redirect?, :redirection?
|
298
|
-
# alias_method :error?, :server_error?
|
123
|
+
# @example
|
124
|
+
# expect(response).to have_http_status(:created)
|
299
125
|
#
|
300
|
-
#
|
301
|
-
# `Rack::
|
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
|
-
#
|
304
|
-
#
|
305
|
-
#
|
306
|
-
#
|
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
|
309
|
-
# @see https://github.com/rails/rails/blob/
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
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
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
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
|