Refactoring: Moved Job#timeplan calculations into dedicated class

This commit is contained in:
Mantas Masalskis 2021-08-19 14:29:28 +02:00 committed by Thorsten Eckel
parent 58c685f833
commit 2632d9fc6b
4 changed files with 318 additions and 320 deletions

View file

@ -13,8 +13,7 @@ class Job < ApplicationModel
store :perform
validates :name, presence: true
before_create :updated_matching, :update_next_run_at
before_update :updated_matching, :update_next_run_at
before_save :updated_matching, :update_next_run_at
sanitized_html :note
@ -117,29 +116,7 @@ job.run(true)
end
def in_timeplan?(time = Time.zone.now)
day_map = {
0 => 'Sun',
1 => 'Mon',
2 => 'Tue',
3 => 'Wed',
4 => 'Thu',
5 => 'Fri',
6 => 'Sat',
}
# check day
return false if !timeplan['days']
return false if !timeplan['days'][day_map[time.wday]]
# check hour
return false if !timeplan['hours']
return false if !timeplan['hours'][time.hour.to_s] && !timeplan['hours'][time.hour]
# check min
return false if !timeplan['minutes']
return false if !timeplan['minutes'][match_minutes(time.min).to_s] && !timeplan['minutes'][match_minutes(time.min)]
true
Job::TimeplanCalculation.new(timeplan).contains?(time)
end
def matching_count
@ -148,120 +125,23 @@ job.run(true)
end
def next_run_at_calculate(time = Time.zone.now)
if last_run_at
diff = time - last_run_at
if diff.positive?
time += 10.minutes
end
end
day_map = {
0 => 'Sun',
1 => 'Mon',
2 => 'Tue',
3 => 'Wed',
4 => 'Thu',
5 => 'Fri',
6 => 'Sat',
}
return nil if !active
return nil if !timeplan['days']
return nil if !timeplan['hours']
return nil if !timeplan['minutes']
# loop week days
(0..7).each do |day_counter|
time_to_check = nil
day_to_check = if day_counter.zero?
time
else
time + 1.day
end
if !timeplan['days'][day_map[day_to_check.wday]]
# start on next day at 00:00:00
time = day_to_check - day_to_check.sec.seconds
time -= day_to_check.min.minutes
time -= day_to_check.hour.hours
next
end
min = day_to_check.min
min = if min < 10
0
elsif min < 20
10
elsif min < 30
20
elsif min < 40
30
elsif min < 50
40
else
50
end
# move to [0-5]0:00 time stamps
day_to_check = day_to_check - day_to_check.min.minutes + min.minutes
day_to_check -= day_to_check.sec.seconds
# loop minutes till next full hour
if day_to_check.min.nonzero?
(0..5).each do |minute_counter|
if minute_counter.nonzero?
break if day_to_check.min.zero?
day_to_check += 10.minutes
end
next if !timeplan['hours'][day_to_check.hour] && !timeplan['hours'][day_to_check.hour.to_s]
next if !timeplan['minutes'][match_minutes(day_to_check.min)] && !timeplan['minutes'][match_minutes(day_to_check.min).to_s]
return day_to_check
end
end
# loop hours
hour_to_check = nil
(0..23).each do |hour_counter|
hour_to_check = day_to_check + hour_counter.hours
# start on next day
if hour_to_check.day != day_to_check.day
time = day_to_check - day_to_check.hour.hours
break
end
# ignore not configured hours
next if !timeplan['hours'][hour_to_check.hour] && !timeplan['hours'][hour_to_check.hour.to_s]
return nil if !hour_to_check
# loop minutes
minute_to_check = nil
(0..5).each do |minute_counter|
minute_to_check = hour_to_check + minute_counter.minutes * 10
next if !timeplan['minutes'][match_minutes(minute_to_check.min)] && !timeplan['minutes'][match_minutes(minute_to_check.min).to_s]
time_to_check = minute_to_check
break
end
next if !minute_to_check
return time_to_check
end
if last_run_at && (time - last_run_at).positive?
time += 10.minutes
end
nil
Job::TimeplanCalculation.new(timeplan).next_at(time)
end
private
def updated_matching
self.matching = matching_count
true
end
def update_next_run_at
self.next_run_at = next_run_at_calculate
true
end
def match_minutes(minutes)

View file

