Created
April 28, 2022 20:39
-
-
Save thijsnado/a2cf1fc61a03b27d6ca058dcc0ef1ecc to your computer and use it in GitHub Desktop.
A rule put in place to prevent sidekiq tests from masking serialization issues
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
require "rubocop-rspec" | |
module Cops | |
# This cop ensures that sidekiq gets exercised even in places where we care | |
# about testing message expectations. The reason for this is sidekiq already | |
# doesn't queue up jobs in test mode and stubbing them out entirely masks | |
# issues with passing incorrect variables to sidekiq. Some example of incorrect | |
# variables are ones that don't cleanly serialize and deserialize as JSON, for example | |
# symbols don't cleanly convert back into symbols when converting to JSON. Another potential | |
# error is passing in the wrong number of arguments. Stubbing out perform_async wholesale won't | |
# catch these issues. | |
class NoSidekiqReceive < RuboCop::Cop::RSpec::Base | |
MSG = "receive(:perform_async) called without safe ancestor method. This could have test mask passing incorrect arguments to a worker." | |
ASYNC_METHODS = [:perform_async, :perform_in, :perform_at] | |
# If one of these ancestor methods are called then using receive(:perform_async) is safe | |
ALLOWED_ANCESTOR_METHODS = [ | |
# If original method is called | |
# in any way sidekiq will complain if parameter | |
# arity doesn't match or arguments passed in | |
# don't serialize/deserialize to JSON properly | |
:and_call_original, | |
:and_wrap_original, | |
# If we are asserting that something is never called | |
# we don't need to worry about rspec masking incorrect | |
# behavior. | |
:never, | |
:not_to | |
] | |
def on_send(node) | |
return unless receive_on_perform_async_called?(node) | |
return if ancestor_calls_safe_method?(node) | |
add_offense(node) | |
end | |
private | |
def receive_on_perform_async_called?(node) | |
return false unless node.method_name == :receive | |
return false unless node.arguments.first.type == :sym | |
return false unless ASYNC_METHODS.include?(node.arguments.first.value) | |
true | |
end | |
def ancestor_calls_safe_method?(node) | |
ancestor_names = [] | |
ancestor = node.parent | |
while ancestor.type == :send | |
ancestor_names << ancestor.method_name | |
ancestor = ancestor.parent | |
end | |
(ALLOWED_ANCESTOR_METHODS & ancestor_names).any? | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment