Skip to content

Commit 2df4a5b

Browse files
authored
Merge pull request #74 from taketo1113/add-http-status-name-consistency-cop
Add `RSpecRails/HttpStatusNameConsistency` cop
2 parents 4a24882 + 83c0f50 commit 2df4a5b

File tree

7 files changed

+172
-0
lines changed

7 files changed

+172
-0
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Master (Unreleased)
44

5+
- Add `RSpecRails/HttpStatusNameConsistency` cop. ([@taketo1113])
6+
57
## 2.31.0 (2025-03-10)
68

79
- Handle unknown HTTP status codes for `RSpecRails/HttpStatus` cop. ([@viralpraxis])
@@ -86,6 +88,7 @@
8688
[@pirj]: https://github.com/pirj
8789
[@r7kamura]: https://github.com/r7kamura
8890
[@splattael]: https://github.com/splattael
91+
[@taketo1113]: https://github.com/taketo1113
8992
[@tmaier]: https://github.com/tmaier
9093
[@viralpraxis]: https://github.com/viralpraxis
9194
[@ydah]: https://github.com/ydah

config/default.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ RSpecRails/HttpStatus:
3535
VersionChanged: '2.20'
3636
Reference: https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/HttpStatus
3737

38+
RSpecRails/HttpStatusNameConsistency:
39+
Description: Enforces consistency by using the current HTTP status names.
40+
Enabled: pending
41+
VersionAdded: "<<next>>"
42+
Reference: https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/HttpStatusNameConsistency
43+
3844
RSpecRails/InferredSpecType:
3945
Description: Identifies redundant spec type.
4046
Enabled: pending

docs/modules/ROOT/pages/cops.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* xref:cops_rspecrails.adoc#rspecrailsavoidsetuphook[RSpecRails/AvoidSetupHook]
66
* xref:cops_rspecrails.adoc#rspecrailshavehttpstatus[RSpecRails/HaveHttpStatus]
77
* xref:cops_rspecrails.adoc#rspecrailshttpstatus[RSpecRails/HttpStatus]
8+
* xref:cops_rspecrails.adoc#rspecrailshttpstatusnameconsistency[RSpecRails/HttpStatusNameConsistency]
89
* xref:cops_rspecrails.adoc#rspecrailsinferredspectype[RSpecRails/InferredSpecType]
910
* xref:cops_rspecrails.adoc#rspecrailsminitestassertions[RSpecRails/MinitestAssertions]
1011
* xref:cops_rspecrails.adoc#rspecrailsnegationbevalid[RSpecRails/NegationBeValid]

