diff --git a/CHANGELOG.md b/CHANGELOG.md index 264af7a..aed9938 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,11 @@ # main +* Added cop `RSpec/StubProducts` ([#18](https://github.com/petalmd/rubocop-petal/pull/18)) + # v0.3.1 -Correct cop name `Migration/SchemaStatementsMethods` in config ([#17](https://github.com/petalmd/rubocop-petal/pull/17)) +* Correct cop name `Migration/SchemaStatementsMethods` in config ([#17](https://github.com/petalmd/rubocop-petal/pull/17)) # v0.3.0 diff --git a/config/default.yml b/config/default.yml index f304a29..75b1d24 100644 --- a/config/default.yml +++ b/config/default.yml @@ -30,6 +30,13 @@ RSpec/CreateListMax: Include: - spec/**/* +RSpec/StubProducts: + Description: 'Suggest to use stub_products instead of veil/unveil_product.' + Enabled: true + SafeAutocorrect: false + Include: + - spec/**/* + Grape/HelpersIncludeModule: Description: 'Prevent using helpers with block to include module' Enabled: true diff --git a/lib/rubocop/cop/rspec/stub_products.rb b/lib/rubocop/cop/rspec/stub_products.rb new file mode 100644 index 0000000..b62109c --- /dev/null +++ b/lib/rubocop/cop/rspec/stub_products.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Suggest to use stub_products instead of veil/unveil_product + # which queries the database and can cause flaky tests. + # + # # bad + # unveil_product('MY_PRODUCT') + # veil_product('MY_PRODUCT') + # + # # good + # stub_products('MY_PRODUCT' => true) + # stub_products('MY_PRODUCT' => false) + # stub_products('MY_PRODUCT' => group) + # stub_products('MY_PRODUCT' => [group1, group2]) + # stub_products(MY_PRODUCT: true) + # + class StubProducts < Base + extend AutoCorrector + + MSG = 'Use `stub_products` instead of veil/unveil_product.' + + def_node_search :veil_product?, <<~PATTERN + (send nil? :veil_product _) + PATTERN + + def_node_search :unveil_product?, <<~PATTERN + (send nil? :unveil_product _) + PATTERN + + def on_send(node) + return unless veil_product?(node) || unveil_product?(node) + + add_offense(node) do |corrector| + if (match = /^\S*\s+(\S+)|\(([^)]+)\)/.match(node.source)) + match1, match2 = match.captures + product_code = match1 || match2 + + product_is_available = !veil_product?(node) + subst = "stub_products(#{product_code} => #{product_is_available})" + + corrector.replace(node, subst) + end + end + end + end + end + end +end diff --git a/spec/rubocop/cop/rspec/stub_products_spec.rb b/spec/rubocop/cop/rspec/stub_products_spec.rb new file mode 100644 index 0000000..02939fe --- /dev/null +++ b/spec/rubocop/cop/rspec/stub_products_spec.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::RSpec::StubProducts, :config do + context 'when using veil_product' do + context 'when veil_product is called with parentheses' do + it 'registers an offense', :aggregate_failures do + expect_offense(<<~RUBY) + veil_product('MY_PRODUCT') + ^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `stub_products` instead of veil/unveil_product. + RUBY + + expect_correction(<<~RUBY) + stub_products('MY_PRODUCT' => false) + RUBY + end + end + + context 'when veil_product is called without parentheses' do + it 'registers an offense', :aggregate_failures do + expect_offense(<<~RUBY) + veil_product 'MY_PRODUCT' + ^^^^^^^^^^^^^^^^^^^^^^^^^ Use `stub_products` instead of veil/unveil_product. + RUBY + + expect_correction(<<~RUBY) + stub_products('MY_PRODUCT' => false) + RUBY + end + end + + context 'when veil_product is called with a variable' do + it 'registers an offense', :aggregate_failures do + product_code = 'MY_PRODUCT' + expect_offense(<<~RUBY, product_code: product_code) + veil_product product_code + ^^^^^^^^^^^^^^^^^^^^^^^^^ Use `stub_products` instead of veil/unveil_product. + RUBY + + expect_correction(<<~RUBY) + stub_products(product_code => false) + RUBY + end + end + end + + context 'when using unveil_product' do + context 'when unveil_product is called with parentheses' do + it 'registers an offense' do + expect_offense(<<~RUBY) + unveil_product('MY_PRODUCT') + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `stub_products` instead of veil/unveil_product. + RUBY + + expect_correction(<<~RUBY) + stub_products('MY_PRODUCT' => true) + RUBY + end + end + + context 'when unveil_product is called without parentheses' do + it 'registers an offense' do + expect_offense(<<~RUBY) + unveil_product 'MY_PRODUCT' + ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `stub_products` instead of veil/unveil_product. + RUBY + + expect_correction(<<~RUBY) + stub_products('MY_PRODUCT' => true) + RUBY + end + end + + context 'when unveil_product is called with a variable' do + it 'registers an offense', :aggregate_failures do + product_code = 'MY_PRODUCT' + expect_offense(<<~RUBY, product_code: product_code) + unveil_product product_code + ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `stub_products` instead of veil/unveil_product. + RUBY + + expect_correction(<<~RUBY) + stub_products(product_code => true) + RUBY + end + end + end + + context 'when not using veil nor unveil_product' do + it 'doesnt register an offense' do + expect_no_offenses(<<~RUBY) + stub_products('MY_PRODUCT' => group) + RUBY + end + end +end