require 'spec_helper' require 'puppet/util/windows/taskscheduler' if Puppet.features.microsoft_windows? shared_examples_for "a trigger that handles start_date and start_time" do let(:trigger) do described_class.new( :name => 'Shared Test Task', :command => 'C:\Windows\System32\notepad.exe' ).translate_hash_to_trigger(trigger_hash) end before :each do allow_any_instance_of(Win32::TaskScheduler).to receive(:save) end describe 'the given start_date' do before :each do trigger_hash['start_time'] = '00:00' end def date_component { 'start_year' => trigger['start_year'], 'start_month' => trigger['start_month'], 'start_day' => trigger['start_day'] } end it 'should be able to be specified in ISO 8601 calendar date format' do trigger_hash['start_date'] = '2011-12-31' expect(date_component).to eq({ 'start_year' => 2011, 'start_month' => 12, 'start_day' => 31 }) end it 'should fail if before 1753-01-01' do trigger_hash['start_date'] = '1752-12-31' expect { date_component }.to raise_error( Puppet::Error, 'start_date must be on or after 1753-01-01' ) end it 'should succeed if on 1753-01-01' do trigger_hash['start_date'] = '1753-01-01' expect(date_component).to eq({ 'start_year' => 1753, 'start_month' => 1, 'start_day' => 1 }) end it 'should succeed if after 1753-01-01' do trigger_hash['start_date'] = '1753-01-02' expect(date_component).to eq({ 'start_year' => 1753, 'start_month' => 1, 'start_day' => 2 }) end end describe 'the given start_time' do before :each do trigger_hash['start_date'] = '2011-12-31' end def time_component { 'start_hour' => trigger['start_hour'], 'start_minute' => trigger['start_minute'] } end it 'should be able to be specified as a 24-hour "hh:mm"' do trigger_hash['start_time'] = '17:13' expect(time_component).to eq({ 'start_hour' => 17, 'start_minute' => 13 }) end it 'should be able to be specified as a 12-hour "hh:mm am"' do trigger_hash['start_time'] = '3:13 am' expect(time_component).to eq({ 'start_hour' => 3, 'start_minute' => 13 }) end it 'should be able to be specified as a 12-hour "hh:mm pm"' do trigger_hash['start_time'] = '3:13 pm' expect(time_component).to eq({ 'start_hour' => 15, 'start_minute' => 13 }) end end end describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if => Puppet.features.microsoft_windows? do before :each do allow(Puppet::Type.type(:scheduled_task)).to receive(:defaultprovider).and_return(described_class) end describe 'when retrieving' do before :each do @mock_task = double() allow_any_instance_of(described_class).to receive(:task).and_return(@mock_task) allow(Win32::TaskScheduler).to receive(:new).and_return(@mock_task) end let(:resource) { Puppet::Type.type(:scheduled_task).new(:name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe') } describe 'the triggers for a task' do describe 'with only one trigger' do before :each do expect(@mock_task).to receive(:trigger_count).and_return(1) end it 'should handle a single daily trigger' do expect(@mock_task).to receive(:trigger).with(0).and_return({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_DAILY, 'start_year' => 2011, 'start_month' => 9, 'start_day' => 12, 'start_hour' => 13, 'start_minute' => 20, 'flags' => 0, 'type' => { 'days_interval' => 2 }, }) expect(resource.provider.trigger).to eq([{ 'start_date' => '2011-9-12', 'start_time' => '13:20', 'schedule' => 'daily', 'every' => '2', 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 0, }]) end it 'should handle a single daily with repeat trigger' do expect(@mock_task).to receive(:trigger).with(0).and_return({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_DAILY, 'start_year' => 2011, 'start_month' => 9, 'start_day' => 12, 'start_hour' => 13, 'start_minute' => 20, 'minutes_interval' => 60, 'minutes_duration' => 180, 'flags' => 0, 'type' => { 'days_interval' => 2 }, }) expect(resource.provider.trigger).to eq([{ 'start_date' => '2011-9-12', 'start_time' => '13:20', 'schedule' => 'daily', 'every' => '2', 'minutes_interval' => 60, 'minutes_duration' => 180, 'enabled' => true, 'index' => 0, }]) end it 'should handle a single weekly trigger' do scheduled_days_of_week = Win32::TaskScheduler::MONDAY | Win32::TaskScheduler::WEDNESDAY | Win32::TaskScheduler::FRIDAY | Win32::TaskScheduler::SUNDAY expect(@mock_task).to receive(:trigger).with(0).and_return({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_WEEKLY, 'start_year' => 2011, 'start_month' => 9, 'start_day' => 12, 'start_hour' => 13, 'start_minute' => 20, 'flags' => 0, 'type' => { 'weeks_interval' => 2, 'days_of_week' => scheduled_days_of_week } }) expect(resource.provider.trigger).to eq([{ 'start_date' => '2011-9-12', 'start_time' => '13:20', 'schedule' => 'weekly', 'every' => '2', 'day_of_week' => ['sun', 'mon', 'wed', 'fri'], 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 0, }]) end it 'should handle a single monthly date-based trigger' do scheduled_months = Win32::TaskScheduler::JANUARY | Win32::TaskScheduler::FEBRUARY | Win32::TaskScheduler::AUGUST | Win32::TaskScheduler::SEPTEMBER | Win32::TaskScheduler::DECEMBER # 1 3 5 15 'last' scheduled_days = 1 | 1 << 2 | 1 << 4 | 1 << 14 | 1 << 31 expect(@mock_task).to receive(:trigger).with(0).and_return({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDATE, 'start_year' => 2011, 'start_month' => 9, 'start_day' => 12, 'start_hour' => 13, 'start_minute' => 20, 'flags' => 0, 'type' => { 'months' => scheduled_months, 'days' => scheduled_days } }) expect(resource.provider.trigger).to eq([{ 'start_date' => '2011-9-12', 'start_time' => '13:20', 'schedule' => 'monthly', 'months' => [1, 2, 8, 9, 12], 'on' => [1, 3, 5, 15, 'last'], 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 0, }]) end it 'should handle a single monthly day-of-week-based trigger' do scheduled_months = Win32::TaskScheduler::JANUARY | Win32::TaskScheduler::FEBRUARY | Win32::TaskScheduler::AUGUST | Win32::TaskScheduler::SEPTEMBER | Win32::TaskScheduler::DECEMBER scheduled_days_of_week = Win32::TaskScheduler::MONDAY | Win32::TaskScheduler::WEDNESDAY | Win32::TaskScheduler::FRIDAY | Win32::TaskScheduler::SUNDAY expect(@mock_task).to receive(:trigger).with(0).and_return({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDOW, 'start_year' => 2011, 'start_month' => 9, 'start_day' => 12, 'start_hour' => 13, 'start_minute' => 20, 'flags' => 0, 'type' => { 'months' => scheduled_months, 'weeks' => Win32::TaskScheduler::FIRST_WEEK, 'days_of_week' => scheduled_days_of_week } }) expect(resource.provider.trigger).to eq([{ 'start_date' => '2011-9-12', 'start_time' => '13:20', 'schedule' => 'monthly', 'months' => [1, 2, 8, 9, 12], 'which_occurrence' => 'first', 'day_of_week' => ['sun', 'mon', 'wed', 'fri'], 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 0, }]) end it 'should handle a single one-time trigger' do expect(@mock_task).to receive(:trigger).with(0).and_return({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2011, 'start_month' => 9, 'start_day' => 12, 'start_hour' => 13, 'start_minute' => 20, 'flags' => 0, }) expect(resource.provider.trigger).to eq([{ 'start_date' => '2011-9-12', 'start_time' => '13:20', 'schedule' => 'once', 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 0, }]) end end it 'should handle multiple triggers' do expect(@mock_task).to receive(:trigger_count).and_return(3) expect(@mock_task).to receive(:trigger).with(0).and_return({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2011, 'start_month' => 10, 'start_day' => 13, 'start_hour' => 14, 'start_minute' => 21, 'flags' => 0, }) expect(@mock_task).to receive(:trigger).with(1).and_return({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2012, 'start_month' => 11, 'start_day' => 14, 'start_hour' => 15, 'start_minute' => 22, 'flags' => 0, }) expect(@mock_task).to receive(:trigger).with(2).and_return({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2013, 'start_month' => 12, 'start_day' => 15, 'start_hour' => 16, 'start_minute' => 23, 'flags' => 0, }) expect(resource.provider.trigger).to match_array([ { 'start_date' => '2011-10-13', 'start_time' => '14:21', 'schedule' => 'once', 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 0, }, { 'start_date' => '2012-11-14', 'start_time' => '15:22', 'schedule' => 'once', 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 1, }, { 'start_date' => '2013-12-15', 'start_time' => '16:23', 'schedule' => 'once', 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 2, } ]) end it 'should handle multiple triggers with repeat triggers' do expect(@mock_task).to receive(:trigger_count).and_return(3) expect(@mock_task).to receive(:trigger).with(0).and_return({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2011, 'start_month' => 10, 'start_day' => 13, 'start_hour' => 14, 'start_minute' => 21, 'minutes_interval' => 15, 'minutes_duration' => 60, 'flags' => 0, }) expect(@mock_task).to receive(:trigger).with(1).and_return({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2012, 'start_month' => 11, 'start_day' => 14, 'start_hour' => 15, 'start_minute' => 22, 'minutes_interval' => 30, 'minutes_duration' => 120, 'flags' => 0, }) expect(@mock_task).to receive(:trigger).with(2).and_return({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2013, 'start_month' => 12, 'start_day' => 15, 'start_hour' => 16, 'start_minute' => 23, 'minutes_interval' => 60, 'minutes_duration' => 240, 'flags' => 0, }) expect(resource.provider.trigger).to match_array([ { 'start_date' => '2011-10-13', 'start_time' => '14:21', 'schedule' => 'once', 'minutes_interval' => 15, 'minutes_duration' => 60, 'enabled' => true, 'index' => 0, }, { 'start_date' => '2012-11-14', 'start_time' => '15:22', 'schedule' => 'once', 'minutes_interval' => 30, 'minutes_duration' => 120, 'enabled' => true, 'index' => 1, }, { 'start_date' => '2013-12-15', 'start_time' => '16:23', 'schedule' => 'once', 'minutes_interval' => 60, 'minutes_duration' => 240, 'enabled' => true, 'index' => 2, } ]) end it 'should skip triggers Win32::TaskScheduler cannot handle' do expect(@mock_task).to receive(:trigger_count).and_return(3) expect(@mock_task).to receive(:trigger).with(0).and_return({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2011, 'start_month' => 10, 'start_day' => 13, 'start_hour' => 14, 'start_minute' => 21, 'flags' => 0, }) expect(@mock_task).to receive(:trigger).with(1).and_raise( Win32::TaskScheduler::Error.new('Unhandled trigger type!') ) expect(@mock_task).to receive(:trigger).with(2).and_return({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2013, 'start_month' => 12, 'start_day' => 15, 'start_hour' => 16, 'start_minute' => 23, 'flags' => 0, }) expect(resource.provider.trigger).to match_array([ { 'start_date' => '2011-10-13', 'start_time' => '14:21', 'schedule' => 'once', 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 0, }, { 'start_date' => '2013-12-15', 'start_time' => '16:23', 'schedule' => 'once', 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 2, } ]) end it 'should skip trigger types Puppet does not handle' do expect(@mock_task).to receive(:trigger_count).and_return(3) expect(@mock_task).to receive(:trigger).with(0).and_return({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2011, 'start_month' => 10, 'start_day' => 13, 'start_hour' => 14, 'start_minute' => 21, 'flags' => 0, }) expect(@mock_task).to receive(:trigger).with(1).and_return({ 'trigger_type' => Win32::TaskScheduler::TASK_EVENT_TRIGGER_AT_LOGON, }) expect(@mock_task).to receive(:trigger).with(2).and_return({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2013, 'start_month' => 12, 'start_day' => 15, 'start_hour' => 16, 'start_minute' => 23, 'flags' => 0, }) expect(resource.provider.trigger).to match_array([ { 'start_date' => '2011-10-13', 'start_time' => '14:21', 'schedule' => 'once', 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 0, }, { 'start_date' => '2013-12-15', 'start_time' => '16:23', 'schedule' => 'once', 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 2, } ]) end end it 'should get the working directory from the working_directory on the task' do expect(@mock_task).to receive(:working_directory).and_return('C:\Windows\System32') expect(resource.provider.working_dir).to eq('C:\Windows\System32') end it 'should get the command from the application_name on the task' do expect(@mock_task).to receive(:application_name).and_return('C:\Windows\System32\notepad.exe') expect(resource.provider.command).to eq('C:\Windows\System32\notepad.exe') end it 'should get the command arguments from the parameters on the task' do expect(@mock_task).to receive(:parameters).and_return('these are my arguments') expect(resource.provider.arguments).to eq('these are my arguments') end it 'should get the user from the account_information on the task' do expect(@mock_task).to receive(:account_information).and_return('this is my user') expect(resource.provider.user).to eq('this is my user') end describe 'whether the task is enabled' do it 'should report tasks with the disabled bit set as disabled' do allow(@mock_task).to receive(:flags).and_return(Win32::TaskScheduler::DISABLED) expect(resource.provider.enabled).to eq(:false) end it 'should report tasks without the disabled bit set as enabled' do allow(@mock_task).to receive(:flags).and_return(~Win32::TaskScheduler::DISABLED) expect(resource.provider.enabled).to eq(:true) end it 'should not consider triggers for determining if the task is enabled' do allow(@mock_task).to receive(:flags).and_return(~Win32::TaskScheduler::DISABLED) allow(@mock_task).to receive(:trigger_count).and_return(1) allow(@mock_task).to receive(:trigger).with(0).and_return({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2011, 'start_month' => 10, 'start_day' => 13, 'start_hour' => 14, 'start_minute' => 21, 'flags' => Win32::TaskScheduler::TASK_TRIGGER_FLAG_DISABLED, }) expect(resource.provider.enabled).to eq(:true) end end end describe '#exists?' do before :each do @mock_task = instance_double(Win32::TaskScheduler) allow_any_instance_of(described_class).to receive(:task).and_return(@mock_task) allow(Win32::TaskScheduler).to receive(:new).and_return(@mock_task) end let(:resource) { Puppet::Type.type(:scheduled_task).new(:name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe') } it "should delegate to Win32::TaskScheduler using the resource's name" do expect(@mock_task).to receive(:exists?).with('Test Task').and_return(true) expect(resource.provider.exists?).to eq(true) end end describe '#clear_task' do before :each do @mock_task = instance_double(Win32::TaskScheduler) @new_mock_task = instance_double(Win32::TaskScheduler) allow(Win32::TaskScheduler).to receive(:new).and_return(@mock_task, @new_mock_task) allow_any_instance_of(described_class).to receive(:exists?).and_return(false) end let(:resource) { Puppet::Type.type(:scheduled_task).new(:name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe') } it 'should clear the cached task object' do expect(resource.provider.task).to eq(@mock_task) expect(resource.provider.task).to eq(@mock_task) resource.provider.clear_task expect(resource.provider.task).to eq(@new_mock_task) end it 'should clear the cached list of triggers for the task' do allow(@mock_task).to receive(:trigger_count).and_return(1) allow(@mock_task).to receive(:trigger).with(0).and_return({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2011, 'start_month' => 10, 'start_day' => 13, 'start_hour' => 14, 'start_minute' => 21, 'flags' => 0, }) allow(@new_mock_task).to receive(:trigger_count).and_return(1) allow(@new_mock_task).to receive(:trigger).with(0).and_return({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2012, 'start_month' => 11, 'start_day' => 14, 'start_hour' => 15, 'start_minute' => 22, 'flags' => 0, }) mock_task_trigger = { 'start_date' => '2011-10-13', 'start_time' => '14:21', 'schedule' => 'once', 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 0, } expect(resource.provider.trigger).to eq([mock_task_trigger]) expect(resource.provider.trigger).to eq([mock_task_trigger]) resource.provider.clear_task expect(resource.provider.trigger).to eq([{ 'start_date' => '2012-11-14', 'start_time' => '15:22', 'schedule' => 'once', 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 0, }]) end end describe '.instances' do it 'should use the list of .job files to construct the list of scheduled_tasks' do job_files = ['foo.job', 'bar.job', 'baz.job'] allow_any_instance_of(Win32::TaskScheduler).to receive(:tasks).and_return(job_files) job_files.each do |job| job = File.basename(job, '.job') expect(described_class).to receive(:new).with(:provider => :win32_taskscheduler, :name => job) end described_class.instances end end describe '#user_insync?', :if => Puppet.features.microsoft_windows? do let(:resource) { described_class.new(:name => 'foobar', :command => 'C:\Windows\System32\notepad.exe') } it 'should consider the user as in sync if the name matches' do expect(Puppet::Util::Windows::SID).to receive(:name_to_sid).with('joe').twice.and_return('SID A') expect(resource).to be_user_insync('joe', ['joe']) end it 'should consider the user as in sync if the current user is fully qualified' do expect(Puppet::Util::Windows::SID).to receive(:name_to_sid).with('joe').and_return('SID A') expect(Puppet::Util::Windows::SID).to receive(:name_to_sid).with('MACHINE\joe').and_return('SID A') expect(resource).to be_user_insync('MACHINE\joe', ['joe']) end it 'should consider a current user of the empty string to be the same as the system user' do expect(Puppet::Util::Windows::SID).to receive(:name_to_sid).with('system').twice.and_return('SYSTEM SID') expect(resource).to be_user_insync('', ['system']) end it 'should consider different users as being different' do expect(Puppet::Util::Windows::SID).to receive(:name_to_sid).with('joe').and_return('SID A') expect(Puppet::Util::Windows::SID).to receive(:name_to_sid).with('bob').and_return('SID B') expect(resource).not_to be_user_insync('joe', ['bob']) end end describe '#trigger_insync?' do let(:resource) { described_class.new(:name => 'foobar', :command => 'C:\Windows\System32\notepad.exe') } it 'should not consider any extra current triggers as in sync' do current = [ {'start_date' => '2011-09-12', 'start_time' => '15:15', 'schedule' => 'once'}, {'start_date' => '2012-10-13', 'start_time' => '16:16', 'schedule' => 'once'} ] desired = {'start_date' => '2011-09-12', 'start_time' => '15:15', 'schedule' => 'once'} expect(resource).not_to be_trigger_insync(current, desired) end it 'should not consider any extra desired triggers as in sync' do current = {'start_date' => '2011-09-12', 'start_time' => '15:15', 'schedule' => 'once'} desired = [ {'start_date' => '2011-09-12', 'start_time' => '15:15', 'schedule' => 'once'}, {'start_date' => '2012-10-13', 'start_time' => '16:16', 'schedule' => 'once'} ] expect(resource).not_to be_trigger_insync(current, desired) end it 'should consider triggers to be in sync if the sets of current and desired triggers are equal' do current = [ {'start_date' => '2011-09-12', 'start_time' => '15:15', 'schedule' => 'once'}, {'start_date' => '2012-10-13', 'start_time' => '16:16', 'schedule' => 'once'} ] desired = [ {'start_date' => '2011-09-12', 'start_time' => '15:15', 'schedule' => 'once'}, {'start_date' => '2012-10-13', 'start_time' => '16:16', 'schedule' => 'once'} ] expect(resource).to be_trigger_insync(current, desired) end end describe '#triggers_same?' do let(:provider) { described_class.new(:name => 'foobar', :command => 'C:\Windows\System32\notepad.exe') } it "should not mutate triggers" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} current.freeze desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30'} desired.freeze expect(provider).to be_triggers_same(current, desired) end it "ignores 'index' in current trigger" do current = {'index' => 0, 'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} expect(provider).to be_triggers_same(current, desired) end it "ignores 'enabled' in current triggger" do current = {'enabled' => true, 'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} expect(provider).to be_triggers_same(current, desired) end it "should not consider a disabled 'current' trigger to be the same" do current = {'schedule' => 'once', 'enabled' => false} desired = {'schedule' => 'once'} expect(provider).not_to be_triggers_same(current, desired) end it 'should not consider triggers with different schedules to be the same' do current = {'schedule' => 'once'} desired = {'schedule' => 'weekly'} expect(provider).not_to be_triggers_same(current, desired) end describe 'start_date' do it "considers triggers to be equal when start_date is not specified in the 'desired' trigger" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'daily', 'start_time' => '15:30', 'every' => 3} expect(provider).to be_triggers_same(current, desired) end end describe 'comparing daily triggers' do it "should consider 'desired' triggers not specifying 'every' to have the same value as the 'current' trigger" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30'} expect(provider).to be_triggers_same(current, desired) end it "should consider different 'start_dates' as different triggers" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'daily', 'start_date' => '2012-09-12', 'start_time' => '15:30', 'every' => 3} expect(provider).not_to be_triggers_same(current, desired) end it "should consider different 'start_times' as different triggers" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:31', 'every' => 3} expect(provider).not_to be_triggers_same(current, desired) end it 'should not consider differences in date formatting to be different triggers' do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'weekly', 'start_date' => '2011-9-12', 'start_time' => '15:30', 'every' => 3} expect(provider).to be_triggers_same(current, desired) end it 'should not consider differences in time formatting to be different triggers' do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '5:30', 'every' => 3} desired = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '05:30', 'every' => 3} expect(provider).to be_triggers_same(current, desired) end it "should consider different 'every' as different triggers" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 1} expect(provider).not_to be_triggers_same(current, desired) end it 'should consider triggers that are the same as being the same' do trigger = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '01:30', 'every' => 1} expect(provider).to be_triggers_same(trigger, trigger) end end describe 'comparing one-time triggers' do it "should consider different 'start_dates' as different triggers" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30'} desired = {'schedule' => 'daily', 'start_date' => '2012-09-12', 'start_time' => '15:30'} expect(provider).not_to be_triggers_same(current, desired) end it "should consider different 'start_times' as different triggers" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30'} desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:31'} expect(provider).not_to be_triggers_same(current, desired) end it 'should not consider differences in date formatting to be different triggers' do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30'} desired = {'schedule' => 'weekly', 'start_date' => '2011-9-12', 'start_time' => '15:30'} expect(provider).to be_triggers_same(current, desired) end it 'should not consider differences in time formatting to be different triggers' do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '1:30'} desired = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '01:30'} expect(provider).to be_triggers_same(current, desired) end it 'should consider triggers that are the same as being the same' do trigger = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '01:30'} expect(provider).to be_triggers_same(trigger, trigger) end end describe 'comparing monthly date-based triggers' do it "should consider 'desired' triggers not specifying 'months' to have the same value as the 'current' trigger" do current = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'on' => [1,'last']} desired = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'on' => [1, 'last']} expect(provider).to be_triggers_same(current, desired) end it "should consider different 'start_dates' as different triggers" do current = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} desired = {'schedule' => 'monthly', 'start_date' => '2011-10-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} expect(provider).not_to be_triggers_same(current, desired) end it "should consider different 'start_times' as different triggers" do current = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} desired = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '22:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} expect(provider).not_to be_triggers_same(current, desired) end it 'should not consider differences in date formatting to be different triggers' do current = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} desired = {'schedule' => 'monthly', 'start_date' => '2011-9-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} expect(provider).to be_triggers_same(current, desired) end it 'should not consider differences in time formatting to be different triggers' do current = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '5:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} desired = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '05:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} expect(provider).to be_triggers_same(current, desired) end it "should consider different 'months' as different triggers" do current = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} desired = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1], 'on' => [1, 3, 5, 7]} expect(provider).not_to be_triggers_same(current, desired) end it "should consider different 'on' as different triggers" do current = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} desired = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 5, 7]} expect(provider).not_to be_triggers_same(current, desired) end it 'should consider triggers that are the same as being the same' do trigger = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} expect(provider).to be_triggers_same(trigger, trigger) end end describe 'comparing monthly day-of-week-based triggers' do it "should consider 'desired' triggers not specifying 'months' to have the same value as the 'current' trigger" do current = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } desired = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } expect(provider).to be_triggers_same(current, desired) end it "should consider different 'start_dates' as different triggers" do current = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } desired = { 'schedule' => 'monthly', 'start_date' => '2011-10-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } expect(provider).not_to be_triggers_same(current, desired) end it "should consider different 'start_times' as different triggers" do current = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } desired = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '22:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } expect(provider).not_to be_triggers_same(current, desired) end it "should consider different 'months' as different triggers" do current = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } desired = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3, 5, 7, 9], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } expect(provider).not_to be_triggers_same(current, desired) end it "should consider different 'which_occurrence' as different triggers" do current = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } desired = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'last', 'day_of_week' => ['mon', 'tues', 'sat'] } expect(provider).not_to be_triggers_same(current, desired) end it "should consider different 'day_of_week' as different triggers" do current = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } desired = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['fri'] } expect(provider).not_to be_triggers_same(current, desired) end it 'should consider triggers that are the same as being the same' do trigger = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } expect(provider).to be_triggers_same(trigger, trigger) end end describe 'comparing weekly triggers' do it "should consider 'desired' triggers not specifying 'day_of_week' to have the same value as the 'current' trigger" do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} desired = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} expect(provider).to be_triggers_same(current, desired) end it "should consider different 'start_dates' as different triggers" do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} desired = {'schedule' => 'weekly', 'start_date' => '2011-10-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} expect(provider).not_to be_triggers_same(current, desired) end it "should consider different 'start_times' as different triggers" do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} desired = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '22:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} expect(provider).not_to be_triggers_same(current, desired) end it 'should not consider differences in date formatting to be different triggers' do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} desired = {'schedule' => 'weekly', 'start_date' => '2011-9-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} expect(provider).to be_triggers_same(current, desired) end it 'should not consider differences in time formatting to be different triggers' do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '1:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} desired = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '01:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} expect(provider).to be_triggers_same(current, desired) end it "should consider different 'every' as different triggers" do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 1, 'day_of_week' => ['mon', 'wed', 'fri']} desired = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} expect(provider).not_to be_triggers_same(current, desired) end it "should consider different 'day_of_week' as different triggers" do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} desired = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['fri']} expect(provider).not_to be_triggers_same(current, desired) end it 'should consider triggers that are the same as being the same' do trigger = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} expect(provider).to be_triggers_same(trigger, trigger) end end end describe '#normalized_date' do it 'should format the date without leading zeros' do expect(described_class.normalized_date('2011-01-01')).to eq('2011-1-1') end end describe '#normalized_time' do it 'should format the time as {24h}:{minutes}' do expect(described_class.normalized_time('8:37 PM')).to eq('20:37') end end describe '#translate_hash_to_trigger' do before :each do @puppet_trigger = { 'start_date' => '2011-1-1', 'start_time' => '01:10' } end let(:provider) { described_class.new(:name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe') } let(:trigger) { provider.translate_hash_to_trigger(@puppet_trigger) } context "working with repeat every x triggers" do before :each do @puppet_trigger['schedule'] = 'once' end it 'should succeed if minutes_interval is equal to 0' do @puppet_trigger['minutes_interval'] = '0' expect(trigger['minutes_interval']).to eq(0) end it 'should default minutes_duration to a full day when minutes_interval is greater than 0 without setting minutes_duration' do @puppet_trigger['minutes_interval'] = '1' expect(trigger['minutes_duration']).to eq(1440) end it 'should succeed if minutes_interval is greater than 0 and minutes_duration is also set' do @puppet_trigger['minutes_interval'] = '1' @puppet_trigger['minutes_duration'] = '2' expect(trigger['minutes_interval']).to eq(1) end it 'should fail if minutes_interval is less than 0' do @puppet_trigger['minutes_interval'] = '-1' expect { trigger }.to raise_error( Puppet::Error, 'minutes_interval must be an integer greater or equal to 0' ) end it 'should fail if minutes_interval is not an integer' do @puppet_trigger['minutes_interval'] = 'abc' expect { trigger }.to raise_error(ArgumentError) end it 'should succeed if minutes_duration is equal to 0' do @puppet_trigger['minutes_duration'] = '0' expect(trigger['minutes_duration']).to eq(0) end it 'should succeed if minutes_duration is greater than 0' do @puppet_trigger['minutes_duration'] = '1' expect(trigger['minutes_duration']).to eq(1) end it 'should fail if minutes_duration is less than 0' do @puppet_trigger['minutes_duration'] = '-1' expect { trigger }.to raise_error( Puppet::Error, 'minutes_duration must be an integer greater than minutes_interval and equal to or greater than 0' ) end it 'should fail if minutes_duration is not an integer' do @puppet_trigger['minutes_duration'] = 'abc' expect { trigger }.to raise_error(ArgumentError) end it 'should succeed if minutes_duration is equal to a full day' do @puppet_trigger['minutes_duration'] = '1440' expect(trigger['minutes_duration']).to eq(1440) end it 'should succeed if minutes_duration is equal to three days' do @puppet_trigger['minutes_duration'] = '4320' expect(trigger['minutes_duration']).to eq(4320) end it 'should succeed if minutes_duration is greater than minutes_duration' do @puppet_trigger['minutes_interval'] = '10' @puppet_trigger['minutes_duration'] = '11' expect(trigger['minutes_interval']).to eq(10) expect(trigger['minutes_duration']).to eq(11) end it 'should fail if minutes_duration is equal to minutes_interval' do # On Windows 2003, the duration must be greater than the interval # on other platforms the values can be equal. @puppet_trigger['minutes_interval'] = '10' @puppet_trigger['minutes_duration'] = '10' expect { trigger }.to raise_error( Puppet::Error, 'minutes_duration must be an integer greater than minutes_interval and equal to or greater than 0' ) end it 'should succeed if minutes_duration and minutes_interval are both set to 0' do @puppet_trigger['minutes_interval'] = '0' @puppet_trigger['minutes_duration'] = '0' expect(trigger['minutes_interval']).to eq(0) expect(trigger['minutes_duration']).to eq(0) end it 'should fail if minutes_duration is less than minutes_interval' do @puppet_trigger['minutes_interval'] = '10' @puppet_trigger['minutes_duration'] = '9' expect { trigger }.to raise_error( Puppet::Error, 'minutes_duration must be an integer greater than minutes_interval and equal to or greater than 0' ) end it 'should fail if minutes_duration is less than minutes_interval and set to 0' do @puppet_trigger['minutes_interval'] = '10' @puppet_trigger['minutes_duration'] = '0' expect { trigger }.to raise_error( Puppet::Error, 'minutes_interval cannot be set without minutes_duration also being set to a number greater than 0' ) end end describe 'when given a one-time trigger' do before :each do @puppet_trigger['schedule'] = 'once' end it 'should set the trigger_type to Win32::TaskScheduler::ONCE' do expect(trigger['trigger_type']).to eq(Win32::TaskScheduler::ONCE) end it 'should not set a type' do expect(trigger).not_to be_has_key('type') end it "should require 'start_date'" do @puppet_trigger.delete('start_date') expect { trigger }.to raise_error( Puppet::Error, /Must specify 'start_date' when defining a one-time trigger/ ) end it "should require 'start_time'" do @puppet_trigger.delete('start_time') expect { trigger }.to raise_error( Puppet::Error, /Must specify 'start_time' when defining a trigger/ ) end it_behaves_like "a trigger that handles start_date and start_time" do let(:trigger_hash) {{'schedule' => 'once' }} end end describe 'when given a daily trigger' do before :each do @puppet_trigger['schedule'] = 'daily' end it "should default 'every' to 1" do expect(trigger['type']['days_interval']).to eq(1) end it "should use the specified value for 'every'" do @puppet_trigger['every'] = 5 expect(trigger['type']['days_interval']).to eq(5) end it "should default 'start_date' to 'today'" do @puppet_trigger.delete('start_date') today = Time.now expect(trigger['start_year']).to eq(today.year) expect(trigger['start_month']).to eq(today.month) expect(trigger['start_day']).to eq(today.day) end it_behaves_like "a trigger that handles start_date and start_time" do let(:trigger_hash) {{'schedule' => 'daily', 'every' => 1}} end end describe 'when given a weekly trigger' do before :each do @puppet_trigger['schedule'] = 'weekly' end it "should default 'every' to 1" do expect(trigger['type']['weeks_interval']).to eq(1) end it "should use the specified value for 'every'" do @puppet_trigger['every'] = 4 expect(trigger['type']['weeks_interval']).to eq(4) end it "should default 'day_of_week' to be every day of the week" do expect(trigger['type']['days_of_week']).to eq(Win32::TaskScheduler::MONDAY | Win32::TaskScheduler::TUESDAY | Win32::TaskScheduler::WEDNESDAY | Win32::TaskScheduler::THURSDAY | Win32::TaskScheduler::FRIDAY | Win32::TaskScheduler::SATURDAY | Win32::TaskScheduler::SUNDAY) end it "should use the specified value for 'day_of_week'" do @puppet_trigger['day_of_week'] = ['mon', 'wed', 'fri'] expect(trigger['type']['days_of_week']).to eq(Win32::TaskScheduler::MONDAY | Win32::TaskScheduler::WEDNESDAY | Win32::TaskScheduler::FRIDAY) end it "should default 'start_date' to 'today'" do @puppet_trigger.delete('start_date') today = Time.now expect(trigger['start_year']).to eq(today.year) expect(trigger['start_month']).to eq(today.month) expect(trigger['start_day']).to eq(today.day) end it_behaves_like "a trigger that handles start_date and start_time" do let(:trigger_hash) {{'schedule' => 'weekly', 'every' => 1, 'day_of_week' => 'mon'}} end end shared_examples_for 'a monthly schedule' do it "should default 'months' to be every month" do expect(trigger['type']['months']).to eq(Win32::TaskScheduler::JANUARY | Win32::TaskScheduler::FEBRUARY | Win32::TaskScheduler::MARCH | Win32::TaskScheduler::APRIL | Win32::TaskScheduler::MAY | Win32::TaskScheduler::JUNE | Win32::TaskScheduler::JULY | Win32::TaskScheduler::AUGUST | Win32::TaskScheduler::SEPTEMBER | Win32::TaskScheduler::OCTOBER | Win32::TaskScheduler::NOVEMBER | Win32::TaskScheduler::DECEMBER) end it "should use the specified value for 'months'" do @puppet_trigger['months'] = [2, 8] expect(trigger['type']['months']).to eq(Win32::TaskScheduler::FEBRUARY | Win32::TaskScheduler::AUGUST) end end describe 'when given a monthly date-based trigger' do before :each do @puppet_trigger['schedule'] = 'monthly' @puppet_trigger['on'] = [7, 14] end it_behaves_like 'a monthly schedule' it "should not allow 'which_occurrence' to be specified" do @puppet_trigger['which_occurrence'] = 'first' expect {trigger}.to raise_error( Puppet::Error, /Neither 'day_of_week' nor 'which_occurrence' can be specified when creating a monthly date-based trigger/ ) end it "should not allow 'day_of_week' to be specified" do @puppet_trigger['day_of_week'] = 'mon' expect {trigger}.to raise_error( Puppet::Error, /Neither 'day_of_week' nor 'which_occurrence' can be specified when creating a monthly date-based trigger/ ) end it "should require 'on'" do @puppet_trigger.delete('on') expect {trigger}.to raise_error( Puppet::Error, /Don't know how to create a 'monthly' schedule with the options: schedule, start_date, start_time/ ) end it "should default 'start_date' to 'today'" do @puppet_trigger.delete('start_date') today = Time.now expect(trigger['start_year']).to eq(today.year) expect(trigger['start_month']).to eq(today.month) expect(trigger['start_day']).to eq(today.day) end it_behaves_like "a trigger that handles start_date and start_time" do let(:trigger_hash) {{'schedule' => 'monthly', 'months' => 1, 'on' => 1}} end end describe 'when given a monthly day-of-week-based trigger' do before :each do @puppet_trigger['schedule'] = 'monthly' @puppet_trigger['which_occurrence'] = 'first' @puppet_trigger['day_of_week'] = 'mon' end it_behaves_like 'a monthly schedule' it "should not allow 'on' to be specified" do @puppet_trigger['on'] = 15 expect {trigger}.to raise_error( Puppet::Error, /Neither 'day_of_week' nor 'which_occurrence' can be specified when creating a monthly date-based trigger/ ) end it "should require 'which_occurrence'" do @puppet_trigger.delete('which_occurrence') expect {trigger}.to raise_error( Puppet::Error, /which_occurrence must be specified when creating a monthly day-of-week based trigger/ ) end it "should require 'day_of_week'" do @puppet_trigger.delete('day_of_week') expect {trigger}.to raise_error( Puppet::Error, /day_of_week must be specified when creating a monthly day-of-week based trigger/ ) end it "should default 'start_date' to 'today'" do @puppet_trigger.delete('start_date') today = Time.now expect(trigger['start_year']).to eq(today.year) expect(trigger['start_month']).to eq(today.month) expect(trigger['start_day']).to eq(today.day) end it_behaves_like "a trigger that handles start_date and start_time" do let(:trigger_hash) {{'schedule' => 'monthly', 'months' => 1, 'which_occurrence' => 'first', 'day_of_week' => 'mon'}} end end end describe '#validate_trigger' do let(:provider) { described_class.new(:name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe') } it 'should succeed if all passed triggers translate from hashes to triggers' do triggers_to_validate = [ {'schedule' => 'once', 'start_date' => '2011-09-13', 'start_time' => '13:50'}, {'schedule' => 'weekly', 'start_date' => '2011-09-13', 'start_time' => '13:50', 'day_of_week' => 'mon'} ] expect(provider.validate_trigger(triggers_to_validate)).to eq(true) end it 'should use the exception from translate_hash_to_trigger when it fails' do triggers_to_validate = [ {'schedule' => 'once', 'start_date' => '2011-09-13', 'start_time' => '13:50'}, {'schedule' => 'monthly', 'this is invalid' => true} ] expect {provider.validate_trigger(triggers_to_validate)}.to raise_error( Puppet::Error, /#{Regexp.escape("Unknown trigger option(s): ['this is invalid']")}/ ) end end describe '#flush' do let(:resource) do Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe', :ensure => @ensure ) end before :each do @mock_task = instance_double(Win32::TaskScheduler) allow(@mock_task).to receive(:exists?).and_return(true) allow(@mock_task).to receive(:activate) allow(Win32::TaskScheduler).to receive(:new).and_return(@mock_task) @command = 'C:\Windows\System32\notepad.exe' end describe 'when :ensure is :present' do before :each do @ensure = :present end it 'should save the task' do expect(@mock_task).to receive(:set_account_information).with(nil, nil) expect(@mock_task).to receive(:save) resource.provider.flush end it 'should fail if the command is not specified' do resource = Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :ensure => @ensure ) expect { resource.provider.flush }.to raise_error( Puppet::Error, 'Parameter command is required.' ) end end describe 'when :ensure is :absent' do before :each do @ensure = :absent allow(@mock_task).to receive(:activate) end it 'should not save the task if :ensure is :absent' do expect(@mock_task).not_to receive(:save) resource.provider.flush end it 'should not fail if the command is not specified' do allow(@mock_task).to receive(:save) resource = Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :ensure => @ensure ) resource.provider.flush end end end describe 'property setter methods' do let(:resource) do Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :command => 'C:\dummy_task.exe' ) end before :each do @mock_task = instance_double(Win32::TaskScheduler) allow(@mock_task).to receive(:exists?).and_return(true) allow(@mock_task).to receive(:activate) allow(Win32::TaskScheduler).to receive(:new).and_return(@mock_task) end describe '#command=' do it 'should set the application_name on the task' do expect(@mock_task).to receive(:application_name=).with('C:\Windows\System32\notepad.exe') resource.provider.command = 'C:\Windows\System32\notepad.exe' end end describe '#arguments=' do it 'should set the parameters on the task' do expect(@mock_task).to receive(:parameters=).with(['/some /arguments /here']) resource.provider.arguments = ['/some /arguments /here'] end end describe '#working_dir=' do it 'should set the working_directory on the task' do expect(@mock_task).to receive(:working_directory=).with('C:\Windows\System32') resource.provider.working_dir = 'C:\Windows\System32' end end describe '#enabled=' do it 'should set the disabled flag if the task should be disabled' do allow(@mock_task).to receive(:flags).and_return(0) expect(@mock_task).to receive(:flags=).with(Win32::TaskScheduler::DISABLED) resource.provider.enabled = :false end it 'should clear the disabled flag if the task should be enabled' do allow(@mock_task).to receive(:flags).and_return(Win32::TaskScheduler::DISABLED) expect(@mock_task).to receive(:flags=).with(0) resource.provider.enabled = :true end end describe '#trigger=' do let(:resource) do Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe', :trigger => @trigger ) end before :each do @mock_task = instance_double(Win32::TaskScheduler) allow(@mock_task).to receive(:exists?).and_return(true) allow(@mock_task).to receive(:activate) allow(Win32::TaskScheduler).to receive(:new).and_return(@mock_task) end it 'should not consider all duplicate current triggers in sync with a single desired trigger' do @trigger = {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10'} current_triggers = [ {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10', 'index' => 0}, {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10', 'index' => 1}, {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10', 'index' => 2}, ] allow(resource.provider).to receive(:trigger).and_return(current_triggers) expect(@mock_task).to receive(:delete_trigger).with(1) expect(@mock_task).to receive(:delete_trigger).with(2) resource.provider.trigger = @trigger end it 'should remove triggers not defined in the resource' do @trigger = {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10'} current_triggers = [ {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10', 'index' => 0}, {'schedule' => 'once', 'start_date' => '2012-09-15', 'start_time' => '15:10', 'index' => 1}, {'schedule' => 'once', 'start_date' => '2013-09-15', 'start_time' => '15:10', 'index' => 2}, ] allow(resource.provider).to receive(:trigger).and_return(current_triggers) expect(@mock_task).to receive(:delete_trigger).with(1) expect(@mock_task).to receive(:delete_trigger).with(2) resource.provider.trigger = @trigger end it 'should add triggers defined in the resource, but not found on the system' do @trigger = [ {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10'}, {'schedule' => 'once', 'start_date' => '2012-09-15', 'start_time' => '15:10'}, {'schedule' => 'once', 'start_date' => '2013-09-15', 'start_time' => '15:10'}, ] current_triggers = [ {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10', 'index' => 0}, ] allow(resource.provider).to receive(:trigger).and_return(current_triggers) expect(@mock_task).to receive(:trigger=).with(resource.provider.translate_hash_to_trigger(@trigger[1])) expect(@mock_task).to receive(:trigger=).with(resource.provider.translate_hash_to_trigger(@trigger[2])) resource.provider.trigger = @trigger end end describe '#user=', :if => Puppet.features.microsoft_windows? do before :each do @mock_task = instance_double(Win32::TaskScheduler) allow(@mock_task).to receive(:exists?).and_return(true) allow(@mock_task).to receive(:activate) allow(Win32::TaskScheduler).to receive(:new).and_return(@mock_task) end it 'should use nil for user and password when setting the user to the SYSTEM account' do allow(Puppet::Util::Windows::SID).to receive(:name_to_sid).with('system').and_return('SYSTEM SID') resource = Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :command => 'C:\dummy_task.exe', :user => 'system' ) expect(@mock_task).to receive(:set_account_information).with(nil, nil) resource.provider.user = 'system' end it 'should use the specified user and password when setting the user to anything other than SYSTEM' do allow(Puppet::Util::Windows::SID).to receive(:name_to_sid).with('my_user_name').and_return('SID A') resource = Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :command => 'C:\dummy_task.exe', :user => 'my_user_name', :password => 'my password' ) expect(@mock_task).to receive(:set_account_information).with('my_user_name', 'my password') resource.provider.user = 'my_user_name' end end end describe '#create' do let(:resource) do Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :enabled => @enabled, :command => @command, :arguments => @arguments, :working_dir => @working_dir, :trigger => { 'schedule' => 'once', 'start_date' => '2011-09-27', 'start_time' => '17:00' } ) end before :each do @enabled = :true @command = 'C:\Windows\System32\notepad.exe' @arguments = '/a /list /of /arguments' @working_dir = 'C:\Windows\Some\Directory' @mock_task = instance_double(Win32::TaskScheduler) allow(@mock_task).to receive(:exists?).and_return(true) allow(@mock_task).to receive(:activate) allow(@mock_task).to receive(:application_name=) allow(@mock_task).to receive(:parameters=) allow(@mock_task).to receive(:working_directory=) allow(@mock_task).to receive(:set_account_information) allow(@mock_task).to receive(:flags) allow(@mock_task).to receive(:flags=) allow(@mock_task).to receive(:trigger_count).and_return(0) allow(@mock_task).to receive(:trigger=) allow(@mock_task).to receive(:save) allow(Win32::TaskScheduler).to receive(:new).and_return(@mock_task) allow_any_instance_of(described_class).to receive(:sync_triggers) end it 'should set the command' do expect(resource.provider).to receive(:command=).with(@command) resource.provider.create end it 'should set the arguments' do expect(resource.provider).to receive(:arguments=).with(@arguments) resource.provider.create end it 'should set the working_dir' do expect(resource.provider).to receive(:working_dir=).with(@working_dir) resource.provider.create end it "should set the user" do expect(resource.provider).to receive(:user=).with(:system) resource.provider.create end it 'should set the enabled property' do expect(resource.provider).to receive(:enabled=) resource.provider.create end it 'should sync triggers' do expect(resource.provider).to receive(:trigger=) resource.provider.create end end describe "Win32::TaskScheduler", :if => Puppet.features.microsoft_windows? do let(:name) { SecureRandom.uuid } describe 'sets appropriate generic trigger defaults' do before(:each) do @now = Time.now allow(Time).to receive(:now).and_return(@now) end it 'for a ONCE schedule' do task = Win32::TaskScheduler.new(name, { 'trigger_type' => Win32::TaskScheduler::ONCE }) expect(task.trigger(0)['start_year']).to eq(@now.year) expect(task.trigger(0)['start_month']).to eq(@now.month) expect(task.trigger(0)['start_day']).to eq(@now.day) end it 'for a DAILY schedule' do trigger = { 'trigger_type' => Win32::TaskScheduler::DAILY, 'type' => { 'days_interval' => 1 } } task = Win32::TaskScheduler.new(name, trigger) expect(task.trigger(0)['start_year']).to eq(@now.year) expect(task.trigger(0)['start_month']).to eq(@now.month) expect(task.trigger(0)['start_day']).to eq(@now.day) end it 'for a WEEKLY schedule' do trigger = { 'trigger_type' => Win32::TaskScheduler::WEEKLY, 'type' => { 'weeks_interval' => 1, 'days_of_week' => 1 } } task = Win32::TaskScheduler.new(name, trigger) expect(task.trigger(0)['start_year']).to eq(@now.year) expect(task.trigger(0)['start_month']).to eq(@now.month) expect(task.trigger(0)['start_day']).to eq(@now.day) end it 'for a MONTHLYDATE schedule' do trigger = { 'trigger_type' => Win32::TaskScheduler::MONTHLYDATE, 'type' => { 'days' => 1, 'months' => 1 } } task = Win32::TaskScheduler.new(name, trigger) expect(task.trigger(0)['start_year']).to eq(@now.year) expect(task.trigger(0)['start_month']).to eq(@now.month) expect(task.trigger(0)['start_day']).to eq(@now.day) end it 'for a MONTHLYDOW schedule' do trigger = { 'trigger_type' => Win32::TaskScheduler::MONTHLYDOW, 'type' => { 'weeks' => 1, 'days_of_week' => 1, 'months' => 1 } } task = Win32::TaskScheduler.new(name, trigger) expect(task.trigger(0)['start_year']).to eq(@now.year) expect(task.trigger(0)['start_month']).to eq(@now.month) expect(task.trigger(0)['start_day']).to eq(@now.day) end end describe 'enforces maximum lengths' do let(:task) { Win32::TaskScheduler.new(name, { 'trigger_type' => Win32::TaskScheduler::ONCE }) } it 'on account user name' do expect { task.set_account_information('a' * (Win32::TaskScheduler::MAX_ACCOUNT_LENGTH + 1), 'pass') }.to raise_error(Puppet::Error) end it 'on application name' do expect { task.application_name = 'a' * (Win32::TaskScheduler::MAX_PATH + 1) }.to raise_error(Puppet::Error) end it 'on parameters' do expect { task.parameters = 'a' * (Win32::TaskScheduler::MAX_PARAMETERS_LENGTH + 1) }.to raise_error(Puppet::Error) end it 'on working directory' do expect { task.working_directory = 'a' * (Win32::TaskScheduler::MAX_PATH + 1) }.to raise_error(Puppet::Error) end it 'on comment' do expect { task.comment = 'a' * (Win32::TaskScheduler::MAX_COMMENT_LENGTH + 1) }.to raise_error(Puppet::Error) end it 'on creator' do expect { task.creator = 'a' * (Win32::TaskScheduler::MAX_ACCOUNT_LENGTH + 1) }.to raise_error(Puppet::Error) end end def delete_task_with_retry(task, name, attempts = 3) failed = false attempts.times do begin task.delete(name) if Win32::TaskScheduler.new.exists?(name) rescue Puppet::Util::Windows::Error failed = true end if failed then sleep 1 else break end end end describe '#exists?' do it 'works with Unicode task names' do task_name = name + "\u16A0\u16C7\u16BB" # ᚠᛇᚻ begin task = Win32::TaskScheduler.new(task_name, { 'trigger_type' => Win32::TaskScheduler::ONCE }) task.save() expect(Puppet::FileSystem.exist?("C:\\Windows\\Tasks\\#{task_name}.job")).to be_truthy expect(task.exists?(task_name)).to be_truthy ensure delete_task_with_retry(task, task_name) end end it 'is case insensitive' do task_name = name + 'abc' # name is a guid, but might not have alpha chars begin task = Win32::TaskScheduler.new(task_name.upcase, { 'trigger_type' => Win32::TaskScheduler::ONCE }) task.save() expect(task.exists?(task_name.downcase)).to be_truthy ensure delete_task_with_retry(task, task_name) end end end describe 'does not corrupt tasks' do it 'when setting maximum length values for all settings' do begin task = Win32::TaskScheduler.new(name, { 'trigger_type' => Win32::TaskScheduler::ONCE }) application_name = 'a' * Win32::TaskScheduler::MAX_PATH parameters = 'b' * Win32::TaskScheduler::MAX_PARAMETERS_LENGTH working_directory = 'c' * Win32::TaskScheduler::MAX_PATH comment = 'd' * Win32::TaskScheduler::MAX_COMMENT_LENGTH creator = 'e' * Win32::TaskScheduler::MAX_ACCOUNT_LENGTH task.application_name = application_name task.parameters = parameters task.working_directory = working_directory task.comment = comment task.creator = creator # saving and reloading (activating) can induce COM load errors when # file is corrupted, which can happen when the upper bounds of these lengths are set too high task.save() task.activate(name) # furthermore, corrupted values may not necessarily be read back properly # note that SYSTEM is always returned as an empty string in account_information expect(task.account_information).to eq('') expect(task.application_name).to eq(application_name) expect(task.parameters).to eq(parameters) expect(task.working_directory).to eq(working_directory) expect(task.comment).to eq(comment) expect(task.creator).to eq(creator) ensure delete_task_with_retry(task, name) end end it 'by preventing a save() not preceded by a set_account_information()' do begin # creates a default new task with SYSTEM user task = Win32::TaskScheduler.new(name, { 'trigger_type' => Win32::TaskScheduler::ONCE }) # save automatically resets the current task task.save() # re-activate named task, try to modify, and save task.activate(name) task.application_name = 'c:/windows/system32/notepad.exe' expect { task.save() }.to raise_error(Puppet::Error, /Account information must be set on the current task to save it properly/) # on a failed save, the current task is still active - add SYSTEM task.set_account_information('', nil) expect(task.save()).to be_instance_of(Win32::TaskScheduler::COM::Task) # the most appropriate additional validation here would be to confirm settings with schtasks.exe # but that test can live inside a system-level acceptance test ensure delete_task_with_retry(task, name) end end end end end