Skip to content

Commit 2bbabdf

Browse files
authored
Merge pull request #11 from ydah/add-new1
feat: add AssertSchemaConformWithoutRequest cop to enforce HTTP request before schema assertion
2 parents 18dfaa6 + 12391f7 commit 2bbabdf

File tree

5 files changed

+195
-0
lines changed

5 files changed

+195
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- [#11](https://github.com/ydah/rubocop-committee/pull/11) Add `Committee/AssertSchemaConformWithoutRequest` cop to warn when schema conformance is asserted without an HTTP request. ([@ydah])

config/default.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ inherit_mode:
66
Committee:
77
Enabled: true
88

9+
Committee/AssertSchemaConformWithoutRequest:
10+
Description: Check if `assert_*_schema_conform` is called without making an HTTP request.
11+
Enabled: pending
12+
VersionAdded: "<<next>>"
13+
914
Committee/RedundantResponseStatusAssertions:
1015
Description: Check for validation of redundant response HTTP status codes.
1116
Enabled: pending

lib/rubocop-committee.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
require_relative "rubocop/committee/version"
66
require_relative "rubocop/committee/plugin"
77

8+
require_relative "rubocop/cop/committee/assert_schema_conform_without_request"
89
require_relative "rubocop/cop/committee/redundant_response_status_assertions"
910
require_relative "rubocop/cop/committee/unspecified_expected_status"
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module Committee
6+
# Check if `assert_*_schema_conform` is called without making an HTTP request.
7+
#
8+
# @example
9+
# # bad
10+
# it 'conforms to schema' do
11+
# assert_schema_conform(200)
12+
# end
13+
#
14+
# # good
15+
# it 'conforms to schema' do
16+
# get '/users'
17+
# assert_schema_conform(200)
18+
# end
19+
#
20+
class AssertSchemaConformWithoutRequest < Base
21+
MSG = "Call an HTTP request method before asserting schema conformance."
22+
23+
REQUEST_METHODS = %i[get post put patch delete head options].freeze
24+
ASSERT_METHODS = %i[
25+
assert_schema_conform
26+
assert_request_schema_confirm
27+
assert_response_schema_confirm
28+
].freeze
29+
EXAMPLE_METHODS = %i[it specify example scenario].freeze
30+
EXAMPLE_GROUP_METHODS = %i[describe context feature].freeze
31+
SUBJECT_METHODS = %i[subject subject!].freeze
32+
BEFORE_HOOK_METHODS = %i[before].freeze
33+
34+
RESTRICT_ON_SEND = ASSERT_METHODS
35+
36+
def on_send(node) # rubocop:disable InternalAffairs/OnSendWithoutOnCSend
37+
example_block = example_block_for(node)
38+
return unless example_block
39+
return if request_in_example_block?(example_block)
40+
return if request_in_subject?(example_block)
41+
return if request_in_before_hook?(example_block)
42+
43+
add_offense(node)
44+
end
45+
46+
private
47+
48+
def example_block_for(node)
49+
node.each_ancestor(:block).find { |ancestor| example_block?(ancestor) }
50+
end
51+
52+
def example_block?(node)
53+
return false unless node.block_type?
54+
55+
send_node = node.send_node
56+
return false unless send_node&.send_type?
57+
58+
EXAMPLE_METHODS.include?(send_node.method_name)
59+
end
60+
61+
def request_in_example_block?(example_block)
62+
example_block.each_descendant(:send).any? { |send_node| request_method?(send_node) }
63+
end
64+
65+
def request_in_subject?(example_block)
66+
return false unless subject_called?(example_block)
67+
68+
example_group_blocks(example_block).any? do |group_block|
69+
group_block.each_descendant(:block).any? do |block|
70+
subject_definition_with_request?(block)
71+
end
72+
end
73+
end
74+
75+
def request_in_before_hook?(example_block)
76+
example_group_blocks(example_block).any? do |group_block|
77+
group_block.each_descendant(:block).any? do |block|
78+
before_hook_with_request?(block)
79+
end
80+
end
81+
end
82+
83+
def subject_called?(example_block)
84+
example_block.each_descendant(:send).any? do |send_node|
85+
send_node.receiver.nil? && SUBJECT_METHODS.include?(send_node.method_name)
86+
end
87+
end
88+
89+
def subject_definition_with_request?(block)
90+
send_node = block.send_node
91+
return false unless send_node&.send_type?
92+
return false unless SUBJECT_METHODS.include?(send_node.method_name)
93+
94+
block.each_descendant(:send).any? { |send_node| request_method?(send_node) }
95+
end
96+
97+
def before_hook_with_request?(block)
98+
send_node = block.send_node
99+
return false unless send_node&.send_type?
100+
return false unless BEFORE_HOOK_METHODS.include?(send_node.method_name)
101+
102+
block.each_descendant(:send).any? { |send_node| request_method?(send_node) }
103+
end
104+
105+
def example_group_blocks(example_block)
106+
example_block.each_ancestor(:block).select { |ancestor| example_group_block?(ancestor) }
107+
end
108+
109+
def example_group_block?(node)
110+
send_node = node.send_node
111+
return false unless send_node&.send_type?
112+
113+
EXAMPLE_GROUP_METHODS.include?(send_node.method_name)
114+
end
115+
116+
def request_method?(send_node)
117+
send_node.receiver.nil? && REQUEST_METHODS.include?(send_node.method_name)
118+
end
119+
end
120+
end
121+
end
122+
end
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::Committee::AssertSchemaConformWithoutRequest, :config do
4+
it "registers an offense when using `assert_schema_conform` without a request" do
5+
expect_offense(<<~RUBY)
6+
it 'conforms to schema' do
7+
assert_schema_conform(200)
8+
^^^^^^^^^^^^^^^^^^^^^^^^^^ Call an HTTP request method before asserting schema conformance.
9+
end
10+
RUBY
11+
end
12+
13+
it "registers an offense when using `assert_response_schema_confirm` without a request" do
14+
expect_offense(<<~RUBY)
15+
it 'conforms to schema' do
16+
assert_response_schema_confirm(200)
17+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Call an HTTP request method before asserting schema conformance.
18+
end
19+
RUBY
20+
end
21+
22+
it "registers an offense when using `assert_request_schema_confirm` without a request" do
23+
expect_offense(<<~RUBY)
24+
it 'conforms to schema' do
25+
assert_request_schema_confirm
26+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Call an HTTP request method before asserting schema conformance.
27+
end
28+
RUBY
29+
end
30+
31+
it "does not register an offense when a request is made in the example" do
32+
expect_no_offenses(<<~RUBY)
33+
it 'conforms to schema' do
34+
get '/users'
35+
assert_schema_conform(200)
36+
end
37+
RUBY
38+
end
39+
40+
it "does not register an offense when a request is made in subject" do
41+
expect_no_offenses(<<~RUBY)
42+
RSpec.describe 'Users API' do
43+
subject { get '/users' }
44+
45+
it 'conforms to schema' do
46+
subject
47+
assert_schema_conform(200)
48+
end
49+
end
50+
RUBY
51+
end
52+
53+
it "does not register an offense when a request is made in a before hook" do
54+
expect_no_offenses(<<~RUBY)
55+
RSpec.describe 'Users API' do
56+
before do
57+
get '/users'
58+
end
59+
60+
it 'conforms to schema' do
61+
assert_schema_conform(200)
62+
end
63+
end
64+
RUBY
65+
end
66+
end

0 commit comments

Comments
 (0)