Skip to content

Commit 3410cb4

Browse files
authored
Merge pull request #12 from ydah/add-new2
feat: add MultipleSchemaConform cop to enforce single schema assertion per request block
2 parents 1241621 + baabb2d commit 3410cb4

File tree

5 files changed

+218
-0
lines changed

5 files changed

+218
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- [#12](https://github.com/ydah/rubocop-committee/pull/12) Add `Committee/MultipleSchemaConform` cop to detect multiple schema conformance assertions within a single request block. ([@ydah])

config/default.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ Committee/AssertSchemaConformWithoutRequest:
1111
Enabled: pending
1212
VersionAdded: "<<next>>"
1313

14+
Committee/MultipleSchemaConform:
15+
Description: Check for multiple schema conformance assertions within the same request block.
16+
Enabled: pending
17+
VersionAdded: "<<next>>"
18+
1419
Committee/RedundantResponseStatusAssertions:
1520
Description: Check for validation of redundant response HTTP status codes.
1621
Enabled: pending

lib/rubocop-committee.rb

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

88
require_relative "rubocop/cop/committee/assert_schema_conform_without_request"
9+
require_relative "rubocop/cop/committee/multiple_schema_conform"
910
require_relative "rubocop/cop/committee/redundant_response_status_assertions"
1011
require_relative "rubocop/cop/committee/unspecified_expected_status"
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module Committee
6+
# Check for multiple schema conformance assertions within the same request block.
7+
#
8+
# @example
9+
# # bad
10+
# it 'returns users' do
11+
# get '/users'
12+
# assert_schema_conform(200)
13+
# assert_response_schema_confirm(200)
14+
# end
15+
#
16+
# # good
17+
# it 'returns users' do
18+
# get '/users'
19+
# assert_schema_conform(200)
20+
# end
21+
#
22+
class MultipleSchemaConform < Base
23+
MSG = "Avoid multiple schema conformance assertions in the same request block."
24+
25+
ASSERT_METHODS = %i[
26+
assert_schema_conform
27+
assert_request_schema_confirm
28+
assert_response_schema_confirm
29+
].freeze
30+
REQUEST_METHODS = %i[get post put patch delete head options].freeze
31+
EXAMPLE_METHODS = %i[it specify example scenario].freeze
32+
33+
RESTRICT_ON_SEND = ASSERT_METHODS
34+
35+
def on_send(node) # rubocop:disable InternalAffairs/OnSendWithoutOnCSend
36+
example_block = example_block_for(node)
37+
return unless example_block
38+
39+
segments = request_segments(example_block)
40+
segment = segment_for(node, segments)
41+
return unless segment
42+
43+
return if assertion_count(segment) < 2
44+
45+
add_offense(node)
46+
end
47+
48+
private
49+
50+
def example_block_for(node)
51+
node.each_ancestor(:block).find { |ancestor| example_block?(ancestor) }
52+
end
53+
54+
def example_block?(node)
55+
return false unless node.block_type?
56+
57+
send_node = node.send_node
58+
return false unless send_node&.send_type?
59+
60+
EXAMPLE_METHODS.include?(send_node.method_name)
61+
end
62+
63+
def request_segments(example_block)
64+
segments = []
65+
current = []
66+
67+
example_block.body.each_child_node do |child|
68+
if request_call?(child)
69+
segments << current if current.any?
70+
current = [child]
71+
else
72+
current << child
73+
end
74+
end
75+
76+
segments << current if current.any?
77+
segments
78+
end
79+
80+
def request_call?(node)
81+
return false unless node&.send_type?
82+
83+
node.receiver.nil? && REQUEST_METHODS.include?(node.method_name)
84+
end
85+
86+
def segment_for(node, segments)
87+
node_range = node.source_range
88+
segments.find do |segment|
89+
segment.any? { |child| range_contains?(child.source_range, node_range) }
90+
end
91+
end
92+
93+
def assertion_count(segment)
94+
segment.sum do |child|
95+
child.each_descendant(:send).count do |send_node|
96+
send_node.receiver.nil? && ASSERT_METHODS.include?(send_node.method_name)
97+
end + (send_in_assert_methods?(child) ? 1 : 0)
98+
end
99+
end
100+
101+
def send_in_assert_methods?(node)
102+
return false unless node&.send_type?
103+
104+
node.receiver.nil? && ASSERT_METHODS.include?(node.method_name)
105+
end
106+
107+
def range_contains?(outer, inner)
108+
outer.begin_pos <= inner.begin_pos && outer.end_pos >= inner.end_pos
109+
end
110+
end
111+
end
112+
end
113+
end
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::Committee::MultipleSchemaConform, :config do
4+
it "registers an offense for multiple assertions in the same request block" do
5+
expect_offense(<<~RUBY)
6+
it 'returns users' do
7+
get '/users'
8+
assert_schema_conform(200)
9+
^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid multiple schema conformance assertions in the same request block.
10+
assert_response_schema_confirm(200)
11+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid multiple schema conformance assertions in the same request block.
12+
end
13+
RUBY
14+
end
15+
16+
it "registers offenses for duplicate assertions in the same request block" do
17+
expect_offense(<<~RUBY)
18+
it 'returns users' do
19+
get '/users'
20+
assert_schema_conform(200)
21+
^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid multiple schema conformance assertions in the same request block.
22+
assert_schema_conform(200)
23+
^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid multiple schema conformance assertions in the same request block.
24+
end
25+
RUBY
26+
end
27+
28+
it "does not register an offense for a single assertion" do
29+
expect_no_offenses(<<~RUBY)
30+
it 'returns users' do
31+
get '/users'
32+
assert_schema_conform(200)
33+
end
34+
RUBY
35+
end
36+
37+
it "does not register an offense when assertions are in separate request blocks" do
38+
expect_no_offenses(<<~RUBY)
39+
it 'creates and retrieves user' do
40+
post '/users', params: { name: 'test' }
41+
assert_schema_conform(201)
42+
43+
get '/users/1'
44+
assert_schema_conform(200)
45+
end
46+
RUBY
47+
end
48+
49+
it "registers an offense for request and response assertions in the same request block" do
50+
expect_offense(<<~RUBY)
51+
it 'returns users' do
52+
get '/users'
53+
assert_request_schema_confirm
54+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid multiple schema conformance assertions in the same request block.
55+
assert_response_schema_confirm(200)
56+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid multiple schema conformance assertions in the same request block.
57+
end
58+
RUBY
59+
end
60+
61+
it "registers offenses for all assertion combinations in the same request block" do
62+
expect_offense(<<~RUBY)
63+
it 'returns users' do
64+
get '/users'
65+
assert_request_schema_confirm
66+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid multiple schema conformance assertions in the same request block.
67+
assert_response_schema_confirm(200)
68+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid multiple schema conformance assertions in the same request block.
69+
assert_schema_conform(200)
70+
^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid multiple schema conformance assertions in the same request block.
71+
end
72+
RUBY
73+
end
74+
75+
it "registers an offense for request assertion with schema conform in the same request block" do
76+
expect_offense(<<~RUBY)
77+
it 'returns users' do
78+
get '/users'
79+
assert_request_schema_confirm
80+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid multiple schema conformance assertions in the same request block.
81+
assert_schema_conform(200)
82+
^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid multiple schema conformance assertions in the same request block.
83+
end
84+
RUBY
85+
end
86+
87+
it "registers an offense for response assertion with schema conform in the same request block" do
88+
expect_offense(<<~RUBY)
89+
it 'returns users' do
90+
get '/users'
91+
assert_response_schema_confirm(200)
92+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid multiple schema conformance assertions in the same request block.
93+
assert_schema_conform(200)
94+
^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid multiple schema conformance assertions in the same request block.
95+
end
96+
RUBY
97+
end
98+
end

0 commit comments

Comments
 (0)