Welcome to mirror list, hosted at ThFree Co, Russian Federation.

have_gitlab_http_status.rb « rspec « cop « rubocop - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 6b1797200603bd1c12c243d54b8465d0ecf0e805 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# 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