119 lines
3 KiB
Ruby
119 lines
3 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'rack/utils'
|
|
|
|
module RuboCop
|
|
module Cop
|
|
module RSpec
|
|
# This cops checks for `have_http_status` usages in specs.
|
|
# It also discourages the usage of numeric HTTP status codes in
|
|
# `have_gitlab_http_status`.
|
|
#
|
|
# @example
|
|
#
|
|
# # bad
|
|
# expect(response).to have_http_status(200)
|
|
# expect(response).to have_http_status(:ok)
|
|
# expect(response).to have_gitlab_http_status(200)
|
|
#
|
|
# # good
|
|
# expect(response).to have_gitlab_http_status(:ok)
|
|
#
|
|
class HaveGitlabHttpStatus < RuboCop::Cop::Cop
|
|
CODE_TO_SYMBOL = Rack::Utils::SYMBOL_TO_STATUS_CODE.invert
|
|
|
|
MSG_MATCHER_NAME =
|
|
'Use `have_gitlab_http_status` instead of `have_http_status`.'
|
|
|
|
MSG_STATUS =
|
|
'Prefer named HTTP status `%{name}` over ' \
|
|
'its numeric representation `%{code}`.'
|
|
|
|
MSG_UNKNOWN = 'HTTP status `%{code}` is unknown. ' \
|
|
'Please provide a valid one or disable this cop.'
|
|
|
|
MSG_DOCS_LINK = 'https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#have_gitlab_http_status'
|
|
|
|
REPLACEMENT = 'have_gitlab_http_status(%{arg})'
|
|
|
|
def_node_matcher :have_http_status?, <<~PATTERN
|
|
(
|
|
send nil?
|
|
{
|
|
:have_http_status
|
|
:have_gitlab_http_status
|
|
}
|
|
_
|
|
)
|
|
PATTERN
|
|
|
|
def on_send(node)
|
|
return unless have_http_status?(node)
|
|
|
|
offenses = [
|
|
offense_for_name(node),
|
|
offense_for_status(node)
|
|
].compact
|
|
|
|
return if offenses.empty?
|
|
|
|
add_offense(node, message: message_for(offenses))
|
|
end
|
|
|
|
def autocorrect(node)
|
|
lambda do |corrector|
|
|
corrector.replace(node.source_range, replacement(node))
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def offense_for_name(node)
|
|
return if method_name(node) == :have_gitlab_http_status
|
|
|
|
MSG_MATCHER_NAME
|
|
end
|
|
|
|
def offense_for_status(node)
|
|
code = extract_numeric_code(node)
|
|
return unless code
|
|
|
|
symbol = code_to_symbol(code)
|
|
return format(MSG_UNKNOWN, code: code) unless symbol
|
|
|
|
format(MSG_STATUS, name: symbol, code: code)
|
|
end
|
|
|
|
def message_for(offenses)
|
|
(offenses + [MSG_DOCS_LINK]).join(' ')
|
|
end
|
|
|
|
def replacement(node)
|
|
code = extract_numeric_code(node)
|
|
arg = code_to_symbol(code) || argument(node).source
|
|
|
|
format(REPLACEMENT, arg: arg)
|
|
end
|
|
|
|
def code_to_symbol(code)
|
|
CODE_TO_SYMBOL[code]&.inspect
|
|
end
|
|
|
|
def extract_numeric_code(node)
|
|
arg_node = argument(node)
|
|
return unless arg_node&.type == :int
|
|
|
|
arg_node.children[0]
|
|
end
|
|
|
|
def method_name(node)
|
|
node.children[1]
|
|
end
|
|
|
|
def argument(node)
|
|
node.children[2]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|