Skip to content

Instantly share code, notes, and snippets.

@thijsnado
Created April 28, 2022 20:39
Show Gist options
  • Save thijsnado/a2cf1fc61a03b27d6ca058dcc0ef1ecc to your computer and use it in GitHub Desktop.
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
# 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