discourse/spec/jobs/jobs_base_spec.rb
Sam 4967541275
FIX: properly log all internal job failures (#17805)
Our internal implementation of #perform on jobs performs remapping.

This happens cause we do "exception aggregation".

Scheduled jobs run on every site in the multisite cluster, and we report
one error per site that failed. During this aggregation we reshape the
context from the original object shape returned by mini_scheduler

The new integration test ensures this interface will remain stable even if
decoupled parts of the code change shapes.

Co-authored-by: Alan Guo Xiang Tan <gxtan1990@gmail.com>
2022-08-05 17:40:22 +10:00

112 lines
2.7 KiB
Ruby

# frozen_string_literal: true
RSpec.describe ::Jobs::Base do
class GoodJob < ::Jobs::Base
attr_accessor :count
def execute(args)
self.count ||= 0
self.count += 1
end
end
class BadJob < ::Jobs::Base
class BadJobError < StandardError
end
attr_accessor :fail_count
def execute(args)
@fail_count ||= 0
@fail_count += 1
raise BadJobError
end
end
it 'handles correct jobs' do
job = GoodJob.new
job.perform({})
expect(job.count).to eq(1)
end
it 'handles errors in multisite' do
RailsMultisite::ConnectionManagement.expects(:all_dbs).returns(['default', 'default', 'default'])
# one exception per database
Discourse.expects(:handle_job_exception).times(3)
bad = BadJob.new
expect { bad.perform({}) }.to raise_error(Jobs::HandledExceptionWrapper)
expect(bad.fail_count).to eq(3)
end
describe '#perform' do
context 'when a job raises an error' do
before do
Discourse.reset_job_exception_stats!
end
after do
Discourse.reset_job_exception_stats!
end
it 'collects stats for failing jobs in Discourse.job_exception_stats' do
bad = BadJob.new
3.times do
# During test env handle_job_exception errors out
# in production this is suppressed
expect { bad.perform({}) }.to raise_error(BadJob::BadJobError)
end
expect(Discourse.job_exception_stats).to eq({ BadJob => 3 })
end
end
end
it 'delegates the process call to execute' do
::Jobs::Base.any_instance.expects(:execute).with('hello' => 'world')
::Jobs::Base.new.perform('hello' => 'world', 'sync_exec' => true)
end
it 'converts to an indifferent access hash' do
::Jobs::Base.any_instance.expects(:execute).with(instance_of(HashWithIndifferentAccess))
::Jobs::Base.new.perform('hello' => 'world', 'sync_exec' => true)
end
context "with fake jobs" do
let(:common_state) { [] }
let(:test_job_1) {
Class.new(Jobs::Base).tap do |klass|
state = common_state
klass.define_method(:execute) do |args|
state << "job_1_executed"
end
end
}
let(:test_job_2) {
Class.new(Jobs::Base).tap do |klass|
state = common_state
job_1 = test_job_1
klass.define_method(:execute) do |args|
state << "job_2_started"
Jobs.enqueue(job_1)
state << "job_2_finished"
end
end
}
it "runs jobs synchronously sequentially in tests" do
Jobs.run_immediately!
Jobs.enqueue(test_job_2)
expect(common_state).to eq([
"job_2_started",
"job_2_finished",
"job_1_executed"
])
end
end
end