@ -0,0 +1,105 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
class Job::TimeplanCalculation
DAY_MAP = {
0 => 'Sun',
1 => 'Mon',
2 => 'Tue',
3 => 'Wed',
4 => 'Thu',
5 => 'Fri',
6 => 'Sat'
}.freeze
attr_reader :timeplan
def initialize(timeplan)
@timeplan = timeplan.deep_transform_keys(&:to_s)
end
def contains?(time)
return false if !valid?
day?(time) && hour?(time) && minute?(time)
end
def next_at(time)
return nil if !valid?
next_run_at_same_day(time) || next_run_at_coming_week(time)
end
private
def valid?
timeplan.key?('days') && timeplan.key?('hours') && timeplan.key?('minutes')
end
def match_minutes(minutes)
minutes / 10 * 10
end
def day?(time)
timeplan['days'][DAY_MAP[time.wday]]
end
def hour?(time)
timeplan.dig 'hours', time.hour.to_s
end
def minute?(time)
timeplan.dig 'minutes', match_minutes(time.min).to_s
end
def loop_minutes(base_time)
return if !hour?(base_time)
0
.step(50, 10)
.lazy
.map { |minute| base_time.change min: minute }
.find { |time| minute?(time) }
end
def loop_hours(base_time)
return if !day?(base_time)
(base_time.hour..23)
.lazy
.map { |hour| loop_minutes base_time.change hour: hour }
.find(&:present?)
end
def loop_partial_hour(base_time)
return if !day?(base_time)
base_time
.min
.step(50, 10)
.lazy
.map { |minute| base_time.change(min: minute) }
.find { |time| hour?(time) && minute?(time) }
end
def next_run_at_same_day(time)
day_to_check = time.change min: match_minutes(time.min)
if day_to_check.min.nonzero?
date = loop_partial_hour(day_to_check)
return date if date
day_to_check = day_to_check.change(min: 0)
day_to_check += 1.hour
end
loop_hours(day_to_check)
end
def next_run_at_coming_week(time)
(1..7)
.lazy
.map { |day| loop_hours (time + day.day).midnight }
.find(&:present?)
end
end

View file

