diff options
Diffstat (limited to 'spec/support')
20 files changed, 802 insertions, 84 deletions
diff --git a/spec/support/key_helpers.rb b/spec/support/key_helpers.rb new file mode 100644 index 0000000000..076f709380 --- /dev/null +++ b/spec/support/key_helpers.rb @@ -0,0 +1,104 @@ +# +# Author:: Tyler Cloke (<tyler@chef.io>) +# Copyright:: Copyright (c) 2015 Chef Software, 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' + +shared_examples_for "a knife key command" do + let(:stderr) { StringIO.new } + let(:command) do + c = described_class.new([]) + c.ui.config[:disable_editing] = true + allow(c.ui).to receive(:stderr).and_return(stderr) + allow(c.ui).to receive(:stdout).and_return(stderr) + allow(c).to receive(:show_usage) + c + end + + context "before apply_params! is called" do + context "when apply_params! is called with invalid args (missing actor)" do + let(:params) { [] } + it "shows the usage" do + expect(command).to receive(:show_usage) + expect { command.apply_params!(params) }.to exit_with_code(1) + end + + it "outputs the proper error" do + expect { command.apply_params!(params) }.to exit_with_code(1) + expect(stderr.string).to include(command.actor_missing_error) + end + + it "exits 1" do + expect { command.apply_params!(params) }.to exit_with_code(1) + end + end + end # before apply_params! is called + + context "after apply_params! is called with valid args" do + before do + command.apply_params!(params) + end + + it "properly defines the actor" do + expect(command.actor).to eq("charmander") + end + end # after apply_params! is called with valid args + + context "when the command is run" do + before do + allow(command).to receive(:service_object).and_return(service_object) + allow(command).to receive(:name_args).and_return(["charmander"]) + end + + context "when the command is successful" do + before do + expect(service_object).to receive(:run) + end + end + end +end # a knife key command + +shared_examples_for "a knife key command with a keyname as the second arg" do + let(:stderr) { StringIO.new } + let(:command) do + c = described_class.new([]) + c.ui.config[:disable_editing] = true + allow(c.ui).to receive(:stderr).and_return(stderr) + allow(c.ui).to receive(:stdout).and_return(stderr) + allow(c).to receive(:show_usage) + c + end + + context "before apply_params! is called" do + context "when apply_params! is called with invalid args (missing keyname)" do + let(:params) { ["charmander"] } + it "shows the usage" do + expect(command).to receive(:show_usage) + expect { command.apply_params!(params) }.to exit_with_code(1) + end + + it "outputs the proper error" do + expect { command.apply_params!(params) }.to exit_with_code(1) + expect(stderr.string).to include(command.keyname_missing_error) + end + + it "exits 1" do + expect { command.apply_params!(params) }.to exit_with_code(1) + end + end + end # before apply_params! is called +end diff --git a/spec/support/lib/chef/provider/openldap_includer.rb b/spec/support/lib/chef/provider/openldap_includer.rb new file mode 100644 index 0000000000..afb0c7cf01 --- /dev/null +++ b/spec/support/lib/chef/provider/openldap_includer.rb @@ -0,0 +1,29 @@ +# +# 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. +# + +class Chef + class Provider + class OpenldapIncluder < Chef::Provider::LWRPBase + provides :openldap_includer + + def action_run + include_recipe "openldap::default" + end + end + end +end diff --git a/spec/support/lib/chef/resource/cat.rb b/spec/support/lib/chef/resource/cat.rb index ecca50cb53..efc78aa59c 100644 --- a/spec/support/lib/chef/resource/cat.rb +++ b/spec/support/lib/chef/resource/cat.rb @@ -23,7 +23,6 @@ class Chef attr_accessor :action def initialize(name, run_context=nil) - @resource_name = :cat super @action = "sell" end diff --git a/spec/support/lib/chef/resource/one_two_three_four.rb b/spec/support/lib/chef/resource/one_two_three_four.rb index 296d2cd970..8f273a0cda 100644 --- a/spec/support/lib/chef/resource/one_two_three_four.rb +++ b/spec/support/lib/chef/resource/one_two_three_four.rb @@ -19,12 +19,8 @@ class Chef class Resource class OneTwoThreeFour < Chef::Resource - attr_reader :i_can_count - def initialize(name, run_context) - @resource_name = :one_two_three_four - super - end + attr_reader :i_can_count def i_can_count(tf) @i_can_count = tf diff --git a/spec/support/lib/chef/resource/openldap_includer.rb b/spec/support/lib/chef/resource/openldap_includer.rb new file mode 100644 index 0000000000..6f443b4c7c --- /dev/null +++ b/spec/support/lib/chef/resource/openldap_includer.rb @@ -0,0 +1,27 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008, 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. +# + + +class Chef + class Resource + class OpenldapIncluder < Chef::Resource::LWRPBase + allowed_actions :run + default_action :run + end + end +end diff --git a/spec/support/lib/chef/resource/with_state.rb b/spec/support/lib/chef/resource/with_state.rb index 226de0a6d2..773ae7ddb8 100644 --- a/spec/support/lib/chef/resource/with_state.rb +++ b/spec/support/lib/chef/resource/with_state.rb @@ -23,15 +23,6 @@ class Chef class Resource class WithState < Chef::Resource attr_accessor :state - - def initialize(name, run_context=nil) - @resource_name = :with_state - super - end - - def state - @state - end end end end diff --git a/spec/support/lib/chef/resource/zen_follower.rb b/spec/support/lib/chef/resource/zen_follower.rb index ddc289e48d..155e6ae729 100644 --- a/spec/support/lib/chef/resource/zen_follower.rb +++ b/spec/support/lib/chef/resource/zen_follower.rb @@ -24,11 +24,6 @@ class Chef provides :follower, platform: "zen" - def initialize(name, run_context=nil) - @resource_name = :zen_follower - super - end - def master(arg=nil) if !arg.nil? @master = arg diff --git a/spec/support/lib/chef/resource/zen_master.rb b/spec/support/lib/chef/resource/zen_master.rb index d47d174e28..4106549d79 100644 --- a/spec/support/lib/chef/resource/zen_master.rb +++ b/spec/support/lib/chef/resource/zen_master.rb @@ -22,13 +22,10 @@ require 'chef/json_compat' class Chef class Resource class ZenMaster < Chef::Resource + allowed_actions :win, :score + attr_reader :peace - def initialize(name, run_context=nil) - @resource_name = :zen_master - super - allowed_actions << :win << :score - end def peace(tf) @peace = tf diff --git a/spec/support/mock/platform.rb b/spec/support/mock/platform.rb index ab2c19baff..7eae82fe7d 100644 --- a/spec/support/mock/platform.rb +++ b/spec/support/mock/platform.rb @@ -6,7 +6,7 @@ # testing code that mixes in platform specific modules like +Chef::Mixin::Securable+ # or +Chef::FileAccessControl+ def platform_mock(platform = :unix, &block) - allow(Chef::Platform).to receive(:windows?).and_return(platform == :windows ? true : false) + allow(ChefConfig).to receive(:windows?).and_return(platform == :windows ? true : false) ENV['SYSTEMDRIVE'] = (platform == :windows ? 'C:' : nil) if platform == :windows diff --git a/spec/support/shared/context/client.rb b/spec/support/shared/context/client.rb new file mode 100644 index 0000000000..eb537e9889 --- /dev/null +++ b/spec/support/shared/context/client.rb @@ -0,0 +1,277 @@ + +require 'spec_helper' + +# Stubs a basic client object +shared_context "client" do + let(:fqdn) { "hostname.example.org" } + let(:hostname) { "hostname" } + let(:machinename) { "machinename.example.org" } + let(:platform) { "example-platform" } + let(:platform_version) { "example-platform-1.0" } + + let(:ohai_data) do + { + :fqdn => fqdn, + :hostname => hostname, + :machinename => machinename, + :platform => platform, + :platform_version => platform_version + } + end + + let(:ohai_system) do + ohai = instance_double("Ohai::System", :all_plugins => true, :data => ohai_data) + allow(ohai).to receive(:[]) do |k| + ohai_data[k] + end + ohai + end + + let(:node) do + Chef::Node.new.tap do |n| + n.name(fqdn) + n.chef_environment("_default") + end + end + + let(:json_attribs) { nil } + let(:client_opts) { {} } + + let(:client) do + Chef::Config[:event_loggers] = [] + Chef::Client.new(json_attribs, client_opts).tap do |c| + c.node = node + end + end + + before do + Chef::Log.logger = Logger.new(StringIO.new) + + # Node/Ohai data + #Chef::Config[:node_name] = fqdn + allow(Ohai::System).to receive(:new).and_return(ohai_system) + end +end + +# Stubs a client for a client run. +# Requires a client object be defined in the scope of this included context. +# e.g.: +# describe "some functionality" do +# include_context "client" +# include_context "a client run" +# ... +# end +shared_context "a client run" do + let(:stdout) { StringIO.new } + let(:stderr) { StringIO.new } + + let(:api_client_exists?) { false } + let(:enable_fork) { false } + + let(:http_cookbook_sync) { double("Chef::REST (cookbook sync)") } + let(:http_node_load) { double("Chef::REST (node)") } + let(:http_node_save) { double("Chef::REST (node save)") } + + let(:runner) { instance_double("Chef::Runner") } + let(:audit_runner) { instance_double("Chef::Audit::Runner", :failed? => false) } + + def stub_for_register + # --Client.register + # Make sure Client#register thinks the client key doesn't + # exist, so it tries to register and create one. + allow(File).to receive(:exists?).and_call_original + expect(File).to receive(:exists?). + with(Chef::Config[:client_key]). + exactly(:once). + and_return(api_client_exists?) + + unless api_client_exists? + # Client.register will register with the validation client name. + expect_any_instance_of(Chef::ApiClient::Registration).to receive(:run) + end + end + + def stub_for_node_load + # Client.register will then turn around create another + # Chef::REST object, this time with the client key it got from the + # previous step. + expect(Chef::REST).to receive(:new). + with(Chef::Config[:chef_server_url], fqdn, Chef::Config[:client_key]). + exactly(:once). + and_return(http_node_load) + + # --Client#build_node + # looks up the node, which we will return, then later saves it. + expect(Chef::Node).to receive(:find_or_create).with(fqdn).and_return(node) + + # --ResourceReporter#node_load_completed + # gets a run id from the server for storing resource history + # (has its own tests, so stubbing it here.) + expect_any_instance_of(Chef::ResourceReporter).to receive(:node_load_completed) + end + + def stub_for_sync_cookbooks + # --Client#setup_run_context + # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync + # + expect_any_instance_of(Chef::CookbookSynchronizer).to receive(:sync_cookbooks) + expect(Chef::REST).to receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_cookbook_sync) + expect(http_cookbook_sync).to receive(:post). + with("environments/_default/cookbook_versions", {:run_list => []}). + and_return({}) + end + + def stub_for_converge + # define me + end + + def stub_for_audit + # define me + end + + def stub_for_node_save + # define me + end + + def stub_for_run + # define me + end + + before do + Chef::Config[:client_fork] = enable_fork + Chef::Config[:cache_path] = windows? ? 'C:\chef' : '/var/chef' + Chef::Config[:why_run] = false + Chef::Config[:audit_mode] = :enabled + + stub_const("Chef::Client::STDOUT_FD", stdout) + stub_const("Chef::Client::STDERR_FD", stderr) + + stub_for_register + stub_for_node_load + stub_for_sync_cookbooks + stub_for_converge + stub_for_audit + stub_for_node_save + + expect_any_instance_of(Chef::RunLock).to receive(:acquire) + expect_any_instance_of(Chef::RunLock).to receive(:save_pid) + expect_any_instance_of(Chef::RunLock).to receive(:release) + + # Post conditions: check that node has been filled in correctly + expect(client).to receive(:run_started) + + stub_for_run + end +end + +shared_context "converge completed" do + def stub_for_converge + # --Client#converge + expect(Chef::Runner).to receive(:new).and_return(runner) + expect(runner).to receive(:converge).and_return(true) + end + + def stub_for_node_save + allow(node).to receive(:data_for_save).and_return(node.for_json) + + # --Client#save_updated_node + expect(Chef::REST).to receive(:new).with(Chef::Config[:chef_server_url], fqdn, Chef::Config[:client_key], validate_utf8: false).and_return(http_node_save) + expect(http_node_save).to receive(:put_rest).with("nodes/#{fqdn}", node.for_json).and_return(true) + end +end + +shared_context "converge failed" do + let(:converge_error) do + err = Chef::Exceptions::UnsupportedAction.new("Action unsupported") + err.set_backtrace([ "/path/recipe.rb:15", "/path/recipe.rb:12" ]) + err + end + + def stub_for_converge + expect(Chef::Runner).to receive(:new).and_return(runner) + expect(runner).to receive(:converge).and_raise(converge_error) + end + + def stub_for_node_save + expect(client).to_not receive(:save_updated_node) + end +end + +shared_context "audit phase completed" do + def stub_for_audit + # -- Client#run_audits + expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner) + expect(audit_runner).to receive(:run).and_return(true) + expect(client.events).to receive(:audit_phase_complete) + end +end + +shared_context "audit phase failed with error" do + let(:audit_error) do + err = RuntimeError.new("Unexpected audit error") + err.set_backtrace([ "/path/recipe.rb:57", "/path/recipe.rb:55" ]) + err + end + + def stub_for_audit + expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner) + expect(Chef::Audit::Logger).to receive(:read_buffer).and_return("Audit mode output!") + expect(audit_runner).to receive(:run).and_raise(audit_error) + expect(client.events).to receive(:audit_phase_failed).with(audit_error, "Audit mode output!") + end +end + +shared_context "audit phase completed with failed controls" do + let(:audit_runner) { instance_double("Chef::Audit::Runner", :failed? => true, + :num_failed => 1, :num_total => 3) } + + let(:audit_error) do + err = Chef::Exceptions::AuditsFailed.new(audit_runner.num_failed, audit_runner.num_total) + err.set_backtrace([ "/path/recipe.rb:108", "/path/recipe.rb:103" ]) + err + end + + def stub_for_audit + expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner) + expect(Chef::Audit::Logger).to receive(:read_buffer).and_return("Audit mode output!") + expect(audit_runner).to receive(:run) + expect(Chef::Exceptions::AuditsFailed).to receive(:new).with( + audit_runner.num_failed, audit_runner.num_total + ).and_return(audit_error) + expect(client.events).to receive(:audit_phase_failed).with(audit_error, "Audit mode output!") + end +end + +shared_context "run completed" do + def stub_for_run + expect(client).to receive(:run_completed_successfully) + + # --ResourceReporter#run_completed + # updates the server with the resource history + # (has its own tests, so stubbing it here.) + expect_any_instance_of(Chef::ResourceReporter).to receive(:run_completed) + # --AuditReporter#run_completed + # posts the audit data to server. + # (has its own tests, so stubbing it here.) + expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_completed) + end +end + +shared_context "run failed" do + def stub_for_run + expect(client).to receive(:run_failed) + + # --ResourceReporter#run_completed + # updates the server with the resource history + # (has its own tests, so stubbing it here.) + expect_any_instance_of(Chef::ResourceReporter).to receive(:run_failed) + # --AuditReporter#run_completed + # posts the audit data to server. + # (has its own tests, so stubbing it here.) + expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_failed) + end + + before do + expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError) + end +end diff --git a/spec/support/shared/examples/client.rb b/spec/support/shared/examples/client.rb new file mode 100644 index 0000000000..330cb40ac6 --- /dev/null +++ b/spec/support/shared/examples/client.rb @@ -0,0 +1,53 @@ + +require 'spec_helper' +require 'spec/support/shared/context/client' + +# requires platform and platform_version be defined +shared_examples "a completed run" do + include_context "run completed" + + it "runs ohai, sets up authentication, loads node state, synchronizes policy, converges, and runs audits" do + # This is what we're testing. + expect(client.run).to be true + + # fork is stubbed, so we can see the outcome of the run + expect(node.automatic_attrs[:platform]).to eq(platform) + expect(node.automatic_attrs[:platform_version]).to eq(platform_version) + end +end + +shared_examples "a completed run with audit failure" do + include_context "run completed" + + before do + expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError) + end + + it "converges, runs audits, saves the node and raises the error in a wrapping error" do + expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error| + expect(error.wrapped_errors.size).to eq(run_errors.size) + run_errors.each do |run_error| + expect(error.wrapped_errors).to include(run_error) + expect(error.backtrace).to include(*run_error.backtrace) + end + end + + # fork is stubbed, so we can see the outcome of the run + expect(node.automatic_attrs[:platform]).to eq(platform) + expect(node.automatic_attrs[:platform_version]).to eq(platform_version) + end +end + +shared_examples "a failed run" do + include_context "run failed" + + it "skips node save and raises the error in a wrapping error" do + expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error| + expect(error.wrapped_errors.size).to eq(run_errors.size) + run_errors.each do |run_error| + expect(error.wrapped_errors).to include(run_error) + expect(error.backtrace).to include(*run_error.backtrace) + end + end + end +end diff --git a/spec/support/shared/functional/file_resource.rb b/spec/support/shared/functional/file_resource.rb index 4f8e2f5b71..3ce3c9c94e 100644 --- a/spec/support/shared/functional/file_resource.rb +++ b/spec/support/shared/functional/file_resource.rb @@ -592,10 +592,6 @@ shared_examples_for "a configured file resource" do File.open(path, "wb") { |f| f.write(wrong_content) } end - it "updates the source file content" do - skip - end - it "marks the resource as updated" do resource.run_action(:create) expect(resource).to be_updated_by_last_action diff --git a/spec/support/shared/functional/securable_resource.rb b/spec/support/shared/functional/securable_resource.rb index e016bb685d..b3c32356aa 100644 --- a/spec/support/shared/functional/securable_resource.rb +++ b/spec/support/shared/functional/securable_resource.rb @@ -163,9 +163,6 @@ shared_examples_for "a securable resource with existing target" do let(:desired_gid) { 1337 } let(:expected_gid) { 1337 } - skip "should set an owner (Rerun specs under root)", :requires_unprivileged_user => true - skip "should set a group (Rerun specs under root)", :requires_unprivileged_user => true - describe "when setting the owner", :requires_root do before do resource.owner expected_user_name @@ -205,11 +202,6 @@ shared_examples_for "a securable resource with existing target" do resource.run_action(:create) end - it "should set permissions as specified" do - pending("Linux does not support lchmod") - expect{ File.lstat(path).mode & 007777 }.to eq(@mode_string.oct & 007777) - end - it "is marked as updated only if changes are made" do expect(resource.updated_by_last_action?).to eq(expect_updated?) end @@ -222,15 +214,28 @@ shared_examples_for "a securable resource with existing target" do resource.run_action(:create) end - it "should set permissions in numeric form as a ruby-interpreted octal" do - pending('Linux does not support lchmod') - expect{ File.lstat(path).mode & 007777 }.to eq(@mode_integer & 007777) - end - it "is marked as updated only if changes are made" do expect(resource.updated_by_last_action?).to eq(expect_updated?) end end + + describe "when setting the suid bit", :requires_root do + before do + @suid_mode = 04776 + resource.mode @suid_mode + resource.run_action(:create) + end + + it "should set the suid bit" do + expect(File.lstat(path).mode & 007777).to eq(@suid_mode & 007777) + end + + it "should retain the suid bit when updating the user" do + resource.user 1338 + resource.run_action(:create) + expect(File.lstat(path).mode & 007777).to eq(@suid_mode & 007777) + end + end end context "on Windows", :windows_only do @@ -288,17 +293,13 @@ shared_examples_for "a securable resource without existing target" do include_context "diff disabled" - context "on Unix", :unix_only do - skip "if we need any securable resource tests on Unix without existing target resource." - end - context "on Windows", :windows_only do include_context "use Windows permissions" - it "sets owner to Administrators on create if owner is not specified" do + it "leaves owner as system default on create if owner is not specified" do expect(File.exist?(path)).to eq(false) resource.run_action(:create) - expect(descriptor.owner).to eq(SID.Administrators) + expect(descriptor.owner).to eq(SID.default_security_object_owner) end it "sets owner when owner is specified" do @@ -318,22 +319,24 @@ shared_examples_for "a securable resource without existing target" do 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' + arbitrary_non_default_owner = SID.Guest + expect(arbitrary_non_default_owner).not_to eq(SID.default_security_object_owner) + + resource.owner 'Guest' # Change to arbitrary_non_default_owner once issue #1508 is fixed resource.run_action(:create) - expect(descriptor.owner).to eq(SID.Guest) + expect(descriptor.owner).to eq(arbitrary_non_default_owner) new_resource = create_resource expect(new_resource.owner).to eq(nil) new_resource.run_action(:create) - expect(descriptor.owner).to eq(SID.Guest) + expect(descriptor.owner).to eq(arbitrary_non_default_owner) end - it "sets group to None on create if group is not specified" do + it "leaves group as system default on create if group is not specified" do expect(resource.group).to eq(nil) expect(File.exist?(path)).to eq(false) resource.run_action(:create) - expect(descriptor.group).to eq(SID.None) + expect(descriptor.group).to eq(SID.default_security_object_group) end it "sets group when group is specified" do @@ -346,23 +349,18 @@ shared_examples_for "a securable resource without existing target" do expect { resource.group 'Lance "The Nose" Glindenberry III' }.to 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") - resource.group "#{ENV['COMPUTERNAME']}\\Administrators" - resource.run_action(:create) - expect{ descriptor.group }.to eq(SID.Everyone) - 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' + arbitrary_non_default_group = SID.Everyone + expect(arbitrary_non_default_group).not_to eq(SID.default_security_object_group) + + resource.group 'Everyone' # Change to arbitrary_non_default_group once issue #1508 is fixed resource.run_action(:create) - expect(descriptor.group).to eq(SID.Everyone) + expect(descriptor.group).to eq(arbitrary_non_default_group) new_resource = create_resource expect(new_resource.group).to eq(nil) new_resource.run_action(:create) - expect(descriptor.group).to eq(SID.Everyone) + expect(descriptor.group).to eq(arbitrary_non_default_group) end describe "with rights and deny_rights attributes" do diff --git a/spec/support/shared/functional/securable_resource_with_reporting.rb b/spec/support/shared/functional/securable_resource_with_reporting.rb index 37fc538801..3176ebba0d 100644 --- a/spec/support/shared/functional/securable_resource_with_reporting.rb +++ b/spec/support/shared/functional/securable_resource_with_reporting.rb @@ -279,14 +279,14 @@ shared_examples_for "a securable resource with reporting" do end it "has empty values for file metadata in 'current_resource'" do - pending "windows reporting not yet fully supported" + skip "windows reporting not yet fully supported" expect(current_resource.owner).to be_nil expect(current_resource.expanded_rights).to be_nil end context "and no security metadata is specified in new_resource" do before do - pending "windows reporting not yet fully supported" + skip "windows reporting not yet fully supported" end it "sets the metadata values on the new_resource as strings after creating" do @@ -322,7 +322,7 @@ shared_examples_for "a securable resource with reporting" do let(:expected_user_name) { 'domain\user' } before do - pending "windows reporting not yet fully supported" + skip "windows reporting not yet fully supported" resource.owner(expected_user_name) resource.run_action(:create) end @@ -336,7 +336,7 @@ shared_examples_for "a securable resource with reporting" do context "when the target file exists" do before do - pending "windows reporting not yet fully supported" + skip "windows reporting not yet fully supported" FileUtils.touch(resource.path) resource.action(:create) end diff --git a/spec/support/shared/functional/windows_script.rb b/spec/support/shared/functional/windows_script.rb index 35b86dc4e8..3499cc98ec 100644 --- a/spec/support/shared/functional/windows_script.rb +++ b/spec/support/shared/functional/windows_script.rb @@ -114,7 +114,7 @@ shared_context Chef::Resource::WindowsScript do describe "when the run action is invoked on Windows" do it "executes the script code" do - resource.code("@whoami > #{script_output_path}") + resource.code("whoami > #{script_output_path}") resource.returns(0) resource.run_action(:run) end diff --git a/spec/support/shared/integration/integration_helper.rb b/spec/support/shared/integration/integration_helper.rb index e6942c62af..927ff2f42b 100644 --- a/spec/support/shared/integration/integration_helper.rb +++ b/spec/support/shared/integration/integration_helper.rb @@ -22,14 +22,19 @@ require 'fileutils' require 'chef/config' require 'chef/json_compat' require 'chef/server_api' -require 'chef_zero/rspec' require 'support/shared/integration/knife_support' require 'support/shared/integration/app_server_support' +require 'cheffish/rspec/chef_run_support' require 'spec_helper' module IntegrationSupport include ChefZero::RSpec + def self.included(includer_class) + includer_class.extend(Cheffish::RSpec::ChefRunSupport) + includer_class.extend(ClassMethods) + end + module ClassMethods include ChefZero::RSpec @@ -49,10 +54,6 @@ module IntegrationSupport end end - def self.included(includer_class) - includer_class.extend(ClassMethods) - end - def api Chef::ServerAPI.new end diff --git a/spec/support/shared/unit/api_versioning.rb b/spec/support/shared/unit/api_versioning.rb new file mode 100644 index 0000000000..a4f353de60 --- /dev/null +++ b/spec/support/shared/unit/api_versioning.rb @@ -0,0 +1,77 @@ +# +# Author:: Tyler Cloke (<tyler@chef.io>) +# Copyright:: Copyright (c) 2015 Chef Software, 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/exceptions" + +shared_examples_for "version handling" do + let(:response_406) { OpenStruct.new(:code => '406') } + let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) } + + before do + allow(rest_v1).to receive(http_verb).and_raise(exception_406) + end + + context "when the server does not support the min or max server API version that Chef::User supports" do + before do + allow(object).to receive(:server_client_api_version_intersection).and_return([]) + end + + it "raises the original exception" do + expect{ object.send(method) }.to raise_error(exception_406) + end + end # when the server does not support the min or max server API version that Chef::User supports +end # version handling + +shared_examples_for "user and client reregister" do + let(:response_406) { OpenStruct.new(:code => '406') } + let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) } + let(:generic_exception) { Exception.new } + let(:min_version) { "2" } + let(:max_version) { "5" } + let(:return_hash_406) { + { + "min_version" => min_version, + "max_version" => max_version, + "request_version" => "30" + } + } + + context "when V0 is not supported by the server" do + context "when the exception is 406 and returns x-ops-server-api-version header" do + before do + allow(rest_v0).to receive(:put).and_raise(exception_406) + allow(response_406).to receive(:[]).with('x-ops-server-api-version').and_return(Chef::JSONCompat.to_json(return_hash_406)) + end + + it "raises an error about only V0 being supported" do + expect(object).to receive(:reregister_only_v0_supported_error_msg).with(max_version, min_version) + expect{ object.reregister }.to raise_error(Chef::Exceptions::OnlyApiVersion0SupportedForAction) + end + + end + context "when the exception is not versioning related" do + before do + allow(rest_v0).to receive(:put).and_raise(generic_exception) + end + + it "raises the original error" do + expect{ object.reregister }.to raise_error(generic_exception) + end + end + end +end diff --git a/spec/support/shared/unit/knife_shared.rb b/spec/support/shared/unit/knife_shared.rb new file mode 100644 index 0000000000..8c9010f3cf --- /dev/null +++ b/spec/support/shared/unit/knife_shared.rb @@ -0,0 +1,40 @@ +# +# Author:: Tyler Cloke (<tyler@chef.io>) +# Copyright:: Copyright (c) 2015 Chef Software, 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 "mandatory field missing" do + context "when field is nil" do + before do + knife.name_args = name_args + end + + it "exits 1" do + expect { knife.run }.to raise_error(SystemExit) + end + + it "prints the usage" do + expect(knife).to receive(:show_usage) + expect { knife.run }.to raise_error(SystemExit) + end + + it "prints a relevant error message" do + expect { knife.run }.to raise_error(SystemExit) + expect(stderr.string).to match /You must specify a #{fieldname}/ + end + end +end diff --git a/spec/support/shared/unit/provider/file.rb b/spec/support/shared/unit/provider/file.rb index 86f32c9e89..7de9698451 100644 --- a/spec/support/shared/unit/provider/file.rb +++ b/spec/support/shared/unit/provider/file.rb @@ -255,7 +255,7 @@ shared_examples_for Chef::Provider::File do context "examining file security metadata on Unix with a file that exists" do before do # fake that we're on unix even if we're on windows - allow(Chef::Platform).to receive(:windows?).and_return(false) + allow(ChefConfig).to receive(:windows?).and_return(false) # mock up the filesystem to behave like unix setup_normal_file stat_struct = double("::File.stat", :mode => 0600, :uid => 0, :gid => 0, :mtime => 10000) @@ -331,7 +331,7 @@ shared_examples_for Chef::Provider::File do context "examining file security metadata on Unix with a file that does not exist" do before do # fake that we're on unix even if we're on windows - allow(Chef::Platform).to receive(:windows?).and_return(false) + allow(ChefConfig).to receive(:windows?).and_return(false) setup_missing_file end @@ -380,7 +380,7 @@ shared_examples_for Chef::Provider::File do before do # fake that we're on unix even if we're on windows - allow(Chef::Platform).to receive(:windows?).and_return(false) + allow(ChefConfig).to receive(:windows?).and_return(false) # mock up the filesystem to behave like unix setup_normal_file stat_struct = double("::File.stat", :mode => 0600, :uid => 0, :gid => 0, :mtime => 10000) @@ -529,26 +529,49 @@ shared_examples_for Chef::Provider::File do :for_reporting => diff_for_reporting ) allow(diff).to receive(:diff).with(resource_path, tempfile_path).and_return(true) expect(provider).to receive(:diff).at_least(:once).and_return(diff) - expect(provider).to receive(:managing_content?).at_least(:once).and_return(true) expect(provider).to receive(:checksum).with(tempfile_path).and_return(tempfile_sha256) - expect(provider).to receive(:checksum).with(resource_path).and_return(tempfile_sha256) + allow(provider).to receive(:managing_content?).and_return(true) + allow(provider).to receive(:checksum).with(resource_path).and_return(tempfile_sha256) + expect(resource).not_to receive(:checksum).with(tempfile_sha256) # do not mutate the new resource expect(provider.deployment_strategy).to receive(:deploy).with(tempfile_path, normalized_path) end context "when the file was created" do before { expect(provider).to receive(:needs_creating?).at_least(:once).and_return(true) } - it "does not backup the file and does not produce a diff for reporting" do + it "does not backup the file" do expect(provider).not_to receive(:do_backup) provider.send(:do_contents_changes) + end + + it "does not produce a diff for reporting" do + provider.send(:do_contents_changes) expect(resource.diff).to be_nil end + + it "renders the final checksum correctly for reporting" do + provider.send(:do_contents_changes) + expect(resource.state_for_resource_reporter[:checksum]).to eql(tempfile_sha256) + end end context "when the file was not created" do - before { expect(provider).to receive(:needs_creating?).at_least(:once).and_return(false) } - it "backs up the file and produces a diff for reporting" do + before do + allow(provider).to receive(:do_backup) # stub do_backup + expect(provider).to receive(:needs_creating?).at_least(:once).and_return(false) + end + + it "backs up the file" do expect(provider).to receive(:do_backup) provider.send(:do_contents_changes) + end + + it "produces a diff for reporting" do + provider.send(:do_contents_changes) expect(resource.diff).to eq(diff_for_reporting) end + + it "renders the final checksum correctly for reporting" do + provider.send(:do_contents_changes) + expect(resource.state_for_resource_reporter[:checksum]).to eql(tempfile_sha256) + end end end diff --git a/spec/support/shared/unit/user_and_client_shared.rb b/spec/support/shared/unit/user_and_client_shared.rb new file mode 100644 index 0000000000..bc5ffa07c2 --- /dev/null +++ b/spec/support/shared/unit/user_and_client_shared.rb @@ -0,0 +1,115 @@ +# +# Author:: Tyler Cloke (<tyler@chef.io>) +# Copyright:: Copyright (c) 2015 Chef Software, 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 "user or client create" do + + context "when server API V1 is valid on the Chef Server receiving the request" do + + it "creates a new object via the API" do + expect(rest_v1).to receive(:post).with(url, payload).and_return({}) + object.create + end + + it "creates a new object via the API with a public_key when it exists" do + object.public_key "some_public_key" + expect(rest_v1).to receive(:post).with(url, payload.merge({:public_key => "some_public_key"})).and_return({}) + object.create + end + + context "raise error when create_key and public_key are both set" do + + before do + object.public_key "key" + object.create_key true + end + + it "rasies the proper error" do + expect { object.create }.to raise_error(error) + end + end + + context "when create_key == true" do + before do + object.create_key true + end + + it "creates a new object via the API with create_key" do + expect(rest_v1).to receive(:post).with(url, payload.merge({:create_key => true})).and_return({}) + object.create + end + end + + context "when chef_key is returned by the server" do + let(:chef_key) { + { + "chef_key" => { + "public_key" => "some_public_key" + } + } + } + + it "puts the public key into the objectr returned by create" do + expect(rest_v1).to receive(:post).with(url, payload).and_return(payload.merge(chef_key)) + new_object = object.create + expect(new_object.public_key).to eq("some_public_key") + end + + context "when private_key is returned in chef_key" do + let(:chef_key) { + { + "chef_key" => { + "public_key" => "some_public_key", + "private_key" => "some_private_key" + } + } + } + + it "puts the private key into the object returned by create" do + expect(rest_v1).to receive(:post).with(url, payload).and_return(payload.merge(chef_key)) + new_object = object.create + expect(new_object.private_key).to eq("some_private_key") + end + end + end # when chef_key is returned by the server + + end # when server API V1 is valid on the Chef Server receiving the request + + context "when server API V1 is not valid on the Chef Server receiving the request" do + + context "when the server supports API V0" do + before do + allow(object).to receive(:server_client_api_version_intersection).and_return([0]) + allow(rest_v1).to receive(:post).and_raise(exception_406) + end + + it "creates a new object via the API" do + expect(rest_v0).to receive(:post).with(url, payload).and_return({}) + object.create + end + + it "creates a new object via the API with a public_key when it exists" do + object.public_key "some_public_key" + expect(rest_v0).to receive(:post).with(url, payload.merge({:public_key => "some_public_key"})).and_return({}) + object.create + end + + end # when the server supports API V0 + end # when server API V1 is not valid on the Chef Server receiving the request + +end # user or client create + |