Skip to content

Commit

Permalink
Fail ActiveJob matcher if arguments don't match signature
Browse files Browse the repository at this point in the history
Preferable for a matcher to fail instead of directly raising
ArgumentError when a mismatch is detected.

#2745 (comment)
  • Loading branch information
odlp authored and JonRowe committed Apr 10, 2024
1 parent 53fefbd commit d3bbe58
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 22 deletions.
30 changes: 23 additions & 7 deletions lib/rspec/rails/matchers/active_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ def thrice
end

def failure_message
return @failure_message if defined?(@failure_message)

"expected to #{self.class::FAILURE_MESSAGE_EXPECTATION_ACTION} #{base_message}".tap do |msg|
if @unmatching_jobs.any?
msg << "\nQueued jobs:"
Expand Down Expand Up @@ -103,13 +105,18 @@ def check(jobs)
@matching_jobs, @unmatching_jobs = jobs.partition do |job|
if job_match?(job) && arguments_match?(job) && queue_match?(job) && at_match?(job)
args = deserialize_arguments(job)
verify_arguments_match_signature!(job, args)
@block.call(*args)
true
else
false
end
end

if (signature_mismatch = detect_args_signature_mismatch(@matching_jobs))
@failure_message = signature_mismatch
return false
end

@matching_jobs_count = @matching_jobs.size

case @expectation_type
Expand Down Expand Up @@ -153,16 +160,25 @@ def arguments_match?(job)
end
end

def verify_arguments_match_signature!(job, args)
job_method = job.fetch(:job).public_instance_method(:perform)
verify_signature!(job_method, args)
def detect_args_signature_mismatch(jobs)
jobs.each do |job|
args = deserialize_arguments(job)

if (signature_mismatch = check_args_signature_mismatch(job.fetch(:job), :perform, args))
return signature_mismatch
end
end

nil
end

def verify_signature!(job_method, args)
signature = Support::MethodSignature.new(job_method)
def check_args_signature_mismatch(job_class, job_method, args)
signature = Support::MethodSignature.new(job_class.public_instance_method(job_method))
verifier = Support::StrictSignatureVerifier.new(signature, args)

raise ArgumentError, verifier.error_message unless verifier.valid?
unless verifier.valid?
"Incorrect arguments passed to #{job_class.name}: #{verifier.error_message}"
end
end

def queue_match?(job)
Expand Down
32 changes: 24 additions & 8 deletions lib/rspec/rails/matchers/have_enqueued_mail.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ def matches?(block)
end

def failure_message
return @failure_message if defined?(@failure_message)

"expected to enqueue #{base_message}".tap do |msg|
msg << "\n#{unmatching_mail_jobs_message}" if unmatching_mail_jobs.any?
end
Expand Down Expand Up @@ -89,19 +91,20 @@ def arguments_match?(job)
super(job)
end

def verify_arguments_match_signature!(job, args)
def detect_args_signature_mismatch(jobs)
return if @method_name.nil?

mailer_method = mailer_class_name.constantize.public_instance_method(@method_name)
mailer_args = args - base_mailer_args
mailer_class = mailer_class_name.constantize

if parameterized_mail?(job)
mailer_args = mailer_args[1..-1] # ignore parameterized params
elsif mailer_args.last.is_a?(Hash) && mailer_args.last.key?(:args)
mailer_args = args.last[:args]
jobs.each do |job|
mailer_args = extract_args_without_parameterized_params(job)

if (signature_mismatch = check_args_signature_mismatch(mailer_class, @method_name, mailer_args))
return signature_mismatch
end
end

verify_signature!(mailer_method, mailer_args)
nil
end

def base_mailer_args
Expand Down Expand Up @@ -172,6 +175,19 @@ def deserialize_arguments(job)
end
end

def extract_args_without_parameterized_params(job)
args = deserialize_arguments(job)
mailer_args = args - base_mailer_args

