summaryrefslogtreecommitdiff
path: root/spec/support/shared
diff options
context:
space:
mode:
Diffstat (limited to 'spec/support/shared')
-rw-r--r--spec/support/shared/functional/directory_resource.rb85
-rw-r--r--spec/support/shared/functional/file_resource.rb173
-rw-r--r--spec/support/shared/functional/knife.rb37
-rw-r--r--spec/support/shared/functional/securable_resource.rb394
-rw-r--r--spec/support/shared/unit/api_error_inspector.rb192
-rw-r--r--spec/support/shared/unit/file_system_support.rb110
6 files changed, 991 insertions, 0 deletions
diff --git a/spec/support/shared/functional/directory_resource.rb b/spec/support/shared/functional/directory_resource.rb
new file mode 100644
index 0000000000..14ab7a10e8
--- /dev/null
+++ b/spec/support/shared/functional/directory_resource.rb
@@ -0,0 +1,85 @@
+#
+# Author:: Seth Chisamore (<schisamo@opscode.com>)
+# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+shared_examples_for "a directory resource" do
+ context "when the target directory does not exist" do
+ it "creates the directory when the :create action is run" do
+ resource.run_action(:create)
+ File.should exist(path)
+ end
+
+ it "recursively creates required directories if requested" do
+ resource.recursive(true)
+ recursive_path = File.join(path, 'red-headed-stepchild')
+ resource.path(recursive_path)
+ resource.run_action(:create)
+ File.should exist(path)
+ File.should exist(recursive_path)
+ end
+ end
+
+ context "when the target directory exists" do
+ before(:each) do
+ FileUtils.mkdir(path)
+ end
+
+ it "does not re-create the directory" do
+ resource.run_action(:create)
+ File.should exist(path)
+ end
+
+ it "deletes the directory when the :delete action is run" do
+ resource.run_action(:delete)
+ File.should_not exist(path)
+ end
+
+ it "recursively deletes directories if requested" do
+ FileUtils.mkdir(File.join(path, 'red-headed-stepchild'))
+ resource.recursive(true)
+ resource.run_action(:delete)
+ File.should_not exist(path)
+ end
+ end
+
+ # Set up the context for security tests
+ def allowed_acl(sid, expected_perms)
+ [
+ ACE.access_allowed(sid, expected_perms[:specific]),
+ ACE.access_allowed(sid, expected_perms[:generic], (Chef::ReservedNames::Win32::API::Security::INHERIT_ONLY_ACE | Chef::ReservedNames::Win32::API::Security::CONTAINER_INHERIT_ACE | Chef::ReservedNames::Win32::API::Security::OBJECT_INHERIT_ACE))
+ ]
+ end
+
+ def denied_acl(sid, expected_perms)
+ [
+ ACE.access_denied(sid, expected_perms[:specific]),
+ ACE.access_denied(sid, expected_perms[:generic], (Chef::ReservedNames::Win32::API::Security::INHERIT_ONLY_ACE | Chef::ReservedNames::Win32::API::Security::CONTAINER_INHERIT_ACE | Chef::ReservedNames::Win32::API::Security::OBJECT_INHERIT_ACE))
+ ]
+ end
+
+ it_behaves_like "a securable resource"
+end
+
+shared_context Chef::Resource::Directory do
+ let(:path) do
+ File.join(Dir.tmpdir, make_tmpname(directory_base, nil))
+ end
+
+ after(:each) do
+ FileUtils.rm_r(path) if File.exists?(path)
+ end
+end
diff --git a/spec/support/shared/functional/file_resource.rb b/spec/support/shared/functional/file_resource.rb
new file mode 100644
index 0000000000..631a5ed742
--- /dev/null
+++ b/spec/support/shared/functional/file_resource.rb
@@ -0,0 +1,173 @@
+#
+# Author:: Seth Chisamore (<schisamo@opscode.com>)
+# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+shared_examples_for "a file with the wrong content" do
+ it "overwrites the file with the updated content when the :create action is run" do
+ Chef::Config[:file_backup_path] = CHEF_SPEC_BACKUP_PATH
+ sleep 1
+ resource.run_action(:create)
+ File.stat(path).mtime.should > @expected_mtime
+ sha256_checksum(path).should_not == @expected_checksum
+ end
+
+ it "doesn't overwrite the file when the :create_if_missing action is run" do
+ sleep 1
+ resource.run_action(:create_if_missing)
+ File.stat(path).mtime.should == @expected_mtime
+ sha256_checksum(path).should == @expected_checksum
+ end
+
+ it "should backup the existing file" do
+ Chef::Config[:file_backup_path] = CHEF_SPEC_BACKUP_PATH
+ resource.run_action(:create)
+ Dir.glob(backup_glob).size.should equal(1)
+ end
+
+ it "should not attempt to backup the existing file if :backup == 0" do
+ Chef::Config[:file_backup_path] = CHEF_SPEC_BACKUP_PATH
+ resource.backup(0)
+ resource.run_action(:create)
+ Dir.glob(backup_glob).size.should equal(0)
+ end
+
+ it "deletes the file when the :delete action is run" do
+ resource.run_action(:delete)
+ File.should_not exist(path)
+ end
+end
+
+shared_examples_for "a file with the correct content" do
+ it "does not overwrite the original when the :create action is run" do
+ resource.run_action(:create)
+ sha256_checksum(path).should == @expected_checksum
+ end
+
+ it "does not update the mtime/atime of the file when the :create action is run" do
+ sleep 1
+ File.stat(path).mtime.should == @expected_mtime
+ File.stat(path).atime.should be_within(2).of(@expected_atime)
+ end
+
+ it "doesn't overwrite the file when the :create_if_missing action is run" do
+ resource.run_action(:create_if_missing)
+ sha256_checksum(path).should == @expected_checksum
+ end
+
+ it "deletes the file when the :delete action is run" do
+ resource.run_action(:delete)
+ File.should_not exist(path)
+ end
+end
+
+shared_examples_for "a file resource" do
+ # note the stripping of the drive letter from the tmpdir on windows
+ let(:backup_glob) { File.join(CHEF_SPEC_BACKUP_PATH, Dir.tmpdir.sub(/^([A-Za-z]:)/, ""), "#{file_base}*") }
+
+ context "when the target file does not exist" do
+ it "creates the file when the :create action is run" do
+ resource.run_action(:create)
+ File.should exist(path)
+ end
+
+ it "creates the file with the correct content when the :create action is run" do
+ resource.run_action(:create)
+ IO.read(path).should == expected_content
+ end
+
+ it "creates the file with the correct content when the :create_if_missing action is run" do
+ resource.run_action(:create_if_missing)
+ IO.read(path).should == expected_content
+ end
+
+ it "deletes the file when the :delete action is run" do
+ resource.run_action(:delete)
+ File.should_not exist(path)
+ end
+ end
+
+ # Set up the context for security tests
+ def allowed_acl(sid, expected_perms)
+ [ ACE.access_allowed(sid, expected_perms[:specific]) ]
+ end
+
+ def denied_acl(sid, expected_perms)
+ [ ACE.access_denied(sid, expected_perms[:specific]) ]
+ end
+
+
+ context "when the target file has the wrong content" do
+ before(:each) do
+ File.open(path, "w") { |f| f.print "This is so wrong!!!" }
+ @expected_mtime = File.stat(path).mtime
+ @expected_checksum = sha256_checksum(path)
+ end
+
+ describe "and the target file has the correct permissions" do
+ include_context "setup correct permissions"
+
+ it_behaves_like "a file with the wrong content"
+
+ it_behaves_like "a securable resource"
+ end
+
+ context "and the target file has incorrect permissions" do
+ include_context "setup broken permissions"
+
+ it_behaves_like "a file with the wrong content"
+
+ it_behaves_like "a securable resource"
+ end
+ end
+
+ context "when the target file has the correct content" do
+ before(:each) do
+ File.open(path, "w") { |f| f.print expected_content }
+ @expected_mtime = File.stat(path).mtime
+ @expected_atime = File.stat(path).atime
+ @expected_checksum = sha256_checksum(path)
+ end
+
+ describe "and the target file has the correct permissions" do
+ include_context "setup correct permissions"
+
+ it_behaves_like "a file with the correct content"
+
+ it_behaves_like "a securable resource"
+ end
+
+ context "and the target file has incorrect permissions" do
+ include_context "setup broken permissions"
+
+ it_behaves_like "a file with the correct content"
+
+ it_behaves_like "a securable resource"
+ end
+ end
+
+end
+
+shared_context Chef::Resource::File do
+ let(:path) do
+ File.join(Dir.tmpdir, make_tmpname(file_base, nil))
+ end
+
+ after(:each) do
+ FileUtils.rm_r(path) if File.exists?(path)
+ FileUtils.rm_r(CHEF_SPEC_BACKUP_PATH) if File.exists?(CHEF_SPEC_BACKUP_PATH)
+ end
+end
diff --git a/spec/support/shared/functional/knife.rb b/spec/support/shared/functional/knife.rb
new file mode 100644
index 0000000000..e96de7c27a
--- /dev/null
+++ b/spec/support/shared/functional/knife.rb
@@ -0,0 +1,37 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: AJ Christensen (<aj@junglist.gen.nz>)
+# Author:: Ho-Sheng Hsiao (<hosh@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+module SpecHelpers
+ module Knife
+ def redefine_argv(value)
+ Object.send(:remove_const, :ARGV)
+ Object.send(:const_set, :ARGV, value)
+ end
+
+ def with_argv(*argv)
+ original_argv = ARGV
+ redefine_argv(argv.flatten)
+ begin
+ yield
+ ensure
+ redefine_argv(original_argv)
+ end
+ end
+ end
+end
diff --git a/spec/support/shared/functional/securable_resource.rb b/spec/support/shared/functional/securable_resource.rb
new file mode 100644
index 0000000000..2eeb16c784
--- /dev/null
+++ b/spec/support/shared/functional/securable_resource.rb
@@ -0,0 +1,394 @@
+#
+# Author:: Seth Chisamore (<schisamo@opscode.com>)
+# Author:: Mark Mzyk (<mmzyk@opscode.com>)
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# TODO test that these work when you are logged on as a user joined to a domain (rather than local computer)
+# TODO test that you can set users from other domains
+
+require 'etc'
+
+shared_context "setup correct permissions" do
+ context "on unix", :unix_only do
+ context "with root", :requires_root do
+ before :each do
+ File.chown(Etc.getpwnam('nobody').uid, 1337, path)
+ File.chmod(0776, path)
+ end
+ end
+
+ context "without root", :requires_unprivileged_user do
+ before :each do
+ File.chmod(0776, path)
+ end
+ end
+ end
+
+ # FIXME: windows
+end
+
+shared_context "setup broken permissions" do
+ context "on unix", :unix_only do
+ context "with root", :requires_root do
+ before :each do
+ File.chown(0, 0, path)
+ File.chmod(0644, path)
+ end
+ end
+
+ context "without root", :requires_unprivileged_user do
+ before :each do
+ File.chmod(0644, path)
+ end
+ end
+ end
+
+ # FIXME: windows
+end
+
+shared_examples_for "a securable resource" do
+ context "on Unix", :unix_only do
+ let(:expected_user_name) { 'nobody' }
+ let(:expected_uid) { Etc.getpwnam(expected_user_name).uid }
+ let(:desired_gid) { 1337 }
+ let(:expected_gid) { 1337 }
+
+ pending "should set an owner (Rerun specs under root)", :requires_unprivileged_user => true
+ pending "should set a group (Rerun specs under root)", :requires_unprivileged_user => true
+
+ it "should set an owner", :requires_root do
+ resource.owner expected_user_name
+ resource.run_action(:create)
+ File.lstat(path).uid.should == expected_uid
+ end
+
+ it "should set a group", :requires_root do
+ resource.group desired_gid
+ resource.run_action(:create)
+ File.lstat(path).gid.should == expected_gid
+ end
+
+ it "should set permissions in string form as an octal number" do
+ mode_string = '776'
+ resource.mode mode_string
+ resource.run_action(:create)
+ pending('Linux does not support lchmod', :if => resource.instance_of?(Chef::Resource::Link) && !os_x? && !freebsd?) do
+ (File.lstat(path).mode & 007777).should == (mode_string.oct & 007777)
+ end
+ end
+
+ it "should set permissions in numeric form as a ruby-interpreted octal" do
+ mode_integer = 0776
+ resource.mode mode_integer
+ resource.run_action(:create)
+ pending('Linux does not support lchmod', :if => resource.instance_of?(Chef::Resource::Link) && !os_x? && !freebsd?) do
+ (File.lstat(path).mode & 007777).should == (mode_integer & 007777)
+ end
+ end
+ end
+
+ context "on Windows", :windows_only do
+
+ if windows?
+ SID = Chef::ReservedNames::Win32::Security::SID
+ ACE = Chef::ReservedNames::Win32::Security::ACE
+ end
+
+ def get_security_descriptor(path)
+ Chef::ReservedNames::Win32::Security.get_named_security_info(path)
+ end
+
+ def explicit_aces
+ descriptor.dacl.select { |ace| ace.explicit? }
+ end
+
+ def extract_ace_properties(aces)
+ hashes = []
+ aces.each do |ace|
+ hashes << { :mask => ace.mask, :type => ace.type, :flags => ace.flags }
+ end
+ hashes
+ end
+
+ # Standard expected rights
+ let(:expected_read_perms) do
+ {
+ :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_READ,
+ :specific => Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_READ,
+ }
+ end
+
+ let(:expected_read_execute_perms) do
+ {
+ :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_READ | Chef::ReservedNames::Win32::API::Security::GENERIC_EXECUTE,
+ :specific => Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_READ | Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_EXECUTE
+ }
+ end
+
+ let(:expected_write_perms) do
+ {
+ :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_WRITE,
+ :specific => Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_WRITE
+ }
+ end
+
+ let(:expected_modify_perms) do
+ {
+ :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_READ | Chef::ReservedNames::Win32::API::Security::GENERIC_WRITE | Chef::ReservedNames::Win32::API::Security::GENERIC_EXECUTE | Chef::ReservedNames::Win32::API::Security::DELETE,
+ :specific => Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_READ | Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_WRITE | Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_EXECUTE | Chef::ReservedNames::Win32::API::Security::DELETE
+ }
+ end
+
+ let(:expected_full_control_perms) do
+ {
+ :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_ALL,
+ :specific => Chef::ReservedNames::Win32::API::Security::FILE_ALL_ACCESS
+ }
+ end
+
+ RSpec::Matchers.define :have_expected_properties do |mask, type, flags|
+ match do |ace|
+ ace.mask == mask
+ ace.type == type
+ ace.flags == flags
+ end
+ end
+
+ def descriptor
+ get_security_descriptor(path)
+ end
+
+ before(:each) do
+ resource.run_action(:delete)
+ end
+
+ it "sets owner to Administrators on create if owner is not specified" do
+ File.exist?(path).should == false
+ resource.run_action(:create)
+ descriptor.owner.should == SID.Administrators
+ end
+
+ it "sets owner when owner is specified" do
+ resource.owner 'Guest'
+ resource.run_action(:create)
+ descriptor.owner.should == SID.Guest
+ end
+
+ it "fails to set owner when owner has invalid characters" do
+ lambda { resource.owner 'Lance "The Nose" Glindenberry III' }.should raise_error#(Chef::Exceptions::ValidationFailed)
+ end
+
+ it "sets owner when owner is specified with a \\" do
+ resource.owner "#{ENV['USERDOMAIN']}\\Guest"
+ resource.run_action(:create)
+ descriptor.owner.should == SID.Guest
+ end
+
+ it "leaves owner alone if owner is not specified and resource already exists" do
+ # Set owner to Guest so it's not the same as the current user (which is the default on create)
+ resource.owner 'Guest'
+ resource.run_action(:create)
+ descriptor.owner.should == SID.Guest
+
+ new_resource = create_resource
+ new_resource.owner.should == nil
+ new_resource.run_action(:create)
+ descriptor.owner.should == SID.Guest
+ end
+
+ it "sets group to None on create if group is not specified" do
+ resource.group.should == nil
+ File.exist?(path).should == false
+ resource.run_action(:create)
+ descriptor.group.should == SID.None
+ end
+
+ it "sets group when group is specified" do
+ resource.group 'Everyone'
+ resource.run_action(:create)
+ descriptor.group.should == SID.Everyone
+ end
+
+ it "fails to set group when group has invalid characters" do
+ lambda { resource.group 'Lance "The Nose" Glindenberry III' }.should raise_error(Chef::Exceptions::ValidationFailed)
+ end
+
+ it "sets group when group is specified with a \\" do
+ pending "Need to find a group containing a backslash that is on most peoples' machines" do
+ resource.group "#{ENV['COMPUTERNAME']}\\Administrators"
+ resource.run_action(:create)
+ descriptor.group.should == SID.Everyone
+ end
+ end
+
+ it "leaves group alone if group is not specified and resource already exists" do
+ # Set group to Everyone so it's not the default (None)
+ resource.group 'Everyone'
+ resource.run_action(:create)
+ descriptor.group.should == SID.Everyone
+
+ new_resource = create_resource
+ new_resource.group.should == nil
+ new_resource.run_action(:create)
+ descriptor.group.should == SID.Everyone
+ end
+
+ describe "with rights and deny_rights attributes" do
+
+ it "correctly sets :read rights" do
+ resource.rights(:read, 'Guest')
+ resource.run_action(:create)
+ explicit_aces.should == allowed_acl(SID.Guest, expected_read_perms)
+ end
+
+ it "correctly sets :read_execute rights" do
+ resource.rights(:read_execute, 'Guest')
+ resource.run_action(:create)
+ explicit_aces.should == allowed_acl(SID.Guest, expected_read_execute_perms)
+ end
+
+ it "correctly sets :write rights" do
+ resource.rights(:write, 'Guest')
+ resource.run_action(:create)
+ explicit_aces.should == allowed_acl(SID.Guest, expected_write_perms)
+ end
+
+ it "correctly sets :modify rights" do
+ resource.rights(:modify, 'Guest')
+ resource.run_action(:create)
+ explicit_aces.should == allowed_acl(SID.Guest, expected_modify_perms)
+ end
+
+ it "correctly sets :full_control rights" do
+ resource.rights(:full_control, 'Guest')
+ resource.run_action(:create)
+ explicit_aces.should == allowed_acl(SID.Guest, expected_full_control_perms)
+ end
+
+ it "correctly sets deny_rights" do
+ # deny is an ACE with full rights, but is a deny type ace, not an allow type
+ resource.deny_rights(:full_control, 'Guest')
+ resource.run_action(:create)
+ explicit_aces.should == denied_acl(SID.Guest, expected_full_control_perms)
+ end
+
+ it "Sets multiple rights" do
+ resource.rights(:read, 'Everyone')
+ resource.rights(:modify, 'Guest')
+ resource.run_action(:create)
+
+ explicit_aces.should ==
+ allowed_acl(SID.Everyone, expected_read_perms) +
+ allowed_acl(SID.Guest, expected_modify_perms)
+ end
+
+ it "Sets deny_rights ahead of rights" do
+ resource.rights(:read, 'Everyone')
+ resource.deny_rights(:modify, 'Guest')
+ resource.run_action(:create)
+
+ explicit_aces.should ==
+ denied_acl(SID.Guest, expected_modify_perms) +
+ allowed_acl(SID.Everyone, expected_read_perms)
+ end
+
+ it "Sets deny_rights ahead of rights when specified in reverse order" do
+ resource.deny_rights(:modify, 'Guest')
+ resource.rights(:read, 'Everyone')
+ resource.run_action(:create)
+
+ explicit_aces.should ==
+ denied_acl(SID.Guest, expected_modify_perms) +
+ allowed_acl(SID.Everyone, expected_read_perms)
+ end
+
+ end
+
+ context "with a mode attribute" do
+ if windows?
+ Security = Chef::ReservedNames::Win32::API::Security
+ end
+
+ it "respects mode in string form as an octal number" do
+ #on windows, mode cannot modify owner and/or group permissons
+ #unless the owner and/or group as appropriate is specified
+ resource.mode '400'
+ resource.owner 'Guest'
+ resource.group 'Everyone'
+ resource.run_action(:create)
+
+ explicit_aces.should == [ ACE.access_allowed(SID.Guest, Security::FILE_GENERIC_READ) ]
+ end
+
+ it "respects mode in numeric form as a ruby-interpreted octal" do
+ resource.mode 0700
+ resource.owner 'Guest'
+ resource.run_action(:create)
+
+ explicit_aces.should == [ ACE.access_allowed(SID.Guest, Security::FILE_GENERIC_READ | Security::FILE_GENERIC_WRITE | Security::FILE_GENERIC_EXECUTE | Security::DELETE) ]
+ end
+
+ it "respects the owner, group and everyone bits of mode" do
+ resource.mode 0754
+ resource.owner 'Guest'
+ resource.group 'Administrators'
+ resource.run_action(:create)
+
+ explicit_aces.should == [
+ ACE.access_allowed(SID.Guest, Security::FILE_GENERIC_READ | Security::FILE_GENERIC_WRITE | Security::FILE_GENERIC_EXECUTE | Security::DELETE),
+ ACE.access_allowed(SID.Administrators, Security::FILE_GENERIC_READ | Security::FILE_GENERIC_EXECUTE),
+ ACE.access_allowed(SID.Everyone, Security::FILE_GENERIC_READ)
+ ]
+ end
+
+ it "respects the individual read, write and execute bits of mode" do
+ resource.mode 0421
+ resource.owner 'Guest'
+ resource.group 'Administrators'
+ resource.run_action(:create)
+
+ explicit_aces.should == [
+ ACE.access_allowed(SID.Guest, Security::FILE_GENERIC_READ),
+ ACE.access_allowed(SID.Administrators, Security::FILE_GENERIC_WRITE | Security::DELETE),
+ ACE.access_allowed(SID.Everyone, Security::FILE_GENERIC_EXECUTE)
+ ]
+ end
+
+ it 'warns when mode tries to set owner bits but owner is not specified' do
+ @warn = []
+ Chef::Log.stub!(:warn) { |msg| @warn << msg }
+
+ resource.mode 0400
+ resource.run_action(:create)
+
+ @warn.include?("Mode 400 includes bits for the owner, but owner is not specified").should be_true
+ end
+
+ it 'warns when mode tries to set group bits but group is not specified' do
+ @warn = []
+ Chef::Log.stub!(:warn) { |msg| @warn << msg }
+
+ resource.mode 0040
+ resource.run_action(:create)
+
+ @warn.include?("Mode 040 includes bits for the group, but group is not specified").should be_true
+ end
+ end
+
+ end
+end
diff --git a/spec/support/shared/unit/api_error_inspector.rb b/spec/support/shared/unit/api_error_inspector.rb
new file mode 100644
index 0000000000..8231ceb195
--- /dev/null
+++ b/spec/support/shared/unit/api_error_inspector.rb
@@ -0,0 +1,192 @@
+#
+# Author:: Daniel DeLeo (<dan@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+
+# == API Error Inspector Examples
+# These tests are work in progress. They exercise the code enough to ensure it
+# runs without error, but don't make assertions about the output. This is
+# because aspects such as how information gets formatted, what's included, etc.
+# are still in flux. When testing an inspector, change the outputter to use
+# STDOUT and manually check the ouput.
+
+shared_examples_for "an api error inspector" do
+
+ before do
+ @node_name = "test-node.example.com"
+ @config = {
+ :validation_client_name => "testorg-validator",
+ :validation_key => "/etc/chef/testorg-validator.pem",
+ :chef_server_url => "https://chef-api.example.com",
+ :node_name => "testnode-name",
+ :client_key => "/etc/chef/client.pem"
+ }
+ @description = Chef::Formatters::ErrorDescription.new("Error registering the node:")
+ @outputter = Chef::Formatters::Outputter.new(StringIO.new, STDERR)
+ #@outputter = Chef::Formatters::Outputter.new(STDOUT, STDERR)
+
+ end
+
+ describe "when explaining a network error" do
+ before do
+ @exception = Errno::ECONNREFUSED.new("connection refused")
+ @inspector = described_class.new(@node_name, @exception, @config)
+ @inspector.add_explanation(@description)
+ end
+
+ it "prints a nice message" do
+ @description.display(@outputter)
+ end
+
+ end
+
+ describe "when explaining a 'private key missing' error" do
+ before do
+ @exception = Chef::Exceptions::PrivateKeyMissing.new("no private key yo")
+ @inspector = described_class.new(@node_name, @exception, @config)
+ @inspector.add_explanation(@description)
+ end
+
+ it "prints a nice message" do
+ @description.display(@outputter)
+ end
+
+ end
+
+ describe "when explaining a 401 caused by clock skew" do
+ before do
+ @response_body = "synchronize the clock on your host"
+ @response = Net::HTTPUnauthorized.new("1.1", "401", "(response) unauthorized")
+ @response.stub!(:body).and_return(@response_body)
+ @exception = Net::HTTPServerException.new("(exception) unauthorized", @response)
+ @inspector = described_class.new(@node_name, @exception, @config)
+ @inspector.add_explanation(@description)
+ end
+
+ it "prints a nice message" do
+ @description.display(@outputter)
+ end
+
+ end
+
+ describe "when explaining a 401 (no clock skew)" do
+ before do
+ @response_body = "check your key and node name"
+ @response = Net::HTTPUnauthorized.new("1.1", "401", "(response) unauthorized")
+ @response.stub!(:body).and_return(@response_body)
+ @exception = Net::HTTPServerException.new("(exception) unauthorized", @response)
+ @inspector = described_class.new(@node_name, @exception, @config)
+ @inspector.add_explanation(@description)
+ end
+
+ it "prints a nice message" do
+ @description.display(@outputter)
+ end
+
+ end
+
+ describe "when explaining a 403" do
+ before do
+ @response_body = "forbidden"
+ @response = Net::HTTPForbidden.new("1.1", "403", "(response) forbidden")
+ @response.stub!(:body).and_return(@response_body)
+ @exception = Net::HTTPServerException.new("(exception) forbidden", @response)
+ @inspector = described_class.new(@node_name, @exception, @config)
+ @inspector.add_explanation(@description)
+ end
+
+ it "prints a nice message" do
+ @description.display(@outputter)
+ end
+
+ end
+
+ describe "when explaining a 400" do
+ before do
+ @response_body = "didn't like your data"
+ @response = Net::HTTPBadRequest.new("1.1", "400", "(response) bad request")
+ @response.stub!(:body).and_return(@response_body)
+ @exception = Net::HTTPServerException.new("(exception) bad request", @response)
+ @inspector = described_class.new(@node_name, @exception, @config)
+ @inspector.add_explanation(@description)
+ end
+
+ it "prints a nice message" do
+ @description.display(@outputter)
+ end
+
+ end
+
+ describe "when explaining a 404" do
+ before do
+ @response_body = "probably caused by a redirect to a get"
+ @response = Net::HTTPNotFound.new("1.1", "404", "(response) not found")
+ @response.stub!(:body).and_return(@response_body)
+ @exception = Net::HTTPServerException.new("(exception) not found", @response)
+ @inspector = described_class.new(@node_name, @exception, @config)
+ @inspector.add_explanation(@description)
+ end
+
+ it "prints a nice message" do
+ @description.display(@outputter)
+ end
+ end
+
+ describe "when explaining a 500" do
+ before do
+ @response_body = "sad trombone"
+ @response = Net::HTTPInternalServerError.new("1.1", "500", "(response) internal server error")
+ @response.stub!(:body).and_return(@response_body)
+ @exception = Net::HTTPFatalError.new("(exception) internal server error", @response)
+ @inspector = described_class.new(@node_name, @exception, @config)
+ @inspector.add_explanation(@description)
+ end
+
+ it "prints a nice message" do
+ @description.display(@outputter)
+ end
+ end
+
+ describe "when explaining a 503" do
+ before do
+ @response_body = "sad trombone orchestra"
+ @response = Net::HTTPBadGateway.new("1.1", "502", "(response) bad gateway")
+ @response.stub!(:body).and_return(@response_body)
+ @exception = Net::HTTPFatalError.new("(exception) bad gateway", @response)
+ @inspector = described_class.new(@node_name, @exception, @config)
+ @inspector.add_explanation(@description)
+ end
+
+ it "prints a nice message" do
+ @description.display(@outputter)
+ end
+ end
+
+ describe "when explaining an unknown error" do
+ before do
+ @exception = RuntimeError.new("(exception) something went wrong")
+ @inspector = described_class.new(@node_name, @exception, @config)
+ @inspector.add_explanation(@description)
+ end
+
+ it "prints a nice message" do
+ @description.display(@outputter)
+ end
+ end
+
+end
diff --git a/spec/support/shared/unit/file_system_support.rb b/spec/support/shared/unit/file_system_support.rb
new file mode 100644
index 0000000000..8516292c67
--- /dev/null
+++ b/spec/support/shared/unit/file_system_support.rb
@@ -0,0 +1,110 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/chef_fs/file_system'
+require 'chef/chef_fs/file_system/base_fs_dir'
+require 'chef/chef_fs/file_system/base_fs_object'
+
+module FileSystemSupport
+ class MemoryFile < Chef::ChefFS::FileSystem::BaseFSObject
+ def initialize(name, parent, value)
+ super(name, parent)
+ @value = value
+ end
+ def read
+ return @value
+ end
+ end
+
+ class MemoryDir < Chef::ChefFS::FileSystem::BaseFSDir
+ def initialize(name, parent)
+ super(name, parent)
+ @children = []
+ end
+ attr_reader :children
+ def child(name)
+ @children.select { |child| child.name == name }.first || Chef::ChefFS::FileSystem::NonexistentFSObject.new(name, self)
+ end
+ def add_child(child)
+ @children.push(child)
+ end
+ def can_have_child?(name, is_dir)
+ root.cannot_be_in_regex ? (name !~ root.cannot_be_in_regex) : true
+ end
+ end
+
+ class MemoryRoot < MemoryDir
+ def initialize(pretty_name, cannot_be_in_regex = nil)
+ super('', nil)
+ @pretty_name = pretty_name
+ @cannot_be_in_regex = cannot_be_in_regex
+ end
+
+ attr_reader :cannot_be_in_regex
+
+ def path_for_printing
+ @pretty_name
+ end
+ end
+
+ def memory_fs(pretty_name, value, cannot_be_in_regex = nil)
+ if !value.is_a?(Hash)
+ raise "memory_fs() must take a Hash"
+ end
+ dir = MemoryRoot.new(pretty_name, cannot_be_in_regex)
+ value.each do |key, child|
+ dir.add_child(memory_fs_value(child, key.to_s, dir))
+ end
+ dir
+ end
+
+ def memory_fs_value(value, name = '', parent = nil)
+ if value.is_a?(Hash)
+ dir = MemoryDir.new(name, parent)
+ value.each do |key, child|
+ dir.add_child(memory_fs_value(child, key.to_s, dir))
+ end
+ dir
+ else
+ MemoryFile.new(name, parent, value || "#{name}\n")
+ end
+ end
+
+ def pattern(p)
+ Chef::ChefFS::FilePattern.new(p)
+ end
+
+ def return_paths(*expected)
+ ReturnPaths.new(expected)
+ end
+
+ def no_blocking_calls_allowed
+ [ MemoryFile, MemoryDir ].each do |c|
+ [ :children, :exists?, :read ].each do |m|
+ c.any_instance.stub(m).and_raise("#{m.to_s} should not be called")
+ end
+ end
+ end
+
+ def list_should_yield_paths(fs, pattern_str, *expected_paths)
+ result_paths = []
+ Chef::ChefFS::FileSystem.list(fs, pattern(pattern_str)) { |result| result_paths << result.path }
+ result_paths.should =~ expected_paths
+ end
+end
+