diff options
Diffstat (limited to 'rubocop')
-rw-r--r-- | rubocop/cop/rspec/env_mocking.rb | 59 | ||||
-rw-r--r-- | rubocop/cop/rspec/invalid_feature_category.rb | 89 |
2 files changed, 148 insertions, 0 deletions
diff --git a/rubocop/cop/rspec/env_mocking.rb b/rubocop/cop/rspec/env_mocking.rb new file mode 100644 index 00000000000..96bd535df08 --- /dev/null +++ b/rubocop/cop/rspec/env_mocking.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'rubocop-rspec' + +module RuboCop + module Cop + module RSpec + # Check for ENV mocking in specs. + # See https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#persistent-in-memory-application-state + # + # @example + # + # # bad + # allow(ENV).to receive(:[]).with('FOO').and_return('bar') + # allow(ENV).to receive(:fetch).with('FOO').and_return('bar') + # allow(ENV).to receive(:[]).with(key).and_return(value) + # allow(ENV).to receive(:[]).with(fetch_key(object)).and_return(fetch_value(object)) + # + # # good + # stub_env('FOO', 'bar') + # stub_env(key, value) + # stub_env(fetch_key(object), fetch_value(object)) + class EnvMocking < RuboCop::Cop::Base + extend RuboCop::Cop::AutoCorrector + + MESSAGE = "Don't mock the ENV, use `stub_env` instead." + + def_node_matcher :env_mocking?, <<~PATTERN + (send + (send nil? :allow + (const {nil? cbase} :ENV) + ) + :to + (send + (send + (send nil? :receive (sym {:[] :fetch})) + :with $_ + ) + :and_return $_ + ) + ... + ) + PATTERN + + def on_send(node) + env_mocking?(node) do |key, value| + add_offense(node, message: MESSAGE) do |corrector| + corrector.replace(node.loc.expression, stub_env(key.source, value.source)) + end + end + end + + def stub_env(key, value) + "stub_env(#{key}, #{value})" + end + end + end + end +end diff --git a/rubocop/cop/rspec/invalid_feature_category.rb b/rubocop/cop/rspec/invalid_feature_category.rb new file mode 100644 index 00000000000..f0789a71d63 --- /dev/null +++ b/rubocop/cop/rspec/invalid_feature_category.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require 'rubocop/cop/rspec/base' +require 'rubocop/cop/rspec/mixin/top_level_group' + +module RuboCop + module Cop + module RSpec + # Ensures that feature categories in specs are valid. + # + # @example + # + # # bad + # RSpec.describe 'foo', feature_category: :invalid do + # end + # + # RSpec.describe 'foo', feature_category: :not_owned do + # end + # + # # good + # + # RSpec.describe 'foo', feature_category: :wiki do + # end + # + # RSpec.describe 'foo', feature_category: :tooling do + # end + # + class InvalidFeatureCategory < RuboCop::Cop::RSpec::Base + MSG = 'Please use a valid feature category. ' \ + 'See https://docs.gitlab.com/ee/development/feature_categorization/#rspec-examples.' + + MSG_SYMBOL = 'Please use a symbol as value.' + + FEATURE_CATEGORIES_PATH = File.expand_path('../../../config/feature_categories.yml', __dir__).freeze + + # List of feature categories which are not defined in config/feature_categories.yml + CUSTOM_FEATURE_CATEGORIES = [ + # https://docs.gitlab.com/ee/development/feature_categorization/#tooling-feature-category + :tooling + ].to_set.freeze + + # @!method feature_category?(node) + def_node_matcher :feature_category_value, <<~PATTERN + (block + (send ... + (hash <(pair (sym :feature_category) $_) ...>) + ) + ... + ) + PATTERN + + def on_block(node) + value_node = feature_category_value(node) + return unless value_node + + unless value_node.sym_type? + add_offense(value_node, message: MSG_SYMBOL) + return + end + + return if valid_feature_category?(value_node) + + add_offense(value_node) + end + + # Used by RuboCop to invalidate its cache if the contents of + # config/feature_categories.json changes. + def external_dependency_checksum + @external_dependency_checksum ||= + Digest::SHA256.file(FEATURE_CATEGORIES_PATH).hexdigest + end + + private + + def valid_feature_category?(node) + CUSTOM_FEATURE_CATEGORIES.include?(node.value) || + self.class.feature_categories.include?(node.value) + end + + def self.feature_categories + @feature_categories ||= YAML + .load_file(FEATURE_CATEGORIES_PATH) + .map(&:to_sym) + .to_set + end + end + end + end +end |