2022-01-01 13:38:12 +00:00
|
|
|
# Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
|
2021-06-01 12:20:20 +00:00
|
|
|
|
2017-05-11 15:18:03 +00:00
|
|
|
require 'rails_helper'
|
2021-04-12 09:49:26 +00:00
|
|
|
require 'models/concerns/has_xss_sanitized_note_examples'
|
2017-05-11 15:18:03 +00:00
|
|
|
|
|
|
|
RSpec.describe Scheduler do
|
|
|
|
|
2019-09-16 15:04:17 +00:00
|
|
|
let(:test_backend_class) do
|
|
|
|
Class.new do
|
|
|
|
def self.start
|
|
|
|
# noop
|
|
|
|
end
|
2017-05-11 15:18:03 +00:00
|
|
|
|
2019-09-16 15:04:17 +00:00
|
|
|
# rubocop:disable Style/TrivialAccessors
|
|
|
|
def self.reschedule=(reschedule)
|
|
|
|
@reschedule = reschedule
|
|
|
|
end
|
|
|
|
# rubocop:enable Style/TrivialAccessors
|
2017-05-11 15:18:03 +00:00
|
|
|
|
2019-09-16 15:04:17 +00:00
|
|
|
def self.reschedule?(_delayed_job)
|
|
|
|
@reschedule || false
|
2017-05-11 15:18:03 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2021-04-12 09:49:26 +00:00
|
|
|
let(:test_backend_name) { 'SpecSpace::DelayedJobBackend' }
|
2017-05-11 15:18:03 +00:00
|
|
|
|
2019-09-16 15:04:17 +00:00
|
|
|
before do
|
|
|
|
stub_const test_backend_name, test_backend_class
|
2017-05-11 15:18:03 +00:00
|
|
|
end
|
|
|
|
|
2021-04-12 09:49:26 +00:00
|
|
|
it_behaves_like 'HasXssSanitizedNote', model_factory: :scheduler
|
|
|
|
|
2017-09-07 16:07:48 +00:00
|
|
|
describe '.failed_jobs' do
|
|
|
|
|
|
|
|
it 'does list failed jobs' do
|
|
|
|
job = create(:scheduler, status: 'error', active: false)
|
|
|
|
failed_list = described_class.failed_jobs
|
|
|
|
expect(failed_list).to be_present
|
|
|
|
expect(failed_list).to include(job)
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
2017-09-07 15:34:32 +00:00
|
|
|
describe '.restart_failed_jobs' do
|
|
|
|
|
|
|
|
it 'does restart failed jobs' do
|
|
|
|
job = create(:scheduler, status: 'error', active: false)
|
|
|
|
described_class.restart_failed_jobs
|
|
|
|
job.reload
|
|
|
|
expect(job.active).to be true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-09-07 15:17:06 +00:00
|
|
|
describe '._start_job' do
|
|
|
|
|
|
|
|
it 'sets error status/message for failed jobs' do
|
|
|
|
job = create(:scheduler)
|
|
|
|
described_class._start_job(job)
|
|
|
|
expect(job.status).to eq 'error'
|
|
|
|
expect(job.active).to be false
|
|
|
|
expect(job.error_message).to be_present
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'executes job that is expected to succeed' do
|
|
|
|
expect(Setting).to receive(:reload)
|
|
|
|
job = create(:scheduler, method: 'Setting.reload')
|
|
|
|
described_class._start_job(job)
|
|
|
|
expect(job.status).to eq 'ok'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-05-11 15:18:03 +00:00
|
|
|
describe '.cleanup' do
|
|
|
|
|
|
|
|
it 'gets called by .threads' do
|
2020-10-22 13:57:01 +00:00
|
|
|
allow(described_class).to receive(:cleanup).and_throw(:called)
|
2017-05-11 15:18:03 +00:00
|
|
|
expect do
|
|
|
|
described_class.threads
|
|
|
|
end.to throw_symbol(:called)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'not called from .threads method' do
|
|
|
|
|
|
|
|
it 'throws an exception' do
|
|
|
|
expect do
|
|
|
|
described_class.cleanup
|
|
|
|
end.to raise_error(RuntimeError)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'throws no exception with force parameter' do
|
|
|
|
expect do
|
|
|
|
described_class.cleanup(force: true)
|
|
|
|
end.not_to raise_error
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# helpers to avoid the throwing behaviour "describe"d above
|
|
|
|
def simulate_threads_call
|
|
|
|
threads
|
|
|
|
end
|
|
|
|
|
|
|
|
def threads
|
|
|
|
described_class.cleanup
|
|
|
|
end
|
|
|
|
|
2018-05-10 13:24:11 +00:00
|
|
|
context 'Delayed::Job' do
|
2017-05-11 15:18:03 +00:00
|
|
|
|
2018-05-10 13:24:11 +00:00
|
|
|
it 'keeps unlocked' do
|
2017-05-11 15:18:03 +00:00
|
|
|
# meta :)
|
|
|
|
described_class.delay.cleanup
|
|
|
|
|
|
|
|
expect do
|
|
|
|
simulate_threads_call
|
2018-05-10 13:24:11 +00:00
|
|
|
end.not_to change {
|
2017-05-11 15:18:03 +00:00
|
|
|
Delayed::Job.count
|
2018-05-10 13:24:11 +00:00
|
|
|
}
|
2017-05-11 15:18:03 +00:00
|
|
|
end
|
|
|
|
|
2018-05-10 13:24:11 +00:00
|
|
|
context 'locked' do
|
2017-05-11 15:18:03 +00:00
|
|
|
|
2018-05-10 13:24:11 +00:00
|
|
|
it 'gets destroyed' do
|
|
|
|
# meta :)
|
|
|
|
described_class.delay.cleanup
|
2017-05-11 15:18:03 +00:00
|
|
|
|
|
|
|
# lock job (simluates interrupted scheduler task)
|
|
|
|
locked_job = Delayed::Job.last
|
2017-09-11 11:16:08 +00:00
|
|
|
locked_job.update!(locked_at: Time.zone.now)
|
2017-05-11 15:18:03 +00:00
|
|
|
|
|
|
|
expect do
|
|
|
|
simulate_threads_call
|
2018-05-10 13:24:11 +00:00
|
|
|
end.to change {
|
2017-05-11 15:18:03 +00:00
|
|
|
Delayed::Job.count
|
2018-05-10 13:24:11 +00:00
|
|
|
}.by(-1)
|
2017-05-11 15:18:03 +00:00
|
|
|
end
|
|
|
|
|
2018-05-10 13:24:11 +00:00
|
|
|
context 'respond to reschedule?' do
|
|
|
|
|
|
|
|
it 'gets rescheduled for positive responses' do
|
|
|
|
SpecSpace::DelayedJobBackend.reschedule = true
|
|
|
|
SpecSpace::DelayedJobBackend.delay.start
|
|
|
|
|
|
|
|
# lock job (simluates interrupted scheduler task)
|
|
|
|
locked_job = Delayed::Job.last
|
|
|
|
locked_job.update!(locked_at: Time.zone.now)
|
|
|
|
|
|
|
|
expect do
|
|
|
|
simulate_threads_call
|
|
|
|
end.to not_change {
|
|
|
|
Delayed::Job.count
|
|
|
|
}.and change {
|
|
|
|
Delayed::Job.last.locked_at
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'gets destroyed for negative responses' do
|
|
|
|
SpecSpace::DelayedJobBackend.reschedule = false
|
|
|
|
SpecSpace::DelayedJobBackend.delay.start
|
|
|
|
|
|
|
|
# lock job (simluates interrupted scheduler task)
|
|
|
|
locked_job = Delayed::Job.last
|
|
|
|
locked_job.update!(locked_at: Time.zone.now)
|
|
|
|
|
|
|
|
expect do
|
|
|
|
simulate_threads_call
|
|
|
|
end.to change {
|
|
|
|
Delayed::Job.count
|
|
|
|
}.by(-1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2017-05-11 15:18:03 +00:00
|
|
|
|
2018-05-10 13:24:11 +00:00
|
|
|
context 'ImportJob' do
|
|
|
|
|
|
|
|
context 'affected job' do
|
|
|
|
|
|
|
|
let(:job) { create(:import_job, started_at: 5.minutes.ago) }
|
|
|
|
|
|
|
|
it 'finishes stuck jobs' do
|
2017-05-11 15:18:03 +00:00
|
|
|
|
|
|
|
expect do
|
|
|
|
simulate_threads_call
|
|
|
|
end.to change {
|
2018-05-10 13:24:11 +00:00
|
|
|
job.reload.finished_at
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'adds an error message to the result' do
|
|
|
|
|
|
|
|
expect do
|
|
|
|
simulate_threads_call
|
|
|
|
end.to change {
|
|
|
|
job.reload.result[:error]
|
|
|
|
}
|
2017-05-11 15:18:03 +00:00
|
|
|
end
|
|
|
|
end
|
2018-05-10 13:24:11 +00:00
|
|
|
|
|
|
|
it "doesn't change jobs added after stop" do
|
|
|
|
|
|
|
|
job = create(:import_job)
|
|
|
|
|
|
|
|
expect do
|
|
|
|
simulate_threads_call
|
|
|
|
end.not_to change {
|
|
|
|
job.reload
|
|
|
|
}
|
|
|
|
end
|
2017-05-11 15:18:03 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|