@ -0,0 +1,207 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
require 'rails_helper'
RSpec.describe Job::TimeplanCalculation do
subject(:instance) { described_class.new(timeplan) }
describe '#contains?' do
context 'without a valid timeplan' do
let(:timeplan) { {} }
it { is_expected.not_to be_contains(Time.zone.now) }
end
context 'with monday 09:20' do
let(:timeplan) { { 'days' => { 'Mon' => true }, 'hours' => { '9' => true }, 'minutes' => { '20' => true } } }
it { is_expected.to be_contains(Time.zone.parse('2020-12-28 09:20')) }
it { is_expected.to be_contains(Time.zone.parse('2020-12-21 09:20')) }
it { is_expected.not_to be_contains(Time.zone.parse('2020-12-21 10:20')) }
it { is_expected.not_to be_contains(Time.zone.parse('2020-12-21 9:10')) }
it { is_expected.not_to be_contains(Time.zone.parse('2020-12-22 9:20')) }
it { is_expected.not_to be_contains(Time.zone.parse('2020-12-20 9:20')) }
end
context 'with monday and tuesday 09:20' do
let(:timeplan) { { 'days' => { 'Mon' => true, 'Tue' => true }, 'hours' => { '9' => true }, 'minutes' => { '20' => true } } }
it { is_expected.to be_contains(Time.zone.parse('2020-12-28 09:20')) }
it { is_expected.to be_contains(Time.zone.parse('2020-12-21 09:20')) }
it { is_expected.not_to be_contains(Time.zone.parse('2020-12-21 10:20')) }
it { is_expected.not_to be_contains(Time.zone.parse('2020-12-21 9:10')) }
it { is_expected.to be_contains(Time.zone.parse('2020-12-22 9:20')) }
it { is_expected.not_to be_contains(Time.zone.parse('2020-12-20 9:20')) }
end
context 'with monday 09:20 and 10:20' do
let(:timeplan) { { 'days' => { 'Mon' => true }, 'hours' => { '9' => true, '10' => true }, 'minutes' => { '20' => true } } }
it { is_expected.to be_contains(Time.zone.parse('2020-12-28 09:20')) }
it { is_expected.to be_contains(Time.zone.parse('2020-12-21 09:20')) }
it { is_expected.to be_contains(Time.zone.parse('2020-12-21 10:20')) }
it { is_expected.not_to be_contains(Time.zone.parse('2020-12-21 9:10')) }
it { is_expected.not_to be_contains(Time.zone.parse('2020-12-22 9:20')) }
it { is_expected.not_to be_contains(Time.zone.parse('2020-12-20 9:20')) }
end
context 'with monday 09:20 and 9:10' do
let(:timeplan) { { 'days' => { 'Mon' => true }, 'hours' => { '9' => true }, 'minutes' => { '20' => true, '10' => true } } }
it { is_expected.to be_contains(Time.zone.parse('2020-12-28 09:20')) }
it { is_expected.to be_contains(Time.zone.parse('2020-12-21 09:20')) }
it { is_expected.not_to be_contains(Time.zone.parse('2020-12-21 10:20')) }
it { is_expected.to be_contains(Time.zone.parse('2020-12-21 9:10')) }
it { is_expected.not_to be_contains(Time.zone.parse('2020-12-22 9:20')) }
it { is_expected.not_to be_contains(Time.zone.parse('2020-12-20 9:20')) }
end
end
describe 'next_at?' do
context 'without a valid timeplan' do
let(:timeplan) { {} }
it { expect(instance.next_at(Time.zone.now)).to be_nil }
end
context 'with monday 09:20' do
let(:timeplan) { { 'days' => { 'Mon' => true }, 'hours' => { '9' => true }, 'minutes' => { '20' => true } } }
it { expect(instance.next_at(Time.zone.parse('2020-12-28 09:31'))).to eq(Time.zone.parse('2021-01-04 09:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-28 09:30'))).to eq(Time.zone.parse('2021-01-04 09:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-28 09:20'))).to eq(Time.zone.parse('2020-12-28 09:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 09:21'))).to eq(Time.zone.parse('2020-12-21 09:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 09:35'))).to eq(Time.zone.parse('2020-12-28 09:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 10:20'))).to eq(Time.zone.parse('2020-12-28 09:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 10:21'))).to eq(Time.zone.parse('2020-12-28 09:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 9:10'))).to eq(Time.zone.parse('2020-12-21 09:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-22 9:20'))).to eq(Time.zone.parse('2020-12-28 09:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-20 9:20'))).to eq(Time.zone.parse('2020-12-21 09:20')) }
end
context 'with monday and tuesday 09:20' do
let(:timeplan) { { 'days' => { 'Mon' => true, 'Tue' => true }, 'hours' => { '9' => true }, 'minutes' => { '20' => true } } }
it { expect(instance.next_at(Time.zone.parse('2020-12-28 09:30'))).to eq(Time.zone.parse('2020-12-29 09:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-29 09:30'))).to eq(Time.zone.parse('2021-01-04 09:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 09:25'))).to eq(Time.zone.parse('2020-12-21 09:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 09:35'))).to eq(Time.zone.parse('2020-12-22 09:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 10:20'))).to eq(Time.zone.parse('2020-12-22 09:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 10:21'))).to eq(Time.zone.parse('2020-12-22 09:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 9:10'))).to eq(Time.zone.parse('2020-12-21 09:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-22 9:30'))).to eq(Time.zone.parse('2020-12-28 09:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-20 9:20'))).to eq(Time.zone.parse('2020-12-21 09:20')) }
end
context 'with monday 09:20 and 10:20' do
let(:timeplan) { { 'days' => { 'Mon' => true }, 'hours' => { '9' => true, '10' => true }, 'minutes' => { '20' => true } } }
it { expect(instance.next_at(Time.zone.parse('2020-12-28 09:30'))).to eq(Time.zone.parse('2020-12-28 10:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 09:30'))).to eq(Time.zone.parse('2020-12-21 10:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 09:35'))).to eq(Time.zone.parse('2020-12-21 10:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 08:07'))).to eq(Time.zone.parse('2020-12-21 09:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 10:30'))).to eq(Time.zone.parse('2020-12-28 09:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 10:31'))).to eq(Time.zone.parse('2020-12-28 09:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 9:10'))).to eq(Time.zone.parse('2020-12-21 09:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 9:47'))).to eq(Time.zone.parse('2020-12-21 10:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-22 9:20'))).to eq(Time.zone.parse('2020-12-28 09:20')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-20 9:30'))).to eq(Time.zone.parse('2020-12-21 09:20')) }
end
context 'with monday 09:30 and 9:10' do
let(:timeplan) { { 'days' => { 'Mon' => true }, 'hours' => { '9' => true }, 'minutes' => { '30' => true, '10' => true } } }
it { expect(instance.next_at(Time.zone.parse('2020-12-28 09:40'))).to eq(Time.zone.parse('2021-01-04 09:10')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 09:40'))).to eq(Time.zone.parse('2020-12-28 09:10')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 09:30'))).to eq(Time.zone.parse('2020-12-21 09:30')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 09:25'))).to eq(Time.zone.parse('2020-12-21 09:30')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 09:35'))).to eq(Time.zone.parse('2020-12-21 09:30')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 09:45'))).to eq(Time.zone.parse('2020-12-28 09:10')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 08:07'))).to eq(Time.zone.parse('2020-12-21 09:10')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 10:20'))).to eq(Time.zone.parse('2020-12-28 09:10')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 10:21'))).to eq(Time.zone.parse('2020-12-28 09:10')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 9:10'))).to eq(Time.zone.parse('2020-12-21 09:10')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-21 9:20'))).to eq(Time.zone.parse('2020-12-21 09:30')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-22 9:20'))).to eq(Time.zone.parse('2020-12-28 09:10')) }
it { expect(instance.next_at(Time.zone.parse('2020-12-20 9:20'))).to eq(Time.zone.parse('2020-12-21 09:10')) }
end
end
describe 'legacy tests moved from Job model' do
let(:job) { create(:job, :never_on) }
let(:timeplan) { job.timeplan }
let(:time) { Time.current }
context 'when the current day, hour, and minute all match true values in #timeplan' do
it 'for Symbol/Integer keys returns true' do
timeplan[:days].transform_keys!(&:to_sym)[time.strftime('%a').to_sym] = true
timeplan[:hours].transform_keys!(&:to_i)[time.hour] = true
timeplan[:minutes].transform_keys!(&:to_i)[time.min.floor(-1)] = true
expect(instance.contains?(time)).to be(true)
end
it 'for String keys returns true' do
timeplan[:days].transform_keys!(&:to_s)[time.strftime('%a')] = true
timeplan[:hours].transform_keys!(&:to_s)[time.hour.to_s] = true
timeplan[:minutes].transform_keys!(&:to_s)[time.min.floor(-1).to_s] = true
expect(instance.contains?(time)).to be(true)
end
end
context 'when the current day does not match a true value in #timeplan' do
it 'for Symbol/Integer keys returns false' do
timeplan[:days].transform_keys!(&:to_sym).transform_values! { true }[time.strftime('%a').to_sym] = false
timeplan[:hours].transform_keys!(&:to_i)[time.hour] = true
timeplan[:minutes].transform_keys!(&:to_i)[time.min.floor(-1)] = true
expect(instance.contains?(time)).to be(false)
end
it 'for String keys returns false' do
timeplan[:days].transform_keys!(&:to_s).transform_values! { true }[time.strftime('%a')] = false
timeplan[:hours].transform_keys!(&:to_s)[time.hour.to_s] = true
timeplan[:minutes].transform_keys!(&:to_s)[time.min.floor(-1).to_s] = true
expect(instance.contains?(time)).to be(false)
end
end
context 'when the current hour does not match a true value in #timeplan' do
it 'for Symbol/Integer keys returns false' do
timeplan[:days].transform_keys!(&:to_sym)[time.strftime('%a').to_sym] = true
timeplan[:hours].transform_keys!(&:to_i).transform_values! { true }[time.hour] = false
timeplan[:minutes].transform_keys!(&:to_i)[time.min.floor(-1)] = true
expect(instance.contains?(time)).to be(false)
end
it 'for String keys returns false' do
timeplan[:days].transform_keys!(&:to_s)[time.strftime('%a')] = true
timeplan[:hours].transform_keys!(&:to_s).transform_values! { true }[time.hour.to_s] = false
timeplan[:minutes].transform_keys!(&:to_s)[time.min.floor(-1).to_s] = true
expect(instance.contains?(time)).to be(false)
end
end
context 'when the current minute does not match a true value in #timeplan' do
it 'for Symbol/Integer keys returns false' do
timeplan[:days].transform_keys!(&:to_sym)[time.strftime('%a').to_sym] = true
timeplan[:hours].transform_keys!(&:to_i)[time.hour] = true
timeplan[:minutes].transform_keys!(&:to_i).transform_values! { true }[time.min.floor(-1)] = false
expect(instance.contains?(time)).to be(false)
end
it 'for String keys returns false' do
timeplan[:days].transform_keys!(&:to_s)[time.strftime('%a')] = true
timeplan[:hours].transform_keys!(&:to_s)[time.hour.to_s] = true
timeplan[:minutes].transform_keys!(&:to_s).transform_values! { true }[time.min.floor(-1).to_s] = false
expect(instance.contains?(time)).to be(false)
end
end
end
end

View file

@ -345,200 +345,6 @@ RSpec.describe Job, type: :model do
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