if parameterized_mail?(job)
mailer_args = mailer_args[1..-1] # ignore parameterized params
elsif mailer_args.last.is_a?(Hash) && mailer_args.last.key?(:args)
mailer_args = args.last[:args]
end

mailer_args
end

def legacy_mail?(job)
RSpec::Rails::FeatureCheck.has_action_mailer_legacy_delivery_job? && job[:job] <= ActionMailer::DeliveryJob
end
Expand Down
20 changes: 16 additions & 4 deletions spec/rspec/rails/matchers/active_job_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -343,15 +343,21 @@ def perform; raise StandardError; end
expect {
two_args_job.perform_later(1)
}.to have_enqueued_job.with(1)
}.to raise_error(ArgumentError, /Wrong number of arguments/)
}.to raise_error(
RSpec::Expectations::ExpectationNotMetError,
/Incorrect arguments passed to TwoArgsJob: Wrong number of arguments/
)
end

it "fails if the job's signature/arguments are mismatched keyword/positional arguments" do
expect {
expect {
keyword_args_job.perform_later(1, 2)
}.to have_enqueued_job.with(1, 2)
}.to raise_error(ArgumentError, /Missing required keyword arguments/)
}.to raise_error(
RSpec::Expectations::ExpectationNotMetError,
/Incorrect arguments passed to KeywordArgsJob: Missing required keyword arguments/
)
end

it "passes with provided arguments containing global id object" do
Expand Down Expand Up @@ -492,15 +498,21 @@ def perform; raise StandardError; end

expect {
expect(two_args_job).to have_been_enqueued.with(1)
}.to raise_error(ArgumentError, /Wrong number of arguments/)
}.to raise_error(
RSpec::Expectations::ExpectationNotMetError,
/Incorrect arguments passed to TwoArgsJob: Wrong number of arguments/
)
end

it "fails if the job's signature/arguments are mismatched keyword/positional arguments" do
keyword_args_job.perform_later(1, 2)

expect {
expect(keyword_args_job).to have_been_enqueued.with(1, 2)
}.to raise_error(ArgumentError, /Missing required keyword arguments/)
}.to raise_error(
RSpec::Expectations::ExpectationNotMetError,
/Incorrect arguments passed to KeywordArgsJob: Missing required keyword arguments/
)
end

it "fails when negated and several jobs enqueued" do
Expand Down
15 changes: 12 additions & 3 deletions spec/rspec/rails/matchers/have_enqueued_mail_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,10 @@ def test_email; end
expect {
TestMailer.email_with_args(1).deliver_later
}.to have_enqueued_mail(TestMailer, :email_with_args).with(1)
}.to raise_error(ArgumentError, /Wrong number of arguments/)
}.to raise_error(
RSpec::Expectations::ExpectationNotMetError,
/Incorrect arguments passed to TestMailer: Wrong number of arguments/
)
end

it "generates a failure message" do
Expand Down Expand Up @@ -402,7 +405,10 @@ def self.name; "NonMailerJob"; end
expect {
TestMailer.with('foo' => 'bar').email_with_args(1).deliver_later
}.to have_enqueued_mail(TestMailer, :email_with_args).with({ 'foo' => 'bar' }, 1)
}.to raise_error(ArgumentError, /Wrong number of arguments/)
}.to raise_error(
RSpec::Expectations::ExpectationNotMetError,
/Incorrect arguments passed to TestMailer: Wrong number of arguments/
)
end
end

Expand Down Expand Up @@ -459,7 +465,10 @@ def self.name; "NonMailerJob"; end
}.to have_enqueued_mail(UnifiedMailer, :email_with_args).with(
a_hash_including(params: { 'foo' => 'bar' }, args: [1])
)
}.to raise_error(ArgumentError, /Wrong number of arguments/)
}.to raise_error(
RSpec::Expectations::ExpectationNotMetError,
/Incorrect arguments passed to UnifiedMailer: Wrong number of arguments/
)
end
end
end
Expand Down

0 comments on commit d3bbe58

Please sign in to comment.