diff --git a/spec/factories/job.rb b/spec/factories/job.rb index 2ed12aa6e..a9ece77c2 100644 --- a/spec/factories/job.rb +++ b/spec/factories/job.rb @@ -6,45 +6,129 @@ FactoryBot.define do active { true } created_by_id { 1 } updated_by_id { 1 } + timeplan do - { 'days' => { 'Mon' => true, - 'Tue' => false, - 'Wed' => false, - 'Thu' => false, - 'Fri' => false, - 'Sat' => false, - 'Sun' => false }, - 'hours' => - { '0' => true, - '1' => false, - '2' => false, - '3' => false, - '4' => false, - '5' => false, - '6' => false, - '7' => false, - '8' => false, - '9' => false, - '10' => false, - '11' => false, - '12' => false, - '13' => false, - '14' => false, - '15' => false, - '16' => false, - '17' => false, - '18' => false, - '19' => false, - '20' => false, - '21' => false, - '22' => false, - '23' => false }, - 'minutes' => { '0' => true, - '10' => false, - '20' => false, - '30' => false, - '40' => false, - '50' => false } } + { days: { Mon: true, + Tue: false, + Wed: false, + Thu: false, + Fri: false, + Sat: false, + Sun: false }, + hours: { 0 => true, + 1 => false, + 2 => false, + 3 => false, + 4 => false, + 5 => false, + 6 => false, + 7 => false, + 8 => false, + 9 => false, + 10 => false, + 11 => false, + 12 => false, + 13 => false, + 14 => false, + 15 => false, + 16 => false, + 17 => false, + 18 => false, + 19 => false, + 20 => false, + 21 => false, + 22 => false, + 23 => false }, + minutes: { 0 => true, + 10 => false, + 20 => false, + 30 => false, + 40 => false, + 50 => false } } + end + + trait :always_on do + timeplan do + { days: { Mon: true, + Tue: true, + Wed: true, + Thu: true, + Fri: true, + Sat: true, + Sun: true }, + hours: { 0 => true, + 1 => true, + 2 => true, + 3 => true, + 4 => true, + 5 => true, + 6 => true, + 7 => true, + 8 => true, + 9 => true, + 10 => true, + 11 => true, + 12 => true, + 13 => true, + 14 => true, + 15 => true, + 16 => true, + 17 => true, + 18 => true, + 19 => true, + 20 => true, + 21 => true, + 22 => true, + 23 => true }, + minutes: { 0 => true, + 10 => true, + 20 => true, + 30 => true, + 40 => true, + 50 => true } } + end + end + + trait :never_on do + timeplan do + { days: { Mon: false, + Tue: false, + Wed: false, + Thu: false, + Fri: false, + Sat: false, + Sun: false }, + hours: { 0 => false, + 1 => false, + 2 => false, + 3 => false, + 4 => false, + 5 => false, + 6 => false, + 7 => false, + 8 => false, + 9 => false, + 10 => false, + 11 => false, + 12 => false, + 13 => false, + 14 => false, + 15 => false, + 16 => false, + 17 => false, + 18 => false, + 19 => false, + 20 => false, + 21 => false, + 22 => false, + 23 => false }, + minutes: { 0 => false, + 10 => false, + 20 => false, + 30 => false, + 40 => false, + 50 => false } } + end end end end diff --git a/spec/models/job_spec.rb b/spec/models/job_spec.rb index 45acb6b78..2b37d11fe 100644 --- a/spec/models/job_spec.rb +++ b/spec/models/job_spec.rb @@ -3,4 +3,648 @@ require 'models/application_model_examples' RSpec.describe Job, type: :model do it_behaves_like 'ApplicationModel', can_assets: { selectors: %i[condition perform] } + + subject(:job) { create(:job) } + + describe 'Class methods:' do + describe '.run' do + let!(:executable_jobs) { jobs.select(&:executable?).select(&:in_timeplan?) } + let!(:nonexecutable_jobs) { jobs - executable_jobs } + + let!(:jobs) do + [ + # executable + create(:job, :always_on, updated_at: 2.minutes.ago), + + # not executable (updated too recently) + create(:job), + + # not executable (inactive) + create(:job, updated_at: 2.minutes.ago, active: false), + + # not executable (started too recently) + create(:job, :always_on, updated_at: 2.minutes.ago, last_run_at: 5.minutes.ago), + + # executable + create(:job, :always_on, updated_at: 2.minutes.ago, last_run_at: 15.minutes.ago), + + # not executable (still running, started too recently) + create(:job, :always_on, updated_at: 2.minutes.ago, running: true, last_run_at: 23.hours.ago), + + # executable + create(:job, :always_on, updated_at: 2.minutes.ago, running: true, last_run_at: 25.hours.ago), + ] + end + + it 'runs all executable jobs (and no others)' do + expect { Job.run } + .to change { executable_jobs.map(&:reload).map(&:last_run_at).any?(&:nil?) }.to(false) + .and not_change { nonexecutable_jobs.map(&:reload).map(&:last_run_at).all?(&:nil?) } + end + end + end + + describe 'Instance methods:' do + describe '#run' do + subject(:job) { create(:job, condition: condition, perform: perform) } + + let(:condition) do + { + 'ticket.state_id' => { + 'operator' => 'is', + 'value' => Ticket::State.where(name: %w[new open]).pluck(:id).map(&:to_s) + }, + 'ticket.created_at' => { + 'operator' => 'before (relative)', + 'value' => '2', + 'range' => 'day' + }, + } + end + + let(:perform) do + { 'ticket.state_id' => { 'value' => Ticket::State.find_by(name: 'closed').id.to_s } } + end + + let!(:matching_ticket) do + create(:ticket, state: Ticket::State.lookup(name: 'new'), created_at: 3.days.ago) + end + + let!(:nonmatching_ticket) do + create(:ticket, state: Ticket::State.lookup(name: 'new'), created_at: 1.day.ago) + end + + context 'when job is not #executable?' do + before { allow(job).to receive(:executable?).and_return(false) } + + it 'does not perform changes on matching tickets' do + expect { job.run }.not_to change { matching_ticket.reload.state } + end + + it 'does not update #last_run_at' do + expect { job.run }.to not_change { job.reload.last_run_at } + end + + context 'but "force" flag is given' do + it 'performs changes on matching tickets' do + expect { job.run(true) } + .to change { matching_ticket.reload.state } + .and not_change { nonmatching_ticket.reload.state } + end + + it 'updates #last_run_at' do + expect { job.run(true) } + .to change { job.reload.last_run_at } + end + end + end + + context 'when job is #executable?' do + before { allow(job).to receive(:executable?).and_return(true) } + + context 'and due (#in_timeplan?)' do + before { allow(job).to receive(:in_timeplan?).and_return(true) } + + it 'updates #last_run_at' do + expect { job.run }.to change { job.reload.last_run_at } + end + + it 'performs changes on matching tickets' do + expect { job.run } + .to change { matching_ticket.reload.state } + .and not_change { nonmatching_ticket.reload.state } + end + + context 'and already marked #running' do + before { job.update(running: true) } + + it 'resets #running to false' do + expect { job.run }.to change { job.reload.running }.to(false) + end + end + + context 'but condition doesn’t match any tickets' do + before { job.update(condition: invalid_condition) } + + let(:invalid_condition) do + { 'ticket.state_id' => { 'operator' => 'is', 'value' => '9999' } } + end + + it 'performs no changes' do + expect { job.run } + .not_to change { matching_ticket.reload.state } + end + end + + describe 'use case: deleting tickets based on tag' do + let(:condition) { { 'ticket.tags' => { 'operator' => 'contains one', 'value' => 'spam' } } } + let(:perform) { { 'ticket.action' => { 'value' => 'delete' } } } + let!(:matching_ticket) { create(:ticket).tap { |t| t.tag_add('spam', 1) } } + let!(:nonmatching_ticket) { create(:ticket) } + + it 'deletes tickets matching the specified tags' do + job.run + + expect { matching_ticket.reload }.to raise_error(ActiveRecord::RecordNotFound) + expect { nonmatching_ticket.reload }.not_to raise_error + end + end + + describe 'use case: deleting tickets based on group' do + let(:condition) { { 'ticket.group_id' => { 'operator' => 'is', 'value' => matching_ticket.group.id } } } + let(:perform) { { 'ticket.action' => { 'value' => 'delete' } } } + let!(:matching_ticket) { create(:ticket) } + let!(:nonmatching_ticket) { create(:ticket) } + + it 'deletes tickets matching the specified groups' do + job.run + + expect { matching_ticket.reload }.to raise_error(ActiveRecord::RecordNotFound) + expect { nonmatching_ticket.reload }.not_to raise_error + end + end + end + + context 'and not due yet' do + before { allow(job).to receive(:in_timeplan?).and_return(false) } + + it 'does not perform changes on matching tickets' do + expect { job.run }.not_to change { matching_ticket.reload.state } + end + + it 'does not update #last_run_at' do + expect { job.run }.to not_change { job.reload.last_run_at } + end + + it 'updates #next_run_at' do + travel_to(Time.current.last_week) # force new value for #next_run_at + + expect { job.run }.to change { job.reload.next_run_at } + end + + context 'but "force" flag is given' do + it 'performs changes on matching tickets' do + expect { job.run(true) } + .to change { matching_ticket.reload.state } + .and not_change { nonmatching_ticket.reload.state } + end + + it 'updates #last_run_at' do + expect { job.run(true) }.to change { job.reload.last_run_at } + end + + it 'updates #next_run_at' do + travel_to(Time.current.last_week) # force new value for #next_run_at + + expect { job.run }.to change { job.reload.next_run_at } + end + end + end + end + end + + describe '#executable?' do + context 'for an inactive Job' do + subject(:job) { create(:job, active: false) } + + it 'returns false' do + expect(job.executable?).to be(false) + end + end + + context 'for an active job' do + context 'updated in the last minute' do + subject(:job) do + create(:job, active: true, + updated_at: 59.seconds.ago) + end + + it 'returns false' do + expect(job.executable?).to be(false) + end + end + + context 'updated over a minute ago' do + context 'that has never run before' do + subject(:job) do + create(:job, active: true, + updated_at: 60.seconds.ago) + end + + it 'returns true' do + expect(job.executable?).to be(true) + end + end + + context 'that was started in the last 10 minutes' do + subject(:job) do + create(:job, active: true, + updated_at: 60.seconds.ago, + last_run_at: 9.minutes.ago) + end + + it 'returns false' do + expect(job.executable?).to be(false) + end + + context '(or, given an argument, up to 10 minutes before that)' do + subject(:job) do + create(:job, active: true, + updated_at: 60.seconds.ago, + last_run_at: 9.minutes.before(Time.current.yesterday)) + end + + it 'returns false' do + expect(job.executable?(Time.current.yesterday)).to be(false) + end + end + end + + context 'that was started over 10 minutes ago' do + subject(:job) do + create(:job, active: true, + updated_at: 60.seconds.ago, + last_run_at: 10.minutes.ago) + end + + it 'returns true' do + expect(job.executable?).to be(true) + end + + context '(or, given an argument, over 10 minutes before that)' do + subject(:job) do + create(:job, active: true, + updated_at: 60.seconds.ago, + last_run_at: 10.minutes.before(Time.current.yesterday)) + end + + it 'returns true' do + expect(job.executable?(Time.current.yesterday)).to be(true) + end + end + + context 'but is still running, up to 24 hours later' do + subject(:job) do + create(:job, active: true, + updated_at: 60.seconds.ago, + running: true, + last_run_at: 23.hours.ago) + end + + it 'returns false' do + expect(job.executable?).to be(false) + end + end + + context 'but is still running, over 24 hours later' do + subject(:job) do + create(:job, active: true, + updated_at: 60.seconds.ago, + running: true, + last_run_at: 24.hours.ago) + end + + it 'returns true' do + expect(job.executable?).to be(true) + end + end + end + end + end + end + + describe '#in_timeplan?' do + subject(:job) { create(:job, :never_on) } + + context 'when the current day, hour, and minute all match true values in #timeplan' do + context 'for Symbol/Integer keys' do + before do + job.update( + timeplan: { + days: job.timeplan[:days] + .transform_keys(&:to_sym) + .merge(Time.current.strftime('%a').to_sym => true), + hours: job.timeplan[:hours] + .transform_keys(&:to_i) + .merge(Time.current.hour => true), + minutes: job.timeplan[:minutes] + .transform_keys(&:to_i) + .merge(Time.current.min.floor(-1) => true), + } + ) + end + + it 'returns true' do + expect(job.in_timeplan?).to be(true) + end + end + + context 'for String keys' do + before do + job.update( + timeplan: { + days: job.timeplan[:days] + .transform_keys(&:to_s) + .merge(Time.current.strftime('%a') => true), + hours: job.timeplan[:hours] + .transform_keys(&:to_s) + .merge(Time.current.hour.to_s => true), + minutes: job.timeplan[:minutes] + .transform_keys(&:to_s) + .merge(Time.current.min.floor(-1).to_s => true), + } + ) + end + + it 'returns true' do + expect(job.in_timeplan?).to be(true) + end + end + end + + context 'when the current day does not match a true value in #timeplan' do + context 'for Symbol/Integer keys' do + before do + job.update( + timeplan: { + days: job.timeplan[:days] + .transform_keys(&:to_sym) + .transform_values { true } + .merge(Time.current.strftime('%a').to_sym => false), + hours: job.timeplan[:hours] + .transform_keys(&:to_i) + .merge(Time.current.hour => true), + minutes: job.timeplan[:minutes] + .transform_keys(&:to_i) + .merge(Time.current.min.floor(-1) => true), + } + ) + end + + it 'returns false' do + expect(job.in_timeplan?).to be(false) + end + end + + context 'for String keys' do + before do + job.update( + timeplan: { + days: job.timeplan[:days] + .transform_keys(&:to_s) + .transform_values { true } + .merge(Time.current.strftime('%a') => false), + hours: job.timeplan[:hours] + .transform_keys(&:to_s) + .merge(Time.current.hour.to_s => true), + minutes: job.timeplan[:minutes] + .transform_keys(&:to_s) + .merge(Time.current.min.floor(-1).to_s => true), + } + ) + end + + it 'returns false' do + expect(job.in_timeplan?).to be(false) + end + end + end + + context 'when the current hour does not match a true value in #timeplan' do + context 'for Symbol/Integer keys' do + before do + job.update( + timeplan: { + days: job.timeplan[:days] + .transform_keys(&:to_sym) + .merge(Time.current.strftime('%a').to_sym => true), + hours: job.timeplan[:hours] + .transform_keys(&:to_i) + .transform_values { true } + .merge(Time.current.hour => false), + minutes: job.timeplan[:minutes] + .transform_keys(&:to_i) + .merge(Time.current.min.floor(-1) => true), + } + ) + end + + it 'returns false' do + expect(job.in_timeplan?).to be(false) + end + end + + context 'for String keys' do + before do + job.update( + timeplan: { + days: job.timeplan[:days] + .transform_keys(&:to_s) + .merge(Time.current.strftime('%a') => true), + hours: job.timeplan[:hours] + .transform_keys(&:to_s) + .transform_values { true } + .merge(Time.current.hour.to_s => false), + minutes: job.timeplan[:minutes] + .transform_keys(&:to_s) + .merge(Time.current.min.floor(-1).to_s => true), + } + ) + end + + it 'returns false' do + expect(job.in_timeplan?).to be(false) + end + end + end + + context 'when the current minute does not match a true value in #timeplan' do + context 'for Symbol/Integer keys' do + before do + job.update( + timeplan: { + days: job.timeplan[:days] + .transform_keys(&:to_sym) + .merge(Time.current.strftime('%a').to_sym => true), + hours: job.timeplan[:hours] + .transform_keys(&:to_i) + .merge(Time.current.hour => true), + minutes: job.timeplan[:minutes] + .transform_keys(&:to_i) + .transform_values { true } + .merge(Time.current.min.floor(-1) => false), + } + ) + end + + it 'returns false' do + expect(job.in_timeplan?).to be(false) + end + end + + context 'for String keys' do + before do + job.update( + timeplan: { + days: job.timeplan[:days] + .transform_keys(&:to_s) + .merge(Time.current.strftime('%a') => true), + hours: job.timeplan[:hours] + .transform_keys(&:to_s) + .merge(Time.current.hour.to_s => true), + minutes: job.timeplan[:minutes] + .transform_keys(&:to_s) + .transform_values { true } + .merge(Time.current.min.floor(-1).to_s => false), + } + ) + end + + it 'returns false' do + expect(job.in_timeplan?).to be(false) + end + end + end + end + end + + describe 'Attributes:' do + describe '#next_run_at' do + subject(:job) { build(:job) } + + it 'is set automatically on save (cannot be set manually)' do + job.next_run_at = 1.day.from_now + + expect { job.save }.to change { job.next_run_at } + end + + context 'for an inactive Job' do + subject(:job) { build(:job, active: false) } + + it 'is nil' do + expect { job.save } + .not_to change { job.next_run_at }.from(nil) + end + end + + context 'for a never-on Job (all #timeplan values are false)' do + subject(:job) { build(:job, :never_on) } + + it 'is nil' do + expect { job.save } + .not_to change { job.next_run_at }.from(nil) + end + end + + context 'when #timeplan contains at least one true value for :day, :hour, and :minute' do + subject(:job) { build(:job, :never_on) } + let(:base_time) { Time.current.beginning_of_week } + + # Tuesday & Thursday @ 12:00a, 12:30a, 6:00p, and 6:30p + before do + job.assign_attributes( + timeplan: { + days: job.timeplan[:days].merge(Tue: true, Thu: true), + hours: job.timeplan[:hours].merge(0 => true, 18 => true), + minutes: job.timeplan[:minutes].merge(0 => true, 30 => true), + } + ) + end + + let(:valid_timeslots) do + [ + base_time + 1.day, # Tue 12:00a + base_time + 1.day + 30.minutes, # Tue 12:30a + base_time + 1.day + 18.hours, # Tue 6:00p + base_time + 1.day + 18.hours + 30.minutes, # Tue 6:30p + base_time + 3.days, # Thu 12:00a + base_time + 3.days + 30.minutes, # Thu 12:30a + base_time + 3.days + 18.hours, # Thu 6:00p + base_time + 3.days + 18.hours + 30.minutes, # Thu 6:30p + ] + end + + context 'for a Job that has never been run before' do + context 'when record is saved at the start of the week' do + before { travel_to(base_time) } + + it 'is set to the first valid timeslot of the week' do + expect { job.save } + .to change { job.next_run_at.to_i } # comparing times is hard; + .to(valid_timeslots.first.to_i) # integers are less precise + end + end + + context 'when record is saved between two valid timeslots' do + before { travel_to(valid_timeslots.third - 1.second) } + + it 'is set to the latter timeslot' do + expect { job.save } + .to change { job.next_run_at.to_i } # comparing times is hard; + .to(valid_timeslots.third.to_i) # integers are less precise + end + end + + context 'when record is saved during a valid timeslot' do + before { travel_to(valid_timeslots.fifth + 9.minutes + 59.seconds) } + + it 'is set to that timeslot' do + expect { job.save } + .to change { job.next_run_at.to_i } # comparing times is hard; + .to(valid_timeslots.fifth.to_i) # integers are less precise + end + end + end + + context 'for a Job that been run before' do + context 'when record is saved in the same timeslot as #last_run_at' do + before do + job.assign_attributes(last_run_at: valid_timeslots.fourth + 5.minutes) + travel_to(valid_timeslots.fourth + 7.minutes) + end + + it 'is set to the next valid timeslot' do + expect { job.save } + .to change { job.next_run_at.to_i } # comparing times is hard; + .to(valid_timeslots.fifth.to_i) # integers are less precise + end + end + end + end + end + + describe '#perform' do + describe 'Validations:' do + describe '"article.note" key' do + let(:perform) do + { 'article.note' => { 'subject' => 'foo', 'internal' => 'true', 'body' => '' } } + end + + it 'fails if an empty "body" is given' do + expect { create(:job, perform: perform) }.to raise_error(Exceptions::UnprocessableEntity) + end + end + + describe '"notification.email" key' do + let(:perform) do + { 'notification.email' => { 'body' => 'foo', 'recipient' => '', 'subject' => 'bar' } } + end + + it 'fails if an empty "recipient" is given' do + expect { create(:job, perform: perform) }.to raise_error(Exceptions::UnprocessableEntity) + end + end + + describe '"notification.sms" key' do + let(:perform) do + { 'notification.sms' => { 'body' => 'foo', 'recipient' => '' } } + end + + it 'fails if an empty "recipient" is given' do + expect { create(:job, perform: perform) }.to raise_error(Exceptions::UnprocessableEntity) + end + end + end + end + end end diff --git a/test/unit/job_test.rb b/test/unit/job_test.rb deleted file mode 100644 index be163339b..000000000 --- a/test/unit/job_test.rb +++ /dev/null @@ -1,1166 +0,0 @@ -require 'test_helper' - -class JobTest < ActiveSupport::TestCase - test 'case 1' do - - # create ticket - time = Time.zone.now - group1 = Group.lookup(name: 'Users') - group2 = Group.create!( - name: 'JobTest2', - updated_by_id: 1, - created_by_id: 1, - ) - ticket1 = Ticket.create!( - title: 'job test 1', - group: group1, - customer_id: 2, - state: Ticket::State.lookup(name: 'new'), - priority: Ticket::Priority.lookup(name: '2 normal'), - created_at: time - 3.days, - updated_at: time - 3.days, - created_by_id: 1, - updated_by_id: 1, - ) - ticket2 = Ticket.create!( - title: 'job test 2', - group: group1, - customer_id: 2, - state: Ticket::State.lookup(name: 'new'), - priority: Ticket::Priority.lookup(name: '2 normal'), - created_at: time - 1.day, - created_by_id: 1, - updated_at: time - 1.day, - updated_by_id: 1, - ) - ticket3 = Ticket.create!( - title: 'job test 3', - group: group2, - customer_id: 2, - state: Ticket::State.lookup(name: 'open'), - priority: Ticket::Priority.lookup(name: '3 high'), - created_at: time - 1.day, - created_by_id: 1, - updated_at: time - 1.day, - updated_by_id: 1, - ) - ticket4 = Ticket.create!( - title: 'job test 4', - group: group2, - customer_id: 2, - state: Ticket::State.lookup(name: 'closed'), - priority: Ticket::Priority.lookup(name: '2 normal'), - created_at: time - 3.days, - created_by_id: 1, - updated_at: time - 3.days, - updated_by_id: 1, - ) - ticket5 = Ticket.create!( - title: 'job test 5', - group: group2, - customer_id: 2, - state: Ticket::State.lookup(name: 'open'), - priority: Ticket::Priority.lookup(name: '2 normal'), - created_at: time - 3.days, - created_by_id: 1, - updated_by_id: 1, - updated_at: time - 3.days, - ) - - # create jobs - job1 = Job.create!( - name: 'Test Job1', - timeplan: { - days: { - Mon: false, - Tue: false, - Wed: false, - Thu: false, - Fri: false, - Sat: false, - Sun: false, - }, - hours: { - 0 => false, - 1 => false, - 2 => false, - 3 => false, - 4 => false, - 5 => false, - 6 => false, - 7 => false, - 8 => false, - 9 => false, - 10 => false, - 11 => false, - 12 => false, - 13 => false, - 14 => false, - 15 => false, - 16 => false, - 17 => false, - 18 => false, - 19 => false, - 20 => false, - 21 => false, - 22 => false, - 23 => false, - }, - minutes: { - 0 => false, - 10 => false, - 20 => false, - 30 => false, - 40 => false, - 50 => false, - }, - }, - condition: { - 'ticket.state_id' => { 'operator' => 'is', 'value' => [Ticket::State.lookup(name: 'new').id.to_s, Ticket::State.lookup(name: 'open').id.to_s] }, - 'ticket.created_at' => { 'operator' => 'before (relative)', 'value' => '2', 'range' => 'day' }, - }, - perform: { - 'ticket.state_id' => { 'value' => Ticket::State.lookup(name: 'closed').id.to_s } - }, - disable_notification: true, - last_run_at: nil, - active: true, - created_by_id: 1, - created_at: time, - updated_by_id: 1, - updated_at: time, - ) - assert_not(job1.next_run_at) - assert_not(job1.executable?) - - job1.last_run_at = time - 15.minutes - job1.save! - assert_not(job1.executable?) - - job1.updated_at = time - 15.minutes - job1.save! - assert(job1.executable?) - - job1.active = false - job1.save! - assert_not(job1.executable?) - - job1.active = true - job1.save! - assert_not(job1.executable?) - - assert_not(job1.in_timeplan?) - - # "freeze" time to avoid timing issues - travel_to(time) - - day_map = { - 0 => 'Sun', - 1 => 'Mon', - 2 => 'Tue', - 3 => 'Wed', - 4 => 'Thu', - 5 => 'Fri', - 6 => 'Sat', - } - job1.timeplan['days'][day_map[time.wday]] = true - job1.save! - assert_not(job1.in_timeplan?(time)) - job1.timeplan['hours'][time.hour.to_s] = true - job1.save! - assert_not(job1.in_timeplan?(time)) - min = time.min - if min < 10 - min = 0 - elsif min < 20 - min = 10 - elsif min < 30 - min = 20 - elsif min < 40 - min = 30 - elsif min < 50 - min = 40 - elsif min < 60 - min = 50 - end - job1.timeplan['minutes'][min.to_s] = true - job1.save! - - assert(job1.in_timeplan?(time)) - - job1.timeplan['hours'][time.hour] = true - job1.save! - - job1.timeplan['minutes'][min] = true - job1.save! - assert(job1.in_timeplan?(time)) - - # execute jobs - job1.updated_at = time - 15.minutes - job1.save! - Job.run - - assert(job1.next_run_at) - assert(job1.executable?) - assert(job1.in_timeplan?) - - # verify changes on tickets - ticket1_later = Ticket.find(ticket1.id) - assert_equal('closed', ticket1_later.state.name) - assert_not_equal(ticket1.updated_at.to_s, ticket1_later.updated_at.to_s) - - ticket2_later = Ticket.find(ticket2.id) - assert_equal('new', ticket2_later.state.name) - assert_equal(ticket2.updated_at.to_s, ticket2_later.updated_at.to_s) - - ticket3_later = Ticket.find(ticket3.id) - assert_equal('open', ticket3_later.state.name) - assert_equal(ticket3.updated_at.to_s, ticket3_later.updated_at.to_s) - - ticket4_later = Ticket.find(ticket4.id) - assert_equal('closed', ticket4_later.state.name) - assert_equal(ticket4.updated_at.to_s, ticket4_later.updated_at.to_s) - - ticket5_later = Ticket.find(ticket5.id) - assert_equal('closed', ticket5_later.state.name) - assert_not_equal(ticket5.updated_at.to_s, ticket5_later.updated_at.to_s) - - # execute jobs again - job1.updated_at = time - 15.minutes - job1.save! - Job.run - - # verify changes on tickets - ticket1_later_next = Ticket.find(ticket1.id) - assert_equal('closed', ticket1_later_next.state.name) - assert_equal(ticket1_later.updated_at.to_s, ticket1_later_next.updated_at.to_s) - - ticket2_later_next = Ticket.find(ticket2.id) - assert_equal('new', ticket2_later_next.state.name) - assert_equal(ticket2_later.updated_at.to_s, ticket2_later_next.updated_at.to_s) - - ticket3_later_next = Ticket.find(ticket3.id) - assert_equal('open', ticket3_later_next.state.name) - assert_equal(ticket3_later.updated_at.to_s, ticket3_later_next.updated_at.to_s) - - ticket4_later_next = Ticket.find(ticket4.id) - assert_equal('closed', ticket4_later_next.state.name) - assert_equal(ticket4_later.updated_at.to_s, ticket4_later_next.updated_at.to_s) - - ticket5_later_next = Ticket.find(ticket5.id) - assert_equal('closed', ticket5_later_next.state.name) - assert_equal(ticket5_later.updated_at.to_s, ticket5_later_next.updated_at.to_s) - - end - - test 'with invalid state_id' do - - # create ticket - group1 = Group.lookup(name: 'Users') - group2 = Group.create!( - name: 'JobTest2', - updated_by_id: 1, - created_by_id: 1, - ) - ticket1 = Ticket.create!( - title: 'job test 1', - group: group1, - customer_id: 2, - state: Ticket::State.lookup(name: 'new'), - priority: Ticket::Priority.lookup(name: '2 normal'), - created_at: Time.zone.now - 3.days, - updated_at: Time.zone.now - 3.days, - created_by_id: 1, - updated_by_id: 1, - ) - ticket2 = Ticket.create!( - title: 'job test 2', - group: group1, - customer_id: 2, - state: Ticket::State.lookup(name: 'new'), - priority: Ticket::Priority.lookup(name: '2 normal'), - created_at: Time.zone.now - 1.day, - created_by_id: 1, - updated_at: Time.zone.now - 1.day, - updated_by_id: 1, - ) - - # create jobs - job1 = Job.create!( - name: 'Test Job1', - timeplan: { - days: { - Mon: true, - Tue: true, - Wed: true, - Thu: true, - Fri: true, - Sat: true, - Sun: true, - }, - hours: { - 0 => true, - 1 => true, - 2 => true, - 3 => true, - 4 => true, - 5 => true, - 6 => true, - 7 => true, - 8 => true, - 9 => true, - 10 => true, - 11 => true, - 12 => true, - 13 => true, - 14 => true, - 15 => true, - 16 => true, - 17 => true, - 18 => true, - 19 => true, - 20 => true, - 21 => true, - 22 => true, - 23 => true, - }, - minutes: { - 0 => true, - 10 => true, - 20 => true, - 30 => true, - 40 => true, - 50 => true, - }, - }, - condition: { - 'ticket.state_id' => { 'operator' => 'is', 'value' => '9999' }, - 'ticket.created_at' => { 'operator' => 'before (relative)', 'value' => '2', 'range' => 'day' }, - }, - perform: { - 'ticket.state_id' => { 'value' => Ticket::State.lookup(name: 'closed').id.to_s } - }, - disable_notification: true, - last_run_at: nil, - updated_at: Time.zone.now - 15.minutes, - active: true, - updated_by_id: 1, - created_by_id: 1, - ) - assert(job1.executable?) - assert(job1.in_timeplan?) - Job.run - - # verify changes on tickets - ticket1_later = Ticket.find(ticket1.id) - assert_equal('new', ticket1_later.state.name) - assert_equal(ticket1.updated_at.to_s, ticket1_later.updated_at.to_s) - - ticket2_later = Ticket.find(ticket2.id) - assert_equal('new', ticket2_later.state.name) - assert_equal(ticket2.updated_at.to_s, ticket2_later.updated_at.to_s) - end - - test 'case 3' do - - # create jobs - job1 = Job.create!( - name: 'Test Job1', - timeplan: { - days: { - Mon: true, - Tue: false, - Wed: false, - Thu: false, - Fri: true, - Sat: false, - Sun: false, - }, - hours: { - 0 => false, - 1 => true, - 2 => false, - 3 => false, - 4 => false, - 5 => false, - 6 => false, - 7 => false, - 8 => false, - 9 => false, - 10 => true, - 11 => false, - 12 => false, - 13 => false, - 14 => false, - 15 => false, - 16 => false, - 17 => false, - 18 => false, - 19 => false, - 20 => false, - 21 => false, - 22 => false, - 23 => false, - }, - minutes: { - 0 => true, - 10 => false, - 20 => false, - 30 => false, - 40 => true, - 50 => false, - }, - }, - condition: { - 'ticket.state_id' => { 'operator' => 'is', 'value' => [Ticket::State.lookup(name: 'new').id.to_s, Ticket::State.lookup(name: 'open').id.to_s] }, - 'ticket.created_at' => { 'operator' => 'before (relative)', 'value' => '2', 'range' => 'day' }, - }, - perform: { - 'ticket.state_id' => { 'value' => Ticket::State.lookup(name: 'closed').id.to_s } - }, - disable_notification: true, - last_run_at: nil, - active: true, - created_by_id: 1, - created_at: Time.zone.now, - updated_by_id: 1, - updated_at: Time.zone.now, - ) - - time_now = Time.zone.parse('2016-03-18 09:17:13 UTC') - next_run_at = job1.next_run_at_calculate(time_now) - assert_equal('2016-03-18 10:00:00 UTC', next_run_at.to_s) - - time_now = Time.zone.parse('2016-03-18 10:37:13 UTC') - next_run_at = job1.next_run_at_calculate(time_now) - assert_equal('2016-03-18 10:40:00 UTC', next_run_at.to_s) - - time_now = Time.zone.parse('2016-03-17 09:17:13 UTC') - next_run_at = job1.next_run_at_calculate(time_now) - assert_equal('2016-03-18 01:00:00 UTC', next_run_at.to_s) - - time_now = Time.zone.parse('2016-03-17 11:17:13 UTC') - next_run_at = job1.next_run_at_calculate(time_now) - assert_equal('2016-03-18 01:00:00 UTC', next_run_at.to_s) - - time_now = Time.zone.parse('2016-03-19 11:17:13 UTC') - next_run_at = job1.next_run_at_calculate(time_now) - assert_equal('2016-03-21 01:00:00 UTC', next_run_at.to_s) - - time_now = Time.zone.parse('2016-03-22 00:59:59 UTC') - next_run_at = job1.next_run_at_calculate(time_now) - assert_equal('2016-03-25 01:00:00 UTC', next_run_at.to_s) - - time_now = Time.zone.parse('2016-03-25 00:59:59 UTC') - next_run_at = job1.next_run_at_calculate(time_now) - assert_equal('2016-03-25 01:00:00 UTC', next_run_at.to_s) - - time_now = Time.zone.parse('2016-03-24 00:59:59 UTC') - next_run_at = job1.next_run_at_calculate(time_now) - assert_equal('2016-03-25 01:00:00 UTC', next_run_at.to_s) - - time_now = Time.zone.parse('2016-03-24 23:59:59 UTC') - next_run_at = job1.next_run_at_calculate(time_now) - assert_equal('2016-03-25 01:00:00 UTC', next_run_at.to_s) - - time_now = Time.zone.parse('2016-03-25 01:00:01 UTC') - next_run_at = job1.next_run_at_calculate(time_now) - assert_equal('2016-03-25 01:00:00 UTC', next_run_at.to_s) - - time_now = Time.zone.parse('2016-03-25 01:09:01 UTC') - next_run_at = job1.next_run_at_calculate(time_now) - assert_equal('2016-03-25 01:00:00 UTC', next_run_at.to_s) - - time_now = Time.zone.parse('2016-03-25 01:09:59 UTC') - next_run_at = job1.next_run_at_calculate(time_now) - assert_equal('2016-03-25 01:00:00 UTC', next_run_at.to_s) - - job1.last_run_at = Time.zone.parse('2016-03-18 10:00:01 UTC') - job1.save! - time_now = Time.zone.parse('2016-03-18 10:00:02 UTC') - next_run_at = job1.next_run_at_calculate(time_now) - assert_equal('2016-03-18 10:40:00 UTC', next_run_at.to_s) - - job1.last_run_at = Time.zone.parse('2016-03-18 10:40:01 UTC') - job1.save! - time_now = Time.zone.parse('2016-03-18 10:40:02 UTC') - next_run_at = job1.next_run_at_calculate(time_now) - assert_equal('2016-03-21 01:00:00 UTC', next_run_at.to_s) - - end - - test 'case 4' do - - # create jobs - job1 = Job.create!( - name: 'Test Job1', - timeplan: { - days: { - Mon: true, - Tue: false, - Wed: false, - Thu: false, - Fri: true, - Sat: false, - Sun: false, - }, - hours: { - 0 => true, - 1 => false, - 2 => false, - 3 => false, - 4 => false, - 5 => false, - 6 => false, - 7 => false, - 8 => false, - 9 => false, - 10 => true, - 11 => false, - 12 => false, - 13 => false, - 14 => false, - 15 => false, - 16 => false, - 17 => false, - 18 => false, - 19 => false, - 20 => false, - 21 => false, - 22 => false, - 23 => false, - }, - minutes: { - 0 => true, - 10 => false, - 20 => false, - 30 => false, - 40 => true, - 50 => false, - }, - }, - condition: { - 'ticket.state_id' => { 'operator' => 'is', 'value' => [Ticket::State.lookup(name: 'new').id.to_s, Ticket::State.lookup(name: 'open').id.to_s] }, - 'ticket.created_at' => { 'operator' => 'before (relative)', 'value' => '2', 'range' => 'day' }, - }, - perform: { - 'ticket.state_id' => { 'value' => Ticket::State.lookup(name: 'closed').id.to_s } - }, - disable_notification: true, - last_run_at: nil, - active: true, - created_by_id: 1, - created_at: Time.zone.now, - updated_by_id: 1, - updated_at: Time.zone.now, - ) - - time_now = Time.zone.parse('2016-03-17 23:51:23 UTC') - next_run_at = job1.next_run_at_calculate(time_now) - assert_equal('2016-03-18 00:00:00 UTC', next_run_at.to_s) - - job1.last_run_at = Time.zone.parse('2016-03-17 23:45:01 UTC') - job1.save! - time_now = Time.zone.parse('2016-03-17 23:51:23 UTC') - next_run_at = job1.next_run_at_calculate(time_now) - assert_equal('2016-03-18 00:00:00 UTC', next_run_at.to_s) - - job1.last_run_at = Time.zone.parse('2016-03-17 23:59:01 UTC') - job1.save! - time_now = Time.zone.parse('2016-03-17 23:59:23 UTC') - next_run_at = job1.next_run_at_calculate(time_now) - assert_equal('2016-03-18 00:00:00 UTC', next_run_at.to_s) - - time_now = Time.zone.parse('2016-03-17 23:59:23 UTC') - assert_not(job1.in_timeplan?(time_now)) - - time_now = Time.zone.parse('2016-03-18 00:01:23 UTC') - assert(job1.in_timeplan?(time_now)) - - end - - test 'check next_run_at' do - - job1 = Job.create!( - name: 'Test Job1', - timeplan: { - days: { - Mon: true, - Tue: false, - Wed: false, - Thu: false, - Fri: false, - Sat: false, - Sun: false, - }, - hours: { - '0' => true, - '1' => false, - '2' => false, - '3' => false, - '4' => false, - '5' => false, - '6' => false, - '7' => false, - '8' => false, - '9' => false, - '10' => false, - '11' => false, - '12' => false, - '13' => false, - '14' => false, - '15' => false, - '16' => false, - '17' => false, - '18' => false, - '19' => false, - '20' => false, - '21' => false, - '22' => false, - '23' => false, - }, - minutes: { - '0' => true, - '10' => false, - '20' => false, - '30' => false, - '40' => false, - '50' => false, - }, - }, - condition: { - 'ticket.state_id' => { 'operator' => 'is', 'value' => [Ticket::State.lookup(name: 'new').id.to_s, Ticket::State.lookup(name: 'open').id.to_s] }, - 'ticket.created_at' => { 'operator' => 'before (relative)', 'value' => '2', 'range' => 'day' }, - }, - perform: { - 'ticket.state_id' => { 'value' => Ticket::State.lookup(name: 'closed').id.to_s } - }, - disable_notification: true, - last_run_at: nil, - active: true, - created_by_id: 1, - created_at: Time.zone.now, - updated_by_id: 1, - updated_at: Time.zone.now, - ) - - time_now = Time.zone.parse('2016-03-17 23:51:23 UTC') - next_run_at = job1.next_run_at_calculate(time_now) - assert_equal('2016-03-21 00:00:00 UTC', next_run_at.to_s) - end - - test 'update next run at' do - - travel_to Time.zone.local(2017, 11, 10, 22, 0o4, 44) - - job1 = Job.create!( - name: 'Test Job1', - timeplan: { - days: { - Mon: false, - Tue: false, - Wed: false, - Thu: false, - Fri: false, - Sat: true, - Sun: false, - }, - hours: { - '0' => false, - '1' => false, - '2' => false, - '3' => false, - '4' => false, - '5' => false, - '6' => false, - '7' => false, - '8' => false, - '9' => false, - '10' => false, - '11' => false, - '12' => false, - '13' => false, - '14' => false, - '15' => false, - '16' => false, - '17' => false, - '18' => false, - '19' => false, - '20' => false, - '21' => false, - '22' => false, - '23' => true, - }, - minutes: { - '0' => true, - '10' => false, - '20' => false, - '30' => false, - '40' => false, - '50' => false, - }, - }, - condition: { - 'ticket.state_id' => { 'operator' => 'is', 'value' => [Ticket::State.lookup(name: 'new').id.to_s, Ticket::State.lookup(name: 'open').id.to_s] }, - }, - perform: { - 'ticket.action' => { 'value' => 'delete' }, - }, - disable_notification: true, - last_run_at: nil, - active: true, - created_by_id: 1, - created_at: Time.zone.now, - updated_by_id: 1, - updated_at: Time.zone.now, - ) - - assert_equal('2017-11-11 23:00:00 UTC', job1.next_run_at.to_s) - assert_not(job1.last_run_at) - - travel_to Time.zone.local(2017, 11, 16, 22, 0o4, 44) - - Job.run - - job1.reload - - assert_equal('2017-11-18 23:00:00 UTC', job1.next_run_at.to_s) - assert_not(job1.last_run_at) - - travel_back - - end - - test 'execute on certain time' do - - travel_to Time.zone.local(2017, 11, 16, 22, 0o4, 44) - - group1 = Group.lookup(name: 'Users') - ticket1 = Ticket.create!( - title: 'job test 1', - group: group1, - customer_id: 2, - state: Ticket::State.lookup(name: 'new'), - priority: Ticket::Priority.lookup(name: '2 normal'), - created_by_id: 1, - updated_by_id: 1, - ) - ticket2 = Ticket.create!( - title: 'job test 2', - group: group1, - customer_id: 2, - state: Ticket::State.lookup(name: 'new'), - priority: Ticket::Priority.lookup(name: '2 normal'), - created_by_id: 1, - updated_by_id: 1, - ) - - job1 = Job.create!( - name: 'Test Job1', - timeplan: { - days: { - Mon: false, - Tue: false, - Wed: false, - Thu: true, - Fri: false, - Sat: false, - Sun: false, - }, - hours: { - '0' => false, - '1' => false, - '2' => false, - '3' => false, - '4' => false, - '5' => false, - '6' => false, - '7' => false, - '8' => false, - '9' => false, - '10' => false, - '11' => false, - '12' => false, - '13' => false, - '14' => false, - '15' => false, - '16' => false, - '17' => false, - '18' => false, - '19' => false, - '20' => false, - '21' => false, - '22' => false, - '23' => true, - }, - minutes: { - '0' => true, - '10' => false, - '20' => false, - '30' => false, - '40' => false, - '50' => false, - }, - }, - condition: { - 'ticket.state_id' => { 'operator' => 'is', 'value' => [Ticket::State.lookup(name: 'new').id.to_s, Ticket::State.lookup(name: 'open').id.to_s] }, - }, - perform: { - 'ticket.action' => { 'value' => 'delete' }, - }, - disable_notification: true, - last_run_at: nil, - active: true, - created_by_id: 1, - created_at: Time.zone.now, - updated_by_id: 1, - updated_at: Time.zone.now, - ) - Job.run - - assert(Ticket.find_by(id: ticket1.id)) - assert(Ticket.find_by(id: ticket2.id)) - - travel_to Time.zone.local(2017, 11, 16, 23, 0o4, 44) - - Job.run - - assert_not(Ticket.find_by(id: ticket1.id)) - assert_not(Ticket.find_by(id: ticket2.id)) - - travel_back - end - - test 'delete based on tag' do - - # create ticket - group1 = Group.lookup(name: 'Users') - group2 = Group.create!( - name: 'JobTest2', - updated_by_id: 1, - created_by_id: 1, - ) - ticket1 = Ticket.create!( - title: 'job test 1', - group: group1, - customer_id: 2, - state: Ticket::State.lookup(name: 'new'), - priority: Ticket::Priority.lookup(name: '2 normal'), - created_at: Time.zone.now - 3.days, - updated_at: Time.zone.now - 3.days, - created_by_id: 1, - updated_by_id: 1, - ) - ticket1.tag_add('spam', 1) - ticket1.tag_add('test1 ', 1) - ticket2 = Ticket.create!( - title: 'job test 2', - group: group1, - customer_id: 2, - state: Ticket::State.lookup(name: 'new'), - priority: Ticket::Priority.lookup(name: '2 normal'), - created_at: Time.zone.now - 1.day, - created_by_id: 1, - updated_at: Time.zone.now - 1.day, - updated_by_id: 1, - ) - - job1 = Job.create!( - name: 'Test Job1', - timeplan: { - days: { - Mon: true, - Tue: true, - Wed: true, - Thu: true, - Fri: true, - Sat: true, - Sun: true, - }, - hours: { - 0 => true, - 1 => true, - 2 => true, - 3 => true, - 4 => true, - 5 => true, - 6 => true, - 7 => true, - 8 => true, - 9 => true, - 10 => true, - 11 => true, - 12 => true, - 13 => true, - 14 => true, - 15 => true, - 16 => true, - 17 => true, - 18 => true, - 19 => true, - 20 => true, - 21 => true, - 22 => true, - 23 => true, - }, - minutes: { - 0 => true, - 10 => true, - 20 => true, - 30 => true, - 40 => true, - 50 => true, - }, - }, - condition: { - 'ticket.tags' => { 'operator' => 'contains one', 'value' => 'spam' }, - }, - perform: { - 'ticket.action' => { 'value' => 'delete' }, - }, - disable_notification: true, - last_run_at: nil, - updated_at: Time.zone.now - 15.minutes, - active: true, - updated_by_id: 1, - created_by_id: 1, - ) - assert(job1.executable?) - assert(job1.in_timeplan?) - Job.run - - assert_not(Ticket.find_by(id: ticket1.id)) - assert(Ticket.find_by(id: ticket2.id)) - - end - - test 'delete spam on time' do - - # create ticket - group1 = Group.lookup(name: 'Users') - group2 = Group.create!( - name: 'JobTest2', - updated_by_id: 1, - created_by_id: 1, - ) - ticket1 = Ticket.create!( - title: 'job test 1', - group: group1, - customer_id: 2, - state: Ticket::State.lookup(name: 'closed'), - priority: Ticket::Priority.lookup(name: '2 normal'), - created_by_id: 1, - updated_by_id: 1, - ) - ticket2 = Ticket.create!( - title: 'job test 2', - group: group2, - customer_id: 2, - state: Ticket::State.lookup(name: 'closed'), - priority: Ticket::Priority.lookup(name: '2 normal'), - created_by_id: 1, - updated_by_id: 1, - ) - travel_to Time.zone.parse('2018-08-13T23:01:01Z') - job1 = Job.create!( - name: 'Spam entfernen', - timeplan: { - days: { - Mon: true, - Tue: false, - Wed: true, - Thu: false, - Fri: true, - Sat: false, - Sun: false, - }, - hours: { - '0' => false, - '1' => false, - '2' => false, - '3' => false, - '4' => false, - '5' => false, - '6' => false, - '7' => false, - '8' => false, - '9' => false, - '10' => false, - '11' => false, - '12' => false, - '13' => false, - '14' => false, - '15' => false, - '16' => false, - '17' => false, - '18' => false, - '19' => false, - '20' => false, - '21' => false, - '22' => false, - '23' => true, - }, - minutes: { - '0' => true, - '10' => false, - '20' => false, - '30' => false, - '40' => false, - '50' => false, - } - }, - condition: { - 'ticket.group_id' => { 'operator' => 'is', 'value' => group1.id }, - 'ticket.state_id' => { 'operator' => 'is', 'value' => Ticket::State.find_by(name: 'closed').id } - }, - perform: { - 'ticket.action' => { 'value' => 'delete' } - }, - disable_notification: true, - last_run_at: nil, - next_run_at: '2018-08-13 23:00:00', - running: false, - processed: 19_393, - matching: 19_428, - updated_at: Time.zone.now - 15.minutes, - active: true, - updated_by_id: 1, - created_by_id: 1, - ) - - assert(job1.executable?) - assert(job1.in_timeplan?) - Job.run - - assert_not(Ticket.find_by(id: ticket1.id)) - assert(Ticket.find_by(id: ticket2.id)) - - ticket1 = Ticket.create!( - title: 'job test 1', - group: group1, - customer_id: 2, - state: Ticket::State.lookup(name: 'closed'), - priority: Ticket::Priority.lookup(name: '2 normal'), - created_by_id: 1, - updated_by_id: 1, - ) - - travel_to Time.zone.parse('2018-08-15T23:01:01Z') - - job1.running = true - job1.save! - - assert_not(job1.executable?) - assert(job1.in_timeplan?) - Job.run - - assert(Ticket.find_by(id: ticket1.id)) - assert(Ticket.find_by(id: ticket2.id)) - - travel_to Time.zone.parse('2018-08-17T23:08:01Z') - assert(job1.executable?) - assert(job1.in_timeplan?) - Job.run - - assert_not(Ticket.find_by(id: ticket1.id)) - assert(Ticket.find_by(id: ticket2.id)) - end - - test 'validates perform with article.note - should fail because of missing body' do - assert_raises(Exception) do - Job.create!( - name: 'some job', - timeplan: { - days: { - Mon: true, - }, - hours: { - '0' => false, - }, - minutes: { - '0' => true, - } - }, - condition: { - 'ticket.state_id' => { 'operator' => 'is', 'value' => Ticket::State.find_by(name: 'closed').id } - }, - perform: { - 'article.note' => { - 'subject' => 'some subject!', - 'internal' => 'true', - }, - }, - disable_notification: true, - active: true, - updated_by_id: 1, - created_by_id: 1, - ) - end - end - - test 'validates perform with notification.email - should fail because of missing recipient' do - assert_raises(Exception) do - Job.create!( - name: 'some job', - timeplan: { - days: { - Mon: true, - }, - hours: { - '0' => false, - }, - minutes: { - '0' => true, - } - }, - condition: { - 'ticket.state_id' => { 'operator' => 'is', 'value' => Ticket::State.find_by(name: 'closed').id } - }, - perform: { - 'notification.email' => { - 'body' => 'some lala', - 'recipient' => '', - 'subject' => 'Thanks for your inquiry!', - }, - }, - disable_notification: true, - active: true, - updated_by_id: 1, - created_by_id: 1, - ) - end - end - - test 'validates perform with notification.sms - should fail because of missing recipient' do - assert_raises(Exception) do - Job.create!( - name: 'some job', - timeplan: { - days: { - Mon: true, - }, - hours: { - '0' => false, - }, - minutes: { - '0' => true, - } - }, - condition: { - 'ticket.state_id' => { 'operator' => 'is', 'value' => Ticket::State.find_by(name: 'closed').id } - }, - perform: { - 'notification.sms' => { - 'body' => 'some lala', - 'recipient' => '', - }, - }, - disable_notification: true, - active: true, - updated_by_id: 1, - created_by_id: 1, - ) - end - end - -end