From 2632d9fc6b2b5b25480258bc9d2fdc523ee0e697 Mon Sep 17 00:00:00 2001 From: Mantas Masalskis Date: Thu, 19 Aug 2021 14:29:28 +0200 Subject: [PATCH] Refactoring: Moved Job#timeplan calculations into dedicated class --- app/models/job.rb | 132 +----------- app/models/job/timeplan_calculation.rb | 105 ++++++++++ spec/models/job/timeplan_calculation_spec.rb | 207 +++++++++++++++++++ spec/models/job_spec.rb | 194 ----------------- 4 files changed, 318 insertions(+), 320 deletions(-) create mode 100644 app/models/job/timeplan_calculation.rb create mode 100644 spec/models/job/timeplan_calculation_spec.rb diff --git a/app/models/job.rb b/app/models/job.rb index b342f3e68..f4b975907 100644 --- a/app/models/job.rb +++ b/app/models/job.rb @@ -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) diff --git a/app/models/job/timeplan_calculation.rb b/app/models/job/timeplan_calculation.rb new file mode 100644 index 000000000..3b989fbc5 --- /dev/null +++ b/app/models/job/timeplan_calculation.rb @@ -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 diff --git a/spec/models/job/timeplan_calculation_spec.rb b/spec/models/job/timeplan_calculation_spec.rb new file mode 100644 index 000000000..9e778b545 --- /dev/null +++ b/spec/models/job/timeplan_calculation_spec.rb @@ -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 diff --git a/spec/models/job_spec.rb b/spec/models/job_spec.rb index f0d18cb62..a17b99782 100644 --- a/spec/models/job_spec.rb +++ b/spec/models/job_spec.rb @@ -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