diff options
Diffstat (limited to 'spec/unit/mixin')
-rw-r--r-- | spec/unit/mixin/checksum_spec.rb | 41 | ||||
-rw-r--r-- | spec/unit/mixin/command_spec.rb | 105 | ||||
-rw-r--r-- | spec/unit/mixin/convert_to_class_name_spec.rb | 50 | ||||
-rw-r--r-- | spec/unit/mixin/deep_merge_spec.rb | 314 | ||||
-rw-r--r-- | spec/unit/mixin/deprecation_spec.rb | 34 | ||||
-rw-r--r-- | spec/unit/mixin/enforce_ownership_and_permissions_spec.rb | 93 | ||||
-rw-r--r-- | spec/unit/mixin/params_validate_spec.rb | 372 | ||||
-rw-r--r-- | spec/unit/mixin/path_sanity_spec.rb | 80 | ||||
-rw-r--r-- | spec/unit/mixin/securable_spec.rb | 254 | ||||
-rw-r--r-- | spec/unit/mixin/shell_out_spec.rb | 109 | ||||
-rw-r--r-- | spec/unit/mixin/template_spec.rb | 104 | ||||
-rw-r--r-- | spec/unit/mixin/xml_escape_spec.rb | 54 |
12 files changed, 1610 insertions, 0 deletions
diff --git a/spec/unit/mixin/checksum_spec.rb b/spec/unit/mixin/checksum_spec.rb new file mode 100644 index 0000000000..dec270e18f --- /dev/null +++ b/spec/unit/mixin/checksum_spec.rb @@ -0,0 +1,41 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2009 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 'spec_helper' +require 'chef/mixin/checksum' +require 'stringio' + +class Chef::CMCCheck + include Chef::Mixin::Checksum +end + +describe Chef::Mixin::Checksum do + before(:each) do + @checksum_user = Chef::CMCCheck.new + @cache = Chef::ChecksumCache.instance + @file = CHEF_SPEC_DATA + "/checksum/random.txt" + @stat = mock("File::Stat", { :mtime => Time.at(0) }) + File.stub!(:stat).and_return(@stat) + end + + it "gets the checksum of a file" do + @checksum_user.checksum(@file).should == "09ee9c8cc70501763563bcf9c218d71b2fbf4186bf8e1e0da07f0f42c80a3394" + end + +end + diff --git a/spec/unit/mixin/command_spec.rb b/spec/unit/mixin/command_spec.rb new file mode 100644 index 0000000000..e143b8728b --- /dev/null +++ b/spec/unit/mixin/command_spec.rb @@ -0,0 +1,105 @@ +# +# Author:: Hongli Lai (hongli@phusion.nl) +# Copyright:: Copyright (c) 2009 Phusion +# 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 'spec_helper' + +describe Chef::Mixin::Command do + + if windows? + + pending("TODO MOVE: this is a platform specific integration test.") + + else + + describe "popen4" do + include Chef::Mixin::Command + + it "should be possible to read the child process's stdout and stderr" do + popen4("sh -c 'echo hello && echo world >&2'") do |pid, stdin, stdout, stderr| + stdout.read.should == "hello\n" + stderr.read.should == "world\n" + end + end + + it "should default all commands to be run in the POSIX standard C locale" do + popen4("echo $LC_ALL") do |pid, stdin, stdout, stderr| + stdout.read.strip.should == "C" + end + end + + it "should respect locale when specified explicitly" do + popen4("echo $LC_ALL", :environment => {"LC_ALL" => "es"}) do |pid, stdin, stdout, stderr| + stdout.read.strip.should == "es" + end + end + + it "should end when the child process reads from STDIN and a block is given" do + lambda {Timeout.timeout(10) do + popen4("ruby -e 'while gets; end'", :waitlast => true) do |pid, stdin, stdout, stderr| + (1..5).each { |i| stdin.puts "#{i}" } + end + end + }.should_not raise_error + end + + describe "when a process detaches but doesn't close STDOUT and STDERR [CHEF-584]" do + + it "returns immediately after the first child process exits" do + lambda {Timeout.timeout(10) do + pid, stdin,stdout,stderr = nil,nil,nil,nil + evil_forker="exit if fork; 10.times { sleep 1}" + popen4("ruby -e '#{evil_forker}'") do |pid,stdin,stdout,stderr| + end + end}.should_not raise_error + end + + end + + end + + describe "run_command" do + include Chef::Mixin::Command + + it "logs the command's stderr and stdout output if the command failed" do + Chef::Log.stub!(:level).and_return(:debug) + begin + run_command(:command => "sh -c 'echo hello; echo world >&2; false'") + violated "Exception expected, but nothing raised." + rescue => e + e.message.should =~ /STDOUT: hello/ + e.message.should =~ /STDERR: world/ + end + end + + describe "when a process detaches but doesn't close STDOUT and STDERR [CHEF-584]" do + it "returns successfully" do + # CHEF-2916 might have added a slight delay here, or our CI + # infrastructure is burdened. Bumping timeout from 2 => 4 -- + # btm + # Serdar - During Solaris tests, we've seen that processes + # are taking a long time to exit. Bumping timeout now to 10. + lambda {Timeout.timeout(10) do + evil_forker="exit if fork; 10.times { sleep 1}" + run_command(:command => "ruby -e '#{evil_forker}'") + end}.should_not raise_error + end + + end + end + end +end diff --git a/spec/unit/mixin/convert_to_class_name_spec.rb b/spec/unit/mixin/convert_to_class_name_spec.rb new file mode 100644 index 0000000000..b78d3f9101 --- /dev/null +++ b/spec/unit/mixin/convert_to_class_name_spec.rb @@ -0,0 +1,50 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2009 Daniel DeLeo +# 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 'spec_helper' + +class ConvertToClassTestHarness + include Chef::Mixin::ConvertToClassName +end + +describe Chef::Mixin::ConvertToClassName do + + before do + @convert = ConvertToClassTestHarness.new + end + + it "converts a_snake_case_word to a CamelCaseWord" do + @convert.convert_to_class_name("now_camelized").should == "NowCamelized" + end + + it "converts a CamelCaseWord to a snake_case_word" do + @convert.convert_to_snake_case("NowImASnake").should == "now_im_a_snake" + end + + it "removes the base classes before snake casing" do + @convert.convert_to_snake_case("NameSpaced::Class::ThisIsWin", "NameSpaced::Class").should == "this_is_win" + end + + it "removes the base classes without explicitly naming them and returns snake case" do + @convert.snake_case_basename("NameSpaced::Class::ExtraWin").should == "extra_win" + end + + it "interprets non-alphanumeric characters in snake case as word boundaries" do + @convert.convert_to_class_name("now_camelized_without-hyphen").should == "NowCamelizedWithoutHyphen" + end +end diff --git a/spec/unit/mixin/deep_merge_spec.rb b/spec/unit/mixin/deep_merge_spec.rb new file mode 100644 index 0000000000..cbc9b1544f --- /dev/null +++ b/spec/unit/mixin/deep_merge_spec.rb @@ -0,0 +1,314 @@ +# +# Author:: Matthew Kent (<mkent@magoazul.com>) +# Author:: Steve Midgley (http://www.misuse.org/science) +# Copyright:: Copyright (c) 2010 Matthew Kent +# Copyright:: Copyright (c) 2008 Steve Midgley +# 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. + +# Notice: +# This code is imported from deep_merge by Steve Midgley. deep_merge is +# available under the MIT license from +# http://trac.misuse.org/science/wiki/DeepMerge + +require 'spec_helper' + +# Test coverage from the original author converted to rspec +describe Chef::Mixin::DeepMerge, "deep_merge!" do + before do + @dm = Chef::Mixin::DeepMerge + @field_ko_prefix = '!merge' + end + + # deep_merge core tests - moving from basic to more complex + + it "tests merging an hash w/array into blank hash" do + hash_src = {'id' => '2'} + hash_dst = {} + @dm.deep_merge!(hash_src.dup, hash_dst) + hash_dst.should == hash_src + end + + it "tests merging an hash w/array into blank hash" do + hash_src = {'region' => {'id' => ['227', '2']}} + hash_dst = {} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == hash_src + end + + it "tests merge from empty hash" do + hash_src = {} + hash_dst = {"property" => ["2","4"]} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {"property" => ["2","4"]} + end + + it "tests merge to empty hash" do + hash_src = {"property" => ["2","4"]} + hash_dst = {} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {"property" => ["2","4"]} + end + + it "tests simple string overwrite" do + hash_src = {"name" => "value"} + hash_dst = {"name" => "value1"} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {"name" => "value"} + end + + it "tests simple string overwrite of empty hash" do + hash_src = {"name" => "value"} + hash_dst = {} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == hash_src + end + + it "tests hashes holding array" do + hash_src = {"property" => ["1","3"]} + hash_dst = {"property" => ["2","4"]} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {"property" => ["2","4","1","3"]} + end + + it "tests hashes holding hashes holding arrays (array with duplicate elements is merged with dest then src" do + hash_src = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["1", "4+"]}} + hash_dst = {"property" => {"bedroom_count" => ["3", "2"], "bathroom_count" => ["2"]}} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {"property" => {"bedroom_count" => ["3","2","1"], "bathroom_count" => ["2", "1", "4+"]}} + end + + it "tests hash holding hash holding array v string (string is overwritten by array)" do + hash_src = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["1", "4+"]}} + hash_dst = {"property" => {"bedroom_count" => "3", "bathroom_count" => ["2"]}} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2","1","4+"]}} + end + + it "tests hash holding hash holding string v array (array is overwritten by string)" do + hash_src = {"property" => {"bedroom_count" => "3", "bathroom_count" => ["1", "4+"]}} + hash_dst = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2"]}} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {"property" => {"bedroom_count" => "3", "bathroom_count" => ["2","1","4+"]}} + end + + it "tests hash holding hash holding hash v array (array is overwritten by hash)" do + hash_src = {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["1", "4+"]}} + hash_dst = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2"]}} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["2","1","4+"]}} + end + + it "tests 3 hash layers holding integers (integers are overwritten by source)" do + hash_src = {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["1", "4+"]}} + hash_dst = {"property" => {"bedroom_count" => {"king_bed" => 2, "queen_bed" => 4}, "bathroom_count" => ["2"]}} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["2","1","4+"]}} + end + + it "tests 3 hash layers holding arrays of int (arrays are merged)" do + hash_src = {"property" => {"bedroom_count" => {"king_bed" => [3], "queen_bed" => [1]}, "bathroom_count" => ["1", "4+"]}} + hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => [2,3], "queen_bed" => [4,1]}, "bathroom_count" => ["2","1","4+"]}} + end + + it "tests 1 hash overwriting 3 hash layers holding arrays of int" do + hash_src = {"property" => "1"} + hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {"property" => "1"} + end + + it "tests 3 hash layers holding arrays of int (arrays are merged) but second hash's array is overwritten" do + hash_src = {"property" => {"bedroom_count" => {"king_bed" => [3], "queen_bed" => [1]}, "bathroom_count" => "1"}} + hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => [2,3], "queen_bed" => [4,1]}, "bathroom_count" => "1"}} + end + + it "tests 3 hash layers holding arrays of int, but one holds int. This one overwrites, but the rest merge" do + hash_src = {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => [1]}, "bathroom_count" => ["1"]}} + hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => [4,1]}, "bathroom_count" => ["2","1"]}} + end + + it "tests 3 hash layers holding arrays of int, but source is incomplete." do + hash_src = {"property" => {"bedroom_count" => {"king_bed" => [3]}, "bathroom_count" => ["1"]}} + hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => [2,3], "queen_bed" => [4]}, "bathroom_count" => ["2","1"]}} + end + + it "tests 3 hash layers holding arrays of int, but source is shorter and has new 2nd level ints." do + hash_src = {"property" => {"bedroom_count" => {2=>3, "king_bed" => [3]}, "bathroom_count" => ["1"]}} + hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {"property" => {"bedroom_count" => {2=>3, "king_bed" => [2,3], "queen_bed" => [4]}, "bathroom_count" => ["2","1"]}} + end + + it "tests 3 hash layers holding arrays of int, but source is empty" do + hash_src = {} + hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} + end + + it "tests 3 hash layers holding arrays of int, but dest is empty" do + hash_src = {"property" => {"bedroom_count" => {2=>3, "king_bed" => [3]}, "bathroom_count" => ["1"]}} + hash_dst = {} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {"property" => {"bedroom_count" => {2=>3, "king_bed" => [3]}, "bathroom_count" => ["1"]}} + end + + it "tests hash holding arrays of arrays" do + hash_src = {["1", "2", "3"] => ["1", "2"]} + hash_dst = {["4", "5"] => ["3"]} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {["1","2","3"] => ["1", "2"], ["4", "5"] => ["3"]} + end + + it "tests merging of hash with blank hash, and make sure that source array split does not function when turned off" do + hash_src = {'property' => {'bedroom_count' => ["1","2,3"]}} + hash_dst = {} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {'property' => {'bedroom_count' => ["1","2,3"]}} + end + + it "tests merging into a blank hash" do + hash_src = {"action"=>"browse", "controller"=>"results"} + hash_dst = {} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == hash_src + end + + it "tests are unmerged hashes passed unmodified w/out :unpack_arrays?" do + hash_src = {"amenity"=>{"id"=>["26,27"]}} + hash_dst = {} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {"amenity"=>{"id"=>["26,27"]}} + end + + it "tests hash of array of hashes" do + hash_src = {"item" => [{"1" => "3"}, {"2" => "4"}]} + hash_dst = {"item" => [{"3" => "5"}]} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {"item" => [{"3" => "5"}, {"1" => "3"}, {"2" => "4"}]} + end + + # Additions since import + it "should overwrite true with false when merging boolean values" do + hash_src = {"valid" => false} + hash_dst = {"valid" => true} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {"valid" => false} + end + + it "should overwrite false with true when merging boolean values" do + hash_src = {"valid" => true} + hash_dst = {"valid" => false} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {"valid" => true} + end + + it "should overwrite a string with an empty string when merging string values" do + hash_src = {"item" => " "} + hash_dst = {"item" => "orange"} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {"item" => " "} + end + + it "should overwrite an empty string with a string when merging string values" do + hash_src = {"item" => "orange"} + hash_dst = {"item" => " "} + @dm.deep_merge!(hash_src, hash_dst) + hash_dst.should == {"item" => "orange"} + end +end # deep_merge! + +# Chef specific +describe Chef::Mixin::DeepMerge do + before do + @dm = Chef::Mixin::DeepMerge + end + + describe "merge" do + it "should merge a hash into an empty hash" do + hash_dst = {} + hash_src = {'id' => '2'} + @dm.merge(hash_dst, hash_src).should == hash_src + end + + it "should merge a nested hash into an empty hash" do + hash_dst = {} + hash_src = {'region' => {'id' => ['227', '2']}} + @dm.merge(hash_dst, hash_src).should == hash_src + end + + it "should overwrite as string value when merging hashes" do + hash_dst = {"name" => "value1"} + hash_src = {"name" => "value"} + @dm.merge(hash_dst, hash_src).should == {"name" => "value"} + end + + it "should merge arrays within hashes" do + hash_dst = {"property" => ["2","4"]} + hash_src = {"property" => ["1","3"]} + @dm.merge(hash_dst, hash_src).should == {"property" => ["2","4","1","3"]} + end + + it "should merge deeply nested hashes" do + hash_dst = {"property" => {"values" => {"are" => "falling", "can" => "change"}}} + hash_src = {"property" => {"values" => {"are" => "stable", "may" => "rise"}}} + @dm.merge(hash_dst, hash_src).should == {"property" => {"values" => {"are" => "stable", "can" => "change", "may" => "rise"}}} + end + + it "should not modify the source or destination during the merge" do + hash_dst = {"property" => ["1","2","3"]} + hash_src = {"property" => ["4","5","6"]} + ret = @dm.merge(hash_dst, hash_src) + hash_dst.should == {"property" => ["1","2","3"]} + hash_src.should == {"property" => ["4","5","6"]} + ret.should == {"property" => ["1","2","3","4","5","6"]} + end + + end + + describe "role_merge" do + it "errors out if knockout merge use is detected in an array" do + hash_dst = {"property" => ["2","4"]} + hash_src = {"property" => ["1","!merge:4"]} + lambda {@dm.role_merge(hash_dst, hash_src)}.should raise_error(Chef::Mixin::DeepMerge::InvalidSubtractiveMerge) + end + + it "errors out if knockout merge use is detected in an array (reversed merge order)" do + hash_dst = {"property" => ["1","!merge:4"]} + hash_src = {"property" => ["2","4"]} + lambda {@dm.role_merge(hash_dst, hash_src)}.should raise_error(Chef::Mixin::DeepMerge::InvalidSubtractiveMerge) + end + + it "errors out if knockout merge use is detected in a string" do + hash_dst = {"property" => ["2","4"]} + hash_src = {"property" => "!merge"} + lambda {@dm.role_merge(hash_dst, hash_src)}.should raise_error(Chef::Mixin::DeepMerge::InvalidSubtractiveMerge) + end + + it "errors out if knockout merge use is detected in a string (reversed merge order)" do + hash_dst = {"property" => "!merge"} + hash_src= {"property" => ["2","4"]} + lambda {@dm.role_merge(hash_dst, hash_src)}.should raise_error(Chef::Mixin::DeepMerge::InvalidSubtractiveMerge) + end + end +end diff --git a/spec/unit/mixin/deprecation_spec.rb b/spec/unit/mixin/deprecation_spec.rb new file mode 100644 index 0000000000..1b62dcd124 --- /dev/null +++ b/spec/unit/mixin/deprecation_spec.rb @@ -0,0 +1,34 @@ +# +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Copyright:: Copyright (c) 2010 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 'spec_helper' +require 'chef/mixin/deprecation' + +describe Chef::Mixin::Deprecation::DeprecatedInstanceVariable do + before do + Chef::Log.logger = Logger.new(StringIO.new) + + @deprecated_ivar = Chef::Mixin::Deprecation::DeprecatedInstanceVariable.new('value', 'an_ivar') + end + + it "forward method calls to the target object" do + @deprecated_ivar.length.should == 5 + @deprecated_ivar.to_sym.should == :value + end + +end diff --git a/spec/unit/mixin/enforce_ownership_and_permissions_spec.rb b/spec/unit/mixin/enforce_ownership_and_permissions_spec.rb new file mode 100644 index 0000000000..53a8260cdc --- /dev/null +++ b/spec/unit/mixin/enforce_ownership_and_permissions_spec.rb @@ -0,0 +1,93 @@ +# +# Author:: Mark Mzyk (<mmzyk@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. +# + +require 'spec_helper' +require 'etc' +require 'ostruct' + +describe Chef::Mixin::EnforceOwnershipAndPermissions do + + before(:each) do + @node = Chef::Node.new + @node.name "make_believe" + @events = Chef::EventDispatch::Dispatcher.new + @run_context = Chef::RunContext.new(@node, {}, @events) + @tmpdir = Dir.mktmpdir + @resource = Chef::Resource::File.new("#{@tmpdir}/madeup.txt") + FileUtils.touch @resource.path + @resource.owner "adam" + @provider = Chef::Provider::File.new(@resource, @run_context) + @provider.current_resource = @resource + end + + after(:each) do + FileUtils.rm_rf(@tmpdir) + end + + it "should call set_all on the file access control object" do + Chef::FileAccessControl.any_instance.should_receive(:set_all) + @provider.enforce_ownership_and_permissions + end + + context "when nothing was updated" do + before do + Chef::FileAccessControl.any_instance.stub(:uid_from_resource).and_return(0) + Chef::FileAccessControl.any_instance.stub(:requires_changes?).and_return(false) + + passwd_struct = if windows? + Struct::Passwd.new("root", "x", 0, 0, "/root", "/bin/bash") + else + Struct::Passwd.new("root", "x", 0, 0, "root", "/root", "/bin/bash") + end + group_struct = OpenStruct.new(:name => "root", :passwd => "x", :gid => 0) + Etc.stub!(:getpwuid).and_return(passwd_struct) + Etc.stub!(:getgrgid).and_return(group_struct) + end + + it "does not set updated_by_last_action on the new resource" do + @provider.new_resource.should_not_receive(:updated_by_last_action) + + Chef::FileAccessControl.any_instance.stub(:set_all) + @provider.run_action(:create) + end + + end + + context "when something was modified" do + before do + Chef::FileAccessControl.any_instance.stub(:requires_changes?).and_return(true) + Chef::FileAccessControl.any_instance.stub(:uid_from_resource).and_return(0) + + passwd_struct = if windows? + Struct::Passwd.new("root", "x", 0, 0, "/root", "/bin/bash") + else + Struct::Passwd.new("root", "x", 0, 0, "root", "/root", "/bin/bash") + end + group_struct = OpenStruct.new(:name => "root", :passwd => "x", :gid => 0) + Etc.stub!(:getpwuid).and_return(passwd_struct) + Etc.stub!(:getgrgid).and_return(group_struct) + end + + it "sets updated_by_last_action on the new resource" do + @provider.new_resource.should_receive(:updated_by_last_action) + Chef::FileAccessControl.any_instance.stub(:set_all) + @provider.run_action(:create) + end + end + +end diff --git a/spec/unit/mixin/params_validate_spec.rb b/spec/unit/mixin/params_validate_spec.rb new file mode 100644 index 0000000000..b79156109b --- /dev/null +++ b/spec/unit/mixin/params_validate_spec.rb @@ -0,0 +1,372 @@ +# +# Author:: Adam Jacob (<adam@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. +# + +require 'spec_helper' + +class TinyClass + include Chef::Mixin::ParamsValidate + + def music(is_good=true) + is_good + end +end + +describe Chef::Mixin::ParamsValidate do + before(:each) do + @vo = TinyClass.new() + end + + it "should allow a hash and a hash as arguments to validate" do + lambda { @vo.validate({:one => "two"}, {}) }.should_not raise_error(ArgumentError) + end + + it "should raise an argument error if validate is called incorrectly" do + lambda { @vo.validate("one", "two") }.should raise_error(ArgumentError) + end + + it "should require validation map keys to be symbols or strings" do + lambda { @vo.validate({:one => "two"}, { :one => true }) }.should_not raise_error(ArgumentError) + lambda { @vo.validate({:one => "two"}, { "one" => true }) }.should_not raise_error(ArgumentError) + lambda { @vo.validate({:one => "two"}, { Hash.new => true }) }.should raise_error(ArgumentError) + end + + it "should allow options to be required with true" do + lambda { @vo.validate({:one => "two"}, { :one => true }) }.should_not raise_error(ArgumentError) + end + + it "should allow options to be optional with false" do + lambda { @vo.validate({}, {:one => false})}.should_not raise_error(ArgumentError) + end + + it "should allow you to check what kind_of? thing an argument is with kind_of" do + lambda { + @vo.validate( + {:one => "string"}, + { + :one => { + :kind_of => String + } + } + ) + }.should_not raise_error(ArgumentError) + + lambda { + @vo.validate( + {:one => "string"}, + { + :one => { + :kind_of => Array + } + } + ) + }.should raise_error(ArgumentError) + end + + it "should allow you to specify an argument is required with required" do + lambda { + @vo.validate( + {:one => "string"}, + { + :one => { + :required => true + } + } + ) + }.should_not raise_error(ArgumentError) + + lambda { + @vo.validate( + {:two => "string"}, + { + :one => { + :required => true + } + } + ) + }.should raise_error(ArgumentError) + + lambda { + @vo.validate( + {:two => "string"}, + { + :one => { + :required => false + } + } + ) + }.should_not raise_error(ArgumentError) + end + + it "should allow you to specify whether an object has a method with respond_to" do + lambda { + @vo.validate( + {:one => @vo}, + { + :one => { + :respond_to => "validate" + } + } + ) + }.should_not raise_error(ArgumentError) + + lambda { + @vo.validate( + {:one => @vo}, + { + :one => { + :respond_to => "monkey" + } + } + ) + }.should raise_error(ArgumentError) + end + + it "should allow you to specify whether an object has all the given methods with respond_to and an array" do + lambda { + @vo.validate( + {:one => @vo}, + { + :one => { + :respond_to => ["validate", "music"] + } + } + ) + }.should_not raise_error(ArgumentError) + + lambda { + @vo.validate( + {:one => @vo}, + { + :one => { + :respond_to => ["monkey", "validate"] + } + } + ) + }.should raise_error(ArgumentError) + end + + it "should let you set a default value with default => value" do + arguments = Hash.new + @vo.validate(arguments, { + :one => { + :default => "is the loneliest number" + } + }) + arguments[:one].should == "is the loneliest number" + end + + it "should let you check regular expressions" do + lambda { + @vo.validate( + { :one => "is good" }, + { + :one => { + :regex => /^is good$/ + } + } + ) + }.should_not raise_error(ArgumentError) + + lambda { + @vo.validate( + { :one => "is good" }, + { + :one => { + :regex => /^is bad$/ + } + } + ) + }.should raise_error(ArgumentError) + end + + it "should let you specify your own callbacks" do + lambda { + @vo.validate( + { :one => "is good" }, + { + :one => { + :callbacks => { + "should be equal to is good" => lambda { |a| + a == "is good" + }, + } + } + } + ) + }.should_not raise_error(ArgumentError) + + lambda { + @vo.validate( + { :one => "is bad" }, + { + :one => { + :callbacks => { + "should be equal to 'is good'" => lambda { |a| + a == "is good" + }, + } + } + } + ) + }.should raise_error(ArgumentError) + end + + it "should let you combine checks" do + args = { :one => "is good", :two => "is bad" } + lambda { + @vo.validate( + args, + { + :one => { + :kind_of => String, + :respond_to => [ :to_s, :upcase ], + :regex => /^is good/, + :callbacks => { + "should be your friend" => lambda { |a| + a == "is good" + } + }, + :required => true + }, + :two => { + :kind_of => String, + :required => false + }, + :three => { :default => "neato mosquito" } + } + ) + }.should_not raise_error(ArgumentError) + args[:three].should == "neato mosquito" + lambda { + @vo.validate( + args, + { + :one => { + :kind_of => String, + :respond_to => [ :to_s, :upcase ], + :regex => /^is good/, + :callbacks => { + "should be your friend" => lambda { |a| + a == "is good" + } + }, + :required => true + }, + :two => { + :kind_of => Hash, + :required => false + }, + :three => { :default => "neato mosquito" } + } + ) + }.should raise_error(ArgumentError) + end + + it "should raise an ArgumentError if the validation map has an unknown check" do + lambda { @vo.validate( + { :one => "two" }, + { + :one => { + :busted => "check" + } + } + ) + }.should raise_error(ArgumentError) + end + + it "should accept keys that are strings in the options" do + lambda { + @vo.validate({ "one" => "two" }, { :one => { :regex => /^two$/ }}) + }.should_not raise_error(ArgumentError) + end + + it "should allow an array to kind_of" do + lambda { + @vo.validate( + {:one => "string"}, + { + :one => { + :kind_of => [ String, Array ] + } + } + ) + }.should_not raise_error(ArgumentError) + lambda { + @vo.validate( + {:one => ["string"]}, + { + :one => { + :kind_of => [ String, Array ] + } + } + ) + }.should_not raise_error(ArgumentError) + lambda { + @vo.validate( + {:one => Hash.new}, + { + :one => { + :kind_of => [ String, Array ] + } + } + ) + }.should raise_error(ArgumentError) + end + + it "asserts that a value returns false from a predicate method" do + lambda do + @vo.validate({:not_blank => "should pass"}, + {:not_blank => {:cannot_be => :nil, :cannot_be => :empty}}) + end.should_not raise_error + lambda do + @vo.validate({:not_blank => ""}, + {:not_blank => {:cannot_be => :nil, :cannot_be => :empty}}) + end.should raise_error(Chef::Exceptions::ValidationFailed) + end + + it "should set and return a value, then return the same value" do + value = "meow" + @vo.set_or_return(:test, value, {}).object_id.should == value.object_id + @vo.set_or_return(:test, nil, {}).object_id.should == value.object_id + end + + it "should set and return a default value when the argument is nil, then return the same value" do + value = "meow" + @vo.set_or_return(:test, nil, { :default => value }).object_id.should == value.object_id + @vo.set_or_return(:test, nil, {}).object_id.should == value.object_id + end + + it "should raise an ArgumentError when argument is nil and required is true" do + lambda { + @vo.set_or_return(:test, nil, { :required => true }) + }.should raise_error(ArgumentError) + end + + it "should not raise an error when argument is nil and required is false" do + lambda { + @vo.set_or_return(:test, nil, { :required => false }) + }.should_not raise_error(ArgumentError) + end + + it "should set and return @name, then return @name for foo when argument is nil" do + value = "meow" + @vo.set_or_return(:name, value, { }).object_id.should == value.object_id + @vo.set_or_return(:foo, nil, { :name_attribute => true }).object_id.should == value.object_id + end + +end diff --git a/spec/unit/mixin/path_sanity_spec.rb b/spec/unit/mixin/path_sanity_spec.rb new file mode 100644 index 0000000000..e38ee7dc8a --- /dev/null +++ b/spec/unit/mixin/path_sanity_spec.rb @@ -0,0 +1,80 @@ +# +# 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. +# + +require 'spec_helper' + +class PathSanityTestHarness + include Chef::Mixin::PathSanity +end + +describe Chef::Mixin::PathSanity do + + before do + @sanity = PathSanityTestHarness.new + end + + describe "when enforcing path sanity" do + before do + Chef::Config[:enforce_path_sanity] = true + @ruby_bindir = '/some/ruby/bin' + @gem_bindir = '/some/gem/bin' + Gem.stub!(:bindir).and_return(@gem_bindir) + RbConfig::CONFIG.stub!(:[]).with('bindir').and_return(@ruby_bindir) + Chef::Platform.stub!(:windows?).and_return(false) + end + + it "adds all useful PATHs that are not yet in PATH to PATH" do + env = {"PATH" => ""} + @sanity.enforce_path_sanity(env) + env["PATH"].should == "#{@ruby_bindir}:#{@gem_bindir}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + end + + it "does not re-add paths that already exist in PATH" do + env = {"PATH" => "/usr/bin:/sbin:/bin"} + @sanity.enforce_path_sanity(env) + env["PATH"].should == "/usr/bin:/sbin:/bin:#{@ruby_bindir}:#{@gem_bindir}:/usr/local/sbin:/usr/local/bin:/usr/sbin" + end + + it "adds the current executing Ruby's bindir and Gem bindir to the PATH" do + env = {"PATH" => ""} + @sanity.enforce_path_sanity(env) + env["PATH"].should == "#{@ruby_bindir}:#{@gem_bindir}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + end + + it "does not create entries for Ruby/Gem bindirs if they exist in SANE_PATH or PATH" do + ruby_bindir = '/usr/bin' + gem_bindir = '/yo/gabba/gabba' + Gem.stub!(:bindir).and_return(gem_bindir) + RbConfig::CONFIG.stub!(:[]).with('bindir').and_return(ruby_bindir) + env = {"PATH" => gem_bindir} + @sanity.enforce_path_sanity(env) + env["PATH"].should == "/yo/gabba/gabba:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + end + + it "builds a valid windows path" do + ruby_bindir = 'C:\ruby\bin' + gem_bindir = 'C:\gems\bin' + Gem.stub!(:bindir).and_return(gem_bindir) + RbConfig::CONFIG.stub!(:[]).with('bindir').and_return(ruby_bindir) + Chef::Platform.stub!(:windows?).and_return(true) + env = {"PATH" => 'C:\Windows\system32;C:\mr\softie'} + @sanity.enforce_path_sanity(env) + env["PATH"].should == "C:\\Windows\\system32;C:\\mr\\softie;#{ruby_bindir};#{gem_bindir}" + end + end +end diff --git a/spec/unit/mixin/securable_spec.rb b/spec/unit/mixin/securable_spec.rb new file mode 100644 index 0000000000..d2e8770c9d --- /dev/null +++ b/spec/unit/mixin/securable_spec.rb @@ -0,0 +1,254 @@ +# +# Author:: Mark Mzyk (<mmzyk@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. +# + +require 'spec_helper' + +describe Chef::Mixin::Securable do + + before(:each) do + @securable = Object.new + @securable.send(:extend, Chef::Mixin::Securable) + @securable.send(:extend, Chef::Mixin::ParamsValidate) + end + + it "should accept a group name or id for group" do + lambda { @securable.group "root" }.should_not raise_error(ArgumentError) + lambda { @securable.group 123 }.should_not raise_error(ArgumentError) + lambda { @securable.group "root*goo" }.should raise_error(ArgumentError) + end + + it "should accept a user name or id for owner" do + lambda { @securable.owner "root" }.should_not raise_error(ArgumentError) + lambda { @securable.owner 123 }.should_not raise_error(ArgumentError) + lambda { @securable.owner "root*goo" }.should raise_error(ArgumentError) + end + + it "allows the owner to be specified as #user" do + @securable.should respond_to(:user) + end + + describe "unix-specific behavior" do + before(:each) do + platform_mock :unix do + @original_config = Chef::Config.hash_dup + load File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "chef", "config.rb") + load File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "chef", "mixin", "securable.rb") + @securable = Object.new + @securable.send(:extend, Chef::Mixin::Securable) + @securable.send(:extend, Chef::Mixin::ParamsValidate) + end + end + + after(:each) do + Chef::Config.configuration = @original_config + end + + it "should accept a group name or id for group with spaces and backslashes" do + lambda { @securable.group 'test\ group' }.should_not raise_error(ArgumentError) + end + + it "should accept a unix file mode in string form as an octal number" do + lambda { @securable.mode "0" }.should_not raise_error(ArgumentError) + lambda { @securable.mode "0000" }.should_not raise_error(ArgumentError) + lambda { @securable.mode "0111" }.should_not raise_error(ArgumentError) + lambda { @securable.mode "0444" }.should_not raise_error(ArgumentError) + + lambda { @securable.mode "111" }.should_not raise_error(ArgumentError) + lambda { @securable.mode "444" }.should_not raise_error(ArgumentError) + lambda { @securable.mode "7777" }.should_not raise_error(ArgumentError) + lambda { @securable.mode "07777" }.should_not raise_error(ArgumentError) + + lambda { @securable.mode "-01" }.should raise_error(ArgumentError) + lambda { @securable.mode "010000" }.should raise_error(ArgumentError) + lambda { @securable.mode "-1" }.should raise_error(ArgumentError) + lambda { @securable.mode "10000" }.should raise_error(ArgumentError) + + lambda { @securable.mode "07778" }.should raise_error(ArgumentError) + lambda { @securable.mode "7778" }.should raise_error(ArgumentError) + lambda { @securable.mode "4095" }.should raise_error(ArgumentError) + + lambda { @securable.mode "0foo1234" }.should raise_error(ArgumentError) + lambda { @securable.mode "foo1234" }.should raise_error(ArgumentError) + end + + it "should accept a unix file mode in numeric form as a ruby-interpreted integer" do + lambda { @securable.mode 0 }.should_not raise_error(ArgumentError) + lambda { @securable.mode 0000 }.should_not raise_error(ArgumentError) + lambda { @securable.mode 444 }.should_not raise_error(ArgumentError) + lambda { @securable.mode 0444 }.should_not raise_error(ArgumentError) + lambda { @securable.mode 07777 }.should_not raise_error(ArgumentError) + + lambda { @securable.mode 292 }.should_not raise_error(ArgumentError) + lambda { @securable.mode 4095 }.should_not raise_error(ArgumentError) + + lambda { @securable.mode 0111 }.should_not raise_error(ArgumentError) + lambda { @securable.mode 73 }.should_not raise_error(ArgumentError) + + lambda { @securable.mode -01 }.should raise_error(ArgumentError) + lambda { @securable.mode 010000 }.should raise_error(ArgumentError) + lambda { @securable.mode -1 }.should raise_error(ArgumentError) + lambda { @securable.mode 4096 }.should raise_error(ArgumentError) + end + end + + describe "windows-specific behavior" do + before(:each) do + platform_mock :windows do + @original_config = Chef::Config.hash_dup + load File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "chef", "config.rb") + load File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "chef", "mixin", "securable.rb") + @securable = Object.new + @securable.send(:extend, Chef::Mixin::Securable) + @securable.send(:extend, Chef::Mixin::ParamsValidate) + end + end + + after(:all) do + Chef::Config.configuration = @original_config if @original_config + end + + after(:each) do + Chef::Config.configuration = @original_config if @original_config + end + + it "should not accept a group name or id for group with spaces and multiple backslashes" do + lambda { @securable.group 'test\ \group' }.should raise_error(ArgumentError) + end + + it "should accept a unix file mode in string form as an octal number" do + lambda { @securable.mode "0" }.should_not raise_error(ArgumentError) + lambda { @securable.mode "0000" }.should_not raise_error(ArgumentError) + lambda { @securable.mode "0111" }.should_not raise_error(ArgumentError) + lambda { @securable.mode "0444" }.should_not raise_error(ArgumentError) + + lambda { @securable.mode "111" }.should_not raise_error(ArgumentError) + lambda { @securable.mode "444" }.should_not raise_error(ArgumentError) + lambda { @securable.mode "7777" }.should raise_error(ArgumentError) + lambda { @securable.mode "07777" }.should raise_error(ArgumentError) + + lambda { @securable.mode "-01" }.should raise_error(ArgumentError) + lambda { @securable.mode "010000" }.should raise_error(ArgumentError) + lambda { @securable.mode "-1" }.should raise_error(ArgumentError) + lambda { @securable.mode "10000" }.should raise_error(ArgumentError) + + lambda { @securable.mode "07778" }.should raise_error(ArgumentError) + lambda { @securable.mode "7778" }.should raise_error(ArgumentError) + lambda { @securable.mode "4095" }.should raise_error(ArgumentError) + + lambda { @securable.mode "0foo1234" }.should raise_error(ArgumentError) + lambda { @securable.mode "foo1234" }.should raise_error(ArgumentError) + end + + it "should accept a unix file mode in numeric form as a ruby-interpreted integer" do + lambda { @securable.mode 0 }.should_not raise_error(ArgumentError) + lambda { @securable.mode 0000 }.should_not raise_error(ArgumentError) + lambda { @securable.mode 444 }.should_not raise_error(ArgumentError) + lambda { @securable.mode 0444 }.should_not raise_error(ArgumentError) + lambda { @securable.mode 07777 }.should raise_error(ArgumentError) + + lambda { @securable.mode 292 }.should_not raise_error(ArgumentError) + lambda { @securable.mode 4095 }.should raise_error(ArgumentError) + + lambda { @securable.mode 0111 }.should_not raise_error(ArgumentError) + lambda { @securable.mode 73 }.should_not raise_error(ArgumentError) + + lambda { @securable.mode -01 }.should raise_error(ArgumentError) + lambda { @securable.mode 010000 }.should raise_error(ArgumentError) + lambda { @securable.mode -1 }.should raise_error(ArgumentError) + lambda { @securable.mode 4096 }.should raise_error(ArgumentError) + end + + it "should allow you to specify :full_control, :modify, :read_execute, :read, and :write rights" do + lambda { @securable.rights :full_control, "The Dude" }.should_not raise_error(ArgumentError) + lambda { @securable.rights :modify, "The Dude" }.should_not raise_error(ArgumentError) + lambda { @securable.rights :read_execute, "The Dude" }.should_not raise_error(ArgumentError) + lambda { @securable.rights :read, "The Dude" }.should_not raise_error(ArgumentError) + lambda { @securable.rights :write, "The Dude" }.should_not raise_error(ArgumentError) + lambda { @securable.rights :to_party, "The Dude" }.should raise_error(ArgumentError) + end + + it "should allow you to specify :full_control, :modify, :read_execute, :read, and :write deny_rights" do + lambda { @securable.deny_rights :full_control, "The Dude" }.should_not raise_error(ArgumentError) + lambda { @securable.deny_rights :modify, "The Dude" }.should_not raise_error(ArgumentError) + lambda { @securable.deny_rights :read_execute, "The Dude" }.should_not raise_error(ArgumentError) + lambda { @securable.deny_rights :read, "The Dude" }.should_not raise_error(ArgumentError) + lambda { @securable.deny_rights :write, "The Dude" }.should_not raise_error(ArgumentError) + lambda { @securable.deny_rights :to_party, "The Dude" }.should raise_error(ArgumentError) + end + + it "should accept a principal as a string or an array" do + lambda { @securable.rights :read, "The Dude" }.should_not raise_error(ArgumentError) + lambda { @securable.rights :read, ["The Dude","Donny"] }.should_not raise_error(ArgumentError) + lambda { @securable.rights :read, 3 }.should raise_error(ArgumentError) + end + + it "should allow you to specify whether the permissions applies_to_children with true/false/:containers_only/:objects_only" do + lambda { @securable.rights :read, "The Dude", :applies_to_children => false }.should_not raise_error(ArgumentError) + lambda { @securable.rights :read, "The Dude", :applies_to_children => true }.should_not raise_error(ArgumentError) + lambda { @securable.rights :read, "The Dude", :applies_to_children => :containers_only }.should_not raise_error(ArgumentError) + lambda { @securable.rights :read, "The Dude", :applies_to_children => :objects_only }.should_not raise_error(ArgumentError) + lambda { @securable.rights :read, "The Dude", :applies_to_children => 'poop' }.should raise_error(ArgumentError) + end + + it "should allow you to specify whether the permissions applies_to_self with true/false" do + lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :applies_to_self => false }.should_not raise_error(ArgumentError) + lambda { @securable.rights :read, "The Dude", :applies_to_self => true }.should_not raise_error(ArgumentError) + lambda { @securable.rights :read, "The Dude", :applies_to_self => 'poop' }.should raise_error(ArgumentError) + end + + it "should allow you to specify whether the permissions applies one_level_deep with true/false" do + lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :one_level_deep => false }.should_not raise_error(ArgumentError) + lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :one_level_deep => true }.should_not raise_error(ArgumentError) + lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :one_level_deep => 'poop' }.should raise_error(ArgumentError) + end + + it "should allow multiple rights and deny_rights declarations" do + @securable.rights :read, "The Dude" + @securable.deny_rights :full_control, "The Dude" + @securable.rights :full_control, "The Dude" + @securable.rights :write, "The Dude" + @securable.deny_rights :read, "The Dude" + @securable.rights.size.should == 3 + @securable.deny_rights.size.should == 2 + end + + it "should allow you to specify whether the permission applies_to_self only if you specified applies_to_children" do + lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :applies_to_self => true }.should_not raise_error(ArgumentError) + lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :applies_to_self => false }.should_not raise_error(ArgumentError) + lambda { @securable.rights :read, "The Dude", :applies_to_children => false, :applies_to_self => true }.should_not raise_error(ArgumentError) + lambda { @securable.rights :read, "The Dude", :applies_to_children => false, :applies_to_self => false }.should raise_error(ArgumentError) + lambda { @securable.rights :read, "The Dude", :applies_to_self => true }.should_not raise_error(ArgumentError) + lambda { @securable.rights :read, "The Dude", :applies_to_self => false }.should_not raise_error(ArgumentError) + end + + it "should allow you to specify whether the permission applies one_level_deep only if you specified applies_to_children" do + lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :one_level_deep => true }.should_not raise_error(ArgumentError) + lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :one_level_deep => false }.should_not raise_error(ArgumentError) + lambda { @securable.rights :read, "The Dude", :applies_to_children => false, :one_level_deep => true }.should raise_error(ArgumentError) + lambda { @securable.rights :read, "The Dude", :applies_to_children => false, :one_level_deep => false }.should_not raise_error(ArgumentError) + lambda { @securable.rights :read, "The Dude", :one_level_deep => true }.should_not raise_error(ArgumentError) + lambda { @securable.rights :read, "The Dude", :one_level_deep => false }.should_not raise_error(ArgumentError) + end + + it "should allow you to specify whether the permissions inherit with true/false" do + lambda { @securable.inherits true }.should_not raise_error(ArgumentError) + lambda { @securable.inherits false }.should_not raise_error(ArgumentError) + lambda { @securable.inherits "monkey" }.should raise_error(ArgumentError) + end + end +end diff --git a/spec/unit/mixin/shell_out_spec.rb b/spec/unit/mixin/shell_out_spec.rb new file mode 100644 index 0000000000..6ca700fcdb --- /dev/null +++ b/spec/unit/mixin/shell_out_spec.rb @@ -0,0 +1,109 @@ +# +# Author:: Ho-Sheng Hsiao (hosh@opscode.com) +# Code derived from spec/unit/mixin/command_spec.rb +# +# Original header: +# Author:: Hongli Lai (hongli@phusion.nl) +# Copyright:: Copyright (c) 2009 Phusion +# 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 'spec_helper' + +describe Chef::Mixin::ShellOut do + include Chef::Mixin::ShellOut + + describe '#run_command_compatible_options' do + subject { run_command_compatible_options(command_args) } + let(:command_args) { [ cmd, options ] } + let(:cmd) { "echo '#{rand(1000)}'" } + + let(:output) { StringIO.new } + let(:capture_log_output) { Chef::Log.logger = Logger.new(output) } + let(:assume_deprecation_log_level) { Chef::Log.stub!(:level).and_return(:warn) } + + context 'without options' do + let(:command_args) { [ cmd ] } + + it 'should not edit command args' do + should eql(command_args) + end + end + + context 'without deprecated options' do + let(:options) { { :environment => environment } } + let(:environment) { { 'LC_ALL' => 'C' } } + + it 'should not edit command args' do + should eql(command_args) + end + end + + def self.should_emit_deprecation_warning_about(old_option, new_option) + it 'should emit a deprecation warning' do + assume_deprecation_log_level and capture_log_output + subject + output.string.should match /DEPRECATION:/ + output.string.should match Regexp.escape(old_option.to_s) + output.string.should match Regexp.escape(new_option.to_s) + end + end + + context 'with :command_log_level option' do + let(:options) { { :command_log_level => command_log_level } } + let(:command_log_level) { :warn } + + it 'should convert :command_log_level to :log_level' do + should eql [ cmd, { :log_level => command_log_level } ] + end + + should_emit_deprecation_warning_about :command_log_level, :log_level + end + + context 'with :command_log_prepend option' do + let(:options) { { :command_log_prepend => command_log_prepend } } + let(:command_log_prepend) { 'PROVIDER:' } + + it 'should convert :command_log_prepend to :log_tag' do + should eql [ cmd, { :log_tag => command_log_prepend } ] + end + + should_emit_deprecation_warning_about :command_log_prepend, :log_tag + end + + context "with 'command_log_level' option" do + let(:options) { { 'command_log_level' => command_log_level } } + let(:command_log_level) { :warn } + + it "should convert 'command_log_level' to :log_level" do + should eql [ cmd, { :log_level => command_log_level } ] + end + + should_emit_deprecation_warning_about :command_log_level, :log_level + end + + context "with 'command_log_prepend' option" do + let(:options) { { 'command_log_prepend' => command_log_prepend } } + let(:command_log_prepend) { 'PROVIDER:' } + + it "should convert 'command_log_prepend' to :log_tag" do + should eql [ cmd, { :log_tag => command_log_prepend } ] + end + + should_emit_deprecation_warning_about :command_log_prepend, :log_tag + end + + end +end diff --git a/spec/unit/mixin/template_spec.rb b/spec/unit/mixin/template_spec.rb new file mode 100644 index 0000000000..3d8a723a75 --- /dev/null +++ b/spec/unit/mixin/template_spec.rb @@ -0,0 +1,104 @@ +# +# Author:: Adam Jacob (<adam@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. +# + +require 'spec_helper' + +class TinyTemplateClass; include Chef::Mixin::Template; end +require 'cgi' +describe Chef::Mixin::Template, "render_template" do + + before :each do + @template = TinyTemplateClass.new + end + + it "should render the template evaluated in the given context" do + @template.render_template("<%= @foo %>", { :foo => "bar" }) do |tmp| + tmp.open.read.should == "bar" + end + end + + it "should provide a node method to access @node" do + @template.render_template("<%= node %>",{:node => "tehShizzle"}) do |tmp| + tmp.open.read.should == "tehShizzle" + end + end + + it "should yield the tempfile it renders the template to" do + @template.render_template("abcdef", {}) do |tempfile| + tempfile.should be_kind_of(Tempfile) + end + end + + describe "when an exception is raised in the template" do + def do_raise + @context = {:chef => "cool"} + @template.render_template("foo\nbar\nbaz\n<%= this_is_not_defined %>\nquin\nqunx\ndunno", @context) {|r| r} + end + + it "should catch and re-raise the exception as a TemplateError" do + lambda { do_raise }.should raise_error(Chef::Mixin::Template::TemplateError) + end + + it "should raise an error if an attempt is made to access node but it is nil" do + lambda {@template.render_template("<%= node %>",{}) {|r| r}}.should raise_error(Chef::Mixin::Template::TemplateError) + end + + describe "the raised TemplateError" do + before :each do + begin + do_raise + rescue Chef::Mixin::Template::TemplateError => e + @exception = e + end + end + + it "should have the original exception" do + @exception.original_exception.should be + @exception.original_exception.message.should =~ /undefined local variable or method `this_is_not_defined'/ + end + + it "should determine the line number of the exception" do + @exception.line_number.should == 4 + end + + it "should provide a source listing of the template around the exception" do + @exception.source_listing.should == " 2: bar\n 3: baz\n 4: <%= this_is_not_defined %>\n 5: quin\n 6: qunx" + end + + it "should provide the evaluation context of the template" do + @exception.context.should == @context + end + + it "should defer the message to the original exception" do + @exception.message.should =~ /undefined local variable or method `this_is_not_defined'/ + end + + it "should provide a nice source location" do + @exception.source_location.should == "on line #4" + end + + it "should create a pretty output for the terminal" do + @exception.to_s.should =~ /Chef::Mixin::Template::TemplateError/ + @exception.to_s.should =~ /undefined local variable or method `this_is_not_defined'/ + @exception.to_s.should include(" 2: bar\n 3: baz\n 4: <%= this_is_not_defined %>\n 5: quin\n 6: qunx") + @exception.to_s.should include(@exception.original_exception.backtrace.first) + end + end + end +end + diff --git a/spec/unit/mixin/xml_escape_spec.rb b/spec/unit/mixin/xml_escape_spec.rb new file mode 100644 index 0000000000..d05854ade4 --- /dev/null +++ b/spec/unit/mixin/xml_escape_spec.rb @@ -0,0 +1,54 @@ +# +# Author:: Adam Jacob (<adam@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. +# + +require 'spec_helper' + +class XMLEscapingTestHarness + include Chef::Mixin::XMLEscape +end + +describe Chef::Mixin::XMLEscape do + before do + @escaper = XMLEscapingTestHarness.new + end + + it "escapes ampersands to '&'" do + @escaper.xml_escape("&").should == "&" + end + + it "escapes angle brackets to < or >" do + @escaper.xml_escape("<").should == "<" + @escaper.xml_escape(">").should == ">" + end + + it "does not modify ASCII strings" do + @escaper.xml_escape('foobarbaz!@#$%^*()').should == 'foobarbaz!@#$%^*()' + end + + it "converts invalid bytes to asterisks" do + @escaper.xml_escape("\x00").should == "*" + end + + it "converts UTF-8 correctly" do + @escaper.xml_escape("\xC2\xA9").should == '©' + end + + it "converts win 1252 characters correctly" do + @escaper.xml_escape("\x80").should == '€' + end +end |