diff --git a/spec/unit/file_serving/metadata_spec.rb b/spec/unit/file_serving/metadata_spec.rb index 2a7196784..0d1e31f01 100755 --- a/spec/unit/file_serving/metadata_spec.rb +++ b/spec/unit/file_serving/metadata_spec.rb @@ -1,462 +1,462 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/metadata' require 'matchers/json' describe Puppet::FileServing::Metadata do let(:foobar) { File.expand_path('/foo/bar') } it "should be a subclass of Base" do expect(Puppet::FileServing::Metadata.superclass).to equal(Puppet::FileServing::Base) end it "should indirect file_metadata" do expect(Puppet::FileServing::Metadata.indirection.name).to eq(:file_metadata) end it "should have a method that triggers attribute collection" do expect(Puppet::FileServing::Metadata.new(foobar)).to respond_to(:collect) end it "should support pson serialization" do expect(Puppet::FileServing::Metadata.new(foobar)).to respond_to(:to_pson) end it "should support deserialization" do expect(Puppet::FileServing::Metadata).to respond_to(:from_data_hash) end describe "when serializing" do let(:metadata) { Puppet::FileServing::Metadata.new(foobar) } it "the data should include the path, relative_path, links, owner, group, mode, checksum, type, and destination" do expect(metadata.to_data_hash.keys.sort).to eq(%w{ path relative_path links owner group mode checksum type destination }.sort) end it "should pass the path in the hash verbatim" do expect(metadata.to_data_hash['path']).to eq(metadata.path) end it "should pass the relative_path in the hash verbatim" do expect(metadata.to_data_hash['relative_path']).to eq(metadata.relative_path) end it "should pass the links in the hash verbatim" do expect(metadata.to_data_hash['links']).to eq(metadata.links) end it "should pass the path owner in the hash verbatim" do expect(metadata.to_data_hash['owner']).to eq(metadata.owner) end it "should pass the group in the hash verbatim" do expect(metadata.to_data_hash['group']).to eq(metadata.group) end it "should pass the mode in the hash verbatim" do expect(metadata.to_data_hash['mode']).to eq(metadata.mode) end it "should pass the ftype in the hash verbatim as the 'type'" do expect(metadata.to_data_hash['type']).to eq(metadata.ftype) end it "should pass the destination verbatim" do expect(metadata.to_data_hash['destination']).to eq(metadata.destination) end it "should pass the checksum in the hash as a nested hash" do expect(metadata.to_data_hash['checksum']).to be_is_a(Hash) end it "should pass the checksum_type in the hash verbatim as the checksum's type" do expect(metadata.to_data_hash['checksum']['type']).to eq(metadata.checksum_type) end it "should pass the checksum in the hash verbatim as the checksum's value" do expect(metadata.to_data_hash['checksum']['value']).to eq(metadata.checksum) end end end describe Puppet::FileServing::Metadata, :uses_checksums => true do include JSONMatchers include PuppetSpec::Files shared_examples_for "metadata collector" do let(:metadata) do data = described_class.new(path) data.collect data end describe "when collecting attributes" do describe "when managing files" do let(:path) { tmpfile('file_serving_metadata') } before :each do FileUtils.touch(path) end describe "checksumming" do with_digest_algorithms do before :each do File.open(path, "wb") {|f| f.print(plaintext)} end it "should default to a checksum of the proper type with the file's current checksum" do expect(metadata.checksum).to eq("{#{digest_algorithm}}#{checksum}") end it "should give a mtime checksum when checksum_type is set" do time = Time.now metadata.checksum_type = "mtime" metadata.expects(:mtime_file).returns(@time) metadata.collect expect(metadata.checksum).to eq("{mtime}#{@time}") end end end it "should validate against the schema" do expect(metadata.to_pson).to validate_against('api/schemas/file_metadata.json') end end describe "when managing directories" do let(:path) { tmpdir('file_serving_metadata_dir') } let(:time) { Time.now } before :each do metadata.expects(:ctime_file).returns(time) end it "should only use checksums of type 'ctime' for directories" do metadata.collect expect(metadata.checksum).to eq("{ctime}#{time}") end it "should only use checksums of type 'ctime' for directories even if checksum_type set" do metadata.checksum_type = "mtime" metadata.expects(:mtime_file).never metadata.collect expect(metadata.checksum).to eq("{ctime}#{time}") end it "should validate against the schema" do metadata.collect expect(metadata.to_pson).to validate_against('api/schemas/file_metadata.json') end end end end describe "WindowsStat", :if => Puppet.features.microsoft_windows? do include PuppetSpec::Files it "should return default owner, group and mode when the given path has an invalid DACL (such as a non-NTFS volume)" do invalid_error = Puppet::Util::Windows::Error.new('Invalid DACL', 1336) path = tmpfile('foo') FileUtils.touch(path) Puppet::Util::Windows::Security.stubs(:get_owner).with(path).raises(invalid_error) Puppet::Util::Windows::Security.stubs(:get_group).with(path).raises(invalid_error) Puppet::Util::Windows::Security.stubs(:get_mode).with(path).raises(invalid_error) stat = Puppet::FileSystem.stat(path) win_stat = Puppet::FileServing::Metadata::WindowsStat.new(stat, path) expect(win_stat.owner).to eq('S-1-5-32-544') expect(win_stat.group).to eq('S-1-0-0') expect(win_stat.mode).to eq(0644) end it "should still raise errors that are not the result of an 'Invalid DACL'" do invalid_error = ArgumentError.new('bar') path = tmpfile('bar') FileUtils.touch(path) Puppet::Util::Windows::Security.stubs(:get_owner).with(path, :use).raises(invalid_error) Puppet::Util::Windows::Security.stubs(:get_group).with(path, :use).raises(invalid_error) Puppet::Util::Windows::Security.stubs(:get_mode).with(path, :use).raises(invalid_error) stat = Puppet::FileSystem.stat(path) expect { Puppet::FileServing::Metadata::WindowsStat.new(stat, path, :use) }.to raise_error("Unsupported Windows source permissions option use") end end shared_examples_for "metadata collector symlinks" do let(:metadata) do data = described_class.new(path) data.collect data end describe "when collecting attributes" do describe "when managing links" do # 'path' is a link that points to 'target' let(:path) { tmpfile('file_serving_metadata_link') } let(:target) { tmpfile('file_serving_metadata_target') } let(:fmode) { Puppet::FileSystem.lstat(path).mode & 0777 } before :each do File.open(target, "wb") {|f| f.print('some content')} set_mode(0644, target) Puppet::FileSystem.symlink(target, path) end it "should read links instead of returning their checksums" do expect(metadata.destination).to eq(target) end it "should validate against the schema" do expect(metadata.to_pson).to validate_against('api/schemas/file_metadata.json') end end end describe Puppet::FileServing::Metadata, " when finding the file to use for setting attributes" do let(:path) { tmpfile('file_serving_metadata_find_file') } before :each do File.open(path, "wb") {|f| f.print('some content')} set_mode(0755, path) end it "should accept a base path to which the file should be relative" do dir = tmpdir('metadata_dir') metadata = described_class.new(dir) metadata.relative_path = 'relative_path' FileUtils.touch(metadata.full_path) metadata.collect end it "should use the set base path if one is not provided" do metadata.collect end it "should raise an exception if the file does not exist" do File.delete(path) expect { metadata.collect}.to raise_error(Errno::ENOENT) end it "should validate against the schema" do expect(metadata.to_pson).to validate_against('api/schemas/file_metadata.json') end end end describe "on POSIX systems", :if => Puppet.features.posix? do let(:owner) {10} let(:group) {20} before :each do File::Stat.any_instance.stubs(:uid).returns owner File::Stat.any_instance.stubs(:gid).returns group end describe "when collecting attributes when managing files" do let(:metadata) do data = described_class.new(path) data.collect data end let(:path) { tmpfile('file_serving_metadata') } before :each do FileUtils.touch(path) end it "should set the owner to the Process's current owner" do expect(metadata.owner).to eq(Process.euid) end it "should set the group to the Process's current group" do expect(metadata.group).to eq(Process.egid) end it "should set the mode to the default mode" do set_mode(33261, path) expect(metadata.mode).to eq(0644) end end it_should_behave_like "metadata collector" it_should_behave_like "metadata collector symlinks" def set_mode(mode, path) File.chmod(mode, path) end end describe "on Windows systems", :if => Puppet.features.microsoft_windows? do let(:owner) {'S-1-1-50'} let(:group) {'S-1-1-51'} before :each do require 'puppet/util/windows/security' Puppet::Util::Windows::Security.stubs(:get_owner).returns owner Puppet::Util::Windows::Security.stubs(:get_group).returns group end describe "when collecting attributes when managing files" do let(:metadata) do data = described_class.new(path) data.collect data end let(:path) { tmpfile('file_serving_metadata') } before :each do FileUtils.touch(path) end it "should set the owner to the Process's current owner" do expect(metadata.owner).to eq("S-1-5-32-544") end it "should set the group to the Process's current group" do expect(metadata.group).to eq("S-1-0-0") end it "should set the mode to the default mode" do set_mode(33261, path) expect(metadata.mode).to eq(0644) end end it_should_behave_like "metadata collector" it_should_behave_like "metadata collector symlinks" if Puppet.features.manages_symlinks? describe "if ACL metadata cannot be collected" do let(:path) { tmpdir('file_serving_metadata_acl') } let(:metadata) do data = described_class.new(path) data.collect data end let (:invalid_dacl_error) do Puppet::Util::Windows::Error.new('Invalid DACL', 1336) end it "should default owner" do Puppet::Util::Windows::Security.stubs(:get_owner).returns nil expect(metadata.owner).to eq('S-1-5-32-544') end it "should default group" do Puppet::Util::Windows::Security.stubs(:get_group).returns nil expect(metadata.group).to eq('S-1-0-0') end it "should default mode" do Puppet::Util::Windows::Security.stubs(:get_mode).returns nil expect(metadata.mode).to eq(0644) end describe "when the path raises an Invalid ACL error" do # these simulate the behavior of a symlink file whose target does not support ACLs it "should default owner" do Puppet::Util::Windows::Security.stubs(:get_owner).raises(invalid_dacl_error) expect(metadata.owner).to eq('S-1-5-32-544') end it "should default group" do Puppet::Util::Windows::Security.stubs(:get_group).raises(invalid_dacl_error) expect(metadata.group).to eq('S-1-0-0') end it "should default mode" do Puppet::Util::Windows::Security.stubs(:get_mode).raises(invalid_dacl_error) expect(metadata.mode).to eq(0644) end end end def set_mode(mode, path) Puppet::Util::Windows::Security.set_mode(mode, path) end end end describe Puppet::FileServing::Metadata, " when pointing to a link", :if => Puppet.features.manages_symlinks?, :uses_checksums => true do with_digest_algorithms do describe "when links are managed" do before do path = "/base/path/my/file" @file = Puppet::FileServing::Metadata.new(path, :links => :manage) stat = stub("stat", :uid => 1, :gid => 2, :ftype => "link", :mode => 0755) stub_file = stub(:readlink => "/some/other/path", :lstat => stat) Puppet::FileSystem.expects(:lstat).with(path).at_least_once.returns stat Puppet::FileSystem.expects(:readlink).with(path).at_least_once.returns "/some/other/path" @file.stubs("#{digest_algorithm}_file".intern).returns(checksum) # Remove these when :managed links are no longer checksumed. if Puppet.features.microsoft_windows? - win_stat = double('win_stat', :owner => 'snarf', :group => 'thundercats', + win_stat = stub('win_stat', :owner => 'snarf', :group => 'thundercats', :ftype => 'link', :mode => 0755) Puppet::FileServing::Metadata::WindowsStat.stubs(:new).returns win_stat end end it "should store the destination of the link in :destination if links are :manage" do @file.collect expect(@file.destination).to eq("/some/other/path") end pending "should not collect the checksum if links are :manage" do # We'd like this to be true, but we need to always collect the checksum because in the server/client/server round trip we lose the distintion between manage and follow. @file.collect expect(@file.checksum).to be_nil end it "should collect the checksum if links are :manage" do # see pending note above @file.collect expect(@file.checksum).to eq("{#{digest_algorithm}}#{checksum}") end end describe "when links are followed" do before do path = "/base/path/my/file" @file = Puppet::FileServing::Metadata.new(path, :links => :follow) stat = stub("stat", :uid => 1, :gid => 2, :ftype => "file", :mode => 0755) Puppet::FileSystem.expects(:stat).with(path).at_least_once.returns stat Puppet::FileSystem.expects(:readlink).never if Puppet.features.microsoft_windows? - win_stat = double('win_stat', :owner => 'snarf', :group => 'thundercats', + win_stat = stub('win_stat', :owner => 'snarf', :group => 'thundercats', :ftype => 'file', :mode => 0755) Puppet::FileServing::Metadata::WindowsStat.stubs(:new).returns win_stat end @file.stubs("#{digest_algorithm}_file".intern).returns(checksum) end it "should not store the destination of the link in :destination if links are :follow" do @file.collect expect(@file.destination).to be_nil end it "should collect the checksum if links are :follow" do @file.collect expect(@file.checksum).to eq("{#{digest_algorithm}}#{checksum}") end end end end diff --git a/spec/unit/module_tool/tar/mini_spec.rb b/spec/unit/module_tool/tar/mini_spec.rb index badba3702..ef0d297a8 100644 --- a/spec/unit/module_tool/tar/mini_spec.rb +++ b/spec/unit/module_tool/tar/mini_spec.rb @@ -1,60 +1,60 @@ require 'spec_helper' require 'puppet/module_tool' describe Puppet::ModuleTool::Tar::Mini, :if => (Puppet.features.minitar? and Puppet.features.zlib?) do let(:sourcefile) { '/the/module.tar.gz' } let(:destdir) { File.expand_path '/the/dest/dir' } let(:sourcedir) { '/the/src/dir' } let(:destfile) { '/the/dest/file.tar.gz' } let(:minitar) { described_class.new } it "unpacks a tar file" do unpacks_the_entry(:file_start, 'thefile') minitar.unpack(sourcefile, destdir, 'uid') end it "does not allow an absolute path" do unpacks_the_entry(:file_start, '/thefile') expect { minitar.unpack(sourcefile, destdir, 'uid') }.to raise_error(Puppet::ModuleTool::Errors::InvalidPathInPackageError, "Attempt to install file into \"/thefile\" under \"#{destdir}\"") end it "does not allow a file to be written outside the destination directory" do unpacks_the_entry(:file_start, '../../thefile') expect { minitar.unpack(sourcefile, destdir, 'uid') }.to raise_error(Puppet::ModuleTool::Errors::InvalidPathInPackageError, "Attempt to install file into \"#{File.expand_path('/the/thefile')}\" under \"#{destdir}\"") end it "does not allow a directory to be written outside the destination directory" do unpacks_the_entry(:dir, '../../thedir') expect { minitar.unpack(sourcefile, destdir, 'uid') }.to raise_error(Puppet::ModuleTool::Errors::InvalidPathInPackageError, "Attempt to install file into \"#{File.expand_path('/the/thedir')}\" under \"#{destdir}\"") end it "packs a tar file" do - writer = double('GzipWriter') + writer = stub('GzipWriter') Zlib::GzipWriter.expects(:open).with(destfile).yields(writer) Archive::Tar::Minitar.expects(:pack).with(sourcedir, writer) minitar.pack(sourcedir, destfile) end def unpacks_the_entry(type, name) - reader = double('GzipReader') + reader = stub('GzipReader') Zlib::GzipReader.expects(:open).with(sourcefile).yields(reader) minitar.expects(:find_valid_files).with(reader).returns([name]) Archive::Tar::Minitar.expects(:unpack).with(reader, destdir, [name]).yields(type, name, nil) end end diff --git a/spec/unit/provider/group/windows_adsi_spec.rb b/spec/unit/provider/group/windows_adsi_spec.rb index b3b3a3f7e..3c44396e6 100644 --- a/spec/unit/provider/group/windows_adsi_spec.rb +++ b/spec/unit/provider/group/windows_adsi_spec.rb @@ -1,223 +1,223 @@ #!/usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:group).provider(:windows_adsi), :if => Puppet.features.microsoft_windows? do let(:resource) do Puppet::Type.type(:group).new( :title => 'testers', :provider => :windows_adsi ) end let(:provider) { resource.provider } - let(:connection) { double 'connection' } + let(:connection) { stub 'connection' } before :each do Puppet::Util::Windows::ADSI.stubs(:computer_name).returns('testcomputername') Puppet::Util::Windows::ADSI.stubs(:connect).returns connection end describe ".instances" do it "should enumerate all groups" do names = ['group1', 'group2', 'group3'] - stub_groups = names.map{|n| double(:name => n)} + stub_groups = names.map{|n| stub(:name => n)} connection.stubs(:execquery).with('select name from win32_group where localaccount = "TRUE"').returns stub_groups expect(described_class.instances.map(&:name)).to match(names) end end describe "group type :members property helpers" do - let(:user1) { double(:account => 'user1', :domain => '.', :to_s => 'user1sid') } - let(:user2) { double(:account => 'user2', :domain => '.', :to_s => 'user2sid') } - let(:user3) { double(:account => 'user3', :domain => '.', :to_s => 'user3sid') } + let(:user1) { stub(:account => 'user1', :domain => '.', :to_s => 'user1sid') } + let(:user2) { stub(:account => 'user2', :domain => '.', :to_s => 'user2sid') } + let(:user3) { stub(:account => 'user3', :domain => '.', :to_s => 'user3sid') } before :each do Puppet::Util::Windows::SID.stubs(:name_to_sid_object).with('user1').returns(user1) Puppet::Util::Windows::SID.stubs(:name_to_sid_object).with('user2').returns(user2) Puppet::Util::Windows::SID.stubs(:name_to_sid_object).with('user3').returns(user3) end describe "#members_insync?" do it "should return false when current is nil" do expect(provider.members_insync?(nil, ['user2'])).to be_falsey end it "should return false when should is nil" do expect(provider.members_insync?(['user1'], nil)).to be_falsey end it "should return true for same lists of members" do expect(provider.members_insync?(['user1', 'user2'], ['user1', 'user2'])).to be_truthy end it "should return true for same lists of unordered members" do expect(provider.members_insync?(['user1', 'user2'], ['user2', 'user1'])).to be_truthy end it "should return true for same lists of members irrespective of duplicates" do expect(provider.members_insync?(['user1', 'user2', 'user2'], ['user2', 'user1', 'user1'])).to be_truthy end context "when auth_membership => true" do before :each do resource[:auth_membership] = true end it "should return false when current contains different users than should" do - provider.members_insync?(['user1'], ['user2']).should be_falsey + expect(provider.members_insync?(['user1'], ['user2'])).to be_falsey end it "should return false when current contains members and should is empty" do - provider.members_insync?(['user1'], []).should be_falsey + expect(provider.members_insync?(['user1'], [])).to be_falsey end it "should return false when current is empty and should contains members" do - provider.members_insync?([], ['user2']).should be_falsey + expect(provider.members_insync?([], ['user2'])).to be_falsey end it "should return false when should user(s) are not the only items in the current" do expect(provider.members_insync?(['user1', 'user2'], ['user1'])).to be_falsey end end context "when auth_membership => false" do before :each do # this is also the default resource[:auth_membership] = false end it "should return false when current contains different users than should" do - provider.members_insync?(['user1'], ['user2']).should be_falsey + expect(provider.members_insync?(['user1'], ['user2'])).to be_falsey end it "should return true when current contains members and should is empty" do - provider.members_insync?(['user1'], []).should be_truthy + expect(provider.members_insync?(['user1'], [])).to be_truthy end it "should return false when current is empty and should contains members" do - provider.members_insync?([], ['user2']).should be_falsey + expect(provider.members_insync?([], ['user2'])).to be_falsey end it "should return true when current user(s) contains at least the should list" do expect(provider.members_insync?(['user1','user2'], ['user1'])).to be_truthy end it "should return true when current user(s) contains at least the should list, even unordered" do expect(provider.members_insync?(['user3','user1','user2'], ['user2','user1'])).to be_truthy end end end describe "#members_to_s" do it "should return an empty string on non-array input" do [Object.new, {}, 1, :symbol, ''].each do |input| expect(provider.members_to_s(input)).to be_empty end end it "should return an empty string on empty or nil users" do expect(provider.members_to_s([])).to be_empty expect(provider.members_to_s(nil)).to be_empty end it "should return a user string like DOMAIN\\USER" do expect(provider.members_to_s(['user1'])).to eq('.\user1') end it "should return a user string like DOMAIN\\USER,DOMAIN2\\USER2" do expect(provider.members_to_s(['user1', 'user2'])).to eq('.\user1,.\user2') end end end describe "when managing members" do before :each do resource[:auth_membership] = true end it "should be able to provide a list of members" do provider.group.stubs(:members).returns ['user1', 'user2', 'user3'] expect(provider.members).to match_array(['user1', 'user2', 'user3']) end it "should be able to set group members" do provider.group.stubs(:members).returns ['user1', 'user2'] member_sids = [ - double(:account => 'user1', :domain => 'testcomputername'), - double(:account => 'user2', :domain => 'testcomputername'), - double(:account => 'user3', :domain => 'testcomputername'), + stub(:account => 'user1', :domain => 'testcomputername'), + stub(:account => 'user2', :domain => 'testcomputername'), + stub(:account => 'user3', :domain => 'testcomputername'), ] provider.group.stubs(:member_sids).returns(member_sids[0..1]) Puppet::Util::Windows::SID.expects(:name_to_sid_object).with('user2').returns(member_sids[1]) Puppet::Util::Windows::SID.expects(:name_to_sid_object).with('user3').returns(member_sids[2]) provider.group.expects(:remove_member_sids).with(member_sids[0]) provider.group.expects(:add_member_sids).with(member_sids[2]) provider.members = ['user2', 'user3'] end end describe 'when creating groups' do it "should be able to create a group" do resource[:members] = ['user1', 'user2'] - group = double 'group' + group = stub 'group' Puppet::Util::Windows::ADSI::Group.expects(:create).with('testers').returns group create = sequence('create') group.expects(:commit).in_sequence(create) # due to PUP-1967, defaultto false will set the default to nil group.expects(:set_members).with(['user1', 'user2'], nil).in_sequence(create) provider.create end it 'should not create a group if a user by the same name exists' do Puppet::Util::Windows::ADSI::Group.expects(:create).with('testers').raises( Puppet::Error.new("Cannot create group if user 'testers' exists.") ) expect{ provider.create }.to raise_error( Puppet::Error, /Cannot create group if user 'testers' exists./ ) end it 'should commit a newly created group' do provider.group.expects( :commit ) provider.flush end end it "should be able to test whether a group exists" do Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) - Puppet::Util::Windows::ADSI.stubs(:connect).returns double('connection') + Puppet::Util::Windows::ADSI.stubs(:connect).returns stub('connection') expect(provider).to be_exists Puppet::Util::Windows::ADSI.stubs(:connect).returns nil expect(provider).not_to be_exists end it "should be able to delete a group" do connection.expects(:Delete).with('group', 'testers') provider.delete end it "should report the group's SID as gid" do Puppet::Util::Windows::SID.expects(:name_to_sid).with('testers').returns('S-1-5-32-547') expect(provider.gid).to eq('S-1-5-32-547') end it "should fail when trying to manage the gid property" do provider.expects(:fail).with { |msg| msg =~ /gid is read-only/ } provider.send(:gid=, 500) end it "should prefer the domain component from the resolved SID" do expect(provider.members_to_s(['.\Administrators'])).to eq('BUILTIN\Administrators') end end diff --git a/spec/unit/provider/scheduled_task/win32_taskscheduler_spec.rb b/spec/unit/provider/scheduled_task/win32_taskscheduler_spec.rb index 1d7e73218..50911b135 100644 --- a/spec/unit/provider/scheduled_task/win32_taskscheduler_spec.rb +++ b/spec/unit/provider/scheduled_task/win32_taskscheduler_spec.rb @@ -1,1607 +1,1607 @@ #! /usr/bin/env ruby 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 Win32::TaskScheduler.any_instance.stubs(: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 Puppet::Type.type(:scheduled_task).stubs(:defaultprovider).returns(described_class) end describe 'when retrieving' do before :each do - @mock_task = double + @mock_task = stub @mock_task.responds_like(Win32::TaskScheduler.new) described_class.any_instance.stubs(:task).returns(@mock_task) Win32::TaskScheduler.stubs(:new).returns(@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 @mock_task.expects(:trigger_count).returns(1) end it 'should handle a single daily trigger' do @mock_task.expects(:trigger).with(0).returns({ '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', '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 @mock_task.expects(:trigger).with(0).returns({ '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'], '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 @mock_task.expects(:trigger).with(0).returns({ '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'], '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 @mock_task.expects(:trigger).with(0).returns({ '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'], 'enabled' => true, 'index' => 0, }]) end it 'should handle a single one-time trigger' do @mock_task.expects(:trigger).with(0).returns({ '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', 'enabled' => true, 'index' => 0, }]) end end it 'should handle multiple triggers' do @mock_task.expects(:trigger_count).returns(3) @mock_task.expects(:trigger).with(0).returns({ '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, }) @mock_task.expects(:trigger).with(1).returns({ '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.expects(:trigger).with(2).returns({ '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', 'enabled' => true, 'index' => 0, }, { 'start_date' => '2012-11-14', 'start_time' => '15:22', 'schedule' => 'once', 'enabled' => true, 'index' => 1, }, { 'start_date' => '2013-12-15', 'start_time' => '16:23', 'schedule' => 'once', 'enabled' => true, 'index' => 2, } ]) end it 'should skip triggers Win32::TaskScheduler cannot handle' do @mock_task.expects(:trigger_count).returns(3) @mock_task.expects(:trigger).with(0).returns({ '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, }) @mock_task.expects(:trigger).with(1).raises( Win32::TaskScheduler::Error.new('Unhandled trigger type!') ) @mock_task.expects(:trigger).with(2).returns({ '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', 'enabled' => true, 'index' => 0, }, { 'start_date' => '2013-12-15', 'start_time' => '16:23', 'schedule' => 'once', 'enabled' => true, 'index' => 2, } ]) end it 'should skip trigger types Puppet does not handle' do @mock_task.expects(:trigger_count).returns(3) @mock_task.expects(:trigger).with(0).returns({ '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, }) @mock_task.expects(:trigger).with(1).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_EVENT_TRIGGER_AT_LOGON, }) @mock_task.expects(:trigger).with(2).returns({ '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', 'enabled' => true, 'index' => 0, }, { 'start_date' => '2013-12-15', 'start_time' => '16:23', 'schedule' => 'once', 'enabled' => true, 'index' => 2, } ]) end end it 'should get the working directory from the working_directory on the task' do @mock_task.expects(:working_directory).returns('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 @mock_task.expects(:application_name).returns('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 @mock_task.expects(:parameters).returns('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 @mock_task.expects(:account_information).returns('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 @mock_task.stubs(:flags).returns(Win32::TaskScheduler::DISABLED) expect(resource.provider.enabled).to eq(:false) end it 'should report tasks without the disabled bit set as enabled' do @mock_task.stubs(:flags).returns(~Win32::TaskScheduler::DISABLED) expect(resource.provider.enabled).to eq(:true) end it 'should not consider triggers for determining if the task is enabled' do @mock_task.stubs(:flags).returns(~Win32::TaskScheduler::DISABLED) @mock_task.stubs(:trigger_count).returns(1) @mock_task.stubs(:trigger).with(0).returns({ '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 = double + @mock_task = stub @mock_task.responds_like(Win32::TaskScheduler.new) described_class.any_instance.stubs(:task).returns(@mock_task) Win32::TaskScheduler.stubs(:new).returns(@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 @mock_task.expects(:exists?).with('Test Task').returns(true) expect(resource.provider.exists?).to eq(true) end end describe '#clear_task' do before :each do - @mock_task = double - @new_mock_task = double + @mock_task = stub + @new_mock_task = stub @mock_task.responds_like(Win32::TaskScheduler.new) @new_mock_task.responds_like(Win32::TaskScheduler.new) Win32::TaskScheduler.stubs(:new).returns(@mock_task, @new_mock_task) described_class.any_instance.stubs(:exists?).returns(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 @mock_task.stubs(:trigger_count).returns(1) @mock_task.stubs(:trigger).with(0).returns({ '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, }) @new_mock_task.stubs(:trigger_count).returns(1) @new_mock_task.stubs(:trigger).with(0).returns({ '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', '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', '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'] Win32::TaskScheduler.any_instance.stubs(:tasks).returns(job_files) job_files.each do |job| job = File.basename(job, '.job') described_class.expects(: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 Puppet::Util::Windows::SID.expects(:name_to_sid).with('joe').twice.returns('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 Puppet::Util::Windows::SID.expects(:name_to_sid).with('joe').returns('SID A') Puppet::Util::Windows::SID.expects(:name_to_sid).with('MACHINE\joe').returns('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 Puppet::Util::Windows::SID.expects(:name_to_sid).with('system').twice.returns('SYSTEM SID') expect(resource).to be_user_insync('', ['system']) end it 'should consider different users as being different' do Puppet::Util::Windows::SID.expects(:name_to_sid).with('joe').returns('SID A') Puppet::Util::Windows::SID.expects(:name_to_sid).with('bob').returns('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) } 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 = double + @mock_task = stub @mock_task.responds_like(Win32::TaskScheduler.new) @mock_task.stubs(:exists?).returns(true) @mock_task.stubs(:activate) Win32::TaskScheduler.stubs(:new).returns(@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 @mock_task.expects(: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 @mock_task.stubs(:activate) end it 'should not save the task if :ensure is :absent' do @mock_task.expects(:save).never resource.provider.flush end it 'should not fail if the command is not specified' do @mock_task.stubs(: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 = double + @mock_task = stub @mock_task.responds_like(Win32::TaskScheduler.new) @mock_task.stubs(:exists?).returns(true) @mock_task.stubs(:activate) Win32::TaskScheduler.stubs(:new).returns(@mock_task) end describe '#command=' do it 'should set the application_name on the task' do @mock_task.expects(: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 @mock_task.expects(: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 @mock_task.expects(: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 @mock_task.stubs(:flags).returns(0) @mock_task.expects(:flags=).with(Win32::TaskScheduler::DISABLED) resource.provider.enabled = :false end it 'should clear the disabled flag if the task should be enabled' do @mock_task.stubs(:flags).returns(Win32::TaskScheduler::DISABLED) @mock_task.expects(: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 = double + @mock_task = stub @mock_task.responds_like(Win32::TaskScheduler.new) @mock_task.stubs(:exists?).returns(true) @mock_task.stubs(:activate) Win32::TaskScheduler.stubs(:new).returns(@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}, ] resource.provider.stubs(:trigger).returns(current_triggers) @mock_task.expects(:delete_trigger).with(1) @mock_task.expects(: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}, ] resource.provider.stubs(:trigger).returns(current_triggers) @mock_task.expects(:delete_trigger).with(1) @mock_task.expects(: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}, ] resource.provider.stubs(:trigger).returns(current_triggers) @mock_task.expects(:trigger=).with(resource.provider.translate_hash_to_trigger(@trigger[1])) @mock_task.expects(: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 = double + @mock_task = stub @mock_task.responds_like(Win32::TaskScheduler.new) @mock_task.stubs(:exists?).returns(true) @mock_task.stubs(:activate) Win32::TaskScheduler.stubs(:new).returns(@mock_task) end it 'should use nil for user and password when setting the user to the SYSTEM account' do Puppet::Util::Windows::SID.stubs(:name_to_sid).with('system').returns('SYSTEM SID') resource = Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :command => 'C:\dummy_task.exe', :user => 'system' ) @mock_task.expects(: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 Puppet::Util::Windows::SID.stubs(:name_to_sid).with('my_user_name').returns('SID A') resource = Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :command => 'C:\dummy_task.exe', :user => 'my_user_name', :password => 'my password' ) @mock_task.expects(: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 = double + @mock_task = stub @mock_task.responds_like(Win32::TaskScheduler.new) @mock_task.stubs(:exists?).returns(true) @mock_task.stubs(:activate) @mock_task.stubs(:application_name=) @mock_task.stubs(:parameters=) @mock_task.stubs(:working_directory=) @mock_task.stubs(:set_account_information) @mock_task.stubs(:flags) @mock_task.stubs(:flags=) @mock_task.stubs(:trigger_count).returns(0) @mock_task.stubs(:trigger=) @mock_task.stubs(:save) Win32::TaskScheduler.stubs(:new).returns(@mock_task) described_class.any_instance.stubs(:sync_triggers) end it 'should set the command' do resource.provider.expects(:command=).with(@command) resource.provider.create end it 'should set the arguments' do resource.provider.expects(:arguments=).with(@arguments) resource.provider.create end it 'should set the working_dir' do resource.provider.expects(:working_dir=).with(@working_dir) resource.provider.create end it "should set the user" do resource.provider.expects(:user=).with(:system) resource.provider.create end it 'should set the enabled property' do resource.provider.expects(:enabled=) resource.provider.create end it 'should sync triggers' do resource.provider.expects(:trigger=) resource.provider.create end end end diff --git a/spec/unit/provider/service/windows_spec.rb b/spec/unit/provider/service/windows_spec.rb index f0bfea1ca..bec454784 100755 --- a/spec/unit/provider/service/windows_spec.rb +++ b/spec/unit/provider/service/windows_spec.rb @@ -1,231 +1,231 @@ #! /usr/bin/env ruby # # Unit testing for the Windows service Provider # require 'spec_helper' require 'win32/service' if Puppet.features.microsoft_windows? describe Puppet::Type.type(:service).provider(:windows), :if => Puppet.features.microsoft_windows? do let(:name) { 'nonexistentservice' } let(:resource) { Puppet::Type.type(:service).new(:name => name, :provider => :windows) } let(:provider) { resource.provider } let(:config) { Struct::ServiceConfigInfo.new } let(:status) { Struct::ServiceStatus.new } before :each do # make sure we never actually execute anything (there are two execute methods) provider.class.expects(:execute).never provider.expects(:execute).never Win32::Service.stubs(:config_info).with(name).returns(config) Win32::Service.stubs(:status).with(name).returns(status) end describe ".instances" do it "should enumerate all services" do - list_of_services = ['snmptrap', 'svchost', 'sshd'].map { |s| double('service', :service_name => s) } + list_of_services = ['snmptrap', 'svchost', 'sshd'].map { |s| stub('service', :service_name => s) } Win32::Service.expects(:services).returns(list_of_services) expect(described_class.instances.map(&:name)).to match_array(['snmptrap', 'svchost', 'sshd']) end end describe "#start" do before :each do config.start_type = Win32::Service.get_start_type(Win32::Service::SERVICE_AUTO_START) end it "should start the service" do provider.expects(:net).with(:start, name) provider.start end it "should raise an error if the start command fails" do provider.expects(:net).with(:start, name).raises(Puppet::ExecutionFailure, "The service name is invalid.") expect { provider.start }.to raise_error(Puppet::Error, /Cannot start #{name}, error was: The service name is invalid./) end it "raises an error if the service doesn't exist" do Win32::Service.expects(:config_info).with(name).raises(SystemCallError, 'OpenService') expect { provider.start }.to raise_error(Puppet::Error, /Cannot get start type for #{name}/) end describe "when the service is disabled" do before :each do config.start_type = Win32::Service.get_start_type(Win32::Service::SERVICE_DISABLED) end it "should refuse to start if not managing enable" do expect { provider.start }.to raise_error(Puppet::Error, /Will not start disabled service/) end it "should enable if managing enable and enable is true" do resource[:enable] = :true provider.expects(:net).with(:start, name) Win32::Service.expects(:configure).with('service_name' => name, 'start_type' => Win32::Service::SERVICE_AUTO_START).returns(Win32::Service) provider.start end it "should manual start if managing enable and enable is false" do resource[:enable] = :false provider.expects(:net).with(:start, name) Win32::Service.expects(:configure).with('service_name' => name, 'start_type' => Win32::Service::SERVICE_DEMAND_START).returns(Win32::Service) provider.start end end end describe "#stop" do it "should stop a running service" do provider.expects(:net).with(:stop, name) provider.stop end it "should raise an error if the stop command fails" do provider.expects(:net).with(:stop, name).raises(Puppet::ExecutionFailure, 'The service name is invalid.') expect { provider.stop }.to raise_error(Puppet::Error, /Cannot stop #{name}, error was: The service name is invalid./) end end describe "#status" do ['stopped', 'paused', 'stop pending', 'pause pending'].each do |state| it "should report a #{state} service as stopped" do status.current_state = state expect(provider.status).to eq(:stopped) end end ["running", "continue pending", "start pending" ].each do |state| it "should report a #{state} service as running" do status.current_state = state expect(provider.status).to eq(:running) end end it "raises an error if the service doesn't exist" do Win32::Service.expects(:status).with(name).raises(SystemCallError, 'OpenService') expect { provider.status }.to raise_error(Puppet::Error, /Cannot get status of #{name}/) end end describe "#restart" do it "should use the supplied restart command if specified" do resource[:restart] = 'c:/bin/foo' provider.expects(:execute).never provider.expects(:execute).with(['c:/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.restart end it "should restart the service" do seq = sequence("restarting") provider.expects(:stop).in_sequence(seq) provider.expects(:start).in_sequence(seq) provider.restart end end describe "#enabled?" do it "should report a service with a startup type of manual as manual" do config.start_type = Win32::Service.get_start_type(Win32::Service::SERVICE_DEMAND_START) expect(provider.enabled?).to eq(:manual) end it "should report a service with a startup type of disabled as false" do config.start_type = Win32::Service.get_start_type(Win32::Service::SERVICE_DISABLED) expect(provider.enabled?).to eq(:false) end it "raises an error if the service doesn't exist" do Win32::Service.expects(:config_info).with(name).raises(SystemCallError, 'OpenService') expect { provider.enabled? }.to raise_error(Puppet::Error, /Cannot get start type for #{name}/) end # We need to guard this section explicitly since rspec will always # construct all examples, even if it isn't going to run them. if Puppet.features.microsoft_windows? [Win32::Service::SERVICE_AUTO_START, Win32::Service::SERVICE_BOOT_START, Win32::Service::SERVICE_SYSTEM_START].each do |start_type_const| start_type = Win32::Service.get_start_type(start_type_const) it "should report a service with a startup type of '#{start_type}' as true" do config.start_type = start_type expect(provider.enabled?).to eq(:true) end end end end describe "#enable" do it "should set service start type to Service_Auto_Start when enabled" do Win32::Service.expects(:configure).with('service_name' => name, 'start_type' => Win32::Service::SERVICE_AUTO_START).returns(Win32::Service) provider.enable end it "raises an error if the service doesn't exist" do Win32::Service.expects(:configure).with(has_entry('service_name' => name)).raises(SystemCallError, 'OpenService') expect { provider.enable }.to raise_error(Puppet::Error, /Cannot enable #{name}/) end end describe "#disable" do it "should set service start type to Service_Disabled when disabled" do Win32::Service.expects(:configure).with('service_name' => name, 'start_type' => Win32::Service::SERVICE_DISABLED).returns(Win32::Service) provider.disable end it "raises an error if the service doesn't exist" do Win32::Service.expects(:configure).with(has_entry('service_name' => name)).raises(SystemCallError, 'OpenService') expect { provider.disable }.to raise_error(Puppet::Error, /Cannot disable #{name}/) end end describe "#manual_start" do it "should set service start type to Service_Demand_Start (manual) when manual" do Win32::Service.expects(:configure).with('service_name' => name, 'start_type' => Win32::Service::SERVICE_DEMAND_START).returns(Win32::Service) provider.manual_start end it "raises an error if the service doesn't exist" do Win32::Service.expects(:configure).with(has_entry('service_name' => name)).raises(SystemCallError, 'OpenService') expect { provider.manual_start }.to raise_error(Puppet::Error, /Cannot enable #{name}/) end end end diff --git a/spec/unit/provider/user/windows_adsi_spec.rb b/spec/unit/provider/user/windows_adsi_spec.rb index 03e87caba..8a9d680aa 100755 --- a/spec/unit/provider/user/windows_adsi_spec.rb +++ b/spec/unit/provider/user/windows_adsi_spec.rb @@ -1,173 +1,173 @@ #!/usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:user).provider(:windows_adsi), :if => Puppet.features.microsoft_windows? do let(:resource) do Puppet::Type.type(:user).new( :title => 'testuser', :comment => 'Test J. User', :provider => :windows_adsi ) end let(:provider) { resource.provider } - let(:connection) { double 'connection' } + let(:connection) { stub 'connection' } before :each do Puppet::Util::Windows::ADSI.stubs(:computer_name).returns('testcomputername') Puppet::Util::Windows::ADSI.stubs(:connect).returns connection end describe ".instances" do it "should enumerate all users" do names = ['user1', 'user2', 'user3'] - stub_users = names.map{|n| double(:name => n)} + stub_users = names.map{|n| stub(:name => n)} connection.stubs(:execquery).with('select name from win32_useraccount where localaccount = "TRUE"').returns(stub_users) expect(described_class.instances.map(&:name)).to match(names) end end it "should provide access to a Puppet::Util::Windows::ADSI::User object" do expect(provider.user).to be_a(Puppet::Util::Windows::ADSI::User) end describe "when managing groups" do it 'should return the list of groups as a comma-separated list' do provider.user.stubs(:groups).returns ['group1', 'group2', 'group3'] expect(provider.groups).to eq('group1,group2,group3') end it "should return absent if there are no groups" do provider.user.stubs(:groups).returns [] expect(provider.groups).to eq('') end it 'should be able to add a user to a set of groups' do resource[:membership] = :minimum provider.user.expects(:set_groups).with('group1,group2', true) provider.groups = 'group1,group2' resource[:membership] = :inclusive provider.user.expects(:set_groups).with('group1,group2', false) provider.groups = 'group1,group2' end end describe "when creating a user" do it "should create the user on the system and set its other properties" do resource[:groups] = ['group1', 'group2'] resource[:membership] = :inclusive resource[:comment] = 'a test user' resource[:home] = 'C:\Users\testuser' - user = double 'user' + user = stub 'user' Puppet::Util::Windows::ADSI::User.expects(:create).with('testuser').returns user user.stubs(:groups).returns(['group2', 'group3']) create = sequence('create') user.expects(:password=).in_sequence(create) user.expects(:commit).in_sequence(create) user.expects(:set_groups).with('group1,group2', false).in_sequence(create) user.expects(:[]=).with('Description', 'a test user') user.expects(:[]=).with('HomeDirectory', 'C:\Users\testuser') provider.create end it "should load the profile if managehome is set" do resource[:password] = '0xDeadBeef' resource[:managehome] = true user = stub_everything 'user' Puppet::Util::Windows::ADSI::User.expects(:create).with('testuser').returns user Puppet::Util::Windows::User.expects(:load_profile).with('testuser', '0xDeadBeef') provider.create end it "should set a user's password" do provider.user.expects(:password=).with('plaintextbad') provider.password = "plaintextbad" end it "should test a valid user password" do resource[:password] = 'plaintext' provider.user.expects(:password_is?).with('plaintext').returns true expect(provider.password).to eq('plaintext') end it "should test a bad user password" do resource[:password] = 'plaintext' provider.user.expects(:password_is?).with('plaintext').returns false expect(provider.password).to be_nil end it 'should not create a user if a group by the same name exists' do Puppet::Util::Windows::ADSI::User.expects(:create).with('testuser').raises( Puppet::Error.new("Cannot create user if group 'testuser' exists.") ) expect{ provider.create }.to raise_error( Puppet::Error, /Cannot create user if group 'testuser' exists./ ) end end it 'should be able to test whether a user exists' do Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) - Puppet::Util::Windows::ADSI.stubs(:connect).returns double('connection') + Puppet::Util::Windows::ADSI.stubs(:connect).returns stub('connection') expect(provider).to be_exists Puppet::Util::Windows::ADSI.stubs(:connect).returns nil expect(provider).not_to be_exists end it 'should be able to delete a user' do connection.expects(:Delete).with('user', 'testuser') provider.delete end it 'should delete the profile if managehome is set' do resource[:managehome] = true sid = 'S-A-B-C' Puppet::Util::Windows::SID.expects(:name_to_sid).with('testuser').returns(sid) Puppet::Util::Windows::ADSI::UserProfile.expects(:delete).with(sid) connection.expects(:Delete).with('user', 'testuser') provider.delete end it "should commit the user when flushed" do provider.user.expects(:commit) provider.flush end it "should return the user's SID as uid" do Puppet::Util::Windows::SID.expects(:name_to_sid).with('testuser').returns('S-1-5-21-1362942247-2130103807-3279964888-1111') expect(provider.uid).to eq('S-1-5-21-1362942247-2130103807-3279964888-1111') end it "should fail when trying to manage the uid property" do provider.expects(:fail).with { |msg| msg =~ /uid is read-only/ } provider.send(:uid=, 500) end [:gid, :shell].each do |prop| it "should fail when trying to manage the #{prop} property" do provider.expects(:fail).with { |msg| msg =~ /No support for managing property #{prop}/ } provider.send("#{prop}=", 'foo') end end end diff --git a/spec/unit/util/execution_spec.rb b/spec/unit/util/execution_spec.rb index 560b70175..de5c4a285 100755 --- a/spec/unit/util/execution_spec.rb +++ b/spec/unit/util/execution_spec.rb @@ -1,675 +1,675 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_system/uniquefile' describe Puppet::Util::Execution do include Puppet::Util::Execution # utility methods to help us test some private methods without being quite so verbose def call_exec_posix(command, arguments, stdin, stdout, stderr) Puppet::Util::Execution.send(:execute_posix, command, arguments, stdin, stdout, stderr) end def call_exec_windows(command, arguments, stdin, stdout, stderr) Puppet::Util::Execution.send(:execute_windows, command, arguments, stdin, stdout, stderr) end describe "execution methods" do let(:pid) { 5501 } let(:process_handle) { 0xDEADBEEF } let(:thread_handle) { 0xCAFEBEEF } - let(:proc_info_stub) { double 'processinfo', :process_handle => process_handle, :thread_handle => thread_handle, :process_id => pid} + let(:proc_info_stub) { stub 'processinfo', :process_handle => process_handle, :thread_handle => thread_handle, :process_id => pid} let(:null_file) { Puppet.features.microsoft_windows? ? 'NUL' : '/dev/null' } def stub_process_wait(exitstatus) if Puppet.features.microsoft_windows? Puppet::Util::Windows::Process.stubs(:wait_process).with(process_handle).returns(exitstatus) FFI::WIN32.stubs(:CloseHandle).with(process_handle) FFI::WIN32.stubs(:CloseHandle).with(thread_handle) else Process.stubs(:waitpid2).with(pid).returns([pid, stub('child_status', :exitstatus => exitstatus)]) end end describe "#execute_posix (stubs)", :unless => Puppet.features.microsoft_windows? do before :each do # Most of the things this method does are bad to do during specs. :/ Kernel.stubs(:fork).returns(pid).yields Process.stubs(:setsid) Kernel.stubs(:exec) Puppet::Util::SUIDManager.stubs(:change_user) Puppet::Util::SUIDManager.stubs(:change_group) # ensure that we don't really close anything! (0..256).each {|n| IO.stubs(:new) } $stdin.stubs(:reopen) $stdout.stubs(:reopen) $stderr.stubs(:reopen) @stdin = File.open(null_file, 'r') @stdout = Puppet::FileSystem::Uniquefile.new('stdout') @stderr = File.open(null_file, 'w') # there is a danger here that ENV will be modified by exec_posix. Normally it would only affect the ENV # of a forked process, but here, we're stubbing Kernel.fork, so the method has the ability to override the # "real" ENV. To guard against this, we'll capture a snapshot of ENV before each test. @saved_env = ENV.to_hash # Now, we're going to effectively "mock" the magic ruby 'ENV' variable by creating a local definition of it # inside of the module we're testing. Puppet::Util::Execution::ENV = {} end after :each do # And here we remove our "mock" version of 'ENV', which will allow us to validate that the real ENV has been # left unharmed. Puppet::Util::Execution.send(:remove_const, :ENV) # capture the current environment and make sure it's the same as it was before the test cur_env = ENV.to_hash # we will get some fairly useless output if we just use the raw == operator on the hashes here, so we'll # be a bit more explicit and laborious in the name of making the error more useful... @saved_env.each_pair { |key,val| expect(cur_env[key]).to eq(val) } expect(cur_env.keys - @saved_env.keys).to eq([]) end it "should fork a child process to execute the command" do Kernel.expects(:fork).returns(pid).yields Kernel.expects(:exec).with('test command') call_exec_posix('test command', {}, @stdin, @stdout, @stderr) end it "should start a new session group" do Process.expects(:setsid) call_exec_posix('test command', {}, @stdin, @stdout, @stderr) end it "should permanently change to the correct user and group if specified" do Puppet::Util::SUIDManager.expects(:change_group).with(55, true) Puppet::Util::SUIDManager.expects(:change_user).with(50, true) call_exec_posix('test command', {:uid => 50, :gid => 55}, @stdin, @stdout, @stderr) end it "should exit failure if there is a problem execing the command" do Kernel.expects(:exec).with('test command').raises("failed to execute!") Puppet::Util::Execution.stubs(:puts) Puppet::Util::Execution.expects(:exit!).with(1) call_exec_posix('test command', {}, @stdin, @stdout, @stderr) end it "should properly execute commands specified as arrays" do Kernel.expects(:exec).with('test command', 'with', 'arguments') call_exec_posix(['test command', 'with', 'arguments'], {:uid => 50, :gid => 55}, @stdin, @stdout, @stderr) end it "should properly execute string commands with embedded newlines" do Kernel.expects(:exec).with("/bin/echo 'foo' ; \n /bin/echo 'bar' ;") call_exec_posix("/bin/echo 'foo' ; \n /bin/echo 'bar' ;", {:uid => 50, :gid => 55}, @stdin, @stdout, @stderr) end it "should return the pid of the child process" do expect(call_exec_posix('test command', {}, @stdin, @stdout, @stderr)).to eq(pid) end end describe "#execute_windows (stubs)", :if => Puppet.features.microsoft_windows? do before :each do Process.stubs(:create).returns(proc_info_stub) stub_process_wait(0) @stdin = File.open(null_file, 'r') @stdout = Puppet::FileSystem::Uniquefile.new('stdout') @stderr = File.open(null_file, 'w') end it "should create a new process for the command" do Process.expects(:create).with( :command_line => "test command", :startup_info => {:stdin => @stdin, :stdout => @stdout, :stderr => @stderr}, :close_handles => false ).returns(proc_info_stub) call_exec_windows('test command', {}, @stdin, @stdout, @stderr) end it "should return the process info of the child process" do expect(call_exec_windows('test command', {}, @stdin, @stdout, @stderr)).to eq(proc_info_stub) end it "should quote arguments containing spaces if command is specified as an array" do Process.expects(:create).with do |args| args[:command_line] == '"test command" with some "arguments \"with spaces"' end.returns(proc_info_stub) call_exec_windows(['test command', 'with', 'some', 'arguments "with spaces'], {}, @stdin, @stdout, @stderr) end end describe "#execute (stubs)" do before :each do stub_process_wait(0) end describe "when an execution stub is specified" do before :each do Puppet::Util::ExecutionStub.set do |command,args,stdin,stdout,stderr| "execution stub output" end end it "should call the block on the stub" do expect(Puppet::Util::Execution.execute("/usr/bin/run_my_execute_stub")).to eq("execution stub output") end it "should not actually execute anything" do Puppet::Util::Execution.expects(:execute_posix).never Puppet::Util::Execution.expects(:execute_windows).never Puppet::Util::Execution.execute("/usr/bin/run_my_execute_stub") end end describe "when setting up input and output files" do include PuppetSpec::Files let(:executor) { Puppet.features.microsoft_windows? ? 'execute_windows' : 'execute_posix' } let(:rval) { Puppet.features.microsoft_windows? ? proc_info_stub : pid } before :each do Puppet::Util::Execution.stubs(:wait_for_output) end it "should set stdin to the stdinfile if specified" do input = tmpfile('stdin') FileUtils.touch(input) Puppet::Util::Execution.expects(executor).with do |_,_,stdin,_,_| stdin.path == input end.returns(rval) Puppet::Util::Execution.execute('test command', :stdinfile => input) end it "should set stdin to the null file if not specified" do Puppet::Util::Execution.expects(executor).with do |_,_,stdin,_,_| stdin.path == null_file end.returns(rval) Puppet::Util::Execution.execute('test command') end describe "when squelch is set" do it "should set stdout and stderr to the null file" do Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == null_file and stderr.path == null_file end.returns(rval) Puppet::Util::Execution.execute('test command', :squelch => true) end end describe "when squelch is not set" do it "should set stdout to a temporary output file" do outfile = Puppet::FileSystem::Uniquefile.new('stdout') Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,_| stdout.path == outfile.path end.returns(rval) Puppet::Util::Execution.execute('test command', :squelch => false) end it "should set stderr to the same file as stdout if combine is true" do outfile = Puppet::FileSystem::Uniquefile.new('stdout') Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == outfile.path end.returns(rval) Puppet::Util::Execution.execute('test command', :squelch => false, :combine => true) end it "should set stderr to the null device if combine is false" do outfile = Puppet::FileSystem::Uniquefile.new('stdout') Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == null_file end.returns(rval) Puppet::Util::Execution.execute('test command', :squelch => false, :combine => false) end it "should combine stdout and stderr if combine is true" do outfile = Puppet::FileSystem::Uniquefile.new('stdout') Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == outfile.path end.returns(rval) Puppet::Util::Execution.execute('test command', :combine => true) end it "should default combine to true when no options are specified" do outfile = Puppet::FileSystem::Uniquefile.new('stdout') Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == outfile.path end.returns(rval) Puppet::Util::Execution.execute('test command') end it "should default combine to false when options are specified, but combine is not" do outfile = Puppet::FileSystem::Uniquefile.new('stdout') Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == null_file end.returns(rval) Puppet::Util::Execution.execute('test command', :failonfail => false) end it "should default combine to false when an empty hash of options is specified" do outfile = Puppet::FileSystem::Uniquefile.new('stdout') Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == null_file end.returns(rval) Puppet::Util::Execution.execute('test command', {}) end end end describe "on Windows", :if => Puppet.features.microsoft_windows? do it "should always close the process and thread handles" do Puppet::Util::Execution.stubs(:execute_windows).returns(proc_info_stub) Puppet::Util::Windows::Process.expects(:wait_process).with(process_handle).raises('whatever') FFI::WIN32.expects(:CloseHandle).with(thread_handle) FFI::WIN32.expects(:CloseHandle).with(process_handle) expect { Puppet::Util::Execution.execute('test command') }.to raise_error(RuntimeError) end it "should return the correct exit status even when exit status is greater than 256" do real_exit_status = 3010 Puppet::Util::Execution.stubs(:execute_windows).returns(proc_info_stub) stub_process_wait(real_exit_status) $CHILD_STATUS.stubs(:exitstatus).returns(real_exit_status % 256) # The exitstatus is changed to be mod 256 so that ruby can fit it into 8 bits. expect(Puppet::Util::Execution.execute('test command', :failonfail => false).exitstatus).to eq(real_exit_status) end end end describe "#execute (posix locale)", :unless => Puppet.features.microsoft_windows? do before :each do # there is a danger here that ENV will be modified by exec_posix. Normally it would only affect the ENV # of a forked process, but, in some of the previous tests in this file we're stubbing Kernel.fork., which could # allow the method to override the "real" ENV. This shouldn't be a problem for these tests because they are # not stubbing Kernel.fork, but, better safe than sorry... so, to guard against this, we'll capture a snapshot # of ENV before each test. @saved_env = ENV.to_hash end after :each do # capture the current environment and make sure it's the same as it was before the test cur_env = ENV.to_hash # we will get some fairly useless output if we just use the raw == operator on the hashes here, so we'll # be a bit more explicit and laborious in the name of making the error more useful... @saved_env.each_pair { |key,val| expect(cur_env[key]).to eq(val) } expect(cur_env.keys - @saved_env.keys).to eq([]) end # build up a printf-style string that contains a command to get the value of an environment variable # from the operating system. We can substitute into this with the names of the desired environment variables later. get_env_var_cmd = 'echo $%s' # a sentinel value that we can use to emulate what locale environment variables might be set to on an international # system. lang_sentinel_value = "en_US.UTF-8" # a temporary hash that contains sentinel values for each of the locale environment variables that we override in # "execute" locale_sentinel_env = {} Puppet::Util::POSIX::LOCALE_ENV_VARS.each { |var| locale_sentinel_env[var] = lang_sentinel_value } it "should override the locale environment variables when :override_locale is not set (defaults to true)" do # temporarily override the locale environment vars with a sentinel value, so that we can confirm that # execute is actually setting them. Puppet::Util.withenv(locale_sentinel_env) do Puppet::Util::POSIX::LOCALE_ENV_VARS.each do |var| # we expect that all of the POSIX vars will have been cleared except for LANG and LC_ALL expected_value = (['LANG', 'LC_ALL'].include?(var)) ? "C" : "" expect(Puppet::Util::Execution.execute(get_env_var_cmd % var).strip).to eq(expected_value) end end end it "should override the LANG environment variable when :override_locale is set to true" do # temporarily override the locale environment vars with a sentinel value, so that we can confirm that # execute is actually setting them. Puppet::Util.withenv(locale_sentinel_env) do Puppet::Util::POSIX::LOCALE_ENV_VARS.each do |var| # we expect that all of the POSIX vars will have been cleared except for LANG and LC_ALL expected_value = (['LANG', 'LC_ALL'].include?(var)) ? "C" : "" expect(Puppet::Util::Execution.execute(get_env_var_cmd % var, {:override_locale => true}).strip).to eq(expected_value) end end end it "should *not* override the LANG environment variable when :override_locale is set to false" do # temporarily override the locale environment vars with a sentinel value, so that we can confirm that # execute is not setting them. Puppet::Util.withenv(locale_sentinel_env) do Puppet::Util::POSIX::LOCALE_ENV_VARS.each do |var| expect(Puppet::Util::Execution.execute(get_env_var_cmd % var, {:override_locale => false}).strip).to eq(lang_sentinel_value) end end end it "should have restored the LANG and locale environment variables after execution" do # we'll do this once without any sentinel values, to give us a little more test coverage orig_env_vals = {} Puppet::Util::POSIX::LOCALE_ENV_VARS.each do |var| orig_env_vals[var] = ENV[var] end # now we can really execute any command--doesn't matter what it is... Puppet::Util::Execution.execute(get_env_var_cmd % 'anything', {:override_locale => true}) # now we check and make sure the original environment was restored Puppet::Util::POSIX::LOCALE_ENV_VARS.each do |var| expect(ENV[var]).to eq(orig_env_vals[var]) end # now, once more... but with our sentinel values Puppet::Util.withenv(locale_sentinel_env) do # now we can really execute any command--doesn't matter what it is... Puppet::Util::Execution.execute(get_env_var_cmd % 'anything', {:override_locale => true}) # now we check and make sure the original environment was restored Puppet::Util::POSIX::LOCALE_ENV_VARS.each do |var| expect(ENV[var]).to eq(locale_sentinel_env[var]) end end end end describe "#execute (posix user env vars)", :unless => Puppet.features.microsoft_windows? do # build up a printf-style string that contains a command to get the value of an environment variable # from the operating system. We can substitute into this with the names of the desired environment variables later. get_env_var_cmd = 'echo $%s' # a sentinel value that we can use to emulate what locale environment variables might be set to on an international # system. user_sentinel_value = "Abracadabra" # a temporary hash that contains sentinel values for each of the locale environment variables that we override in # "execute" user_sentinel_env = {} Puppet::Util::POSIX::USER_ENV_VARS.each { |var| user_sentinel_env[var] = user_sentinel_value } it "should unset user-related environment vars during execution" do # first we set up a temporary execution environment with sentinel values for the user-related environment vars # that we care about. Puppet::Util.withenv(user_sentinel_env) do # with this environment, we loop over the vars in question Puppet::Util::POSIX::USER_ENV_VARS.each do |var| # ensure that our temporary environment is set up as we expect expect(ENV[var]).to eq(user_sentinel_env[var]) # run an "exec" via the provider and ensure that it unsets the vars expect(Puppet::Util::Execution.execute(get_env_var_cmd % var).strip).to eq("") # ensure that after the exec, our temporary env is still intact expect(ENV[var]).to eq(user_sentinel_env[var]) end end end it "should have restored the user-related environment variables after execution" do # we'll do this once without any sentinel values, to give us a little more test coverage orig_env_vals = {} Puppet::Util::POSIX::USER_ENV_VARS.each do |var| orig_env_vals[var] = ENV[var] end # now we can really execute any command--doesn't matter what it is... Puppet::Util::Execution.execute(get_env_var_cmd % 'anything') # now we check and make sure the original environment was restored Puppet::Util::POSIX::USER_ENV_VARS.each do |var| expect(ENV[var]).to eq(orig_env_vals[var]) end # now, once more... but with our sentinel values Puppet::Util.withenv(user_sentinel_env) do # now we can really execute any command--doesn't matter what it is... Puppet::Util::Execution.execute(get_env_var_cmd % 'anything') # now we check and make sure the original environment was restored Puppet::Util::POSIX::USER_ENV_VARS.each do |var| expect(ENV[var]).to eq(user_sentinel_env[var]) end end end end describe "#execute (debug logging)" do before :each do stub_process_wait(0) if Puppet.features.microsoft_windows? Puppet::Util::Execution.stubs(:execute_windows).returns(proc_info_stub) else Puppet::Util::Execution.stubs(:execute_posix).returns(pid) end end it "should log if no uid or gid specified" do Puppet::Util::Execution.expects(:debug).with("Executing: 'echo hello'") Puppet::Util::Execution.execute('echo hello') end it "should log numeric uid if specified" do Puppet::Util::Execution.expects(:debug).with("Executing with uid=100: 'echo hello'") Puppet::Util::Execution.execute('echo hello', {:uid => 100}) end it "should log numeric gid if specified" do Puppet::Util::Execution.expects(:debug).with("Executing with gid=500: 'echo hello'") Puppet::Util::Execution.execute('echo hello', {:gid => 500}) end it "should log numeric uid and gid if specified" do Puppet::Util::Execution.expects(:debug).with("Executing with uid=100 gid=500: 'echo hello'") Puppet::Util::Execution.execute('echo hello', {:uid => 100, :gid => 500}) end it "should log string uid if specified" do Puppet::Util::Execution.expects(:debug).with("Executing with uid=myuser: 'echo hello'") Puppet::Util::Execution.execute('echo hello', {:uid => 'myuser'}) end it "should log string gid if specified" do Puppet::Util::Execution.expects(:debug).with("Executing with gid=mygroup: 'echo hello'") Puppet::Util::Execution.execute('echo hello', {:gid => 'mygroup'}) end it "should log string uid and gid if specified" do Puppet::Util::Execution.expects(:debug).with("Executing with uid=myuser gid=mygroup: 'echo hello'") Puppet::Util::Execution.execute('echo hello', {:uid => 'myuser', :gid => 'mygroup'}) end it "should log numeric uid and string gid if specified" do Puppet::Util::Execution.expects(:debug).with("Executing with uid=100 gid=mygroup: 'echo hello'") Puppet::Util::Execution.execute('echo hello', {:uid => 100, :gid => 'mygroup'}) end end describe "after execution" do before :each do stub_process_wait(0) if Puppet.features.microsoft_windows? Puppet::Util::Execution.stubs(:execute_windows).returns(proc_info_stub) else Puppet::Util::Execution.stubs(:execute_posix).returns(pid) end end it "should wait for the child process to exit" do Puppet::Util::Execution.stubs(:wait_for_output) Puppet::Util::Execution.execute('test command') end it "should close the stdin/stdout/stderr files used by the child" do stdin = mock 'file', :close stdout = mock 'file', :close stderr = mock 'file', :close File.expects(:open). times(3). returns(stdin). then.returns(stdout). then.returns(stderr) Puppet::Util::Execution.execute('test command', {:squelch => true, :combine => false}) end it "should read and return the output if squelch is false" do stdout = Puppet::FileSystem::Uniquefile.new('test') Puppet::FileSystem::Uniquefile.stubs(:new).returns(stdout) stdout.write("My expected command output") expect(Puppet::Util::Execution.execute('test command')).to eq("My expected command output") end it "should not read the output if squelch is true" do stdout = Puppet::FileSystem::Uniquefile.new('test') Puppet::FileSystem::Uniquefile.stubs(:new).returns(stdout) stdout.write("My expected command output") expect(Puppet::Util::Execution.execute('test command', :squelch => true)).to eq('') end it "should delete the file used for output if squelch is false" do stdout = Puppet::FileSystem::Uniquefile.new('test') path = stdout.path Puppet::FileSystem::Uniquefile.stubs(:new).returns(stdout) Puppet::Util::Execution.execute('test command') expect(Puppet::FileSystem.exist?(path)).to be_falsey end it "should not raise an error if the file is open" do stdout = Puppet::FileSystem::Uniquefile.new('test') Puppet::FileSystem::Uniquefile.stubs(:new).returns(stdout) file = File.new(stdout.path, 'r') Puppet::Util::Execution.execute('test command') end it "should raise an error if failonfail is true and the child failed" do stub_process_wait(1) expect { subject.execute('fail command', :failonfail => true) }.to raise_error(Puppet::ExecutionFailure, /Execution of 'fail command' returned 1/) end it "should not raise an error if failonfail is false and the child failed" do stub_process_wait(1) subject.execute('fail command', :failonfail => false) end it "should not raise an error if failonfail is true and the child succeeded" do stub_process_wait(0) subject.execute('fail command', :failonfail => true) end it "should not raise an error if failonfail is false and the child succeeded" do stub_process_wait(0) subject.execute('fail command', :failonfail => false) end it "should default failonfail to true when no options are specified" do stub_process_wait(1) expect { subject.execute('fail command') }.to raise_error(Puppet::ExecutionFailure, /Execution of 'fail command' returned 1/) end it "should default failonfail to false when options are specified, but failonfail is not" do stub_process_wait(1) subject.execute('fail command', { :combine => true }) end it "should default failonfail to false when an empty hash of options is specified" do stub_process_wait(1) subject.execute('fail command', {}) end it "should raise an error if a nil option is specified" do expect { Puppet::Util::Execution.execute('fail command', nil) }.to raise_error(TypeError, /(can\'t convert|no implicit conversion of) nil into Hash/) end end end describe "#execpipe" do it "should execute a string as a string" do Puppet::Util::Execution.expects(:open).with('| echo hello 2>&1').returns('hello') Puppet::Util::Execution.expects(:exitstatus).returns(0) expect(Puppet::Util::Execution.execpipe('echo hello')).to eq('hello') end it "should print meaningful debug message for string argument" do Puppet::Util::Execution.expects(:debug).with("Executing 'echo hello'") Puppet::Util::Execution.expects(:open).with('| echo hello 2>&1').returns('hello') Puppet::Util::Execution.expects(:exitstatus).returns(0) Puppet::Util::Execution.execpipe('echo hello') end it "should print meaningful debug message for array argument" do Puppet::Util::Execution.expects(:debug).with("Executing 'echo hello'") Puppet::Util::Execution.expects(:open).with('| echo hello 2>&1').returns('hello') Puppet::Util::Execution.expects(:exitstatus).returns(0) Puppet::Util::Execution.execpipe(['echo','hello']) end it "should execute an array by pasting together with spaces" do Puppet::Util::Execution.expects(:open).with('| echo hello 2>&1').returns('hello') Puppet::Util::Execution.expects(:exitstatus).returns(0) expect(Puppet::Util::Execution.execpipe(['echo', 'hello'])).to eq('hello') end it "should fail if asked to fail, and the child does" do Puppet::Util::Execution.stubs(:open).with('| echo hello 2>&1').returns('error message') Puppet::Util::Execution.expects(:exitstatus).returns(1) expect { Puppet::Util::Execution.execpipe('echo hello') }. to raise_error Puppet::ExecutionFailure, /error message/ end it "should not fail if asked not to fail, and the child does" do Puppet::Util::Execution.stubs(:open).returns('error message') expect(Puppet::Util::Execution.execpipe('echo hello', false)).to eq('error message') end end end diff --git a/spec/unit/util/log/destinations_spec.rb b/spec/unit/util/log/destinations_spec.rb index 328c03d97..ae87d6d05 100755 --- a/spec/unit/util/log/destinations_spec.rb +++ b/spec/unit/util/log/destinations_spec.rb @@ -1,236 +1,236 @@ #! /usr/bin/env ruby require 'spec_helper' require 'json' require 'puppet/util/log' describe Puppet::Util::Log.desttypes[:report] do before do @dest = Puppet::Util::Log.desttypes[:report] end it "should require a report at initialization" do expect(@dest.new("foo").report).to eq("foo") end it "should send new messages to the report" do report = mock 'report' dest = @dest.new(report) report.expects(:<<).with("my log") dest.handle "my log" end end describe Puppet::Util::Log.desttypes[:file] do include PuppetSpec::Files before do File.stubs(:open) # prevent actually creating the file File.stubs(:chown) # prevent chown on non existing file from failing @class = Puppet::Util::Log.desttypes[:file] end it "should default to autoflush false" do expect(@class.new(tmpfile('log')).autoflush).to eq(true) end describe "when matching" do shared_examples_for "file destination" do it "should match an absolute path" do expect(@class.match?(abspath)).to be_truthy end it "should not match a relative path" do expect(@class.match?(relpath)).to be_falsey end end describe "on POSIX systems", :if => Puppet.features.posix? do let (:abspath) { '/tmp/log' } let (:relpath) { 'log' } it_behaves_like "file destination" it "logs an error if it can't chown the file owner & group" do FileUtils.expects(:chown).with(Puppet[:user], Puppet[:group], abspath).raises(Errno::EPERM) Puppet.features.expects(:root?).returns(true) Puppet.expects(:err).with("Unable to set ownership to #{Puppet[:user]}:#{Puppet[:group]} for log file: #{abspath}") @class.new(abspath) end it "doesn't attempt to chown when running as non-root" do FileUtils.expects(:chown).with(Puppet[:user], Puppet[:group], abspath).never Puppet.features.expects(:root?).returns(false) @class.new(abspath) end end describe "on Windows systems", :if => Puppet.features.microsoft_windows? do let (:abspath) { 'C:\\temp\\log.txt' } let (:relpath) { 'log.txt' } it_behaves_like "file destination" end end end describe Puppet::Util::Log.desttypes[:syslog] do let (:klass) { Puppet::Util::Log.desttypes[:syslog] } # these tests can only be run when syslog is present, because # we can't stub the top-level Syslog module describe "when syslog is available", :if => Puppet.features.syslog? do before :each do Syslog.stubs(:opened?).returns(false) Syslog.stubs(:const_get).returns("LOG_KERN").returns(0) Syslog.stubs(:open) end it "should open syslog" do Syslog.expects(:open) klass.new end it "should close syslog" do Syslog.expects(:close) dest = klass.new dest.close end it "should send messages to syslog" do syslog = mock 'syslog' syslog.expects(:info).with("don't panic") Syslog.stubs(:open).returns(syslog) msg = Puppet::Util::Log.new(:level => :info, :message => "don't panic") dest = klass.new dest.handle(msg) end end describe "when syslog is unavailable" do it "should not be a suitable log destination" do Puppet.features.stubs(:syslog?).returns(false) expect(klass.suitable?(:syslog)).to be_falsey end end end describe Puppet::Util::Log.desttypes[:logstash_event] do describe "when using structured log format with logstash_event schema" do before :each do @msg = Puppet::Util::Log.new(:level => :info, :message => "So long, and thanks for all the fish.", :source => "a dolphin") end it "format should fix the hash to have the correct structure" do dest = described_class.new result = dest.format(@msg) expect(result["version"]).to eq(1) expect(result["level"]).to eq(:info) expect(result["message"]).to eq("So long, and thanks for all the fish.") expect(result["source"]).to eq("a dolphin") # timestamp should be within 10 seconds expect(Time.parse(result["@timestamp"])).to be >= ( Time.now - 10 ) end it "format returns a structure that can be converted to json" do dest = described_class.new hash = dest.format(@msg) JSON.parse(hash.to_json) end it "handle should send the output to stdout" do $stdout.expects(:puts).once dest = described_class.new dest.handle(@msg) end end end describe Puppet::Util::Log.desttypes[:console] do let (:klass) { Puppet::Util::Log.desttypes[:console] } describe "when color is available" do before :each do subject.stubs(:console_has_color?).returns(true) end it "should support color output" do Puppet[:color] = true expect(subject.colorize(:red, 'version')).to eq("\e[0;31mversion\e[0m") end it "should withhold color output when not appropriate" do Puppet[:color] = false expect(subject.colorize(:red, 'version')).to eq("version") end it "should handle multiple overlapping colors in a stack-like way" do Puppet[:color] = true vstring = subject.colorize(:red, 'version') expect(subject.colorize(:green, "(#{vstring})")).to eq("\e[0;32m(\e[0;31mversion\e[0;32m)\e[0m") end it "should handle resets in a stack-like way" do Puppet[:color] = true vstring = subject.colorize(:reset, 'version') expect(subject.colorize(:green, "(#{vstring})")).to eq("\e[0;32m(\e[mversion\e[0;32m)\e[0m") end it "should include the log message's source/context in the output when available" do Puppet[:color] = false $stdout.expects(:puts).with("Info: a hitchhiker: don't panic") msg = Puppet::Util::Log.new(:level => :info, :message => "don't panic", :source => "a hitchhiker") dest = klass.new dest.handle(msg) end end end describe ":eventlog", :if => Puppet::Util::Platform.windows? do let(:klass) { Puppet::Util::Log.desttypes[:eventlog] } def expects_message_with_type(klass, level, eventlog_type, eventlog_id) - eventlog = double('eventlog') + eventlog = stub('eventlog') eventlog.expects(:report_event).with(has_entries(:source => "Puppet", :event_type => eventlog_type, :event_id => eventlog_id, :data => "a hitchhiker: don't panic")) Win32::EventLog.stubs(:open).returns(eventlog) msg = Puppet::Util::Log.new(:level => level, :message => "don't panic", :source => "a hitchhiker") dest = klass.new dest.handle(msg) end it "supports the eventlog feature" do expect(Puppet.features.eventlog?).to be_truthy end it "logs to the Application event log" do - eventlog = double('eventlog') - Win32::EventLog.expects(:open).with('Application').returns(double('eventlog')) + eventlog = stub('eventlog') + Win32::EventLog.expects(:open).with('Application').returns(stub('eventlog')) klass.new end it "logs :debug level as an information type event" do expects_message_with_type(klass, :debug, klass::EVENTLOG_INFORMATION_TYPE, 0x1) end it "logs :warning level as an warning type event" do expects_message_with_type(klass, :warning, klass::EVENTLOG_WARNING_TYPE, 0x2) end it "logs :err level as an error type event" do expects_message_with_type(klass, :err, klass::EVENTLOG_ERROR_TYPE, 0x3) end end diff --git a/spec/unit/util/log_spec.rb b/spec/unit/util/log_spec.rb index 0d5e46ee3..4bd230411 100755 --- a/spec/unit/util/log_spec.rb +++ b/spec/unit/util/log_spec.rb @@ -1,378 +1,378 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/log' describe Puppet::Util::Log do include PuppetSpec::Files def log_notice(message) Puppet::Util::Log.new(:level => :notice, :message => message) end it "should write a given message to the specified destination" do arraydest = [] Puppet::Util::Log.newdestination(Puppet::Test::LogCollector.new(arraydest)) Puppet::Util::Log.new(:level => :notice, :message => "foo") message = arraydest.last.message expect(message).to eq("foo") end describe ".setup_default" do it "should default to :syslog" do Puppet.features.stubs(:syslog?).returns(true) Puppet::Util::Log.expects(:newdestination).with(:syslog) Puppet::Util::Log.setup_default end it "should fall back to :eventlog" do Puppet.features.stubs(:syslog?).returns(false) Puppet.features.stubs(:eventlog?).returns(true) Puppet::Util::Log.expects(:newdestination).with(:eventlog) Puppet::Util::Log.setup_default end it "should fall back to :file" do Puppet.features.stubs(:syslog?).returns(false) Puppet.features.stubs(:eventlog?).returns(false) Puppet::Util::Log.expects(:newdestination).with(Puppet[:puppetdlog]) Puppet::Util::Log.setup_default end end describe "#with_destination" do it "does nothing when nested" do logs = [] destination = Puppet::Test::LogCollector.new(logs) Puppet::Util::Log.with_destination(destination) do Puppet::Util::Log.with_destination(destination) do log_notice("Inner block") end log_notice("Outer block") end log_notice("Outside") expect(logs.collect(&:message)).to include("Inner block", "Outer block") expect(logs.collect(&:message)).not_to include("Outside") end it "logs when called a second time" do logs = [] destination = Puppet::Test::LogCollector.new(logs) Puppet::Util::Log.with_destination(destination) do log_notice("First block") end log_notice("Between blocks") Puppet::Util::Log.with_destination(destination) do log_notice("Second block") end expect(logs.collect(&:message)).to include("First block", "Second block") expect(logs.collect(&:message)).not_to include("Between blocks") end it "doesn't close the destination if already set manually" do logs = [] destination = Puppet::Test::LogCollector.new(logs) Puppet::Util::Log.newdestination(destination) Puppet::Util::Log.with_destination(destination) do log_notice "Inner block" end log_notice "Outer block" Puppet::Util::Log.close(destination) expect(logs.collect(&:message)).to include("Inner block", "Outer block") end end describe Puppet::Util::Log::DestConsole do before do @console = Puppet::Util::Log::DestConsole.new @console.stubs(:console_has_color?).returns(true) end it "should colorize if Puppet[:color] is :ansi" do Puppet[:color] = :ansi expect(@console.colorize(:alert, "abc")).to eq("\e[0;31mabc\e[0m") end it "should colorize if Puppet[:color] is 'yes'" do Puppet[:color] = "yes" expect(@console.colorize(:alert, "abc")).to eq("\e[0;31mabc\e[0m") end it "should htmlize if Puppet[:color] is :html" do Puppet[:color] = :html expect(@console.colorize(:alert, "abc")).to eq("abc") end it "should do nothing if Puppet[:color] is false" do Puppet[:color] = false expect(@console.colorize(:alert, "abc")).to eq("abc") end it "should do nothing if Puppet[:color] is invalid" do Puppet[:color] = "invalid option" expect(@console.colorize(:alert, "abc")).to eq("abc") end end describe Puppet::Util::Log::DestSyslog do before do @syslog = Puppet::Util::Log::DestSyslog.new end end describe Puppet::Util::Log::DestEventlog, :if => Puppet.features.eventlog? do before :each do - Win32::EventLog.stubs(:open).returns(double 'mylog') + Win32::EventLog.stubs(:open).returns(stub 'mylog') Win32::EventLog.stubs(:report_event) Win32::EventLog.stubs(:close) Puppet.features.stubs(:eventlog?).returns(true) end it "should restrict its suitability" do Puppet.features.expects(:eventlog?).returns(false) expect(Puppet::Util::Log::DestEventlog.suitable?('whatever')).to eq(false) end it "should open the 'Application' event log" do Win32::EventLog.expects(:open).with('Application') Puppet::Util::Log.newdestination(:eventlog) end it "should close the event log" do - log = double('myeventlog') + log = stub('myeventlog') log.expects(:close) Win32::EventLog.expects(:open).returns(log) Puppet::Util::Log.newdestination(:eventlog) Puppet::Util::Log.close(:eventlog) end it "should handle each puppet log level" do log = Puppet::Util::Log::DestEventlog.new Puppet::Util::Log.eachlevel do |level| expect(log.to_native(level)).to be_is_a(Array) end end end describe "instances" do before do Puppet::Util::Log.stubs(:newmessage) end [:level, :message, :time, :remote].each do |attr| it "should have a #{attr} attribute" do log = Puppet::Util::Log.new :level => :notice, :message => "A test message" expect(log).to respond_to(attr) expect(log).to respond_to(attr.to_s + "=") end end it "should fail if created without a level" do expect { Puppet::Util::Log.new(:message => "A test message") }.to raise_error(ArgumentError) end it "should fail if created without a message" do expect { Puppet::Util::Log.new(:level => :notice) }.to raise_error(ArgumentError) end it "should make available the level passed in at initialization" do expect(Puppet::Util::Log.new(:level => :notice, :message => "A test message").level).to eq(:notice) end it "should make available the message passed in at initialization" do expect(Puppet::Util::Log.new(:level => :notice, :message => "A test message").message).to eq("A test message") end # LAK:NOTE I don't know why this behavior is here, I'm just testing what's in the code, # at least at first. it "should always convert messages to strings" do expect(Puppet::Util::Log.new(:level => :notice, :message => :foo).message).to eq("foo") end it "should flush the log queue when the first destination is specified" do Puppet::Util::Log.close_all Puppet::Util::Log.expects(:flushqueue) Puppet::Util::Log.newdestination(:console) end it "should convert the level to a symbol if it's passed in as a string" do expect(Puppet::Util::Log.new(:level => "notice", :message => :foo).level).to eq(:notice) end it "should fail if the level is not a symbol or string" do expect { Puppet::Util::Log.new(:level => 50, :message => :foo) }.to raise_error(ArgumentError) end it "should fail if the provided level is not valid" do Puppet::Util::Log.expects(:validlevel?).with(:notice).returns false expect { Puppet::Util::Log.new(:level => :notice, :message => :foo) }.to raise_error(ArgumentError) end it "should set its time to the initialization time" do time = mock 'time' Time.expects(:now).returns time expect(Puppet::Util::Log.new(:level => "notice", :message => :foo).time).to equal(time) end it "should make available any passed-in tags" do log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :tags => %w{foo bar}) expect(log.tags).to be_include("foo") expect(log.tags).to be_include("bar") end it "should use a passed-in source" do Puppet::Util::Log.any_instance.expects(:source=).with "foo" Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => "foo") end [:file, :line].each do |attr| it "should use #{attr} if provided" do Puppet::Util::Log.any_instance.expects(attr.to_s + "=").with "foo" Puppet::Util::Log.new(:level => "notice", :message => :foo, attr => "foo") end end it "should default to 'Puppet' as its source" do expect(Puppet::Util::Log.new(:level => "notice", :message => :foo).source).to eq("Puppet") end it "should register itself with Log" do Puppet::Util::Log.expects(:newmessage) Puppet::Util::Log.new(:level => "notice", :message => :foo) end it "should update Log autoflush when Puppet[:autoflush] is set" do Puppet::Util::Log.expects(:autoflush=).once.with(true) Puppet[:autoflush] = true end it "should have a method for determining if a tag is present" do expect(Puppet::Util::Log.new(:level => "notice", :message => :foo)).to respond_to(:tagged?) end it "should match a tag if any of the tags are equivalent to the passed tag as a string" do expect(Puppet::Util::Log.new(:level => "notice", :message => :foo, :tags => %w{one two})).to be_tagged(:one) end it "should tag itself with its log level" do expect(Puppet::Util::Log.new(:level => "notice", :message => :foo)).to be_tagged(:notice) end it "should return its message when converted to a string" do expect(Puppet::Util::Log.new(:level => "notice", :message => :foo).to_s).to eq("foo") end it "should include its time, source, level, and message when prepared for reporting" do log = Puppet::Util::Log.new(:level => "notice", :message => :foo) report = log.to_report expect(report).to be_include("notice") expect(report).to be_include("foo") expect(report).to be_include(log.source) expect(report).to be_include(log.time.to_s) end it "should not create unsuitable log destinations" do Puppet.features.stubs(:syslog?).returns(false) Puppet::Util::Log::DestSyslog.expects(:suitable?) Puppet::Util::Log::DestSyslog.expects(:new).never Puppet::Util::Log.newdestination(:syslog) end describe "when setting the source as a RAL object" do let(:path) { File.expand_path('/foo/bar') } it "should tag itself with any tags the source has" do source = Puppet::Type.type(:file).new :path => path log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => source) source.tags.each do |tag| expect(log.tags).to be_include(tag) end end it "should set the source to 'path', when available" do source = Puppet::Type.type(:file).new :path => path source.tags = ["tag", "tag2"] log = Puppet::Util::Log.new(:level => "notice", :message => :foo) log.expects(:tag).with("file") log.expects(:tag).with("tag") log.expects(:tag).with("tag2") log.source = source expect(log.source).to eq("/File[#{path}]") end it "should copy over any file and line information" do source = Puppet::Type.type(:file).new :path => path source.file = "/my/file" source.line = 50 log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => source) expect(log.line).to eq(50) expect(log.file).to eq("/my/file") end end describe "when setting the source as a non-RAL object" do it "should not try to copy over file, version, line, or tag information" do source = mock source.expects(:file).never log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => source) end end end describe "to_yaml" do it "should not include the @version attribute" do log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :version => 100) expect(log.to_yaml_properties).not_to include('@version') end it "should include attributes @level, @message, @source, @tags, and @time" do log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :version => 100) expect(log.to_yaml_properties).to match_array([:@level, :@message, :@source, :@tags, :@time]) end it "should include attributes @file and @line if specified" do log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :file => "foo", :line => 35) expect(log.to_yaml_properties).to include(:@file) expect(log.to_yaml_properties).to include(:@line) end end it "should round trip through pson" do log = Puppet::Util::Log.new(:level => 'notice', :message => 'hooray', :file => 'thefile', :line => 1729, :source => 'specs', :tags => ['a', 'b', 'c']) tripped = Puppet::Util::Log.from_data_hash(PSON.parse(log.to_pson)) expect(tripped.file).to eq(log.file) expect(tripped.line).to eq(log.line) expect(tripped.level).to eq(log.level) expect(tripped.message).to eq(log.message) expect(tripped.source).to eq(log.source) expect(tripped.tags).to eq(log.tags) expect(tripped.time).to eq(log.time) end end diff --git a/spec/unit/util/windows/adsi_spec.rb b/spec/unit/util/windows/adsi_spec.rb index fd8d37f13..4fc4af5d3 100755 --- a/spec/unit/util/windows/adsi_spec.rb +++ b/spec/unit/util/windows/adsi_spec.rb @@ -1,395 +1,395 @@ #!/usr/bin/env ruby require 'spec_helper' require 'puppet/util/windows' describe Puppet::Util::Windows::ADSI, :if => Puppet.features.microsoft_windows? do - let(:connection) { double 'connection' } + let(:connection) { stub 'connection' } before(:each) do Puppet::Util::Windows::ADSI.instance_variable_set(:@computer_name, 'testcomputername') Puppet::Util::Windows::ADSI.stubs(:connect).returns connection end after(:each) do Puppet::Util::Windows::ADSI.instance_variable_set(:@computer_name, nil) end it "should generate the correct URI for a resource" do expect(Puppet::Util::Windows::ADSI.uri('test', 'user')).to eq("WinNT://./test,user") end it "should be able to get the name of the computer" do expect(Puppet::Util::Windows::ADSI.computer_name).to eq('testcomputername') end it "should be able to provide the correct WinNT base URI for the computer" do expect(Puppet::Util::Windows::ADSI.computer_uri).to eq("WinNT://.") end it "should generate a fully qualified WinNT URI" do expect(Puppet::Util::Windows::ADSI.computer_uri('testcomputername')).to eq("WinNT://testcomputername") end describe ".computer_name" do it "should return a non-empty ComputerName string" do Puppet::Util::Windows::ADSI.instance_variable_set(:@computer_name, nil) expect(Puppet::Util::Windows::ADSI.computer_name).not_to be_empty end end describe ".sid_uri" do it "should raise an error when the input is not a SID object" do [Object.new, {}, 1, :symbol, '', nil].each do |input| expect { Puppet::Util::Windows::ADSI.sid_uri(input) }.to raise_error(Puppet::Error, /Must use a valid SID object/) end end it "should return a SID uri for a well-known SID (SYSTEM)" do sid = Win32::Security::SID.new('SYSTEM') expect(Puppet::Util::Windows::ADSI.sid_uri(sid)).to eq('WinNT://S-1-5-18') end end describe Puppet::Util::Windows::ADSI::User do let(:username) { 'testuser' } let(:domain) { 'DOMAIN' } let(:domain_username) { "#{domain}\\#{username}"} it "should generate the correct URI" do Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) expect(Puppet::Util::Windows::ADSI::User.uri(username)).to eq("WinNT://./#{username},user") end it "should generate the correct URI for a user with a domain" do Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) expect(Puppet::Util::Windows::ADSI::User.uri(username, domain)).to eq("WinNT://#{domain}/#{username},user") end it "should be able to parse a username without a domain" do expect(Puppet::Util::Windows::ADSI::User.parse_name(username)).to eq([username, '.']) end it "should be able to parse a username with a domain" do expect(Puppet::Util::Windows::ADSI::User.parse_name(domain_username)).to eq([username, domain]) end it "should raise an error with a username that contains a /" do expect { Puppet::Util::Windows::ADSI::User.parse_name("#{domain}/#{username}") }.to raise_error(Puppet::Error, /Value must be in DOMAIN\\user style syntax/) end it "should be able to create a user" do - adsi_user = double('adsi') + adsi_user = stub('adsi') connection.expects(:Create).with('user', username).returns(adsi_user) Puppet::Util::Windows::ADSI::Group.expects(:exists?).with(username).returns(false) user = Puppet::Util::Windows::ADSI::User.create(username) expect(user).to be_a(Puppet::Util::Windows::ADSI::User) expect(user.native_user).to eq(adsi_user) end it "should be able to check the existence of a user" do Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{username},user").returns connection expect(Puppet::Util::Windows::ADSI::User.exists?(username)).to be_truthy end it "should be able to check the existence of a domain user" do Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://#{domain}/#{username},user").returns connection expect(Puppet::Util::Windows::ADSI::User.exists?(domain_username)).to be_truthy end it "should be able to confirm the existence of a user with a well-known SID" do system_user = Win32::Security::SID::LocalSystem # ensure that the underlying OS is queried here allow(Puppet::Util::Windows::ADSI).to receive(:connect).and_call_original expect(Puppet::Util::Windows::ADSI::User.exists?(system_user)).to be_truthy end it "should return nil with an unknown SID" do bogus_sid = 'S-1-2-3-4' # ensure that the underlying OS is queried here allow(Puppet::Util::Windows::ADSI).to receive(:connect).and_call_original expect(Puppet::Util::Windows::ADSI::User.exists?(bogus_sid)).to be_falsey end it "should be able to delete a user" do connection.expects(:Delete).with('user', username) Puppet::Util::Windows::ADSI::User.delete(username) end it "should return an enumeration of IADsUser wrapped objects" do Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) name = 'Administrator' - wmi_users = [double('WMI', :name => name)] + wmi_users = [stub('WMI', :name => name)] Puppet::Util::Windows::ADSI.expects(:execquery).with('select name from win32_useraccount where localaccount = "TRUE"').returns(wmi_users) - native_user = double('IADsUser') + native_user = stub('IADsUser') homedir = "C:\\Users\\#{name}" native_user.expects(:Get).with('HomeDirectory').returns(homedir) Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{name},user").returns(native_user) users = Puppet::Util::Windows::ADSI::User.to_a expect(users.length).to eq(1) expect(users[0].name).to eq(name) expect(users[0]['HomeDirectory']).to eq(homedir) end describe "an instance" do - let(:adsi_user) { double('user', :objectSID => []) } - let(:sid) { double(:account => username, :domain => 'testcomputername') } + let(:adsi_user) { stub('user', :objectSID => []) } + let(:sid) { stub(:account => username, :domain => 'testcomputername') } let(:user) { Puppet::Util::Windows::ADSI::User.new(username, adsi_user) } it "should provide its groups as a list of names" do names = ["group1", "group2"] - groups = names.map { |name| double('group', :Name => name) } + groups = names.map { |name| stub('group', :Name => name) } adsi_user.expects(:Groups).returns(groups) expect(user.groups).to match(names) end it "should be able to test whether a given password is correct" do Puppet::Util::Windows::ADSI::User.expects(:logon).with(username, 'pwdwrong').returns(false) Puppet::Util::Windows::ADSI::User.expects(:logon).with(username, 'pwdright').returns(true) expect(user.password_is?('pwdwrong')).to be_falsey expect(user.password_is?('pwdright')).to be_truthy end it "should be able to set a password" do adsi_user.expects(:SetPassword).with('pwd') adsi_user.expects(:SetInfo).at_least_once flagname = "UserFlags" fADS_UF_DONT_EXPIRE_PASSWD = 0x10000 adsi_user.expects(:Get).with(flagname).returns(0) adsi_user.expects(:Put).with(flagname, fADS_UF_DONT_EXPIRE_PASSWD) user.password = 'pwd' end it "should generate the correct URI" do Puppet::Util::Windows::SID.stubs(:octet_string_to_sid_object).returns(sid) expect(user.uri).to eq("WinNT://testcomputername/#{username},user") end describe "when given a set of groups to which to add the user" do let(:groups_to_set) { 'group1,group2' } before(:each) do Puppet::Util::Windows::SID.stubs(:octet_string_to_sid_object).returns(sid) user.expects(:groups).returns ['group2', 'group3'] end describe "if membership is specified as inclusive" do it "should add the user to those groups, and remove it from groups not in the list" do - group1 = double 'group1' + group1 = stub 'group1' group1.expects(:Add).with("WinNT://testcomputername/#{username},user") - group3 = double 'group1' + group3 = stub 'group1' group3.expects(:Remove).with("WinNT://testcomputername/#{username},user") Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sid).returns("WinNT://testcomputername/#{username},user").twice Puppet::Util::Windows::ADSI.expects(:connect).with('WinNT://./group1,group').returns group1 Puppet::Util::Windows::ADSI.expects(:connect).with('WinNT://./group3,group').returns group3 user.set_groups(groups_to_set, false) end end describe "if membership is specified as minimum" do it "should add the user to the specified groups without affecting its other memberships" do - group1 = double 'group1' + group1 = stub 'group1' group1.expects(:Add).with("WinNT://testcomputername/#{username},user") Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sid).returns("WinNT://testcomputername/#{username},user") Puppet::Util::Windows::ADSI.expects(:connect).with('WinNT://./group1,group').returns group1 user.set_groups(groups_to_set, true) end end end end end describe Puppet::Util::Windows::ADSI::Group do let(:groupname) { 'testgroup' } describe "an instance" do - let(:adsi_group) { double 'group' } + let(:adsi_group) { stub 'group' } let(:group) { Puppet::Util::Windows::ADSI::Group.new(groupname, adsi_group) } - let(:someone_sid){ double(:account => 'someone', :domain => 'testcomputername')} + let(:someone_sid){ stub(:account => 'someone', :domain => 'testcomputername')} describe "should be able to use SID objects" do let(:system) { Puppet::Util::Windows::SID.name_to_sid_object('SYSTEM') } let(:invalid) { Puppet::Util::Windows::SID.name_to_sid_object('foobar') } it "to add a member" do adsi_group.expects(:Add).with("WinNT://S-1-5-18") group.add_member_sids(system) end it "and raise when passed a non-SID object to add" do expect{ group.add_member_sids(invalid)}.to raise_error(Puppet::Error, /Must use a valid SID object/) end it "to remove a member" do adsi_group.expects(:Remove).with("WinNT://S-1-5-18") group.remove_member_sids(system) end it "and raise when passed a non-SID object to remove" do expect{ group.remove_member_sids(invalid)}.to raise_error(Puppet::Error, /Must use a valid SID object/) end end it "should provide its groups as a list of names" do names = ['user1', 'user2'] - users = names.map { |name| double('user', :Name => name) } + users = names.map { |name| stub('user', :Name => name) } adsi_group.expects(:Members).returns(users) expect(group.members).to match(names) end it "should be able to add a list of users to a group" do names = ['DOMAIN\user1', 'user2'] sids = [ - double(:account => 'user1', :domain => 'DOMAIN'), - double(:account => 'user2', :domain => 'testcomputername'), - double(:account => 'user3', :domain => 'DOMAIN2'), + stub(:account => 'user1', :domain => 'DOMAIN'), + stub(:account => 'user2', :domain => 'testcomputername'), + stub(:account => 'user3', :domain => 'DOMAIN2'), ] # use stubbed objectSid on member to return stubbed SID Puppet::Util::Windows::SID.expects(:octet_string_to_sid_object).with([0]).returns(sids[0]) Puppet::Util::Windows::SID.expects(:octet_string_to_sid_object).with([1]).returns(sids[1]) Puppet::Util::Windows::SID.expects(:name_to_sid_object).with('user2').returns(sids[1]) Puppet::Util::Windows::SID.expects(:name_to_sid_object).with('DOMAIN2\user3').returns(sids[2]) Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sids[0]).returns("WinNT://DOMAIN/user1,user") Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sids[2]).returns("WinNT://DOMAIN2/user3,user") - members = names.each_with_index.map{|n,i| double(:Name => n, :objectSID => [i])} + members = names.each_with_index.map{|n,i| stub(:Name => n, :objectSID => [i])} adsi_group.expects(:Members).returns members adsi_group.expects(:Remove).with('WinNT://DOMAIN/user1,user') adsi_group.expects(:Add).with('WinNT://DOMAIN2/user3,user') group.set_members(['user2', 'DOMAIN2\user3']) end it "should raise an error when a username does not resolve to a SID" do expect { adsi_group.expects(:Members).returns [] group.set_members(['foobar']) }.to raise_error(Puppet::Error, /Could not resolve username: foobar/) end it "should generate the correct URI" do Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) expect(group.uri).to eq("WinNT://./#{groupname},group") end end it "should generate the correct URI" do Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) expect(Puppet::Util::Windows::ADSI::Group.uri("people")).to eq("WinNT://./people,group") end it "should be able to create a group" do - adsi_group = double("adsi") + adsi_group = stub("adsi") connection.expects(:Create).with('group', groupname).returns(adsi_group) Puppet::Util::Windows::ADSI::User.expects(:exists?).with(groupname).returns(false) group = Puppet::Util::Windows::ADSI::Group.create(groupname) expect(group).to be_a(Puppet::Util::Windows::ADSI::Group) expect(group.native_group).to eq(adsi_group) end it "should be able to confirm the existence of a group" do Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{groupname},group").returns connection expect(Puppet::Util::Windows::ADSI::Group.exists?(groupname)).to be_truthy end it "should be able to confirm the existence of a group with a well-known SID" do service_group = Win32::Security::SID::Service # ensure that the underlying OS is queried here allow(Puppet::Util::Windows::ADSI).to receive(:connect).and_call_original expect(Puppet::Util::Windows::ADSI::Group.exists?(service_group)).to be_truthy end it "should return nil with an unknown SID" do bogus_sid = 'S-1-2-3-4' # ensure that the underlying OS is queried here allow(Puppet::Util::Windows::ADSI).to receive(:connect).and_call_original expect(Puppet::Util::Windows::ADSI::Group.exists?(bogus_sid)).to be_falsey end it "should be able to delete a group" do connection.expects(:Delete).with('group', groupname) Puppet::Util::Windows::ADSI::Group.delete(groupname) end it "should return an enumeration of IADsGroup wrapped objects" do Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) name = 'Administrators' - wmi_groups = [double('WMI', :name => name)] + wmi_groups = [stub('WMI', :name => name)] Puppet::Util::Windows::ADSI.expects(:execquery).with('select name from win32_group where localaccount = "TRUE"').returns(wmi_groups) - native_group = double('IADsGroup') - native_group.expects(:Members).returns([double(:Name => 'Administrator')]) + native_group = stub('IADsGroup') + native_group.expects(:Members).returns([stub(:Name => 'Administrator')]) Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{name},group").returns(native_group) groups = Puppet::Util::Windows::ADSI::Group.to_a expect(groups.length).to eq(1) expect(groups[0].name).to eq(name) expect(groups[0].members).to eq(['Administrator']) end end describe Puppet::Util::Windows::ADSI::UserProfile do it "should be able to delete a user profile" do connection.expects(:Delete).with("Win32_UserProfile.SID='S-A-B-C'") Puppet::Util::Windows::ADSI::UserProfile.delete('S-A-B-C') end it "should warn on 2003" do connection.expects(:Delete).raises(RuntimeError, "Delete (WIN32OLERuntimeError) OLE error code:80041010 in SWbemServicesEx Invalid class HRESULT error code:0x80020009 Exception occurred.") Puppet.expects(:warning).with("Cannot delete user profile for 'S-A-B-C' prior to Vista SP1") Puppet::Util::Windows::ADSI::UserProfile.delete('S-A-B-C') end end end diff --git a/spec/unit/util/windows/registry_spec.rb b/spec/unit/util/windows/registry_spec.rb index cca14df3c..76fd5da68 100755 --- a/spec/unit/util/windows/registry_spec.rb +++ b/spec/unit/util/windows/registry_spec.rb @@ -1,141 +1,141 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/windows' describe Puppet::Util::Windows::Registry, :if => Puppet::Util::Platform.windows? do subject do class TestRegistry include Puppet::Util::Windows::Registry end TestRegistry.new end let(:name) { 'HKEY_LOCAL_MACHINE' } let(:path) { 'Software\Microsoft' } context "#root" do it "should lookup the root hkey" do expect(subject.root(name)).to be_instance_of(Win32::Registry::PredefinedKey) end it "should raise for unknown root keys" do expect { subject.root('HKEY_BOGUS') }.to raise_error(Puppet::Error, /Invalid registry key/) end end context "#open" do - let(:hkey) { double 'hklm' } - let(:subkey) { double 'subkey' } + let(:hkey) { stub 'hklm' } + let(:subkey) { stub 'subkey' } before :each do subject.stubs(:root).returns(hkey) end it "should yield the opened the subkey" do hkey.expects(:open).with do |p, _| expect(p).to eq(path) end.yields(subkey) yielded = nil subject.open(name, path) {|reg| yielded = reg} expect(yielded).to eq(subkey) end if Puppet::Util::Platform.windows? [described_class::KEY64, described_class::KEY32].each do |access| it "should open the key for read access 0x#{access.to_s(16)}" do mode = described_class::KEY_READ | access hkey.expects(:open).with(path, mode) subject.open(name, path, mode) {|reg| } end end end it "should default to KEY64" do hkey.expects(:open).with(path, described_class::KEY_READ | described_class::KEY64) subject.open(hkey, path) {|hkey| } end it "should raise for a path that doesn't exist" do hkey.expects(:keyname).returns('HKEY_LOCAL_MACHINE') hkey.expects(:open).raises(Win32::Registry::Error.new(2)) # file not found expect do subject.open(hkey, 'doesnotexist') {|hkey| } end.to raise_error(Puppet::Error, /Failed to open registry key 'HKEY_LOCAL_MACHINE\\doesnotexist'/) end end context "#values" do - let(:key) { double('uninstall') } + let(:key) { stub('uninstall') } it "should return each value's name and data" do key.expects(:each_value).multiple_yields( ['string', 1, 'foo'], ['dword', 4, 0] ) expect(subject.values(key)).to eq({ 'string' => 'foo', 'dword' => 0 }) end it "should return an empty hash if there are no values" do key.expects(:each_value) expect(subject.values(key)).to eq({}) end context "when reading non-ASCII values" do # registered trademark symbol let(:data) do str = [0xAE].pack("C") str.force_encoding('US-ASCII') str end def expects_registry_value(array) key.expects(:each_value).multiple_yields(array) subject.values(key).first[1] end # The win32console gem applies this regex to strip out ANSI escape # sequences. If the registered trademark had encoding US-ASCII, # the regex would fail with 'invalid byte sequence in US-ASCII' def strip_ansi_escapes(value) value.sub(/([^\e]*)?\e([\[\(])([0-9\;\=]*)([a-zA-Z@])(.*)/, '\5') end it "encodes REG_SZ according to the current code page" do reg_value = ['string', Win32::Registry::REG_SZ, data] value = expects_registry_value(reg_value) strip_ansi_escapes(value) end it "encodes REG_EXPAND_SZ based on the current code page" do reg_value = ['expand', Win32::Registry::REG_EXPAND_SZ, "%SYSTEMROOT%\\#{data}"] value = expects_registry_value(reg_value) strip_ansi_escapes(value) end it "encodes REG_MULTI_SZ based on the current code page" do reg_value = ['multi', Win32::Registry::REG_MULTI_SZ, ["one#{data}", "two#{data}"]] value = expects_registry_value(reg_value) value.each { |str| strip_ansi_escapes(str) } end it "passes REG_DWORD through" do reg_value = ['dword', Win32::Registry::REG_DWORD, '1'] value = expects_registry_value(reg_value) expect(Integer(value)).to eq(1) end end end end