docs/modules/ROOT/pages/cops_rspecrails.adoc

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,38 @@ it { is_expected.to have_http_status :ok }
211211
212212
* https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/HttpStatus
213213
214+
[#rspecrailshttpstatusnameconsistency]
215+
== RSpecRails/HttpStatusNameConsistency
216+
217+
|===
218+
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed
219+
220+
| Pending
221+
| Yes
222+
| Always
223+
| <<next>>
224+
| -
225+
|===
226+
227+
Enforces consistency by using the current HTTP status names.
228+
229+
[#examples-rspecrailshttpstatusnameconsistency]
230+
=== Examples
231+
232+
[source,ruby]
233+
----
234+
# bad
235+
it { is_expected.to have_http_status :unprocessable_entity }
236+
237+
# good
238+
it { is_expected.to have_http_status :unprocessable_content }
239+
----
240+
241+
[#references-rspecrailshttpstatusnameconsistency]
242+
=== References
243+
244+
* https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/HttpStatusNameConsistency
245+
214246
[#rspecrailsinferredspectype]
215247
== RSpecRails/InferredSpecType
216248
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module RSpecRails
6+
# Enforces consistency by using the current HTTP status names.
7+
#
8+
# @example
9+
#
10+
# # bad
11+
# it { is_expected.to have_http_status :unprocessable_entity }
12+
#
13+
# # good
14+
# it { is_expected.to have_http_status :unprocessable_content }
15+
#
16+
class HttpStatusNameConsistency < ::RuboCop::Cop::Base
17+
extend AutoCorrector
18+
19+
requires_gem 'rack', '>= 3.1.0'
20+
21+
MSG = 'Use `Prefer `:%<preferred>s` over `:%<current>s`.'
22+
23+
RESTRICT_ON_SEND = %i[have_http_status].freeze
24+
25+
PREFERRED_STATUSES = {
26+
unprocessable_entity: :unprocessable_content,
27+
payload_too_large: :content_too_large
28+
}.freeze
29+
30+
# @!method http_status(node)
31+
def_node_matcher :http_status, <<~PATTERN
32+
(send nil? :have_http_status ${sym})
33+
PATTERN
34+
35+
def on_send(node)
36+
http_status(node) do |arg|
37+
check_status_name_consistency(arg)
38+
end
39+
end
40+
alias on_csend on_send
41+
42+
private
43+
44+
def check_status_name_consistency(node)
45+
return unless node.sym_type? && PREFERRED_STATUSES.key?(node.value)
46+
47+
current_status = node.value
48+
preferred_status = PREFERRED_STATUSES[current_status]
49+
50+
message = format(MSG, current: current_status,
51+
preferred: preferred_status)
52+
53+
add_offense(node, message: message) do |corrector|
54+
corrector.replace(node, ":#{preferred_status}")
55+
end
56+
end
57+
end
58+
end
59+
end
60+
end

lib/rubocop/cop/rspec_rails_cops.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require_relative 'rspec_rails/avoid_setup_hook'
44
require_relative 'rspec_rails/have_http_status'
55
require_relative 'rspec_rails/http_status'
6+
require_relative 'rspec_rails/http_status_name_consistency'
67
require_relative 'rspec_rails/inferred_spec_type'
78
require_relative 'rspec_rails/minitest_assertions'
89
require_relative 'rspec_rails/negation_be_valid'
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::RSpecRails::HttpStatusNameConsistency, :config do
4+
context 'when Rack is older than 3.1' do
5+
let(:gem_versions) { { 'rack' => '3.0.0' } }
6+
7+
it 'does nothing when using :unprocessable_entity' do
8+
expect_no_offenses(<<~RUBY)
9+
it { is_expected.to have_http_status :unprocessable_entity }
10+
RUBY
11+
end
12+
13+
it 'does nothing when using :payload_too_large' do
14+
expect_no_offenses(<<~RUBY)
15+
it { is_expected.to have_http_status :payload_too_large }
16+
RUBY
17+
end
18+
end
19+
20+
context 'when Rack is 3.1 or later' do
21+
let(:gem_versions) { { 'rack' => '3.1.0' } }
22+
23+
it 'registers an offense when using :unprocessable_entity' do
24+
expect_offense(<<~RUBY)
25+
it { is_expected.to have_http_status :unprocessable_entity }
26+
^^^^^^^^^^^^^^^^^^^^^ Use `Prefer `:unprocessable_content` over `:unprocessable_entity`.
27+
RUBY
28+
29+
expect_correction(<<~RUBY)
30+
it { is_expected.to have_http_status :unprocessable_content }
31+
RUBY
32+
end
33+
34+
it 'does not register an offense when using :unprocessable_content' do
35+
expect_no_offenses(<<~RUBY)
36+
it { is_expected.to have_http_status :unprocessable_content }
37+
RUBY
38+
end
39+
40+
it 'registers an offense when using :payload_too_large' do
41+
expect_offense(<<~RUBY)
42+
it { is_expected.to have_http_status :payload_too_large }
43+
^^^^^^^^^^^^^^^^^^ Use `Prefer `:content_too_large` over `:payload_too_large`.
44+
RUBY
45+
46+
expect_correction(<<~RUBY)
47+
it { is_expected.to have_http_status :content_too_large }
48+
RUBY
49+
end
50+
51+
it 'does not register an offense when using :content_too_large' do
52+
expect_no_offenses(<<~RUBY)
53+
it { is_expected.to have_http_status :content_too_large }
54+
RUBY
55+
end
56+
57+
it 'does nothing when using numeric value' do
58+
expect_no_offenses(<<~RUBY)
59+
it { is_expected.to have_http_status 200 }
60+
RUBY
61+
end
62+
63+
it 'does nothing when using string value' do
64+
expect_no_offenses(<<~RUBY)
65+
it { is_expected.to have_http_status "200" }
66+
RUBY
67+
end
68+
end
69+
end

0 commit comments

Comments
 (0)