diff options
Diffstat (limited to 'spec/unit')
126 files changed, 7115 insertions, 2905 deletions
diff --git a/spec/unit/api_client_spec.rb b/spec/unit/api_client_spec.rb index 7668e31f5a..ba0eca3284 100644 --- a/spec/unit/api_client_spec.rb +++ b/spec/unit/api_client_spec.rb @@ -53,6 +53,20 @@ describe Chef::ApiClient do expect { @client.admin(Hash.new) }.to raise_error(ArgumentError) end + it "has an create_key flag attribute" do + @client.create_key(true) + expect(@client.create_key).to be_truthy + end + + it "create_key defaults to false" do + expect(@client.create_key).to be_falsey + end + + it "allows only boolean values for the create_key flag" do + expect { @client.create_key(false) }.not_to raise_error + expect { @client.create_key(Hash.new) }.to raise_error(ArgumentError) + end + it "has a 'validator' flag attribute" do @client.validator(true) expect(@client.validator).to be_truthy @@ -115,6 +129,12 @@ describe Chef::ApiClient do expect(@json).to include(%q{"validator":false}) end + it "includes the 'create_key' flag when present" do + @client.create_key(true) + @json = @client.to_json + expect(@json).to include(%q{"create_key":true}) + end + it "includes the private key when present" do @client.private_key("monkeypants") expect(@client.to_json).to include(%q{"private_key":"monkeypants"}) @@ -131,7 +151,7 @@ describe Chef::ApiClient do describe "when deserializing from JSON (string) using ApiClient#from_json" do let(:client_string) do - "{\"name\":\"black\",\"public_key\":\"crowes\",\"private_key\":\"monkeypants\",\"admin\":true,\"validator\":true}" + "{\"name\":\"black\",\"public_key\":\"crowes\",\"private_key\":\"monkeypants\",\"admin\":true,\"validator\":true,\"create_key\":true}" end let(:client) do @@ -158,6 +178,10 @@ describe Chef::ApiClient do expect(client.admin).to be_truthy end + it "preserves the create_key status" do + expect(client.create_key).to be_truthy + end + it "preserves the 'validator' status" do expect(client.validator).to be_truthy end @@ -175,6 +199,7 @@ describe Chef::ApiClient do "private_key" => "monkeypants", "admin" => true, "validator" => true, + "create_key" => true, "json_class" => "Chef::ApiClient" } end @@ -199,6 +224,10 @@ describe Chef::ApiClient do expect(client.admin).to be_truthy end + it "preserves the create_key status" do + expect(client.create_key).to be_truthy + end + it "preserves the 'validator' status" do expect(client.validator).to be_truthy end @@ -214,14 +243,16 @@ describe Chef::ApiClient do before(:each) do client = { - "name" => "black", - "clientname" => "black", - "public_key" => "crowes", - "private_key" => "monkeypants", - "admin" => true, - "validator" => true, - "json_class" => "Chef::ApiClient" + "name" => "black", + "clientname" => "black", + "public_key" => "crowes", + "private_key" => "monkeypants", + "admin" => true, + "create_key" => true, + "validator" => true, + "json_class" => "Chef::ApiClient" } + @http_client = double("Chef::REST mock") allow(Chef::REST).to receive(:new).and_return(@http_client) expect(@http_client).to receive(:get).with("clients/black").and_return(client) @@ -244,6 +275,10 @@ describe Chef::ApiClient do expect(@client.admin).to be_a_kind_of(TrueClass) end + it "preserves the create_key status" do + expect(@client.create_key).to be_a_kind_of(TrueClass) + end + it "preserves the 'validator' status" do expect(@client.validator).to be_a_kind_of(TrueClass) end @@ -297,24 +332,34 @@ describe Chef::ApiClient do end context "and the client exists" do + let(:chef_rest_v0_mock) { double('chef rest root v0 object') } + let(:payload) { + {:name => "lost-my-key", :admin => false, :validator => false, :private_key => true} + } + before do @api_client_without_key = Chef::ApiClient.new @api_client_without_key.name("lost-my-key") - expect(@http_client).to receive(:get).with("clients/lost-my-key").and_return(@api_client_without_key) - end + allow(@api_client_without_key).to receive(:chef_rest_v0).and_return(chef_rest_v0_mock) + #allow(@api_client_with_key).to receive(:http_api).and_return(_api_mock) + allow(chef_rest_v0_mock).to receive(:put).with("clients/lost-my-key", payload).and_return(@api_client_with_key) + allow(chef_rest_v0_mock).to receive(:get).with("clients/lost-my-key").and_return(@api_client_without_key) + allow(@http_client).to receive(:get).with("clients/lost-my-key").and_return(@api_client_without_key) + end context "and the client exists on a Chef 11-like server" do before do @api_client_with_key = Chef::ApiClient.new @api_client_with_key.name("lost-my-key") @api_client_with_key.private_key("the new private key") - expect(@http_client).to receive(:put). - with("clients/lost-my-key", :name => "lost-my-key", :admin => false, :validator => false, :private_key => true). - and_return(@api_client_with_key) + allow(@api_client_with_key).to receive(:chef_rest_v0).and_return(chef_rest_v0_mock) end it "returns an ApiClient with a private key" do + expect(chef_rest_v0_mock).to receive(:put).with("clients/lost-my-key", payload). + and_return(@api_client_with_key) + response = Chef::ApiClient.reregister("lost-my-key") # no sane == method for ApiClient :'( expect(response).to eq(@api_client_without_key) @@ -327,7 +372,7 @@ describe Chef::ApiClient do context "and the client exists on a Chef 10-like server" do before do @api_client_with_key = {"name" => "lost-my-key", "private_key" => "the new private key"} - expect(@http_client).to receive(:put). + expect(chef_rest_v0_mock).to receive(:put). with("clients/lost-my-key", :name => "lost-my-key", :admin => false, :validator => false, :private_key => true). and_return(@api_client_with_key) end @@ -345,4 +390,134 @@ describe Chef::ApiClient do end end + + describe "Versioned API Interactions" do + let(:response_406) { OpenStruct.new(:code => '406') } + let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) } + let(:payload) { + { + :name => "some_name", + :validator => true, + :admin => true + } + } + + before do + @client = Chef::ApiClient.new + allow(@client).to receive(:chef_rest_v0).and_return(double('chef rest root v0 object')) + allow(@client).to receive(:chef_rest_v1).and_return(double('chef rest root v1 object')) + @client.name "some_name" + @client.validator true + @client.admin true + end + + describe "create" do + + # from spec/support/shared/unit/user_and_client_shared.rb + it_should_behave_like "user or client create" do + let(:object) { @client } + let(:error) { Chef::Exceptions::InvalidClientAttribute } + let(:rest_v0) { @client.chef_rest_v0 } + let(:rest_v1) { @client.chef_rest_v1 } + let(:url) { "clients" } + end + + context "when API V1 is not supported by the server" do + # from spec/support/shared/unit/api_versioning.rb + it_should_behave_like "version handling" do + let(:object) { @client } + let(:method) { :create } + let(:http_verb) { :post } + let(:rest_v1) { @client.chef_rest_v1 } + end + end + + end # create + + describe "update" do + context "when a valid client is defined" do + + shared_examples_for "client updating" do + it "updates the client" do + expect(rest). to receive(:put).with("clients/some_name", payload) + @client.update + end + + context "when only the name field exists" do + + before do + # needed since there is no way to set to nil via code + @client.instance_variable_set(:@validator, nil) + @client.instance_variable_set(:@admin, nil) + end + + after do + @client.validator true + @client.admin true + end + + it "updates the client with only the name" do + expect(rest). to receive(:put).with("clients/some_name", {:name => "some_name"}) + @client.update + end + end + + end + + context "when API V1 is supported by the server" do + + it_should_behave_like "client updating" do + let(:rest) { @client.chef_rest_v1 } + end + + end # when API V1 is supported by the server + + context "when API V1 is not supported by the server" do + context "when no version is supported" do + # from spec/support/shared/unit/api_versioning.rb + it_should_behave_like "version handling" do + let(:object) { @client } + let(:method) { :create } + let(:http_verb) { :post } + let(:rest_v1) { @client.chef_rest_v1 } + end + end # when no version is supported + + context "when API V0 is supported" do + + before do + allow(@client.chef_rest_v1).to receive(:put).and_raise(exception_406) + allow(@client).to receive(:server_client_api_version_intersection).and_return([0]) + end + + it_should_behave_like "client updating" do + let(:rest) { @client.chef_rest_v0 } + end + + end + + end # when API V1 is not supported by the server + end # when a valid client is defined + end # update + + # DEPRECATION + # This can be removed after API V0 support is gone + describe "reregister" do + context "when server API V0 is valid on the Chef Server receiving the request" do + it "creates a new object via the API" do + expect(@client.chef_rest_v0).to receive(:put).with("clients/#{@client.name}", payload.merge({:private_key => true})).and_return({}) + @client.reregister + end + end # when server API V0 is valid on the Chef Server receiving the request + + context "when server API V0 is not supported by the Chef Server" do + # from spec/support/shared/unit/api_versioning.rb + it_should_behave_like "user and client reregister" do + let(:object) { @client } + let(:rest_v0) { @client.chef_rest_v0 } + end + end # when server API V0 is not supported by the Chef Server + end # reregister + + end end diff --git a/spec/unit/application/client_spec.rb b/spec/unit/application/client_spec.rb index c753ca0ab8..64a6bcc9d2 100644 --- a/spec/unit/application/client_spec.rb +++ b/spec/unit/application/client_spec.rb @@ -60,7 +60,7 @@ describe Chef::Application::Client, "reconfigure" do context "when interval is given" do before do Chef::Config[:interval] = 600 - allow(Chef::Platform).to receive(:windows?).and_return(false) + allow(ChefConfig).to receive(:windows?).and_return(false) end it "should terminate with message" do @@ -77,7 +77,7 @@ Enable chef-client interval runs by setting `:client_fork = true` in your config context "when interval is given on windows" do before do Chef::Config[:interval] = 600 - allow(Chef::Platform).to receive(:windows?).and_return(true) + allow(ChefConfig).to receive(:windows?).and_return(true) end it "should not terminate" do @@ -165,11 +165,6 @@ Enable chef-client interval runs by setting `:client_fork = true` in your config before do allow(Chef::Log).to receive(:warn) end - - it "emits a warning that audit mode is an experimental feature" do - expect(Chef::Log).to receive(:warn).with(/Audit mode is an experimental feature/) - app.reconfigure - end end shared_examples "unrecognized setting" do diff --git a/spec/unit/audit/audit_reporter_spec.rb b/spec/unit/audit/audit_reporter_spec.rb index 4bf889510a..46c2a96b4c 100644 --- a/spec/unit/audit/audit_reporter_spec.rb +++ b/spec/unit/audit/audit_reporter_spec.rb @@ -88,6 +88,29 @@ describe Chef::Audit::AuditReporter do reporter.run_completed(node) end + context "when audit phase failed" do + + let(:audit_error) { double("AuditError", :class => "Chef::Exceptions::AuditError", + :message => "Audit phase failed with error message: derpderpderp", + :backtrace => ["/path/recipe.rb:57", "/path/library.rb:106"]) } + + before do + reporter.instance_variable_set(:@audit_phase_error, audit_error) + end + + it "reports an error" do + reporter.run_completed(node) + expect(run_data).to have_key(:error) + expect(run_data).to have_key(:error) + expect(run_data[:error]).to eq <<-EOM.strip! +Chef::Exceptions::AuditError: Audit phase failed with error message: derpderpderp +/path/recipe.rb:57 +/path/library.rb:106 +EOM + end + + end + context "when unable to post to server" do let(:error) do @@ -215,9 +238,13 @@ describe Chef::Audit::AuditReporter do let(:audit_data) { Chef::Audit::AuditData.new(node.name, run_id) } let(:run_data) { audit_data.to_hash } - let(:error) { double("AuditError", :class => "Chef::Exception::AuditError", - :message => "Well that certainly didn't work", - :backtrace => ["line 0", "line 1", "line 2"]) } + let(:audit_error) { double("AuditError", :class => "Chef::Exceptions::AuditError", + :message => "Audit phase failed with error message: derpderpderp", + :backtrace => ["/path/recipe.rb:57", "/path/library.rb:106"]) } + + let(:run_error) { double("RunError", :class => "Chef::Exceptions::RunError", + :message => "This error shouldn't be reported.", + :backtrace => ["fix it", "fix it", "fix it"]) } before do allow(reporter).to receive(:auditing_enabled?).and_return(true) @@ -226,15 +253,32 @@ describe Chef::Audit::AuditReporter do allow(audit_data).to receive(:to_hash).and_return(run_data) end - it "adds the error information to the reported data" do - expect(rest).to receive(:create_url) - expect(rest).to receive(:post) - reporter.run_failed(error) - expect(run_data).to have_key(:error) - expect(run_data[:error]).to eq "Chef::Exception::AuditError: Well that certainly didn't work\n" + - "line 0\nline 1\nline 2" + context "when no prior exception is stored" do + it "reports no error" do + expect(rest).to receive(:create_url) + expect(rest).to receive(:post) + reporter.run_failed(run_error) + expect(run_data).to_not have_key(:error) + end end + context "when some prior exception is stored" do + before do + reporter.instance_variable_set(:@audit_phase_error, audit_error) + end + + it "reports the prior error" do + expect(rest).to receive(:create_url) + expect(rest).to receive(:post) + reporter.run_failed(run_error) + expect(run_data).to have_key(:error) + expect(run_data[:error]).to eq <<-EOM.strip! +Chef::Exceptions::AuditError: Audit phase failed with error message: derpderpderp +/path/recipe.rb:57 +/path/library.rb:106 +EOM + end + end end shared_context "audit data" do @@ -270,14 +314,14 @@ describe Chef::Audit::AuditReporter do it "notifies audit phase finished to debug log" do expect(Chef::Log).to receive(:debug).with(/Audit Reporter completed/) - reporter.audit_phase_complete + reporter.audit_phase_complete("Output from audit mode") end it "collects audit data" do ordered_control_groups.each do |_name, group| expect(audit_data).to receive(:add_control_group).with(group) end - reporter.audit_phase_complete + reporter.audit_phase_complete("Output from audit mode") end end @@ -288,14 +332,14 @@ describe Chef::Audit::AuditReporter do it "notifies audit phase failed to debug log" do expect(Chef::Log).to receive(:debug).with(/Audit Reporter failed/) - reporter.audit_phase_failed(error) + reporter.audit_phase_failed(error, "Output from audit mode") end it "collects audit data" do ordered_control_groups.each do |_name, group| expect(audit_data).to receive(:add_control_group).with(group) end - reporter.audit_phase_failed(error) + reporter.audit_phase_failed(error, "Output from audit mode") end end diff --git a/spec/unit/audit/logger_spec.rb b/spec/unit/audit/logger_spec.rb new file mode 100644 index 0000000000..9dd9ce2cd9 --- /dev/null +++ b/spec/unit/audit/logger_spec.rb @@ -0,0 +1,42 @@ +# +# Copyright:: Copyright (c) 2014 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' + +describe Chef::Audit::Logger do + + before(:each) do + Chef::Audit::Logger.instance_variable_set(:@buffer, nil) + end + + it 'calling puts creates @buffer and adds the message' do + Chef::Audit::Logger.puts("Output message") + expect(Chef::Audit::Logger.read_buffer).to eq("Output message\n") + end + + it 'calling puts multiple times adds to the message' do + Chef::Audit::Logger.puts("Output message") + Chef::Audit::Logger.puts("Output message") + Chef::Audit::Logger.puts("Output message") + expect(Chef::Audit::Logger.read_buffer).to eq("Output message\nOutput message\nOutput message\n") + end + + it 'calling it before @buffer is set returns an empty string' do + expect(Chef::Audit::Logger.read_buffer).to eq("") + end + +end diff --git a/spec/unit/audit/runner_spec.rb b/spec/unit/audit/runner_spec.rb index 0bd4c18388..1de024260f 100644 --- a/spec/unit/audit/runner_spec.rb +++ b/spec/unit/audit/runner_spec.rb @@ -68,8 +68,8 @@ describe Chef::Audit::Runner do in_sub_process do runner.send(:setup) - expect(RSpec.configuration.output_stream).to eq(log_location) - expect(RSpec.configuration.error_stream).to eq(log_location) + expect(RSpec.configuration.output_stream).to eq(Chef::Audit::Logger) + expect(RSpec.configuration.error_stream).to eq(Chef::Audit::Logger) expect(RSpec.configuration.formatters.size).to eq(2) expect(RSpec.configuration.formatters).to include(instance_of(Chef::Audit::AuditEventProxy)) diff --git a/spec/unit/chef_fs/file_pattern_spec.rb b/spec/unit/chef_fs/file_pattern_spec.rb index a9f06e8424..ed5f314605 100644 --- a/spec/unit/chef_fs/file_pattern_spec.rb +++ b/spec/unit/chef_fs/file_pattern_spec.rb @@ -157,7 +157,7 @@ describe Chef::ChefFS::FilePattern do end end - context 'with simple pattern "a\*\b"', :pending => (Chef::Platform.windows?) do + context 'with simple pattern "a\*\b"', :skip => (Chef::Platform.windows?) do let(:pattern) { Chef::ChefFS::FilePattern.new('a\*\b') } it 'match?' do expect(pattern.match?('a*b')).to be_truthy @@ -264,7 +264,7 @@ describe Chef::ChefFS::FilePattern do end end - context 'with star pattern "/abc/d[a-z][0-9]f/ghi"', :pending => (Chef::Platform.windows?) do + context 'with star pattern "/abc/d[a-z][0-9]f/ghi"', :skip => (Chef::Platform.windows?) do let(:pattern) { Chef::ChefFS::FilePattern.new('/abc/d[a-z][0-9]f/ghi') } it 'match?' do expect(pattern.match?('/abc/de1f/ghi')).to be_truthy @@ -352,11 +352,7 @@ describe Chef::ChefFS::FilePattern do expect(pattern.could_match_children?('/abc/def/ghi')).to be_truthy expect(pattern.could_match_children?('abc')).to be_falsey end - it 'could_match_children? /abc** returns false for /xyz' do - pending 'Make could_match_children? more rigorous' - # At the moment, we return false for this, but in the end it would be nice to return true: - expect(pattern.could_match_children?('/xyz')).to be_falsey - end + it 'exact_child_name_under' do expect(pattern.exact_child_name_under('/')).to eq(nil) expect(pattern.exact_child_name_under('/abc')).to eq(nil) @@ -440,14 +436,6 @@ describe Chef::ChefFS::FilePattern do expect(p('/.').exact_path).to eq('/') expect(p('/.').match?('/')).to be_truthy end - it 'handles dot by itself', :pending => "decide what to do with dot by itself" do - expect(p('.').normalized_pattern).to eq('.') - expect(p('.').exact_path).to eq('.') - expect(p('.').match?('.')).to be_truthy - expect(p('./').normalized_pattern).to eq('.') - expect(p('./').exact_path).to eq('.') - expect(p('./').match?('.')).to be_truthy - end it 'handles dotdot' do expect(p('abc/../def').normalized_pattern).to eq('def') expect(p('abc/../def').exact_path).to eq('def') diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb index fa8317744c..1e4bbb5c56 100644 --- a/spec/unit/client_spec.rb +++ b/spec/unit/client_spec.rb @@ -19,6 +19,8 @@ # require 'spec_helper' +require 'spec/support/shared/context/client' +require 'spec/support/shared/examples/client' require 'chef/run_context' require 'chef/rest' @@ -28,55 +30,7 @@ class FooError < RuntimeError end describe Chef::Client do - - let(:hostname) { "hostname" } - let(:machinename) { "machinename.example.org" } - let(:fqdn) { "hostname.example.org" } - - let(:ohai_data) do - { :fqdn => fqdn, - :hostname => hostname, - :machinename => machinename, - :platform => 'example-platform', - :platform_version => 'example-platform-1.0', - :data => {} - } - end - - let(:ohai_system) do - ohai_system = double( "Ohai::System", - :all_plugins => true, - :data => ohai_data) - allow(ohai_system).to receive(:[]) do |key| - ohai_data[key] - end - ohai_system - 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 + include_context "client" context "when minimal ohai is configured" do before do @@ -88,7 +42,6 @@ describe Chef::Client do expect(ohai_system).to receive(:all_plugins).with(expected_filter) client.run_ohai end - end describe "authentication protocol selection" do @@ -117,7 +70,6 @@ describe Chef::Client do describe "configuring output formatters" do context "when no formatter has been configured" do - context "and STDOUT is a TTY" do before do allow(STDOUT).to receive(:tty?).and_return(true) @@ -203,135 +155,12 @@ describe Chef::Client do end describe "a full client run" do - shared_context "a client run" do - let(:http_node_load) { double("Chef::REST (node)") } - let(:http_cookbook_sync) { double("Chef::REST (cookbook sync)") } - let(:http_node_save) { double("Chef::REST (node save)") } - let(:runner) { double("Chef::Runner") } - let(:audit_runner) { instance_double("Chef::Audit::Runner", :failed? => false) } - - let(:api_client_exists?) { false } - - let(:stdout) { StringIO.new } - let(:stderr) { StringIO.new } - - let(:enable_fork) { 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 - # --Client#converge - expect(Chef::Runner).to receive(:new).and_return(runner) - expect(runner).to receive(:converge).and_return(true) - end - - 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) - 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]).and_return(http_node_save) - expect(http_node_save).to receive(:put_rest).with("nodes/#{fqdn}", node.for_json).and_return(true) - end - - def stub_for_run - 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) - 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 - - 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 - stub_for_run - end - end - shared_examples_for "a successful client run" do include_context "a client run" + include_context "converge completed" + include_context "audit phase completed" - it "runs ohai, sets up authentication, loads node state, synchronizes policy, converges, and runs audits" do - # This is what we're testing. - client.run - - # fork is stubbed, so we can see the outcome of the run - expect(node.automatic_attrs[:platform]).to eq("example-platform") - expect(node.automatic_attrs[:platform_version]).to eq("example-platform-1.0") - end + include_examples "a completed run" end describe "when running chef-client without fork" do @@ -339,24 +168,19 @@ describe Chef::Client do end describe "when the client key already exists" do - let(:api_client_exists?) { true } - include_examples "a successful client run" + include_examples "a successful client run" do + let(:api_client_exists?) { true } + end end - describe "when an override run list is given" do - let(:client_opts) { {:override_runlist => "recipe[override_recipe]"} } - - it "should permit spaces in overriding run list" do + context "when an override run list is given" do + it "permits spaces in overriding run list" do Chef::Client.new(nil, :override_runlist => 'role[a], role[b]') end - describe "when running the client" do + describe "calling run" do include_examples "a successful client run" do - - before do - # Client will try to compile and run override_recipe - expect_any_instance_of(Chef::RunContext::CookbookCompiler).to receive(:compile) - end + let(:client_opts) { {:override_runlist => "recipe[override_recipe]"} } def stub_for_sync_cookbooks # --Client#setup_run_context @@ -373,13 +197,22 @@ describe Chef::Client do # Expect NO node save expect(node).not_to receive(:save) end + + before do + # Client will try to compile and run override_recipe + expect_any_instance_of(Chef::RunContext::CookbookCompiler).to receive(:compile) + end end end end describe "when a permanent run list is passed as an option" do - include_examples "a successful client run" do + it "sets the new run list on the node" do + client.run + expect(node.run_list).to eq(Chef::RunList.new(new_runlist)) + end + include_examples "a successful client run" do let(:new_runlist) { "recipe[new_run_list_recipe]" } let(:client_opts) { {:runlist => new_runlist} } @@ -399,214 +232,61 @@ describe Chef::Client do # do not create a fixture for this. expect_any_instance_of(Chef::RunContext::CookbookCompiler).to receive(:compile) end - - it "sets the new run list on the node" do - client.run - expect(node.run_list).to eq(Chef::RunList.new(new_runlist)) - end end end - describe "when converge fails" do - include_context "a client run" do - let(:e) { Exception.new } - def stub_for_converge - expect(Chef::Runner).to receive(:new).and_return(runner) - expect(runner).to receive(:converge).and_raise(e) - expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError) - end - - def stub_for_node_save - expect(client).to_not receive(:save_updated_node) - end - - def stub_for_run - 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) - expect(client).to receive(:run_failed) + describe "when converge completes successfully" do + include_context "a client run" + include_context "converge completed" - expect_any_instance_of(Chef::ResourceReporter).to receive(:run_failed) - expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_failed) + describe "when audit phase errors" do + include_context "audit phase failed with error" + include_examples "a completed run with audit failure" do + let(:run_errors) { [audit_error] } end end - it "runs the audits and raises the error" do - expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error| - expect(error.wrapped_errors.size).to eq(1) - expect(error.wrapped_errors[0]).to eq(e) - end + describe "when audit phase completed" do + include_context "audit phase completed" + include_examples "a completed run" end - end - - describe "when the audit phase fails" do - context "with an exception" do - context "when audit mode is enabled" do - include_context "a client run" do - let(:e) { Exception.new } - def stub_for_audit - expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner) - expect(audit_runner).to receive(:run).and_raise(e) - expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError) - end - - def stub_for_run - 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) - expect(client).to receive(:run_failed) - - expect_any_instance_of(Chef::ResourceReporter).to receive(:run_failed) - expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_failed) - end - end - it "should save the node after converge and raise exception" do - expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error| - expect(error.wrapped_errors.size).to eq(1) - expect(error.wrapped_errors[0]).to eq(e) - end - end - end - - context "when audit mode is disabled" do - include_context "a client run" do - before do - Chef::Config[:audit_mode] = :disabled - end - - let(:e) { FooError.new } - - def stub_for_audit - expect(Chef::Audit::Runner).to_not receive(:new) - end - - def stub_for_converge - expect(Chef::Runner).to receive(:new).and_return(runner) - expect(runner).to receive(:converge).and_raise(e) - expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(FooError) - end - - def stub_for_node_save - expect(client).to_not receive(:save_updated_node) - end - - def stub_for_run - 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) - expect(client).to receive(:run_failed) - - expect_any_instance_of(Chef::ResourceReporter).to receive(:run_failed) - - end - - it "re-raises an unwrapped exception" do - expect { client.run }.to raise_error(FooError) - end - end - end - - - end - - context "with failed audits" do - include_context "a client run" do - let(:audit_runner) do - instance_double("Chef::Audit::Runner", :run => true, :failed? => true, :num_failed => 1, :num_total => 1) - end - - def stub_for_audit - expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner) - expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError) - end - - def stub_for_run - 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) - expect(client).to receive(:run_failed) - - expect_any_instance_of(Chef::ResourceReporter).to receive(:run_failed) - expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_failed) - end - end - - it "should save the node after converge and raise exception" do - expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error| - expect(error.wrapped_errors.size).to eq(1) - expect(error.wrapped_errors[0]).to be_instance_of(Chef::Exceptions::AuditsFailed) - end + describe "when audit phase completed with failed controls" do + include_context "audit phase completed with failed controls" + include_examples "a completed run with audit failure" do + let(:run_errors) { [audit_error] } end end end - describe "when why_run mode is enabled" do - include_context "a client run" do - - before do - Chef::Config[:why_run] = true - end - - def stub_for_audit - expect(Chef::Audit::Runner).to_not receive(:new) - end - - def stub_for_node_save - # This is how we should be mocking external calls - not letting it fall all the way through to the - # REST call - expect(node).to receive(:save) - end - - it "runs successfully without enabling the audit runner" do - client.run + describe "when converge errors" do + include_context "a client run" + include_context "converge failed" - # fork is stubbed, so we can see the outcome of the run - expect(node.automatic_attrs[:platform]).to eq("example-platform") - expect(node.automatic_attrs[:platform_version]).to eq("example-platform-1.0") + describe "when audit phase errors" do + include_context "audit phase failed with error" + include_examples "a failed run" do + let(:run_errors) { [converge_error, audit_error] } end end - end - - describe "when audits are disabled" do - include_context "a client run" do - before do - Chef::Config[:audit_mode] = :disabled - end - - def stub_for_audit - expect(Chef::Audit::Runner).to_not receive(:new) + describe "when audit phase completed" do + include_context "audit phase completed" + include_examples "a failed run" do + let(:run_errors) { [converge_error] } end + end - it "runs successfully without enabling the audit runner" do - client.run - - # fork is stubbed, so we can see the outcome of the run - expect(node.automatic_attrs[:platform]).to eq("example-platform") - expect(node.automatic_attrs[:platform_version]).to eq("example-platform-1.0") + describe "when audit phase completed with failed controls" do + include_context "audit phase completed with failed controls" + include_examples "a failed run" do + let(:run_errors) { [converge_error, audit_error] } end end end - end - describe "when handling run failures" do - it "should remove the run_lock on failure of #load_node" do @run_lock = double("Chef::RunLock", :acquire => true) allow(Chef::RunLock).to receive(:new).and_return(@run_lock) @@ -680,6 +360,7 @@ describe Chef::Client do # check pre-conditions. expect(node[:roles]).to be_nil expect(node[:recipes]).to be_nil + expect(node[:expanded_run_list]).to be_nil allow(client.policy_builder).to receive(:node).and_return(node) @@ -692,7 +373,10 @@ describe Chef::Client do expect(node[:roles]).to include("role_containing_cookbook1") expect(node[:recipes]).not_to be_nil expect(node[:recipes].length).to eq(1) - expect(node[:recipes]).to include("cookbook1") + expect(node[:recipes]).to include("cookbook1::default") + expect(node[:expanded_run_list]).not_to be_nil + expect(node[:expanded_run_list].length).to eq(1) + expect(node[:expanded_run_list]).to include("cookbook1::default") end it "should set the environment from the specified configuration value" do @@ -715,7 +399,7 @@ describe Chef::Client do describe "windows_admin_check" do context "platform is not windows" do before do - allow(Chef::Platform).to receive(:windows?).and_return(false) + allow(ChefConfig).to receive(:windows?).and_return(false) end it "shouldn't be called" do @@ -726,7 +410,7 @@ describe Chef::Client do context "platform is windows" do before do - allow(Chef::Platform).to receive(:windows?).and_return(true) + allow(ChefConfig).to receive(:windows?).and_return(true) end it "should be called" do @@ -775,6 +459,7 @@ describe Chef::Client do Chef::Config[:solo] = true Chef::Config[:cookbook_path] = ["/path/to/invalid/cookbook_path"] end + context "when any directory of cookbook_path contains no cookbook" do it "raises CookbookNotFound error" do expect do @@ -819,4 +504,20 @@ describe Chef::Client do end end + + describe "always attempt to run handlers" do + subject { client } + before do + # fail on the first thing in begin block + allow_any_instance_of(Chef::RunLock).to receive(:save_pid).and_raise(NoMethodError) + end + + it "should run exception handlers on early fail" do + expect(subject).to receive(:run_failed) + expect { subject.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error| + expect(error.wrapped_errors.size).to eq 1 + expect(error.wrapped_errors).to include(NoMethodError) + end + end + end end diff --git a/spec/unit/config_spec.rb b/spec/unit/config_spec.rb deleted file mode 100644 index 6ea67246b5..0000000000 --- a/spec/unit/config_spec.rb +++ /dev/null @@ -1,544 +0,0 @@ -# -# Author:: Adam Jacob (<adam@opscode.com>) -# Author:: Kyle Goodwin (<kgoodwin@primerevenue.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' -require 'chef/exceptions' -require 'chef/util/path_helper' - -describe Chef::Config do - describe "config attribute writer: chef_server_url" do - before do - Chef::Config.chef_server_url = "https://junglist.gen.nz" - end - - it "sets the server url" do - expect(Chef::Config.chef_server_url).to eq("https://junglist.gen.nz") - end - - context "when the url has a leading space" do - before do - Chef::Config.chef_server_url = " https://junglist.gen.nz" - end - - it "strips the space from the url when setting" do - expect(Chef::Config.chef_server_url).to eq("https://junglist.gen.nz") - end - - end - - context "when the url is a frozen string" do - before do - Chef::Config.chef_server_url = " https://junglist.gen.nz".freeze - end - - it "strips the space from the url when setting without raising an error" do - expect(Chef::Config.chef_server_url).to eq("https://junglist.gen.nz") - end - end - - end - - describe "when configuring formatters" do - # if TTY and not(force-logger) - # formatter = configured formatter or default formatter - # formatter goes to STDOUT/ERR - # if log file is writeable - # log level is configured level or info - # log location is file - # else - # log level is warn - # log location is STDERR - # end - # elsif not(TTY) and force formatter - # formatter = configured formatter or default formatter - # if log_location specified - # formatter goes to log_location - # else - # formatter goes to STDOUT/ERR - # end - # else - # formatter = "null" - # log_location = configured-value or defualt - # log_level = info or defualt - # end - # - it "has an empty list of formatters by default" do - expect(Chef::Config.formatters).to eq([]) - end - - it "configures a formatter with a short name" do - Chef::Config.add_formatter(:doc) - expect(Chef::Config.formatters).to eq([[:doc, nil]]) - end - - it "configures a formatter with a file output" do - Chef::Config.add_formatter(:doc, "/var/log/formatter.log") - expect(Chef::Config.formatters).to eq([[:doc, "/var/log/formatter.log"]]) - end - - end - - describe "class method: manage_secret_key" do - before do - allow(Chef::FileCache).to receive(:load).and_return(true) - allow(Chef::FileCache).to receive(:has_key?).with("chef_server_cookie_id").and_return(false) - end - - it "should generate and store a chef server cookie id" do - expect(Chef::FileCache).to receive(:store).with("chef_server_cookie_id", /\w{40}/).and_return(true) - Chef::Config.manage_secret_key - end - - describe "when the filecache has a chef server cookie id key" do - before do - allow(Chef::FileCache).to receive(:has_key?).with("chef_server_cookie_id").and_return(true) - end - - it "should not generate and store a chef server cookie id" do - expect(Chef::FileCache).not_to receive(:store).with("chef_server_cookie_id", /\w{40}/) - Chef::Config.manage_secret_key - end - end - - end - - [ false, true ].each do |is_windows| - - context "On #{is_windows ? 'Windows' : 'Unix'}" do - def to_platform(*args) - Chef::Config.platform_specific_path(*args) - end - - before :each do - allow(Chef::Platform).to receive(:windows?).and_return(is_windows) - end - - describe "class method: platform_specific_path" do - if is_windows - it "should return a windows path on windows systems" do - path = "/etc/chef/cookbooks" - allow(Chef::Config).to receive(:env).and_return({ 'SYSTEMDRIVE' => 'C:' }) - # match on a regex that looks for the base path with an optional - # system drive at the beginning (c:) - # system drive is not hardcoded b/c it can change and b/c it is not present on linux systems - expect(Chef::Config.platform_specific_path(path)).to eq("C:\\chef\\cookbooks") - end - else - it "should return given path on non-windows systems" do - path = "/etc/chef/cookbooks" - expect(Chef::Config.platform_specific_path(path)).to eq("/etc/chef/cookbooks") - end - end - end - - describe "default values" do - let :primary_cache_path do - if is_windows - "#{Chef::Config.env['SYSTEMDRIVE']}\\chef" - else - "/var/chef" - end - end - - let :secondary_cache_path do - if is_windows - "#{Chef::Config[:user_home]}\\.chef" - else - "#{Chef::Config[:user_home]}/.chef" - end - end - - before do - if is_windows - allow(Chef::Config).to receive(:env).and_return({ 'SYSTEMDRIVE' => 'C:' }) - Chef::Config[:user_home] = 'C:\Users\charlie' - else - Chef::Config[:user_home] = '/Users/charlie' - end - - allow(Chef::Config).to receive(:path_accessible?).and_return(false) - end - - describe "Chef::Config[:cache_path]" do - context "when /var/chef exists and is accessible" do - it "defaults to /var/chef" do - allow(Chef::Config).to receive(:path_accessible?).with(to_platform("/var/chef")).and_return(true) - expect(Chef::Config[:cache_path]).to eq(primary_cache_path) - end - end - - context "when /var/chef does not exist and /var is accessible" do - it "defaults to /var/chef" do - allow(File).to receive(:exists?).with(to_platform("/var/chef")).and_return(false) - allow(Chef::Config).to receive(:path_accessible?).with(to_platform("/var")).and_return(true) - expect(Chef::Config[:cache_path]).to eq(primary_cache_path) - end - end - - context "when /var/chef does not exist and /var is not accessible" do - it "defaults to $HOME/.chef" do - allow(File).to receive(:exists?).with(to_platform("/var/chef")).and_return(false) - allow(Chef::Config).to receive(:path_accessible?).with(to_platform("/var")).and_return(false) - expect(Chef::Config[:cache_path]).to eq(secondary_cache_path) - end - end - - context "when /var/chef exists and is not accessible" do - it "defaults to $HOME/.chef" do - allow(File).to receive(:exists?).with(to_platform("/var/chef")).and_return(true) - allow(File).to receive(:readable?).with(to_platform("/var/chef")).and_return(true) - allow(File).to receive(:writable?).with(to_platform("/var/chef")).and_return(false) - - expect(Chef::Config[:cache_path]).to eq(secondary_cache_path) - end - end - - context "when chef is running in local mode" do - before do - Chef::Config.local_mode = true - end - - context "and config_dir is /a/b/c" do - before do - Chef::Config.config_dir to_platform('/a/b/c') - end - - it "cache_path is /a/b/c/local-mode-cache" do - expect(Chef::Config.cache_path).to eq(to_platform('/a/b/c/local-mode-cache')) - end - end - - context "and config_dir is /a/b/c/" do - before do - Chef::Config.config_dir to_platform('/a/b/c/') - end - - it "cache_path is /a/b/c/local-mode-cache" do - expect(Chef::Config.cache_path).to eq(to_platform('/a/b/c/local-mode-cache')) - end - end - end - end - - it "Chef::Config[:file_backup_path] defaults to /var/chef/backup" do - allow(Chef::Config).to receive(:cache_path).and_return(primary_cache_path) - backup_path = is_windows ? "#{primary_cache_path}\\backup" : "#{primary_cache_path}/backup" - expect(Chef::Config[:file_backup_path]).to eq(backup_path) - end - - it "Chef::Config[:ssl_verify_mode] defaults to :verify_peer" do - expect(Chef::Config[:ssl_verify_mode]).to eq(:verify_peer) - end - - it "Chef::Config[:ssl_ca_path] defaults to nil" do - expect(Chef::Config[:ssl_ca_path]).to be_nil - end - - # TODO can this be removed? - if !is_windows - it "Chef::Config[:ssl_ca_file] defaults to nil" do - expect(Chef::Config[:ssl_ca_file]).to be_nil - end - end - - it "Chef::Config[:data_bag_path] defaults to /var/chef/data_bags" do - allow(Chef::Config).to receive(:cache_path).and_return(primary_cache_path) - data_bag_path = is_windows ? "#{primary_cache_path}\\data_bags" : "#{primary_cache_path}/data_bags" - expect(Chef::Config[:data_bag_path]).to eq(data_bag_path) - end - - it "Chef::Config[:environment_path] defaults to /var/chef/environments" do - allow(Chef::Config).to receive(:cache_path).and_return(primary_cache_path) - environment_path = is_windows ? "#{primary_cache_path}\\environments" : "#{primary_cache_path}/environments" - expect(Chef::Config[:environment_path]).to eq(environment_path) - end - - describe "setting the config dir" do - - context "when the config file is /etc/chef/client.rb" do - - before do - Chef::Config.config_file = to_platform("/etc/chef/client.rb") - end - - it "config_dir is /etc/chef" do - expect(Chef::Config.config_dir).to eq(to_platform("/etc/chef")) - end - - context "and chef is running in local mode" do - before do - Chef::Config.local_mode = true - end - - it "config_dir is /etc/chef" do - expect(Chef::Config.config_dir).to eq(to_platform("/etc/chef")) - end - end - - context "when config_dir is set to /other/config/dir/" do - before do - Chef::Config.config_dir = to_platform("/other/config/dir/") - end - - it "yields the explicit value" do - expect(Chef::Config.config_dir).to eq(to_platform("/other/config/dir/")) - end - end - - end - - context "when the user's home dir is /home/charlie/" do - before do - Chef::Config.user_home = to_platform("/home/charlie") - end - - it "config_dir is /home/charlie/.chef/" do - expect(Chef::Config.config_dir).to eq(Chef::Util::PathHelper.join(to_platform("/home/charlie/.chef"), '')) - end - - context "and chef is running in local mode" do - before do - Chef::Config.local_mode = true - end - - it "config_dir is /home/charlie/.chef/" do - expect(Chef::Config.config_dir).to eq(Chef::Util::PathHelper.join(to_platform("/home/charlie/.chef"), '')) - end - end - end - - end - - if is_windows - describe "finding the windows embedded dir" do - let(:default_config_location) { "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-11.6.0/lib/chef/config.rb" } - let(:alternate_install_location) { "c:/my/alternate/install/place/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-11.6.0/lib/chef/config.rb" } - let(:non_omnibus_location) { "c:/my/dev/stuff/lib/ruby/gems/1.9.1/gems/chef-11.6.0/lib/chef/config.rb" } - - let(:default_ca_file) { "c:/opscode/chef/embedded/ssl/certs/cacert.pem" } - - it "finds the embedded dir in the default location" do - allow(Chef::Config).to receive(:_this_file).and_return(default_config_location) - expect(Chef::Config.embedded_dir).to eq("c:/opscode/chef/embedded") - end - - it "finds the embedded dir in a custom install location" do - allow(Chef::Config).to receive(:_this_file).and_return(alternate_install_location) - expect(Chef::Config.embedded_dir).to eq("c:/my/alternate/install/place/chef/embedded") - end - - it "doesn't error when not in an omnibus install" do - allow(Chef::Config).to receive(:_this_file).and_return(non_omnibus_location) - expect(Chef::Config.embedded_dir).to be_nil - end - - it "sets the ssl_ca_cert path if the cert file is available" do - allow(Chef::Config).to receive(:_this_file).and_return(default_config_location) - allow(File).to receive(:exist?).with(default_ca_file).and_return(true) - expect(Chef::Config.ssl_ca_file).to eq(default_ca_file) - end - end - end - end - - describe "Chef::Config[:user_home]" do - it "should set when HOME is provided" do - expected = to_platform("/home/kitten") - allow(Chef::Util::PathHelper).to receive(:home).and_return(expected) - expect(Chef::Config[:user_home]).to eq(expected) - end - - it "falls back to the current working directory when HOME and USERPROFILE is not set" do - allow(Chef::Util::PathHelper).to receive(:home).and_return(nil) - expect(Chef::Config[:user_home]).to eq(Dir.pwd) - end - end - - describe "Chef::Config[:encrypted_data_bag_secret]" do - let(:db_secret_default_path){ to_platform("/etc/chef/encrypted_data_bag_secret") } - - before do - allow(File).to receive(:exist?).with(db_secret_default_path).and_return(secret_exists) - end - - context "/etc/chef/encrypted_data_bag_secret exists" do - let(:secret_exists) { true } - it "sets the value to /etc/chef/encrypted_data_bag_secret" do - expect(Chef::Config[:encrypted_data_bag_secret]).to eq db_secret_default_path - end - end - - context "/etc/chef/encrypted_data_bag_secret does not exist" do - let(:secret_exists) { false } - it "sets the value to nil" do - expect(Chef::Config[:encrypted_data_bag_secret]).to be_nil - end - end - end - - describe "Chef::Config[:event_handlers]" do - it "sets a event_handlers to an empty array by default" do - expect(Chef::Config[:event_handlers]).to eq([]) - end - it "should be able to add custom handlers" do - o = Object.new - Chef::Config[:event_handlers] << o - expect(Chef::Config[:event_handlers]).to be_include(o) - end - end - - describe "Chef::Config[:user_valid_regex]" do - context "on a platform that is not Windows" do - it "allows one letter usernames" do - any_match = Chef::Config[:user_valid_regex].any? { |regex| regex.match('a') } - expect(any_match).to be_truthy - end - end - end - - describe "Chef::Config[:internal_locale]" do - let(:shell_out) do - double("Chef::Mixin::ShellOut double", :exitstatus => 0, :stdout => locales) - end - - let(:locales) { locale_array.join("\n") } - - before do - allow(Chef::Config).to receive(:shell_out_with_systems_locale!).with("locale -a").and_return(shell_out) - end - - shared_examples_for "a suitable locale" do - it "returns an English UTF-8 locale" do - expect(Chef::Log).to_not receive(:warn).with(/Please install an English UTF-8 locale for Chef to use/) - expect(Chef::Log).to_not receive(:debug).with(/Defaulting to locale en_US.UTF-8 on Windows/) - expect(Chef::Log).to_not receive(:debug).with(/No usable locale -a command found/) - expect(Chef::Config.guess_internal_locale).to eq expected_locale - end - end - - context "when the result includes 'C.UTF-8'" do - include_examples "a suitable locale" do - let(:locale_array) { [expected_locale, "en_US.UTF-8"] } - let(:expected_locale) { "C.UTF-8" } - end - end - - context "when the result includes 'en_US.UTF-8'" do - include_examples "a suitable locale" do - let(:locale_array) { ["en_CA.UTF-8", expected_locale, "en_NZ.UTF-8"] } - let(:expected_locale) { "en_US.UTF-8" } - end - end - - context "when the result includes 'en_US.utf8'" do - include_examples "a suitable locale" do - let(:locale_array) { ["en_CA.utf8", "en_US.utf8", "en_NZ.utf8"] } - let(:expected_locale) { "en_US.UTF-8" } - end - end - - context "when the result includes 'en.UTF-8'" do - include_examples "a suitable locale" do - let(:locale_array) { ["en.ISO8859-1", expected_locale] } - let(:expected_locale) { "en.UTF-8" } - end - end - - context "when the result includes 'en_*.UTF-8'" do - include_examples "a suitable locale" do - let(:locale_array) { [expected_locale, "en_CA.UTF-8", "en_GB.UTF-8"] } - let(:expected_locale) { "en_AU.UTF-8" } - end - end - - context "when the result includes 'en_*.utf8'" do - include_examples "a suitable locale" do - let(:locale_array) { ["en_AU.utf8", "en_CA.utf8", "en_GB.utf8"] } - let(:expected_locale) { "en_AU.UTF-8" } - end - end - - context "when the result does not include 'en_*.UTF-8'" do - let(:locale_array) { ["af_ZA", "af_ZA.ISO8859-1", "af_ZA.ISO8859-15", "af_ZA.UTF-8"] } - - it "should fall back to C locale" do - expect(Chef::Log).to receive(:warn).with("Please install an English UTF-8 locale for Chef to use, falling back to C locale and disabling UTF-8 support.") - expect(Chef::Config.guess_internal_locale).to eq 'C' - end - end - - context "on error" do - let(:locale_array) { [] } - - before do - allow(Chef::Config).to receive(:shell_out_with_systems_locale!).and_raise("THIS IS AN ERROR") - end - - it "should default to 'en_US.UTF-8'" do - if is_windows - expect(Chef::Log).to receive(:debug).with("Defaulting to locale en_US.UTF-8 on Windows, until it matters that we do something else.") - else - expect(Chef::Log).to receive(:debug).with("No usable locale -a command found, assuming you have en_US.UTF-8 installed.") - end - expect(Chef::Config.guess_internal_locale).to eq "en_US.UTF-8" - end - end - end - end - end - - describe "Treating deprecation warnings as errors" do - - context "when using our default RSpec configuration" do - - it "defaults to treating deprecation warnings as errors" do - expect(Chef::Config[:treat_deprecation_warnings_as_errors]).to be(true) - end - - it "sets CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS environment variable" do - expect(ENV['CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS']).to eq("1") - end - - it "treats deprecation warnings as errors in child processes when testing" do - # Doing a full integration test where we launch a child process is slow - # and liable to break for weird reasons (bundler env stuff, etc.), so - # we're just checking that the presence of the environment variable - # causes treat_deprecation_warnings_as_errors to be set to true after a - # config reset. - Chef::Config.reset - expect(Chef::Config[:treat_deprecation_warnings_as_errors]).to be(true) - end - - end - - context "outside of our test environment" do - - before do - ENV.delete('CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS') - Chef::Config.reset - end - - it "defaults to NOT treating deprecation warnings as errors" do - expect(Chef::Config[:treat_deprecation_warnings_as_errors]).to be(false) - end - end - - - end -end diff --git a/spec/unit/cookbook/cookbook_version_loader_spec.rb b/spec/unit/cookbook/cookbook_version_loader_spec.rb index 2c4ad11787..23ffc21f7f 100644 --- a/spec/unit/cookbook/cookbook_version_loader_spec.rb +++ b/spec/unit/cookbook/cookbook_version_loader_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' describe Chef::Cookbook::CookbookVersionLoader do before do - allow(Chef::Platform).to receive(:windows?) { false } + allow(ChefConfig).to receive(:windows?) { false } end describe "loading a cookbook" do diff --git a/spec/unit/cookbook/metadata_spec.rb b/spec/unit/cookbook/metadata_spec.rb index 760ae5dd2a..d2954726e8 100644 --- a/spec/unit/cookbook/metadata_spec.rb +++ b/spec/unit/cookbook/metadata_spec.rb @@ -304,6 +304,21 @@ describe Chef::Cookbook::Metadata do end end end + + it "strips out self-dependencies", :chef_lt_13_only do + metadata.name('foo') + expect(Chef::Log).to receive(:warn).with( + "Ignoring self-dependency in cookbook foo, please remove it (in the future this will be fatal)." + ) + metadata.depends('foo') + expect(metadata.dependencies).to eql({}) + end + + it "errors on self-dependencies", :chef_gte_13_only do + metadata.name('foo') + expect { metadata.depends('foo') }.to raise_error + # FIXME: add the error type + end end describe "attribute groupings" do diff --git a/spec/unit/cookbook/syntax_check_spec.rb b/spec/unit/cookbook/syntax_check_spec.rb index 471fc01831..ee4e0bed02 100644 --- a/spec/unit/cookbook/syntax_check_spec.rb +++ b/spec/unit/cookbook/syntax_check_spec.rb @@ -21,7 +21,7 @@ require "chef/cookbook/syntax_check" describe Chef::Cookbook::SyntaxCheck do before do - allow(Chef::Platform).to receive(:windows?) { false } + allow(ChefConfig).to receive(:windows?) { false } end let(:cookbook_path) { File.join(CHEF_SPEC_DATA, 'cookbooks', 'openldap') } diff --git a/spec/unit/cookbook_loader_spec.rb b/spec/unit/cookbook_loader_spec.rb index 45a985bafd..b1384bffe7 100644 --- a/spec/unit/cookbook_loader_spec.rb +++ b/spec/unit/cookbook_loader_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' describe Chef::CookbookLoader do before do - allow(Chef::Platform).to receive(:windows?) {false} + allow(ChefConfig).to receive(:windows?) {false} end let(:repo_paths) do [ diff --git a/spec/unit/cookbook_site_streaming_uploader_spec.rb b/spec/unit/cookbook_site_streaming_uploader_spec.rb index ef0f649163..0041a142dc 100644 --- a/spec/unit/cookbook_site_streaming_uploader_spec.rb +++ b/spec/unit/cookbook_site_streaming_uploader_spec.rb @@ -121,27 +121,6 @@ describe Chef::CookbookSiteStreamingUploader do }) end - describe "http verify mode" do - before do - @uri = "https://cookbooks.dummy.com/api/v1/cookbooks" - uri_info = URI.parse(@uri) - @http = Net::HTTP.new(uri_info.host, uri_info.port) - expect(Net::HTTP).to receive(:new).with(uri_info.host, uri_info.port).and_return(@http) - end - - it "should be VERIFY_NONE when ssl_verify_mode is :verify_none" do - Chef::Config[:ssl_verify_mode] = :verify_none - Chef::CookbookSiteStreamingUploader.make_request(:post, @uri, 'bill', @secret_filename) - expect(@http.verify_mode).to eq(OpenSSL::SSL::VERIFY_NONE) - end - - it "should be VERIFY_PEER when ssl_verify_mode is :verify_peer" do - Chef::Config[:ssl_verify_mode] = :verify_peer - Chef::CookbookSiteStreamingUploader.make_request(:post, @uri, 'bill', @secret_filename) - expect(@http.verify_mode).to eq(OpenSSL::SSL::VERIFY_PEER) - end - end - end # make_request describe "StreamPart" do diff --git a/spec/unit/cookbook_spec.rb b/spec/unit/cookbook_spec.rb index 7b3cda2af1..f36b031309 100644 --- a/spec/unit/cookbook_spec.rb +++ b/spec/unit/cookbook_spec.rb @@ -59,15 +59,6 @@ describe Chef::CookbookVersion do expect(@cookbook.fully_qualified_recipe_names.include?("openldap::three")).to eq(true) end - it "should find a preferred file" do - skip - end - - it "should not return an unchanged preferred file" do - pending - expect(@cookbook.preferred_filename(@node, :files, 'a-filename', 'the-checksum')).to be_nil - end - it "should raise an ArgumentException if you try to load a bad recipe name" do expect { @cookbook.load_recipe("doesnt_exist", @node) }.to raise_error(ArgumentError) end diff --git a/spec/unit/cookbook_version_spec.rb b/spec/unit/cookbook_version_spec.rb index 440dd9da6c..4990aef004 100644 --- a/spec/unit/cookbook_version_spec.rb +++ b/spec/unit/cookbook_version_spec.rb @@ -306,26 +306,6 @@ describe Chef::CookbookVersion do subject(:cbv) { Chef::CookbookVersion.new("version validation", '/tmp/blah') } - describe "HTTP Resource behaviors", pending: "will be deprected when CookbookManifest API is stablized" do - - it "errors on #save_url" do - expect { cbv.save_url }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) - end - - it "errors on #force_save_url" do - expect { cbv.force_save_url }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) - end - - it "errors on #to_hash" do - expect { cbv.to_hash }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) - end - - it "errors on #to_json" do - expect { cbv.to_json }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) - end - - end - it "errors on #status and #status=" do expect { cbv.status = :wat }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) expect { cbv.status }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) diff --git a/spec/unit/data_bag_spec.rb b/spec/unit/data_bag_spec.rb index f6db1e222a..bd9a99a1de 100644 --- a/spec/unit/data_bag_spec.rb +++ b/spec/unit/data_bag_spec.rb @@ -22,7 +22,7 @@ require 'chef/data_bag' describe Chef::DataBag do before(:each) do @data_bag = Chef::DataBag.new - allow(Chef::Platform)::to receive(:windows?) { false } + allow(ChefConfig).to receive(:windows?) { false } end describe "initialize" do diff --git a/spec/unit/deprecation_spec.rb b/spec/unit/deprecation_spec.rb index f824cb7c76..2e1f3c39f3 100644 --- a/spec/unit/deprecation_spec.rb +++ b/spec/unit/deprecation_spec.rb @@ -95,4 +95,59 @@ describe Chef::Deprecation do expect { test_instance.deprecated_method(10) }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) end + context "When a class has deprecated_attr, _reader and _writer" do + before(:context) do + class DeprecatedAttrTest + extend Chef::Mixin::Deprecation + def initialize + @a = @r = @w = 1 + end + deprecated_attr :a, "a" + deprecated_attr_reader :r, "r" + deprecated_attr_writer :w, "w" + end + end + + it "The deprecated_attr emits warnings" do + test = DeprecatedAttrTest.new + expect { test.a = 10 }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) + expect { test.a }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) + end + + it "The deprecated_attr_writer emits warnings, and does not create a reader" do + test = DeprecatedAttrTest.new + expect { test.w = 10 }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) + expect { test.w }.to raise_error(NoMethodError) + end + + it "The deprecated_attr_reader emits warnings, and does not create a writer" do + test = DeprecatedAttrTest.new + expect { test.r = 10 }.to raise_error(NoMethodError) + expect { test.r }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) + end + + context "With deprecation warnings not throwing exceptions" do + before do + Chef::Config[:treat_deprecation_warnings_as_errors] = false + end + + it "The deprecated_attr can be written to and read from" do + test = DeprecatedAttrTest.new + test.a = 10 + expect(test.a).to eq 10 + end + + it "The deprecated_attr_reader can be read from" do + test = DeprecatedAttrTest.new + expect(test.r).to eq 1 + end + + it "The deprecated_attr_writer can be written to" do + test = DeprecatedAttrTest.new + test.w = 10 + expect(test.instance_eval { @w }).to eq 10 + end + end + end + end diff --git a/spec/unit/dsl/resources_spec.rb b/spec/unit/dsl/resources_spec.rb new file mode 100644 index 0000000000..581c835290 --- /dev/null +++ b/spec/unit/dsl/resources_spec.rb @@ -0,0 +1,85 @@ +# +# Author:: Noah Kantrowitz (<noah@coderanger.net>) +# Copyright:: Copyright (c) 2015 Noah Kantrowitz +# 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/dsl/resources' + +describe Chef::DSL::Resources do + let(:declared_resources) { [] } + let(:test_class) do + r = declared_resources + Class.new do + include Chef::DSL::Resources + define_method(:declare_resource) do |dsl_name, name, _created_at, &_block| + r << [dsl_name, name] + end + end + end + subject { declared_resources } + after do + # Always clean up after ourselves. + described_class.remove_resource_dsl(:test_resource) + end + + context 'with a resource added' do + before do + Chef::DSL::Resources.add_resource_dsl(:test_resource) + test_class.new.instance_eval do + test_resource 'test_name' do + end + end + end + it { is_expected.to eq [[:test_resource, 'test_name']]} + end + + context 'with no resource added' do + subject do + test_class.new.instance_eval do + test_resource 'test_name' do + end + end + end + + it { expect { subject }.to raise_error NoMethodError } + end + + context 'with a resource added and removed' do + before do + Chef::DSL::Resources.add_resource_dsl(:test_resource) + Chef::DSL::Resources.remove_resource_dsl(:test_resource) + end + subject do + test_class.new.instance_eval do + test_resource 'test_name' do + end + end + end + + it { expect { subject }.to raise_error NoMethodError } + end + + context 'with a nameless resource' do + before do + Chef::DSL::Resources.add_resource_dsl(:test_resource) + test_class.new.instance_eval do + test_resource { } + end + end + it { is_expected.to eq [[:test_resource, nil]]} + end +end diff --git a/spec/unit/event_dispatch/dispatcher_spec.rb b/spec/unit/event_dispatch/dispatcher_spec.rb new file mode 100644 index 0000000000..7e43b1933f --- /dev/null +++ b/spec/unit/event_dispatch/dispatcher_spec.rb @@ -0,0 +1,61 @@ +# +# Author:: Daniel DeLeo (<dan@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' +require 'chef/event_dispatch/dispatcher' + +describe Chef::EventDispatch::Dispatcher do + + subject(:dispatcher) { Chef::EventDispatch::Dispatcher.new } + + let(:event_sink) { instance_double("Chef::EventDispatch::Base") } + + it "has no subscribers by default" do + expect(dispatcher.subscribers).to be_empty + end + + context "when an event sink is registered" do + + before do + dispatcher.register(event_sink) + end + + it "it has the event sink as a subscriber" do + expect(dispatcher.subscribers.size).to eq(1) + expect(dispatcher.subscribers.first).to eq(event_sink) + end + + it "forwards events to the subscribed event sink" do + # the events all have different arity and such so we just hit a few different events: + + expect(event_sink).to receive(:run_start).with("12.4.0") + dispatcher.run_start("12.4.0") + + expect(event_sink).to receive(:synchronized_cookbook).with("apache2") + dispatcher.synchronized_cookbook("apache2") + + exception = StandardError.new("foo") + expect(event_sink).to receive(:recipe_file_load_failed).with("/path/to/file.rb", exception) + dispatcher.recipe_file_load_failed("/path/to/file.rb", exception) + end + + end + +end + diff --git a/spec/unit/exceptions_spec.rb b/spec/unit/exceptions_spec.rb index d35ecc8ec8..fd90aeab71 100644 --- a/spec/unit/exceptions_spec.rb +++ b/spec/unit/exceptions_spec.rb @@ -113,7 +113,7 @@ describe Chef::Exceptions do context "initialized with 1 error and nil" do let(:e) { Chef::Exceptions::RunFailedWrappingError.new(RuntimeError.new("foo"), nil) } let(:num_errors) { 1 } - let(:backtrace) { ["1) RuntimeError - foo", ""] } + let(:backtrace) { ["1) RuntimeError - foo"] } include_examples "RunFailedWrappingError expectations" end @@ -121,7 +121,7 @@ describe Chef::Exceptions do context "initialized with 2 errors" do let(:e) { Chef::Exceptions::RunFailedWrappingError.new(RuntimeError.new("foo"), RuntimeError.new("bar")) } let(:num_errors) { 2 } - let(:backtrace) { ["1) RuntimeError - foo", "", "2) RuntimeError - bar", ""] } + let(:backtrace) { ["1) RuntimeError - foo", "", "2) RuntimeError - bar"] } include_examples "RunFailedWrappingError expectations" end diff --git a/spec/unit/file_content_management/deploy/mv_windows_spec.rb b/spec/unit/file_content_management/deploy/mv_windows_spec.rb index c52001cd26..2d1981befc 100644 --- a/spec/unit/file_content_management/deploy/mv_windows_spec.rb +++ b/spec/unit/file_content_management/deploy/mv_windows_spec.rb @@ -115,6 +115,66 @@ describe Chef::FileContentManagement::Deploy::MvWindows do end + context "and the target file has null dacl and sacl" do + + before do + allow(target_file_security_descriptor).to receive(:dacl_present?).and_return(true) + allow(target_file_security_descriptor).to receive(:dacl).and_return(nil) + allow(target_file_security_descriptor).to receive(:dacl_inherits?).and_return(false) + + allow(target_file_security_descriptor).to receive(:sacl_present?).and_return(true) + allow(target_file_security_descriptor).to receive(:sacl).and_return(nil) + allow(target_file_security_descriptor).to receive(:sacl_inherits?).and_return(false) + + expect(updated_target_security_object).to receive(:set_dacl).with(nil, false) + expect(updated_target_security_object).to receive(:set_sacl).with(nil, false) + end + + + it "fixes up permissions and moves the file into place" do + content_deployer.deploy(staging_file_path, target_file_path) + end + + end + + context "and the target has an empty dacl and sacl" do + let(:original_target_file_dacl) { [] } + let(:original_target_file_sacl) { [] } + + let(:empty_dacl) { double("Windows ACL with no dacl ACEs") } + let(:empty_sacl) { double("Windows ACL with no sacl ACEs") } + + before do + allow(target_file_security_descriptor).to receive(:dacl_present?).and_return(true) + allow(target_file_security_descriptor).to receive(:dacl_inherits?).and_return(false) + + allow(target_file_security_descriptor).to receive(:dacl).and_return(original_target_file_dacl) + expect(Chef::ReservedNames::Win32::Security::ACL). + to receive(:create). + with([]). + and_return(empty_dacl) + + + allow(target_file_security_descriptor).to receive(:sacl_present?).and_return(true) + allow(target_file_security_descriptor).to receive(:sacl_inherits?).and_return(false) + + allow(target_file_security_descriptor).to receive(:sacl).and_return(original_target_file_sacl) + expect(Chef::ReservedNames::Win32::Security::ACL). + to receive(:create). + with([]). + and_return(empty_sacl) + + + expect(updated_target_security_object).to receive(:set_dacl).with(empty_dacl, false) + expect(updated_target_security_object).to receive(:set_sacl).with(empty_sacl, false) + end + + + it "fixes up permissions and moves the file into place" do + content_deployer.deploy(staging_file_path, target_file_path) + end + end + context "and the target has a dacl and sacl" do let(:inherited_dacl_ace) { double("Windows dacl ace (inherited)", :inherited? => true) } let(:not_inherited_dacl_ace) { double("Windows dacl ace (not inherited)", :inherited? => false) } diff --git a/spec/unit/formatters/doc_spec.rb b/spec/unit/formatters/doc_spec.rb new file mode 100644 index 0000000000..d018207f49 --- /dev/null +++ b/spec/unit/formatters/doc_spec.rb @@ -0,0 +1,46 @@ +# +# Author:: Daniel DeLeo (<dan@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' + +describe Chef::Formatters::Base do + + let(:out) { StringIO.new } + let(:err) { StringIO.new } + + subject(:formatter) { Chef::Formatters::Doc.new(out, err) } + + it "prints a policyfile's name and revision ID" do + minimal_policyfile = { + "revision_id"=> "613f803bdd035d574df7fa6da525b38df45a74ca82b38b79655efed8a189e073", + "name"=> "jenkins", + "run_list"=> [ + "recipe[apt::default]", + "recipe[java::default]", + "recipe[jenkins::master]", + "recipe[policyfile_demo::default]" + ], + "cookbook_locks"=> { } + } + + formatter.policyfile_loaded(minimal_policyfile) + expect(out.string).to include("Using policy 'jenkins' at revision '613f803bdd035d574df7fa6da525b38df45a74ca82b38b79655efed8a189e073'") + end + +end diff --git a/spec/unit/formatters/error_inspectors/api_error_formatting_spec.rb b/spec/unit/formatters/error_inspectors/api_error_formatting_spec.rb new file mode 100644 index 0000000000..b8c2de2b8b --- /dev/null +++ b/spec/unit/formatters/error_inspectors/api_error_formatting_spec.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 'spec_helper' +require 'chef/formatters/error_inspectors/api_error_formatting' + +describe Chef::Formatters::APIErrorFormatting do + let(:class_instance) { (Class.new { include Chef::Formatters::APIErrorFormatting }).new } + let(:error_description) { instance_double(Chef::Formatters::ErrorDescription) } + let(:response) { double("response") } + before do + allow(response).to receive(:body) + end + + + context "when describe_406_error is called" do + context "when response['x-ops-server-api-version'] exists" do + let(:min_version) { "2" } + let(:max_version) { "5" } + let(:request_version) { "30" } + let(:return_hash) { + { + "min_version" => min_version, + "max_version" => max_version, + "request_version" => request_version + } + } + + before do + # mock out the header + allow(response).to receive(:[]).with('x-ops-server-api-version').and_return(Chef::JSONCompat.to_json(return_hash)) + end + + it "prints an error about client and server API version incompatibility with a min API version" do + expect(error_description).to receive(:section).with("Incompatible server API version:",/a min API version of #{min_version}/) + class_instance.describe_406_error(error_description, response) + end + + it "prints an error about client and server API version incompatibility with a max API version" do + expect(error_description).to receive(:section).with("Incompatible server API version:",/a max API version of #{max_version}/) + class_instance.describe_406_error(error_description, response) + end + + it "prints an error describing the request API version" do + expect(error_description).to receive(:section).with("Incompatible server API version:",/a request with an API version of #{request_version}/) + class_instance.describe_406_error(error_description, response) + end + end + + context "when response.body['error'] != 'invalid-x-ops-server-api-version'" do + + before do + allow(response).to receive(:[]).with('x-ops-server-api-version').and_return(nil) + end + + it "forwards the error_description to describe_http_error" do + expect(class_instance).to receive(:describe_http_error).with(error_description) + class_instance.describe_406_error(error_description, response) + end + end + end +end diff --git a/spec/unit/formatters/error_inspectors/compile_error_inspector_spec.rb b/spec/unit/formatters/error_inspectors/compile_error_inspector_spec.rb index ac19e91922..5f95beb259 100644 --- a/spec/unit/formatters/error_inspectors/compile_error_inspector_spec.rb +++ b/spec/unit/formatters/error_inspectors/compile_error_inspector_spec.rb @@ -37,69 +37,122 @@ end E describe Chef::Formatters::ErrorInspectors::CompileErrorInspector do - before do - @node_name = "test-node.example.com" - @description = Chef::Formatters::ErrorDescription.new("Error Evaluating File:") - @exception = NoMethodError.new("undefined method `this_is_not_a_valid_method' for Chef::Resource::File") - @outputter = Chef::Formatters::IndentableOutputStream.new(StringIO.new, STDERR) - #@outputter = Chef::Formatters::IndentableOutputStream.new(STDOUT, STDERR) - end + let(:node_name) { "test-node.example.com" } - describe "when scrubbing backtraces" do - it "shows backtrace lines from cookbook files" do - # Error inspector originally used file_cache_path which is incorrect on - # chef-solo. Using cookbook_path should do the right thing for client and - # solo. - allow(Chef::Config).to receive(:cookbook_path).and_return([ "/home/someuser/dev-laptop/cookbooks" ]) - @trace = [ - "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:14:in `from_file'", - "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:11:in `from_file'", - "/home/someuser/.multiruby/gems/chef/lib/chef/client.rb:123:in `run'" - ] - @exception.set_backtrace(@trace) - @path = "/var/chef/cache/cookbooks/syntax-err/recipes/default.rb" - @inspector = described_class.new(@path, @exception) + let(:description) { Chef::Formatters::ErrorDescription.new("Error Evaluating File:") } - @expected_filtered_trace = [ - "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:14:in `from_file'", - "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:11:in `from_file'", - ] - expect(@inspector.filtered_bt).to eq(@expected_filtered_trace) - end + let(:exception) do + e = NoMethodError.new("undefined method `this_is_not_a_valid_method' for Chef::Resource::File") + e.set_backtrace(trace) + e end - describe "when explaining an error in the compile phase" do - before do - allow(Chef::Config).to receive(:cookbook_path).and_return([ "/var/chef/cache/cookbooks" ]) - recipe_lines = BAD_RECIPE.split("\n").map {|l| l << "\n" } - expect(IO).to receive(:readlines).with("/var/chef/cache/cookbooks/syntax-err/recipes/default.rb").and_return(recipe_lines) - @trace = [ - "/var/chef/cache/cookbooks/syntax-err/recipes/default.rb:14:in `from_file'", - "/var/chef/cache/cookbooks/syntax-err/recipes/default.rb:11:in `from_file'", - "/usr/local/lib/ruby/gems/chef/lib/chef/client.rb:123:in `run'" # should not display - ] - @exception.set_backtrace(@trace) - @path = "/var/chef/cache/cookbooks/syntax-err/recipes/default.rb" - @inspector = described_class.new(@path, @exception) - @inspector.add_explanation(@description) - end + # Change to $stdout to print error messages for manual inspection + let(:stdout) { StringIO.new } + + let(:outputter) { Chef::Formatters::IndentableOutputStream.new(StringIO.new, STDERR) } - it "finds the line number of the error from the stacktrace" do - expect(@inspector.culprit_line).to eq(14) + subject(:inspector) { described_class.new(path_to_failed_file, exception) } + + describe "finding the code responsible for the error" do + + context "when the stacktrace includes cookbook files" do + + let(:trace) do + [ + "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:14:in `from_file'", + "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:11:in `from_file'", + "/home/someuser/.multiruby/gems/chef/lib/chef/client.rb:123:in `run'" + ] + end + + let(:expected_filtered_trace) do + [ + "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:14:in `from_file'", + "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:11:in `from_file'", + ] + end + + let(:path_to_failed_file) { "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb" } + + before do + # Error inspector originally used file_cache_path which is incorrect on + # chef-solo. Using cookbook_path should do the right thing for client and + # solo. + allow(Chef::Config).to receive(:cookbook_path).and_return([ "/home/someuser/dev-laptop/cookbooks" ]) + end + + describe "when scrubbing backtraces" do + it "shows backtrace lines from cookbook files" do + expect(inspector.filtered_bt).to eq(expected_filtered_trace) + end + end + + describe "when explaining an error in the compile phase" do + before do + recipe_lines = BAD_RECIPE.split("\n").map {|l| l << "\n" } + expect(IO).to receive(:readlines).with(path_to_failed_file).and_return(recipe_lines) + inspector.add_explanation(description) + end + + it "reports the error was not located within cookbooks" do + expect(inspector.found_error_in_cookbooks?).to be(true) + end + + it "finds the line number of the error from the stacktrace" do + expect(inspector.culprit_line).to eq(14) + end + + it "prints a pretty message" do + description.display(outputter) + end + end end - it "prints a pretty message" do - @description.display(@outputter) + context "when the error does not contain any lines from cookbooks" do + + let(:trace) do + [ + "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:144:in `rescue in block in load_libraries'", + "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:138:in `block in load_libraries'", + "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:230:in `call'", + "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:230:in `block (2 levels) in foreach_cookbook_load_segment'", + "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:229:in `each'", + "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:229:in `block in foreach_cookbook_load_segment'", + "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:227:in `each'", + "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:227:in `foreach_cookbook_load_segment'", + "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:137:in `load_libraries'" + ] + end + + let(:exception) do + e = Chef::Exceptions::RecipeNotFound.new("recipe nope:nope not found") + e.set_backtrace(trace) + e + end + + let(:path_to_failed_file) { nil } + + it "gives a full, non-filtered trace" do + expect(inspector.filtered_bt).to eq(trace) + end + + it "does not error when displaying the error" do + expect { description.display(outputter) }.to_not raise_error + end + + it "reports the error was not located within cookbooks" do + expect(inspector.found_error_in_cookbooks?).to be(false) + end + end end describe "when explaining an error on windows" do - before do - allow(Chef::Config).to receive(:cookbook_path).and_return([ "C:/opscode/chef/var/cache/cookbooks" ]) - recipe_lines = BAD_RECIPE.split("\n").map {|l| l << "\n" } - expect(IO).to receive(:readlines).at_least(1).times.with(/:\/opscode\/chef\/var\/cache\/cookbooks\/foo\/recipes\/default.rb/).and_return(recipe_lines) - @trace = [ + + let(:trace_with_upcase_drive) do + [ "C:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb:14 in `from_file'", "C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:144:in `rescue in block in load_libraries'", "C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:138:in `block in load_libraries'", @@ -122,81 +175,65 @@ describe Chef::Formatters::ErrorInspectors::CompileErrorInspector do "C:/opscode/chef/bin/chef-client:19:in `load'", "C:/opscode/chef/bin/chef-client:19:in `<main>'" ] - @exception.set_backtrace(@trace) - @path = "/var/chef/cache/cookbooks/syntax-err/recipes/default.rb" - @inspector = described_class.new(@path, @exception) - @inspector.add_explanation(@description) end + let(:trace) { trace_with_upcase_drive } - describe "and examining the stack trace for a recipe" do - it "find the culprit recipe name when the drive letter is upper case" do - expect(@inspector.culprit_file).to eq("C:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb") + let(:path_to_failed_file) { "/var/cache/cookbooks/foo/recipes/default.rb" } + + before do + allow(Chef::Config).to receive(:cookbook_path).and_return([ "C:/opscode/chef/var/cache/cookbooks" ]) + recipe_lines = BAD_RECIPE.split("\n").map {|l| l << "\n" } + expect(IO).to receive(:readlines).at_least(1).times.with(full_path_to_failed_file).and_return(recipe_lines) + inspector.add_explanation(description) + end + + context "when the drive letter in the path is uppercase" do + + let(:full_path_to_failed_file) { "C:/opscode/chef#{path_to_failed_file}" } + + it "reports the error was not located within cookbooks" do + expect(inspector.found_error_in_cookbooks?).to be(true) end - it "find the culprit recipe name when the drive letter is lower case" do - @trace.each { |line| line.gsub!(/^C:/, "c:") } - @exception.set_backtrace(@trace) - @inspector = described_class.new(@path, @exception) - @inspector.add_explanation(@description) - expect(@inspector.culprit_file).to eq("c:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb") + it "finds the culprit recipe name" do + expect(inspector.culprit_file).to eq("C:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb") end - end - it "finds the line number of the error from the stack trace" do - expect(@inspector.culprit_line).to eq(14) - end + it "finds the line number of the error from the stack trace" do + expect(inspector.culprit_line).to eq(14) + end - it "prints a pretty message" do - @description.display(@outputter) + it "prints a pretty message" do + description.display(outputter) + end end - end - describe "when explaining an error on windows, and the backtrace lowercases the drive letter" do - before do - allow(Chef::Config).to receive(:cookbook_path).and_return([ "C:/opscode/chef/var/cache/cookbooks" ]) - recipe_lines = BAD_RECIPE.split("\n").map {|l| l << "\n" } - expect(IO).to receive(:readlines).with("c:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb").and_return(recipe_lines) - @trace = [ - "c:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb:14 in `from_file'", - "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:144:in `rescue in block in load_libraries'", - "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:138:in `block in load_libraries'", - "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:230:in `call'", - "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:230:in `block (2 levels) in foreach_cookbook_load_segment'", - "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:229:in `each'", - "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:229:in `block in foreach_cookbook_load_segment'", - "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:227:in `each'", - "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:227:in `foreach_cookbook_load_segment'", - "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:137:in `load_libraries'", - "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:62:in `load'", - "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/client.rb:198:in `setup_run_context'", - "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/client.rb:418:in `do_run'", - "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/client.rb:176:in `run'", - "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/application/client.rb:283:in `block in run_application'", - "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/application/client.rb:270:in `loop'", - "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/application/client.rb:270:in `run_application'", - "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/application.rb:70:in `run'", - "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/bin/chef-client:26:in `<top (required)>'", - "c:/opscode/chef/bin/chef-client:19:in `load'", - "c:/opscode/chef/bin/chef-client:19:in `<main>'" - ] - @exception.set_backtrace(@trace) - @path = "/var/chef/cache/cookbooks/syntax-err/recipes/default.rb" - @inspector = described_class.new(@path, @exception) - @inspector.add_explanation(@description) - end + context "when the drive letter in the path is lowercase" do - it "finds the culprit recipe name from the stacktrace" do - expect(@inspector.culprit_file).to eq("c:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb") - end + let(:trace) do + trace_with_upcase_drive.map { |line| line.gsub(/^C:/, "c:") } + end - it "finds the line number of the error from the stack trace" do - expect(@inspector.culprit_line).to eq(14) - end + let(:full_path_to_failed_file) { "c:/opscode/chef#{path_to_failed_file}" } - it "prints a pretty message" do - @description.display(@outputter) + it "reports the error was not located within cookbooks" do + expect(inspector.found_error_in_cookbooks?).to be(true) + end + + it "finds the culprit recipe name from the stacktrace" do + expect(inspector.culprit_file).to eq("c:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb") + end + + it "finds the line number of the error from the stack trace" do + expect(inspector.culprit_line).to eq(14) + end + + it "prints a pretty message" do + description.display(outputter) + end end + end end diff --git a/spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb b/spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb index a42d234601..5594d6e18a 100644 --- a/spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb +++ b/spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb @@ -126,6 +126,13 @@ describe Chef::Formatters::ErrorInspectors::ResourceFailureInspector do expect(@inspector.recipe_snippet).to match(/^# In C:\/Users\/btm/) end + it "parses a Windows path" do + source_line = "C:\\Windows\\Temp\\packer\\cookbooks\\fake_file.rb:2: undefined local variable or method `non_existent' for main:Object (NameError)" + @resource.source_line = source_line + @inspector = Chef::Formatters::ErrorInspectors::ResourceFailureInspector.new(@resource, :create, @exception) + expect(@inspector.recipe_snippet).to match(/^# In C:\\Windows\\Temp\\packer\\/) + end + it "parses a unix path" do source_line = "/home/btm/src/chef/chef/spec/unit/fake_file.rb:2: undefined local variable or method `non_existent' for main:Object (NameError)" @resource.source_line = source_line diff --git a/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb b/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb index 4cf3ba827a..acf1b15fd8 100644 --- a/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb +++ b/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb @@ -24,6 +24,7 @@ describe Chef::GuardInterpreter::ResourceGuardInterpreter do node.default["kernel"] = Hash.new node.default["kernel"][:machine] = :x86_64.to_s + node.automatic[:os] = 'windows' node end @@ -83,6 +84,14 @@ describe Chef::GuardInterpreter::ResourceGuardInterpreter do expect(guard_interpreter.evaluate).to eq(true) end + it "does not corrupt the run_context of the node" do + node_run_context_before_guard_execution = parent_resource.run_context + expect(node_run_context_before_guard_execution.object_id).to eq(parent_resource.node.run_context.object_id) + guard_interpreter.evaluate + node_run_context_after_guard_execution = parent_resource.run_context + expect(node_run_context_after_guard_execution.object_id).to eq(parent_resource.node.run_context.object_id) + end + describe "script command opts switch" do let(:command_opts) { {} } let(:guard_interpreter) { Chef::GuardInterpreter::ResourceGuardInterpreter.new(parent_resource, "exit 0", command_opts) } @@ -144,4 +153,3 @@ describe Chef::GuardInterpreter::ResourceGuardInterpreter do end end end - diff --git a/spec/unit/http/authenticator_spec.rb b/spec/unit/http/authenticator_spec.rb new file mode 100644 index 0000000000..48bbdcf76c --- /dev/null +++ b/spec/unit/http/authenticator_spec.rb @@ -0,0 +1,78 @@ +# +# 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' +require 'chef/http/authenticator' + +describe Chef::HTTP::Authenticator do + let(:class_instance) { Chef::HTTP::Authenticator.new } + let(:method) { double("method") } + let(:url) { double("url") } + let(:headers) { Hash.new } + let(:data) { double("data") } + + before do + allow(class_instance).to receive(:authentication_headers).and_return({}) + end + + context "when handle_request is called" do + shared_examples_for "merging the server API version into the headers" do + it "merges the default version of X-Ops-Server-API-Version into the headers" do + # headers returned + expect(class_instance.handle_request(method, url, headers, data)[2]). + to include({'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION}) + end + + context "when api_version is set to something other than the default" do + let(:class_instance) { Chef::HTTP::Authenticator.new({:api_version => '-10'}) } + + it "merges the requested version of X-Ops-Server-API-Version into the headers" do + expect(class_instance.handle_request(method, url, headers, data)[2]). + to include({'X-Ops-Server-API-Version' => '-10'}) + end + end + end + + context "when !sign_requests?" do + before do + allow(class_instance).to receive(:sign_requests?).and_return(false) + end + + it_behaves_like "merging the server API version into the headers" + + it "authentication_headers is not called" do + expect(class_instance).to_not receive(:authentication_headers) + class_instance.handle_request(method, url, headers, data) + end + + end + + context "when sign_requests?" do + before do + allow(class_instance).to receive(:sign_requests?).and_return(true) + end + + it_behaves_like "merging the server API version into the headers" + + it "calls authentication_headers with the proper input" do + expect(class_instance).to receive(:authentication_headers).with(method, url, data).and_return({}) + class_instance.handle_request(method, url, headers, data) + end + end + end +end diff --git a/spec/unit/http/basic_client_spec.rb b/spec/unit/http/basic_client_spec.rb index 32b32a5f4c..b7552f54aa 100644 --- a/spec/unit/http/basic_client_spec.rb +++ b/spec/unit/http/basic_client_spec.rb @@ -109,5 +109,21 @@ describe "HTTP Connection" do end end + + context "when an empty proxy is set by the environment" do + let(:env) do + { + "https_proxy" => "" + } + end + + before do + allow(subject).to receive(:env).and_return(env) + end + + it "to not fail with URI parse exception" do + expect { subject.proxy_uri }.to_not raise_error + end + end end end diff --git a/spec/unit/json_compat_spec.rb b/spec/unit/json_compat_spec.rb index 4631429bd6..7482ba8a28 100644 --- a/spec/unit/json_compat_spec.rb +++ b/spec/unit/json_compat_spec.rb @@ -72,19 +72,19 @@ describe Chef::JSONCompat do end end - # On FreeBSD 10.1 i386 rspec fails with a SystemStackError loading the expect line with more that 254 entries + # On FreeBSD 10.1 i386 rspec fails with a SystemStackError loading the expect line with more that 252 entries # https://github.com/chef/chef/issues/3101 - describe "with a file with 254 or less nested entries" do - let(:json) { IO.read(File.join(CHEF_SPEC_DATA, 'big.json')) } + describe "with the file with 252 or less nested entries" do + let(:json) { IO.read(File.join(CHEF_SPEC_DATA, 'nested.json')) } let(:hash) { Chef::JSONCompat.from_json(json) } - describe "when a 254 json file is loaded" do + describe "when the 252 json file is loaded" do it "should create a Hash from the file" do expect(hash).to be_kind_of(Hash) end - it "should has 'test' as a 254 nested value" do - expect(hash['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']).to eq('test') + it "should has 'test' as a 252 nested value" do + expect(hash['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']).to eq('test') end end end diff --git a/spec/unit/key_spec.rb b/spec/unit/key_spec.rb new file mode 100644 index 0000000000..94ebbf6ae8 --- /dev/null +++ b/spec/unit/key_spec.rb @@ -0,0 +1,634 @@ +# +# 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' + +require 'chef/key' + +describe Chef::Key do + # whether user or client irrelevent to these tests + let(:key) { Chef::Key.new("original_actor", "user") } + let(:public_key_string) do + <<EOS +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvPo+oNPB7uuNkws0fC02 +KxSwdyqPLu0fhI1pOweNKAZeEIiEz2PkybathHWy8snSXGNxsITkf3eyvIIKa8OZ +WrlqpI3yv/5DOP8HTMCxnFuMJQtDwMcevlqebX4bCxcByuBpNYDcAHjjfLGSfMjn +E5lZpgYWwnpic4kSjYcL9ORK9nYvlWV9P/kCYmRhIjB4AhtpWRiOfY/TKi3P2LxT +IjSmiN/ihHtlhV/VSnBJ5PzT/lRknlrJ4kACoz7Pq9jv+aAx5ft/xE9yDa2DYs0q +Tfuc9dUYsFjptWYrV6pfEQ+bgo1OGBXORBFcFL+2D7u9JYquKrMgosznHoEkQNLo +0wIDAQAB +-----END PUBLIC KEY----- +EOS + end + + shared_examples_for "fields with username type validation" do + context "when invalid input is passed" do + # It is not feasible to check all invalid characters. Here are a few + # that we probably care about. + it "should raise an ArgumentError" do + # capital letters + expect { key.send(field, "Bar") }.to raise_error(ArgumentError) + # slashes + expect { key.send(field, "foo/bar") }.to raise_error(ArgumentError) + # ? + expect { key.send(field, "foo?") }.to raise_error(ArgumentError) + # & + expect { key.send(field, "foo&") }.to raise_error(ArgumentError) + # spaces + expect { key.send(field, "foo ") }.to raise_error(ArgumentError) + end + end + end + + shared_examples_for "string fields that are settable" do + context "when it is set with valid input" do + it "should set the field" do + key.send(field, valid_input) + expect(key.send(field)).to eq(valid_input) + end + end + + context "when you feed it anything but a string" do + it "should raise an ArgumentError" do + expect { key.send(field, Hash.new) }.to raise_error(ArgumentError) + end + end + end + + + describe "when a new Chef::Key object is initialized with invalid input" do + it "should raise an InvalidKeyArgument" do + expect { Chef::Key.new("original_actor", "not_a_user_or_client") }.to raise_error(Chef::Exceptions::InvalidKeyArgument) + end + end + + describe "when a new Chef::Key object is initialized with valid input" do + it "should be a Chef::Key" do + expect(key).to be_a_kind_of(Chef::Key) + end + + it "should properly set the actor" do + expect(key.actor).to eq("original_actor") + end + end + + describe "when actor field is set" do + it_should_behave_like "string fields that are settable" do + let(:field) { :actor } + let(:valid_input) { "new_field_value" } + end + + it_should_behave_like "fields with username type validation" do + let(:field) { :actor } + end + end + + describe "when the name field is set" do + it_should_behave_like "string fields that are settable" do + let(:field) { :name } + let(:valid_input) { "new_field_value" } + end + end + + describe "when the private_key field is set" do + it_should_behave_like "string fields that are settable" do + let(:field) { :private_key } + let(:valid_input) { "new_field_value" } + end + end + + describe "when the public_key field is set" do + it_should_behave_like "string fields that are settable" do + let(:field) { :public_key } + let(:valid_input) { "new_field_value" } + end + + context "when create_key is true" do + before do + key.create_key true + end + + it "should raise an InvalidKeyAttribute" do + expect { key.public_key public_key_string }.to raise_error(Chef::Exceptions::InvalidKeyAttribute) + end + end + end + + describe "when the create_key field is set" do + context "when it is set to true" do + it "should set the field" do + key.create_key(true) + expect(key.create_key).to eq(true) + end + end + + context "when it is set to false" do + it "should set the field" do + key.create_key(false) + expect(key.create_key).to eq(false) + end + end + + context "when anything but a TrueClass or FalseClass is passed" do + it "should raise an ArgumentError" do + expect { key.create_key "not_a_boolean" }.to raise_error(ArgumentError) + end + end + + context "when public_key is defined" do + before do + key.public_key public_key_string + end + + it "should raise an InvalidKeyAttribute" do + expect { key.create_key true }.to raise_error(Chef::Exceptions::InvalidKeyAttribute) + end + end + end + + describe "when the expiration_date field is set" do + context "when a valid date is passed" do + it_should_behave_like "string fields that are settable" do + let(:field) { :public_key } + let(:valid_input) { "2020-12-24T21:00:00Z" } + end + end + + context "when infinity is passed" do + it_should_behave_like "string fields that are settable" do + let(:field) { :public_key } + let(:valid_input) { "infinity" } + end + end + + context "when an invalid date is passed" do + it "should raise an ArgumentError" do + expect { key.expiration_date "invalid_date" }.to raise_error(ArgumentError) + # wrong years + expect { key.expiration_date "20-12-24T21:00:00Z" }.to raise_error(ArgumentError) + end + + context "when it is a valid UTC date missing a Z" do + it "should raise an ArgumentError" do + expect { key.expiration_date "2020-12-24T21:00:00" }.to raise_error(ArgumentError) + end + end + end + end # when the expiration_date field is set + + describe "when serializing to JSON" do + shared_examples_for "common json operations" do + it "should serializes as a JSON object" do + expect(json).to match(/^\{.+\}$/) + end + + it "should include the actor value under the key relative to the actor_field_name passed" do + expect(json).to include(%Q("#{new_key.actor_field_name}":"original_actor")) + end + + it "should include the name field when present" do + new_key.name("monkeypants") + expect(new_key.to_json).to include(%q{"name":"monkeypants"}) + end + + it "should not include the name if not present" do + expect(json).to_not include("name") + end + + it "should include the public_key field when present" do + new_key.public_key "this_public_key" + expect(new_key.to_json).to include(%q("public_key":"this_public_key")) + end + + it "should not include the public_key if not present" do + expect(json).to_not include("public_key") + end + + it "should include the private_key field when present" do + new_key.private_key "this_public_key" + expect(new_key.to_json).to include(%q("private_key":"this_public_key")) + end + + it "should not include the private_key if not present" do + expect(json).to_not include("private_key") + end + + it "should include the expiration_date field when present" do + new_key.expiration_date "2020-12-24T21:00:00Z" + expect(new_key.to_json).to include(%Q("expiration_date":"2020-12-24T21:00:00Z")) + end + + it "should not include the expiration_date if not present" do + expect(json).to_not include("expiration_date") + end + + it "should include the create_key field when present" do + new_key.create_key true + expect(new_key.to_json).to include(%q("create_key":true)) + end + + it "should not include the create_key if not present" do + expect(json).to_not include("create_key") + end + end + + context "when key is for a user" do + it_should_behave_like "common json operations" do + let(:new_key) { Chef::Key.new("original_actor", "user") } + let(:json) do + new_key.to_json + end + end + end + + context "when key is for a client" do + it_should_behave_like "common json operations" do + let(:new_key) { Chef::Key.new("original_actor", "client") } + let(:json) do + new_key.to_json + end + end + end + + end # when serializing to JSON + + describe "when deserializing from JSON" do + shared_examples_for "a deserializable object" do + it "deserializes to a Chef::Key object" do + expect(key).to be_a_kind_of(Chef::Key) + end + + it "preserves the actor" do + expect(key.actor).to eq("turtle") + end + + it "preserves the name" do + expect(key.name).to eq("key_name") + end + + it "includes the public key if present" do + expect(key.public_key).to eq(public_key_string) + end + + it "includes the expiration_date if present" do + expect(key.expiration_date).to eq("infinity") + end + + it "includes the private_key if present" do + expect(key.private_key).to eq("some_private_key") + end + + it "includes the create_key if present" do + expect(key_with_create_key_field.create_key).to eq(true) + end + end + + context "when deserializing a key for a user" do + it_should_behave_like "a deserializable object" do + let(:key) do + o = { "user" => "turtle", + "name" => "key_name", + "public_key" => public_key_string, + "private_key" => "some_private_key", + "expiration_date" => "infinity"} + Chef::Key.from_json(o.to_json) + end + let(:key_with_create_key_field) do + o = { "user" => "turtle", + "create_key" => true } + Chef::Key.from_json(o.to_json) + end + end + end + + context "when deserializing a key for a client" do + it_should_behave_like "a deserializable object" do + let(:key) do + o = { "client" => "turtle", + "name" => "key_name", + "public_key" => public_key_string, + "private_key" => "some_private_key", + "expiration_date" => "infinity"} + Chef::Key.from_json(o.to_json) + end + let(:key_with_create_key_field) do + o = { "client" => "turtle", + "create_key" => true } + Chef::Key.from_json(o.to_json) + end + end + end + end # when deserializing from JSON + + + describe "API Interactions" do + let(:rest) do + Chef::Config[:chef_server_root] = "http://www.example.com" + Chef::Config[:chef_server_url] = "http://www.example.com/organizations/test_org" + r = double('rest') + allow(Chef::REST).to receive(:new).and_return(r) + r + end + + let(:user_key) do + o = Chef::Key.new("foobar", "user") + o + end + + let(:client_key) do + o = Chef::Key.new("foobar", "client") + o + end + + describe "list" do + context "when listing keys for a user" do + let(:response) { [{"uri" => "http://www.example.com/users/keys/foobar", "name"=>"foobar", "expired"=>false}] } + let(:inflated_response) { {"foobar" => user_key} } + + it "lists all keys" do + expect(rest).to receive(:get_rest).with("users/#{user_key.actor}/keys").and_return(response) + expect(Chef::Key.list_by_user("foobar")).to eq(response) + end + + it "inflate all keys" do + allow(Chef::Key).to receive(:load_by_user).with(user_key.actor, "foobar").and_return(user_key) + expect(rest).to receive(:get_rest).with("users/#{user_key.actor}/keys").and_return(response) + expect(Chef::Key.list_by_user("foobar", true)).to eq(inflated_response) + end + + end + + context "when listing keys for a client" do + let(:response) { [{"uri" => "http://www.example.com/users/keys/foobar", "name"=>"foobar", "expired"=>false}] } + let(:inflated_response) { {"foobar" => client_key} } + + it "lists all keys" do + expect(rest).to receive(:get_rest).with("clients/#{client_key.actor}/keys").and_return(response) + expect(Chef::Key.list_by_client("foobar")).to eq(response) + end + + it "inflate all keys" do + allow(Chef::Key).to receive(:load_by_client).with(client_key.actor, "foobar").and_return(client_key) + expect(rest).to receive(:get_rest).with("clients/#{user_key.actor}/keys").and_return(response) + expect(Chef::Key.list_by_client("foobar", true)).to eq(inflated_response) + end + + end + end + + + describe "create" do + shared_examples_for "create key" do + context "when a field is missing" do + it "should raise a MissingKeyAttribute" do + expect { key.create }.to raise_error(Chef::Exceptions::MissingKeyAttribute) + end + end + + context "when the name field is missing" do + before do + key.public_key public_key_string + key.expiration_date "2020-12-24T21:00:00Z" + end + + it "creates a new key via the API with the fingerprint as the name" do + expect(rest).to receive(:post_rest).with(url, + {"name" => "12:3e:33:73:0b:f4:ec:72:dc:f0:4c:51:62:27:08:76:96:24:f4:4a", + "public_key" => key.public_key, + "expiration_date" => key.expiration_date}).and_return({}) + key.create + end + end + + context "when every field is populated" do + before do + key.name "key_name" + key.public_key public_key_string + key.expiration_date "2020-12-24T21:00:00Z" + key.create_key false + end + + context "when create_key is false" do + it "creates a new key via the API" do + expect(rest).to receive(:post_rest).with(url, + {"name" => key.name, + "public_key" => key.public_key, + "expiration_date" => key.expiration_date}).and_return({}) + key.create + end + end + + context "when create_key is true and public_key is nil" do + + before do + key.delete_public_key + key.create_key true + $expected_output = { + actor_type => "foobar", + "name" => key.name, + "create_key" => true, + "expiration_date" => key.expiration_date + } + $expected_input = { + "name" => key.name, + "create_key" => true, + "expiration_date" => key.expiration_date + } + end + + it "should create a new key via the API" do + expect(rest).to receive(:post_rest).with(url, $expected_input).and_return({}) + key.create + end + + context "when the server returns the private_key via key.create" do + before do + allow(rest).to receive(:post_rest).with(url, $expected_input).and_return({"private_key" => "this_private_key"}) + end + + it "key.create returns the original key plus the private_key" do + expect(key.create.to_hash).to eq($expected_output.merge({"private_key" => "this_private_key"})) + end + end + end + + context "when create_key is false and public_key is nil" do + before do + key.delete_public_key + key.create_key false + end + it "should raise an InvalidKeyArgument" do + expect { key.create }.to raise_error(Chef::Exceptions::MissingKeyAttribute) + end + end + end + end + + context "when creating a user key" do + it_should_behave_like "create key" do + let(:url) { "users/#{key.actor}/keys" } + let(:key) { user_key } + let(:actor_type) { "user" } + end + end + + context "when creating a client key" do + it_should_behave_like "create key" do + let(:url) { "clients/#{client_key.actor}/keys" } + let(:key) { client_key } + let(:actor_type) { "client" } + end + end + end # create + + describe "update" do + shared_examples_for "update key" do + context "when name is missing and no argument was passed to update" do + it "should raise an MissingKeyAttribute" do + expect { key.update }.to raise_error(Chef::Exceptions::MissingKeyAttribute) + end + end + + context "when some fields are populated" do + before do + key.name "key_name" + key.expiration_date "2020-12-24T21:00:00Z" + end + + it "should update the key via the API" do + expect(rest).to receive(:put_rest).with(url, key.to_hash).and_return({}) + key.update + end + end + + context "when @name is not nil and a arg is passed to update" do + before do + key.name "new_name" + end + + it "passes @name in the body and the arg in the PUT URL" do + expect(rest).to receive(:put_rest).with(update_name_url, key.to_hash).and_return({}) + key.update("old_name") + end + end + + context "when the server returns a public_key and create_key is true" do + before do + key.name "key_name" + key.create_key true + allow(rest).to receive(:put_rest).with(url, key.to_hash).and_return({ + "key" => "key_name", + "public_key" => public_key_string + }) + + end + + it "returns a key with public_key populated" do + new_key = key.update + expect(new_key.public_key).to eq(public_key_string) + end + + it "returns a key without create_key set" do + new_key = key.update + expect(new_key.create_key).to be_nil + end + end + end + + context "when updating a user key" do + it_should_behave_like "update key" do + let(:url) { "users/#{key.actor}/keys/#{key.name}" } + let(:update_name_url) { "users/#{key.actor}/keys/old_name" } + let(:key) { user_key } + end + end + + context "when updating a client key" do + it_should_behave_like "update key" do + let(:url) { "clients/#{client_key.actor}/keys/#{key.name}" } + let(:update_name_url) { "clients/#{client_key.actor}/keys/old_name" } + let(:key) { client_key } + end + end + + end #update + + describe "load" do + shared_examples_for "load" do + it "should load a named key from the API" do + expect(rest).to receive(:get_rest).with(url).and_return({"user" => "foobar", "name" => "test_key_name", "public_key" => public_key_string, "expiration_date" => "infinity"}) + key = Chef::Key.send(load_method, "foobar", "test_key_name") + expect(key.actor).to eq("foobar") + expect(key.name).to eq("test_key_name") + expect(key.public_key).to eq(public_key_string) + expect(key.expiration_date).to eq("infinity") + end + end + + describe "load_by_user" do + it_should_behave_like "load" do + let(:load_method) { :load_by_user } + let(:url) { "users/foobar/keys/test_key_name" } + end + end + + describe "load_by_client" do + it_should_behave_like "load" do + let(:load_method) { :load_by_client } + let(:url) { "clients/foobar/keys/test_key_name" } + end + end + + end #load + + describe "destroy" do + shared_examples_for "destroy key" do + context "when name is missing" do + it "should raise an MissingKeyAttribute" do + expect { Chef::Key.new("username", "user").destroy }.to raise_error(Chef::Exceptions::MissingKeyAttribute) + end + end + + before do + key.name "key_name" + end + context "when name is not missing" do + it "should delete the key via the API" do + expect(rest).to receive(:delete_rest).with(url).and_return({}) + key.destroy + end + end + end + + context "when destroying a user key" do + it_should_behave_like "destroy key" do + let(:url) { "users/#{key.actor}/keys/#{key.name}" } + let(:key) { user_key } + end + end + + context "when destroying a client key" do + it_should_behave_like "destroy key" do + let(:url) { "clients/#{client_key.actor}/keys/#{key.name}" } + let(:key) { client_key } + end + end + end + end # API Interactions +end diff --git a/spec/unit/knife/bootstrap_spec.rb b/spec/unit/knife/bootstrap_spec.rb index f1ca510ed3..0195e6d406 100644 --- a/spec/unit/knife/bootstrap_spec.rb +++ b/spec/unit/knife/bootstrap_spec.rb @@ -23,7 +23,7 @@ require 'net/ssh' describe Chef::Knife::Bootstrap do before do - allow(Chef::Platform).to receive(:windows?) { false } + allow(ChefConfig).to receive(:windows?) { false } end let(:knife) do Chef::Log.logger = Logger.new(StringIO.new) @@ -531,6 +531,7 @@ describe Chef::Knife::Bootstrap do describe "when running the bootstrap" do let(:knife_ssh) do knife.name_args = ["foo.example.com"] + knife.config[:chef_node_name] = "foo.example.com" knife.config[:ssh_user] = "rooty" knife.config[:identity_file] = "~/.ssh/me.rsa" allow(knife).to receive(:render_template).and_return("") @@ -590,6 +591,12 @@ describe Chef::Knife::Bootstrap do expect(knife.chef_vault_handler).not_to receive(:run).with(node_name: knife.config[:chef_node_name]) knife.run end + + it "raises an exception if the config[:chef_node_name] is not present" do + knife.config[:chef_node_name] = nil + + expect { knife.run }.to raise_error(SystemExit) + end end context "when the validation key is not present" do @@ -604,6 +611,12 @@ describe Chef::Knife::Bootstrap do expect(knife.chef_vault_handler).to receive(:run).with(node_name: knife.config[:chef_node_name]) knife.run end + + it "raises an exception if the config[:chef_node_name] is not present" do + knife.config[:chef_node_name] = nil + + expect { knife.run }.to raise_error(SystemExit) + end end context "when the validation_key is nil" do diff --git a/spec/unit/knife/client_create_spec.rb b/spec/unit/knife/client_create_spec.rb index 10d386b5ff..8fecfc885f 100644 --- a/spec/unit/knife/client_create_spec.rb +++ b/spec/unit/knife/client_create_spec.rb @@ -22,6 +22,8 @@ Chef::Knife::ClientCreate.load_deps describe Chef::Knife::ClientCreate do let(:stderr) { StringIO.new } + let(:stdout) { StringIO.new } + let(:default_client_hash) do { @@ -32,84 +34,153 @@ describe Chef::Knife::ClientCreate do end let(:client) do - c = double("Chef::ApiClient") - allow(c).to receive(:save).and_return({"private_key" => ""}) - allow(c).to receive(:to_s).and_return("client[adam]") - c + Chef::ApiClient.new end let(:knife) do k = Chef::Knife::ClientCreate.new - k.name_args = [ "adam" ] - k.ui.config[:disable_editing] = true + k.name_args = [] + allow(k).to receive(:client).and_return(client) + allow(k).to receive(:edit_data).with(client).and_return(client) allow(k.ui).to receive(:stderr).and_return(stderr) - allow(k.ui).to receive(:stdout).and_return(stderr) + allow(k.ui).to receive(:stdout).and_return(stdout) k end + before do + allow(client).to receive(:to_s).and_return("client[adam]") + allow(knife).to receive(:create_client).and_return(client) + end + before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" end describe "run" do - it "should create and save the ApiClient" do - expect(Chef::ApiClient).to receive(:from_hash).and_return(client) - expect(client).to receive(:save) - knife.run + context "when nothing is passed" do + # from spec/support/shared/unit/knife_shared.rb + it_should_behave_like "mandatory field missing" do + let(:name_args) { [] } + let(:fieldname) { 'client name' } + end end - it "should print a message upon creation" do - expect(Chef::ApiClient).to receive(:from_hash).and_return(client) - expect(client).to receive(:save) - knife.run - expect(stderr.string).to match /Created client.*adam/i - end + context "when clientname is passed" do + before do + knife.name_args = ['adam'] + end - it "should set the Client name" do - expect(Chef::ApiClient).to receive(:from_hash).with(hash_including("name" => "adam")).and_return(client) - knife.run - end + context "when public_key and prevent_keygen are passed" do + before do + knife.config[:public_key] = "some_key" + knife.config[:prevent_keygen] = true + 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 cannot pass --public-key and --prevent-keygen/ + end + end - it "by default it is not an admin" do - expect(Chef::ApiClient).to receive(:from_hash).with(hash_including("admin" => false)).and_return(client) - knife.run - end + it "should create the ApiClient" do + expect(knife).to receive(:create_client) + knife.run + end - it "by default it is not a validator" do - expect(Chef::ApiClient).to receive(:from_hash).with(hash_including("validator" => false)).and_return(client) - knife.run - end + it "should print a message upon creation" do + expect(knife).to receive(:create_client) + knife.run + expect(stderr.string).to match /Created client.*adam/i + end - it "should allow you to edit the data" do - expect(knife).to receive(:edit_hash).with(default_client_hash).and_return(default_client_hash) - allow(Chef::ApiClient).to receive(:from_hash).and_return(client) - knife.run - end + it "should set the Client name" do + knife.run + expect(client.name).to eq("adam") + end - describe "with -f or --file" do - it "should write the private key to a file" do - knife.config[:file] = "/tmp/monkeypants" - allow_any_instance_of(Chef::ApiClient).to receive(:save).and_return({ 'private_key' => "woot" }) - filehandle = double("Filehandle") - expect(filehandle).to receive(:print).with('woot') - expect(File).to receive(:open).with("/tmp/monkeypants", "w").and_yield(filehandle) + it "by default it is not an admin" do knife.run + expect(client.admin).to be_falsey end - end - describe "with -a or --admin" do - it "should create an admin client" do - knife.config[:admin] = true - expect(Chef::ApiClient).to receive(:from_hash).with(hash_including("admin" => true)).and_return(client) + it "by default it is not a validator" do knife.run + expect(client.admin).to be_falsey end - end - describe "with --validator" do - it "should create an validator client" do - knife.config[:validator] = true - expect(Chef::ApiClient).to receive(:from_hash).with(hash_including("validator" => true)).and_return(client) + it "by default it should set create_key to true" do knife.run + expect(client.create_key).to be_truthy + end + + it "should allow you to edit the data" do + expect(knife).to receive(:edit_data).with(client).and_return(client) + knife.run + end + + describe "with -f or --file" do + before do + client.private_key "woot" + end + + it "should write the private key to a file" do + knife.config[:file] = "/tmp/monkeypants" + filehandle = double("Filehandle") + expect(filehandle).to receive(:print).with('woot') + expect(File).to receive(:open).with("/tmp/monkeypants", "w").and_yield(filehandle) + knife.run + end + end + + describe "with -a or --admin" do + before do + knife.config[:admin] = true + end + + it "should create an admin client" do + knife.run + expect(client.admin).to be_truthy + end + end + + describe "with -p or --public-key" do + before do + knife.config[:public_key] = 'some_key' + allow(File).to receive(:read).and_return('some_key') + allow(File).to receive(:expand_path) + end + + it "sets the public key" do + knife.run + expect(client.public_key).to eq('some_key') + end + end + + describe "with -k or --prevent-keygen" do + before do + knife.config[:prevent_keygen] = true + end + + it "does not set create_key" do + knife.run + expect(client.create_key).to be_falsey + end + end + + describe "with --validator" do + before do + knife.config[:validator] = true + end + + it "should create an validator client" do + knife.run + expect(client.validator).to be_truthy + end end end end diff --git a/spec/unit/knife/core/subcommand_loader_spec.rb b/spec/unit/knife/core/subcommand_loader_spec.rb index 7f9308b28a..219a1f2906 100644 --- a/spec/unit/knife/core/subcommand_loader_spec.rb +++ b/spec/unit/knife/core/subcommand_loader_spec.rb @@ -22,14 +22,14 @@ describe Chef::Knife::SubcommandLoader do let(:loader) { Chef::Knife::SubcommandLoader.new(File.join(CHEF_SPEC_DATA, 'knife-site-subcommands')) } let(:home) { File.join(CHEF_SPEC_DATA, 'knife-home') } let(:plugin_dir) { File.join(home, '.chef', 'plugins', 'knife') } - + before do - allow(Chef::Platform).to receive(:windows?) { false } - Chef::Util::PathHelper.class_variable_set(:@@home_dir, home) + allow(ChefConfig).to receive(:windows?) { false } + Chef::Util::PathHelper.class_variable_set(:@@home_dir, home) end after do - Chef::Util::PathHelper.class_variable_set(:@@home_dir, nil) + Chef::Util::PathHelper.class_variable_set(:@@home_dir, nil) end it "builds a list of the core subcommand file require paths" do @@ -106,6 +106,18 @@ describe Chef::Knife::SubcommandLoader do # Chef 12.0.0.rc.0 gem also: "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.rc.0/lib/chef/knife/thing.rb", + # Test that we ignore the platform suffix when checking for different + # gem versions. + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-x86-mingw32/lib/chef/knife/valid.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-i386-mingw64/lib/chef/knife/valid-too.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-mswin32/lib/chef/knife/also-valid.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-universal-mingw32/lib/chef/knife/universal-is-valid.rb", + # ...but don't ignore the .rc / .dev parts in the case when we have + # platform suffixes + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.rc.0-x86-mingw32/lib/chef/knife/invalid.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.dev-mswin32/lib/chef/knife/invalid-too.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.dev.0-x86-mingw64/lib/chef/knife/still-invalid.rb", + # This command is "extra" compared to what's in the embedded/apps/chef install: "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/data_bag_secret_options.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-vault-2.2.4/lib/chef/knife/decrypt.rb", @@ -133,6 +145,10 @@ describe Chef::Knife::SubcommandLoader do "/opt/chefdk/embedded/apps/chef/lib/chef/knife/bootstrap.rb", "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_bulk_delete.rb", "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_create.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-x86-mingw32/lib/chef/knife/valid.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-i386-mingw64/lib/chef/knife/valid-too.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-mswin32/lib/chef/knife/also-valid.rb", + "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-universal-mingw32/lib/chef/knife/universal-is-valid.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-vault-2.2.4/lib/chef/knife/decrypt.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/knife-spork-1.4.1/lib/chef/knife/spork-bump.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-foo-#{Chef::VERSION}/lib/chef/knife/chef-foo.rb", diff --git a/spec/unit/knife/core/ui_spec.rb b/spec/unit/knife/core/ui_spec.rb index ac42ad6dd6..ab420518a3 100644 --- a/spec/unit/knife/core/ui_spec.rb +++ b/spec/unit/knife/core/ui_spec.rb @@ -368,6 +368,20 @@ EOM @ui.config[:attribute] = "keys.keys" expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { "keys.keys" => "values" } }) end + + it "should return the name attribute" do + allow_any_instance_of(Chef::Node).to receive(:name).and_return("chef.localdomain") + input = Chef::Node.new + @ui.config[:attribute] = "name" + expect(@ui.format_for_display(input)).to eq( {"chef.localdomain"=>{"name"=>"chef.localdomain"} }) + end + + it "returns nil when given an attribute path that isn't a name or attribute" do + input = { "keys" => {"keys" => "values"}, "hi" => "ho", "id" => "sample-data-bag-item" } + non_existing_path = "nope.nada.nothingtoseehere" + @ui.config[:attribute] = non_existing_path + expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { non_existing_path => nil } }) + end end describe "with --run-list passed" do @@ -420,7 +434,7 @@ EOM before(:each) do stdout = double('StringIO', :tty? => true) allow(@ui).to receive(:stdout).and_return(stdout) - allow(Chef::Platform).to receive(:windows?) { true } + allow(ChefConfig).to receive(:windows?) { true } Chef::Config.reset end diff --git a/spec/unit/knife/data_bag_from_file_spec.rb b/spec/unit/knife/data_bag_from_file_spec.rb index 3882bff349..8b6502145c 100644 --- a/spec/unit/knife/data_bag_from_file_spec.rb +++ b/spec/unit/knife/data_bag_from_file_spec.rb @@ -26,7 +26,7 @@ Chef::Knife::DataBagFromFile.load_deps describe Chef::Knife::DataBagFromFile do before :each do - allow(Chef::Platform).to receive(:windows?) { false } + allow(ChefConfig).to receive(:windows?) { false } Chef::Config[:node_name] = "webmonkey.example.com" FileUtils.mkdir_p([db_folder, db_folder2]) db_file.write(Chef::JSONCompat.to_json(plain_data)) diff --git a/spec/unit/knife/environment_from_file_spec.rb b/spec/unit/knife/environment_from_file_spec.rb index d150e5ee64..11ad23c919 100644 --- a/spec/unit/knife/environment_from_file_spec.rb +++ b/spec/unit/knife/environment_from_file_spec.rb @@ -23,7 +23,7 @@ Chef::Knife::EnvironmentFromFile.load_deps describe Chef::Knife::EnvironmentFromFile do before(:each) do - allow(Chef::Platform).to receive(:windows?) { false } + allow(ChefConfig).to receive(:windows?) { false } @knife = Chef::Knife::EnvironmentFromFile.new @stdout = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) diff --git a/spec/unit/knife/key_create_spec.rb b/spec/unit/knife/key_create_spec.rb new file mode 100644 index 0000000000..5998e10274 --- /dev/null +++ b/spec/unit/knife/key_create_spec.rb @@ -0,0 +1,224 @@ +# +# 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' +require 'chef/knife/user_key_create' +require 'chef/knife/client_key_create' +require 'chef/knife/key_create' +require 'chef/key' + +describe "key create commands that inherit knife" do + shared_examples_for "a key create command" do + let(:stderr) { StringIO.new } + let(:params) { [] } + let(:service_object) { instance_double(Chef::Knife::KeyCreate) } + 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 "after apply_params! is called with valid args" do + let(:params) { ["charmander"] } + before do + command.apply_params!(params) + end + + context "when the service object is called" do + it "creates a new instance of Chef::Knife::KeyCreate with the correct args" do + expect(Chef::Knife::KeyCreate).to receive(:new). + with("charmander", command.actor_field_name, command.ui, command.config). + and_return(service_object) + command.service_object + end + end # when the service object is called + end # after apply_params! is called with valid args + end # a key create command + + describe Chef::Knife::UserKeyCreate do + it_should_behave_like "a key create command" + # defined in key_helper.rb + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyCreate) } + let(:params) { ["charmander"] } + end + end + + describe Chef::Knife::ClientKeyCreate do + it_should_behave_like "a key create command" + # defined in key_helper.rb + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyCreate) } + let(:params) { ["charmander"] } + end + end +end + +describe Chef::Knife::KeyCreate do + let(:public_key) { + "-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvPo+oNPB7uuNkws0fC02 +KxSwdyqPLu0fhI1pOweNKAZeEIiEz2PkybathHWy8snSXGNxsITkf3eyvIIKa8OZ +WrlqpI3yv/5DOP8HTMCxnFuMJQtDwMcevlqebX4bCxcByuBpNYDcAHjjfLGSfMjn +E5lZpgYWwnpic4kSjYcL9ORK9nYvlWV9P/kCYmRhIjB4AhtpWRiOfY/TKi3P2LxT +IjSmiN/ihHtlhV/VSnBJ5PzT/lRknlrJ4kACoz7Pq9jv+aAx5ft/xE9yDa2DYs0q +Tfuc9dUYsFjptWYrV6pfEQ+bgo1OGBXORBFcFL+2D7u9JYquKrMgosznHoEkQNLo +0wIDAQAB +-----END PUBLIC KEY-----" + } + let(:config) { Hash.new } + let(:actor) { "charmander" } + let(:ui) { instance_double("Chef::Knife::UI") } + + shared_examples_for "key create run command" do + let(:key_create_object) { + described_class.new(actor, actor_field_name, ui, config) + } + + context "when public_key and key_name weren't passed" do + it "raises a Chef::Exceptions::KeyCommandInputError with the proper error message" do + expect{ key_create_object.run }.to raise_error(Chef::Exceptions::KeyCommandInputError, key_create_object.public_key_or_key_name_error_msg) + end + end + + context "when the command is run" do + let(:expected_hash) { + { + actor_field_name => "charmander" + } + } + + before do + allow(File).to receive(:read).and_return(public_key) + allow(File).to receive(:expand_path) + + allow(key_create_object).to receive(:output_private_key_to_file) + allow(key_create_object).to receive(:display_private_key) + allow(key_create_object).to receive(:edit_data).and_return(expected_hash) + allow(key_create_object).to receive(:create_key_from_hash).and_return(Chef::Key.from_hash(expected_hash)) + allow(key_create_object).to receive(:display_info) + end + + context "when a valid hash is passed" do + let(:key_name) { "charmander-key" } + let(:valid_expiration_date) { "2020-12-24T21:00:00Z" } + let(:expected_hash) { + { + actor_field_name => "charmander", + "public_key" => public_key, + "expiration_date" => valid_expiration_date, + "key_name" => key_name + } + } + before do + key_create_object.config[:public_key] = "public_key_path" + key_create_object.config[:expiration_Date] = valid_expiration_date, + key_create_object.config[:key_name] = key_name + end + + it "creates the proper hash" do + expect(key_create_object).to receive(:create_key_from_hash).with(expected_hash) + key_create_object.run + end + end + + context "when public_key is passed" do + let(:expected_hash) { + { + actor_field_name => "charmander", + "public_key" => public_key + } + } + before do + key_create_object.config[:public_key] = "public_key_path" + end + + it "calls File.expand_path with the public_key input" do + expect(File).to receive(:expand_path).with("public_key_path") + key_create_object.run + end + end # when public_key is passed + + context "when public_key isn't passed and key_name is" do + let(:expected_hash) { + { + actor_field_name => "charmander", + "name" => "charmander-key", + "create_key" => true + } + } + before do + key_create_object.config[:key_name] = "charmander-key" + end + + it "should set create_key to true" do + expect(key_create_object).to receive(:create_key_from_hash).with(expected_hash) + key_create_object.run + end + end + + context "when the server returns a private key" do + let(:expected_hash) { + { + actor_field_name => "charmander", + "public_key" => public_key, + "private_key" => "super_private" + } + } + + before do + key_create_object.config[:public_key] = "public_key_path" + end + + context "when file is not passed" do + it "calls display_private_key with the private_key" do + expect(key_create_object).to receive(:display_private_key).with("super_private") + key_create_object.run + end + end + + context "when file is passed" do + before do + key_create_object.config[:file] = "/fake/file" + end + + it "calls output_private_key_to_file with the private_key" do + expect(key_create_object).to receive(:output_private_key_to_file).with("super_private") + key_create_object.run + end + end + end # when the server returns a private key + end # when the command is run + end #key create run command" + + context "when actor_field_name is 'user'" do + it_should_behave_like "key create run command" do + let(:actor_field_name) { "user" } + end + end + + context "when actor_field_name is 'client'" do + it_should_behave_like "key create run command" do + let(:actor_field_name) { "client" } + end + end +end + diff --git a/spec/unit/knife/key_delete_spec.rb b/spec/unit/knife/key_delete_spec.rb new file mode 100644 index 0000000000..1d4b9f825f --- /dev/null +++ b/spec/unit/knife/key_delete_spec.rb @@ -0,0 +1,135 @@ +# +# 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' +require 'chef/knife/user_key_delete' +require 'chef/knife/client_key_delete' +require 'chef/knife/key_delete' +require 'chef/key' + +describe "key delete commands that inherit knife" do + shared_examples_for "a key delete command" do + let(:stderr) { StringIO.new } + let(:params) { [] } + let(:service_object) { instance_double(Chef::Knife::KeyDelete) } + 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 "after apply_params! is called with valid args" do + let(:params) { ["charmander", "charmander-key"] } + before do + command.apply_params!(params) + end + + context "when the service object is called" do + it "creates a new instance of Chef::Knife::KeyDelete with the correct args" do + expect(Chef::Knife::KeyDelete).to receive(:new). + with("charmander-key", "charmander", command.actor_field_name, command.ui). + and_return(service_object) + command.service_object + end + end # when the service object is called + end # after apply_params! is called with valid args + end # a key delete command + + describe Chef::Knife::UserKeyDelete do + it_should_behave_like "a key delete command" + # defined in key_helpers.rb + it_should_behave_like "a knife key command with a keyname as the second arg" + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyDelete) } + let(:params) { ["charmander", "charmander-key"] } + end + end + + describe Chef::Knife::ClientKeyDelete do + it_should_behave_like "a key delete command" + # defined in key_helpers.rb + it_should_behave_like "a knife key command with a keyname as the second arg" + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyDelete) } + let(:params) { ["charmander", "charmander-key"] } + end + end +end + +describe Chef::Knife::KeyDelete do + let(:actor) { "charmander" } + let(:keyname) { "charmander-key" } + let(:ui) { instance_double("Chef::Knife::UI") } + + shared_examples_for "key delete run command" do + let(:key_delete_object) { + described_class.new(keyname, actor, actor_field_name, ui) + } + + before do + allow_any_instance_of(Chef::Key).to receive(:destroy) + allow(key_delete_object).to receive(:print_destroyed) + allow(key_delete_object).to receive(:confirm!) + end + + context "when the command is run" do + it "calls Chef::Key.new with the proper input" do + expect(Chef::Key).to receive(:new).with(actor, actor_field_name).and_call_original + key_delete_object.run + end + + it "calls name on the Chef::Key instance with the proper input" do + expect_any_instance_of(Chef::Key).to receive(:name).with(keyname) + key_delete_object.run + end + + it "calls destroy on the Chef::Key instance" do + expect_any_instance_of(Chef::Key).to receive(:destroy).once + key_delete_object.run + end + + it "calls confirm!" do + expect(key_delete_object).to receive(:confirm!) + key_delete_object.run + end + + it "calls print_destroyed" do + expect(key_delete_object).to receive(:print_destroyed) + key_delete_object.run + end + end # when the command is run + + + end # key delete run command + + context "when actor_field_name is 'user'" do + it_should_behave_like "key delete run command" do + let(:actor_field_name) { "user" } + end + end + + context "when actor_field_name is 'client'" do + it_should_behave_like "key delete run command" do + let(:actor_field_name) { "client" } + end + end +end + diff --git a/spec/unit/knife/key_edit_spec.rb b/spec/unit/knife/key_edit_spec.rb new file mode 100644 index 0000000000..538b91de2d --- /dev/null +++ b/spec/unit/knife/key_edit_spec.rb @@ -0,0 +1,267 @@ +# +# 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' +require 'chef/knife/user_key_edit' +require 'chef/knife/client_key_edit' +require 'chef/knife/key_edit' +require 'chef/key' + +describe "key edit commands that inherit knife" do + shared_examples_for "a key edit command" do + let(:stderr) { StringIO.new } + let(:params) { [] } + let(:service_object) { instance_double(Chef::Knife::KeyEdit) } + 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 "after apply_params! is called with valid args" do + let(:params) { ["charmander", "charmander-key"] } + before do + command.apply_params!(params) + end + + context "when the service object is called" do + it "creates a new instance of Chef::Knife::KeyEdit with the correct args" do + expect(Chef::Knife::KeyEdit).to receive(:new). + with("charmander-key", "charmander", command.actor_field_name, command.ui, command.config). + and_return(service_object) + command.service_object + end + end # when the service object is called + end # after apply_params! is called with valid args + end # a key edit command + + describe Chef::Knife::UserKeyEdit do + it_should_behave_like "a key edit command" + # defined in key_helpers.rb + it_should_behave_like "a knife key command with a keyname as the second arg" + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyEdit) } + let(:params) { ["charmander", "charmander-key"] } + end + end + + describe Chef::Knife::ClientKeyEdit do + it_should_behave_like "a key edit command" + # defined in key_helpers.rb + it_should_behave_like "a knife key command with a keyname as the second arg" + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyEdit) } + let(:params) { ["charmander", "charmander-key"] } + end + end +end + +describe Chef::Knife::KeyEdit do + let(:public_key) { + "-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvPo+oNPB7uuNkws0fC02 +KxSwdyqPLu0fhI1pOweNKAZeEIiEz2PkybathHWy8snSXGNxsITkf3eyvIIKa8OZ +WrlqpI3yv/5DOP8HTMCxnFuMJQtDwMcevlqebX4bCxcByuBpNYDcAHjjfLGSfMjn +E5lZpgYWwnpic4kSjYcL9ORK9nYvlWV9P/kCYmRhIjB4AhtpWRiOfY/TKi3P2LxT +IjSmiN/ihHtlhV/VSnBJ5PzT/lRknlrJ4kACoz7Pq9jv+aAx5ft/xE9yDa2DYs0q +Tfuc9dUYsFjptWYrV6pfEQ+bgo1OGBXORBFcFL+2D7u9JYquKrMgosznHoEkQNLo +0wIDAQAB +-----END PUBLIC KEY-----" + } + let(:config) { Hash.new } + let(:actor) { "charmander" } + let(:keyname) { "charmander-key" } + let(:ui) { instance_double("Chef::Knife::UI") } + + shared_examples_for "key edit run command" do + let(:key_edit_object) { + described_class.new(keyname, actor, actor_field_name, ui, config) + } + + context "when the command is run" do + let(:expected_hash) { + { + actor_field_name => "charmander" + } + } + let(:new_keyname) { "charizard-key" } + + before do + allow(File).to receive(:read).and_return(public_key) + allow(File).to receive(:expand_path) + + allow(key_edit_object).to receive(:output_private_key_to_file) + allow(key_edit_object).to receive(:display_private_key) + allow(key_edit_object).to receive(:edit_data).and_return(expected_hash) + allow(key_edit_object).to receive(:display_info) + end + + + context "when public_key and create_key are passed" do + before do + key_edit_object.config[:public_key] = "public_key_path" + key_edit_object.config[:create_key] = true + end + + it "raises a Chef::Exceptions::KeyCommandInputError with the proper error message" do + expect{ key_edit_object.run }.to raise_error(Chef::Exceptions::KeyCommandInputError, key_edit_object.public_key_and_create_key_error_msg) + end + end + + context "when key_name is passed" do + let(:expected_hash) { + { + actor_field_name => "charmander", + "name" => new_keyname + } + } + before do + key_edit_object.config[:key_name] = new_keyname + allow_any_instance_of(Chef::Key).to receive(:update) + end + + it "update_key_from_hash gets passed a hash with new key name" do + expect(key_edit_object).to receive(:update_key_from_hash).with(expected_hash).and_return(Chef::Key.from_hash(expected_hash)) + key_edit_object.run + end + + it "Chef::Key.update is passed a string containing the original keyname" do + expect_any_instance_of(Chef::Key).to receive(:update).with(/#{keyname}/).and_return(Chef::Key.from_hash(expected_hash)) + key_edit_object.run + end + + it "Chef::Key.update is not passed a string containing the new keyname" do + expect_any_instance_of(Chef::Key).not_to receive(:update).with(/#{new_keyname}/) + allow_any_instance_of(Chef::Key).to receive(:update).and_return(Chef::Key.from_hash(expected_hash)) + key_edit_object.run + end + end + + context "when public_key, key_name, and expiration_date are passed" do + let(:expected_hash) { + { + actor_field_name => "charmander", + "public_key" => public_key, + "name" => new_keyname, + "expiration_date" => "infinity" + } + } + before do + key_edit_object.config[:public_key] = "this-public-key" + key_edit_object.config[:key_name] = new_keyname + key_edit_object.config[:expiration_date] = "infinity" + allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash)) + end + + it "passes the right hash to update_key_from_hash" do + expect(key_edit_object).to receive(:update_key_from_hash).with(expected_hash) + key_edit_object.run + end + end + + context "when create_key is passed" do + let(:expected_hash) { + { + actor_field_name => "charmander", + "create_key" => true + } + } + + before do + key_edit_object.config[:create_key] = true + allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash)) + end + + it "passes the right hash to update_key_from_hash" do + expect(key_edit_object).to receive(:update_key_from_hash).with(expected_hash) + key_edit_object.run + end + end + + context "when public_key is passed" do + let(:expected_hash) { + { + actor_field_name => "charmander", + "public_key" => public_key + } + } + before do + allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash)) + key_edit_object.config[:public_key] = "public_key_path" + end + + it "calls File.expand_path with the public_key input" do + expect(File).to receive(:expand_path).with("public_key_path") + key_edit_object.run + end + end # when public_key is passed + + context "when the server returns a private key" do + let(:expected_hash) { + { + actor_field_name => "charmander", + "public_key" => public_key, + "private_key" => "super_private" + } + } + + before do + allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash)) + key_edit_object.config[:public_key] = "public_key_path" + end + + context "when file is not passed" do + it "calls display_private_key with the private_key" do + expect(key_edit_object).to receive(:display_private_key).with("super_private") + key_edit_object.run + end + end + + context "when file is passed" do + before do + key_edit_object.config[:file] = "/fake/file" + end + + it "calls output_private_key_to_file with the private_key" do + expect(key_edit_object).to receive(:output_private_key_to_file).with("super_private") + key_edit_object.run + end + end + end # when the server returns a private key + + end # when the command is run + + + + end # key edit run command + + context "when actor_field_name is 'user'" do + it_should_behave_like "key edit run command" do + let(:actor_field_name) { "user" } + end + end + + context "when actor_field_name is 'client'" do + it_should_behave_like "key edit run command" do + let(:actor_field_name) { "client" } + end + end +end diff --git a/spec/unit/knife/key_helper.rb b/spec/unit/knife/key_helper.rb new file mode 100644 index 0000000000..36ababc09a --- /dev/null +++ b/spec/unit/knife/key_helper.rb @@ -0,0 +1,74 @@ +# +# 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(:params) { [] } + 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" do + 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 + let(:params) { ["charmander"] } + 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 diff --git a/spec/unit/knife/key_list_spec.rb b/spec/unit/knife/key_list_spec.rb new file mode 100644 index 0000000000..aabe02ac02 --- /dev/null +++ b/spec/unit/knife/key_list_spec.rb @@ -0,0 +1,216 @@ +# +# 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' +require 'chef/knife/user_key_list' +require 'chef/knife/client_key_list' +require 'chef/knife/key_list' +require 'chef/key' + +describe "key list commands that inherit knife" do + shared_examples_for "a key list command" do + let(:stderr) { StringIO.new } + let(:params) { [] } + let(:service_object) { instance_double(Chef::Knife::KeyList) } + 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 "after apply_params! is called with valid args" do + let(:params) { ["charmander"] } + before do + command.apply_params!(params) + end + + context "when the service object is called" do + it "creates a new instance of Chef::Knife::KeyList with the correct args" do + expect(Chef::Knife::KeyList).to receive(:new). + with("charmander", command.list_method, command.ui, command.config). + and_return(service_object) + command.service_object + end + end # when the service object is called + end # after apply_params! is called with valid args + end # a key list command + + describe Chef::Knife::UserKeyList do + it_should_behave_like "a key list command" + # defined in key_helpers.rb + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyList) } + let(:params) { ["charmander"] } + end + end + + describe Chef::Knife::ClientKeyList do + it_should_behave_like "a key list command" + # defined in key_helpers.rb + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyList) } + let(:params) { ["charmander"] } + end + end +end + +describe Chef::Knife::KeyList do + let(:config) { Hash.new } + let(:actor) { "charmander" } + let(:ui) { instance_double("Chef::Knife::UI") } + + shared_examples_for "key list run command" do + let(:key_list_object) { + described_class.new(actor, list_method, ui, config) + } + + before do + allow(Chef::Key).to receive(list_method).and_return(http_response) + allow(key_list_object).to receive(:display_info) + # simply pass the string though that colorize takes in + allow(key_list_object).to receive(:colorize).with(kind_of(String)) do |input| + input + end + end + + context "when only_expired and only_non_expired were both passed" do + before do + key_list_object.config[:only_expired] = true + key_list_object.config[:only_non_expired] = true + end + + it "raises a Chef::Exceptions::KeyCommandInputError with the proper error message" do + expect{ key_list_object.run }.to raise_error(Chef::Exceptions::KeyCommandInputError, key_list_object.expired_and_non_expired_msg) + end + end + + context "when the command is run" do + before do + key_list_object.config[:only_expired] = false + key_list_object.config[:only_non_expired] = false + key_list_object.config[:with_details] = false + end + + it "calls Chef::Key with the proper list command and input" do + expect(Chef::Key).to receive(list_method).with(actor) + key_list_object.run + end + + it "displays all the keys" do + expect(key_list_object).to receive(:display_info).with(/non-expired/).twice + expect(key_list_object).to receive(:display_info).with(/out-of-date/).once + key_list_object.run + end + + context "when only_expired is called" do + before do + key_list_object.config[:only_expired] = true + end + + it "excludes displaying non-expired keys" do + expect(key_list_object).to receive(:display_info).with(/non-expired/).exactly(0).times + key_list_object.run + end + + it "displays the expired keys" do + expect(key_list_object).to receive(:display_info).with(/out-of-date/).once + key_list_object.run + end + end # when only_expired is called + + context "when only_non_expired is called" do + before do + key_list_object.config[:only_non_expired] = true + end + + it "excludes displaying expired keys" do + expect(key_list_object).to receive(:display_info).with(/out-of-date/).exactly(0).times + key_list_object.run + end + + it "displays the non-expired keys" do + expect(key_list_object).to receive(:display_info).with(/non-expired/).twice + key_list_object.run + end + end # when only_expired is called + + context "when with_details is false" do + before do + key_list_object.config[:with_details] = false + end + + it "does not display the uri" do + expect(key_list_object).to receive(:display_info).with(/https/).exactly(0).times + key_list_object.run + end + + it "does not display the expired status" do + expect(key_list_object).to receive(:display_info).with(/\(expired\)/).exactly(0).times + key_list_object.run + end + end # when with_details is false + + context "when with_details is true" do + before do + key_list_object.config[:with_details] = true + end + + it "displays the uri" do + expect(key_list_object).to receive(:display_info).with(/https/).exactly(3).times + key_list_object.run + end + + it "displays the expired status" do + expect(key_list_object).to receive(:display_info).with(/\(expired\)/).once + key_list_object.run + end + end # when with_details is true + + end # when the command is run + + end # key list run command + + context "when list_method is :list_by_user" do + it_should_behave_like "key list run command" do + let(:list_method) { :list_by_user } + let(:http_response) { + [ + {"uri"=>"https://api.opscode.piab/users/charmander/keys/non-expired1", "name"=>"non-expired1", "expired"=>false}, + {"uri"=>"https://api.opscode.piab/users/charmander/keys/non-expired2", "name"=>"non-expired2", "expired"=>false}, + {"uri"=>"https://api.opscode.piab/users/mary/keys/out-of-date", "name"=>"out-of-date", "expired"=>true} + ] + } + end + end + + context "when list_method is :list_by_client" do + it_should_behave_like "key list run command" do + let(:list_method) { :list_by_client } + let(:http_response) { + [ + {"uri"=>"https://api.opscode.piab/organizations/pokemon/clients/charmander/keys/non-expired1", "name"=>"non-expired1", "expired"=>false}, + {"uri"=>"https://api.opscode.piab/organizations/pokemon/clients/charmander/keys/non-expired2", "name"=>"non-expired2", "expired"=>false}, + {"uri"=>"https://api.opscode.piab/organizations/pokemon/clients/mary/keys/out-of-date", "name"=>"out-of-date", "expired"=>true} + ] + } + end + end +end diff --git a/spec/unit/knife/key_show_spec.rb b/spec/unit/knife/key_show_spec.rb new file mode 100644 index 0000000000..5a0d839e4f --- /dev/null +++ b/spec/unit/knife/key_show_spec.rb @@ -0,0 +1,126 @@ +# +# 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' +require 'chef/knife/user_key_show' +require 'chef/knife/client_key_show' +require 'chef/knife/key_show' +require 'chef/key' + +describe "key show commands that inherit knife" do + shared_examples_for "a key show command" do + let(:stderr) { StringIO.new } + let(:params) { [] } + let(:service_object) { instance_double(Chef::Knife::KeyShow) } + 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 "after apply_params! is called with valid args" do + let(:params) { ["charmander", "charmander-key"] } + before do + command.apply_params!(params) + end + + context "when the service object is called" do + it "creates a new instance of Chef::Knife::KeyShow with the correct args" do + expect(Chef::Knife::KeyShow).to receive(:new). + with("charmander-key", "charmander", command.load_method, command.ui). + and_return(service_object) + command.service_object + end + end # when the service object is called + end # after apply_params! is called with valid args + end # a key show command + + describe Chef::Knife::UserKeyShow do + it_should_behave_like "a key show command" + # defined in key_helpers.rb + it_should_behave_like "a knife key command with a keyname as the second arg" + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyShow) } + let(:params) { ["charmander", "charmander-key"] } + end + end + + describe Chef::Knife::ClientKeyShow do + it_should_behave_like "a key show command" + # defined in key_helpers.rb + it_should_behave_like "a knife key command with a keyname as the second arg" + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyShow) } + let(:params) { ["charmander", "charmander-key"] } + end + end +end + +describe Chef::Knife::KeyShow do + let(:actor) { "charmander" } + let(:keyname) { "charmander" } + let(:ui) { instance_double("Chef::Knife::UI") } + let(:expected_hash) { + { + actor_field_name => "charmander", + "name" => "charmander-key", + "public_key" => "some-public-key", + "expiration_date" => "infinity" + } + } + + shared_examples_for "key show run command" do + let(:key_show_object) { + described_class.new(keyname, actor, load_method, ui) + } + + before do + allow(key_show_object).to receive(:display_output) + allow(Chef::Key).to receive(load_method).and_return(Chef::Key.from_hash(expected_hash)) + end + + context "when the command is run" do + it "loads the key using the proper method and args" do + expect(Chef::Key).to receive(load_method).with(actor, keyname) + key_show_object.run + end + + it "displays the key" do + expect(key_show_object).to receive(:display_output) + key_show_object.run + end + end + end + + context "when load_method is :load_by_user" do + it_should_behave_like "key show run command" do + let(:load_method) { :load_by_user } + let(:actor_field_name) { 'user' } + end + end + + context "when load_method is :load_by_client" do + it_should_behave_like "key show run command" do + let(:load_method) { :load_by_client } + let(:actor_field_name) { 'user' } + end + end +end diff --git a/spec/unit/knife/osc_user_create_spec.rb b/spec/unit/knife/osc_user_create_spec.rb new file mode 100644 index 0000000000..1b17d0d22f --- /dev/null +++ b/spec/unit/knife/osc_user_create_spec.rb @@ -0,0 +1,93 @@ +# +# Author:: Steven Danna (<steve@opscode.com>) +# Copyright:: Copyright (c) 2012 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' + +Chef::Knife::OscUserCreate.load_deps + +# DEPRECATION NOTE +# This code only remains to support users still operating with +# Open Source Chef Server 11 and should be removed once support +# for OSC 11 ends. New development should occur in user_create_spec.rb. + +describe Chef::Knife::OscUserCreate do + before(:each) do + @knife = Chef::Knife::OscUserCreate.new + + @stdout = StringIO.new + @stderr = StringIO.new + allow(@knife.ui).to receive(:stdout).and_return(@stdout) + allow(@knife.ui).to receive(:stderr).and_return(@stderr) + + @knife.name_args = [ 'a_user' ] + @knife.config[:user_password] = "foobar" + @user = Chef::OscUser.new + @user.name "a_user" + @user_with_private_key = Chef::OscUser.new + @user_with_private_key.name "a_user" + @user_with_private_key.private_key 'private_key' + allow(@user).to receive(:create).and_return(@user_with_private_key) + allow(Chef::OscUser).to receive(:new).and_return(@user) + allow(Chef::OscUser).to receive(:from_hash).and_return(@user) + allow(@knife).to receive(:edit_data).and_return(@user.to_hash) + end + + it "creates a new user" do + expect(Chef::OscUser).to receive(:new).and_return(@user) + expect(@user).to receive(:create) + @knife.run + expect(@stderr.string).to match /created user.+a_user/i + end + + it "sets the password" do + @knife.config[:user_password] = "a_password" + expect(@user).to receive(:password).with("a_password") + @knife.run + end + + it "exits with an error if password is blank" do + @knife.config[:user_password] = '' + expect { @knife.run }.to raise_error SystemExit + expect(@stderr.string).to match /You must specify a non-blank password/ + end + + it "sets the user name" do + expect(@user).to receive(:name).with("a_user") + @knife.run + end + + it "sets the public key if given" do + @knife.config[:user_key] = "/a/filename" + allow(File).to receive(:read).with(File.expand_path("/a/filename")).and_return("a_key") + expect(@user).to receive(:public_key).with("a_key") + @knife.run + end + + it "allows you to edit the data" do + expect(@knife).to receive(:edit_data).with(@user) + @knife.run + end + + it "writes the private key to a file when --file is specified" do + @knife.config[:file] = "/tmp/a_file" + filehandle = double("filehandle") + expect(filehandle).to receive(:print).with('private_key') + expect(File).to receive(:open).with("/tmp/a_file", "w").and_yield(filehandle) + @knife.run + end +end diff --git a/spec/unit/knife/osc_user_delete_spec.rb b/spec/unit/knife/osc_user_delete_spec.rb new file mode 100644 index 0000000000..0e16393ffe --- /dev/null +++ b/spec/unit/knife/osc_user_delete_spec.rb @@ -0,0 +1,44 @@ +# +# Author:: Steven Danna (<steve@opscode.com>) +# Copyright:: Copyright (c) 2012 Opscode, Inc +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' + +# DEPRECATION NOTE +# This code only remains to support users still operating with +# Open Source Chef Server 11 and should be removed once support +# for OSC 11 ends. New development should occur in user_delete_spec.rb. + +describe Chef::Knife::OscUserDelete do + before(:each) do + Chef::Knife::OscUserDelete.load_deps + @knife = Chef::Knife::OscUserDelete.new + @knife.name_args = [ 'my_user' ] + end + + it 'deletes the user' do + expect(@knife).to receive(:delete_object).with(Chef::OscUser, 'my_user') + @knife.run + end + + it 'prints usage and exits when a user name is not provided' do + @knife.name_args = [] + expect(@knife).to receive(:show_usage) + expect(@knife.ui).to receive(:fatal) + expect { @knife.run }.to raise_error(SystemExit) + end +end diff --git a/spec/unit/knife/osc_user_edit_spec.rb b/spec/unit/knife/osc_user_edit_spec.rb new file mode 100644 index 0000000000..71a9192389 --- /dev/null +++ b/spec/unit/knife/osc_user_edit_spec.rb @@ -0,0 +1,52 @@ +# +# Author:: Steven Danna (<steve@opscode.com>) +# Copyright:: Copyright (c) 2012 Opscode, Inc +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' + +# DEPRECATION NOTE +# This code only remains to support users still operating with +# Open Source Chef Server 11 and should be removed once support +# for OSC 11 ends. New development should occur in user_edit_spec.rb. + +describe Chef::Knife::OscUserEdit do + before(:each) do + @stderr = StringIO.new + @stdout = StringIO.new + + Chef::Knife::OscUserEdit.load_deps + @knife = Chef::Knife::OscUserEdit.new + allow(@knife.ui).to receive(:stderr).and_return(@stderr) + allow(@knife.ui).to receive(:stdout).and_return(@stdout) + @knife.name_args = [ 'my_user' ] + @knife.config[:disable_editing] = true + end + + it 'loads and edits the user' do + data = { :name => "my_user" } + allow(Chef::OscUser).to receive(:load).with("my_user").and_return(data) + expect(@knife).to receive(:edit_data).with(data).and_return(data) + @knife.run + end + + it 'prints usage and exits when a user name is not provided' do + @knife.name_args = [] + expect(@knife).to receive(:show_usage) + expect(@knife.ui).to receive(:fatal) + expect { @knife.run }.to raise_error(SystemExit) + end +end diff --git a/spec/unit/knife/osc_user_list_spec.rb b/spec/unit/knife/osc_user_list_spec.rb new file mode 100644 index 0000000000..59a15be058 --- /dev/null +++ b/spec/unit/knife/osc_user_list_spec.rb @@ -0,0 +1,37 @@ +# +# Author:: Steven Danna +# Copyright:: Copyright (c) 2012 Opscode, Inc +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' + +# DEPRECATION NOTE +# This code only remains to support users still operating with +# Open Source Chef Server 11 and should be removed once support +# for OSC 11 ends. New development should occur in user_list_spec.rb. + +describe Chef::Knife::OscUserList do + before(:each) do + Chef::Knife::OscUserList.load_deps + @knife = Chef::Knife::OscUserList.new + end + + it 'lists the users' do + expect(Chef::OscUser).to receive(:list) + expect(@knife).to receive(:format_list_for_display) + @knife.run + end +end diff --git a/spec/unit/knife/osc_user_reregister_spec.rb b/spec/unit/knife/osc_user_reregister_spec.rb new file mode 100644 index 0000000000..406bbf1f3e --- /dev/null +++ b/spec/unit/knife/osc_user_reregister_spec.rb @@ -0,0 +1,58 @@ +# +# Author:: Steven Danna (<steve@opscode.com>) +# Copyright:: Copyright (c) 2012 Opscode, Inc +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' + +# DEPRECATION NOTE +# This code only remains to support users still operating with +# Open Source Chef Server 11 and should be removed once support +# for OSC 11 ends. New development should occur in user_reregister_spec.rb. + +describe Chef::Knife::OscUserReregister do + before(:each) do + Chef::Knife::OscUserReregister.load_deps + @knife = Chef::Knife::OscUserReregister.new + @knife.name_args = [ 'a_user' ] + @user_mock = double('user_mock', :private_key => "private_key") + allow(Chef::OscUser).to receive(:load).and_return(@user_mock) + @stdout = StringIO.new + allow(@knife.ui).to receive(:stdout).and_return(@stdout) + end + + it 'prints usage and exits when a user name is not provided' do + @knife.name_args = [] + expect(@knife).to receive(:show_usage) + expect(@knife.ui).to receive(:fatal) + expect { @knife.run }.to raise_error(SystemExit) + end + + it 'reregisters the user and prints the key' do + expect(@user_mock).to receive(:reregister).and_return(@user_mock) + @knife.run + expect(@stdout.string).to match( /private_key/ ) + end + + it 'writes the private key to a file when --file is specified' do + expect(@user_mock).to receive(:reregister).and_return(@user_mock) + @knife.config[:file] = '/tmp/a_file' + filehandle = StringIO.new + expect(File).to receive(:open).with('/tmp/a_file', 'w').and_yield(filehandle) + @knife.run + expect(filehandle.string).to eq("private_key") + end +end diff --git a/spec/unit/knife/osc_user_show_spec.rb b/spec/unit/knife/osc_user_show_spec.rb new file mode 100644 index 0000000000..67b9b45809 --- /dev/null +++ b/spec/unit/knife/osc_user_show_spec.rb @@ -0,0 +1,46 @@ +# +# Author:: Steven Danna (<steve@opscode.com>) +# Copyright:: Copyright (c) 2012 Opscode, Inc +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' + +# DEPRECATION NOTE +# This code only remains to support users still operating with +# Open Source Chef Server 11 and should be removed once support +# for OSC 11 ends. New development should occur user_show_spec.rb. + +describe Chef::Knife::OscUserShow do + before(:each) do + Chef::Knife::OscUserShow.load_deps + @knife = Chef::Knife::OscUserShow.new + @knife.name_args = [ 'my_user' ] + @user_mock = double('user_mock') + end + + it 'loads and displays the user' do + expect(Chef::OscUser).to receive(:load).with('my_user').and_return(@user_mock) + expect(@knife).to receive(:format_for_display).with(@user_mock) + @knife.run + end + + it 'prints usage and exits when a user name is not provided' do + @knife.name_args = [] + expect(@knife).to receive(:show_usage) + expect(@knife.ui).to receive(:fatal) + expect { @knife.run }.to raise_error(SystemExit) + end +end diff --git a/spec/unit/knife/ssh_spec.rb b/spec/unit/knife/ssh_spec.rb index a838a21edc..723280bead 100644 --- a/spec/unit/knife/ssh_spec.rb +++ b/spec/unit/knife/ssh_spec.rb @@ -28,10 +28,10 @@ describe Chef::Knife::Ssh do before do @knife = Chef::Knife::Ssh.new @knife.merge_configs - @knife.config[:attribute] = "fqdn" @node_foo = Chef::Node.new @node_foo.automatic_attrs[:fqdn] = "foo.example.org" @node_foo.automatic_attrs[:ipaddress] = "10.0.0.1" + @node_bar = Chef::Node.new @node_bar.automatic_attrs[:fqdn] = "bar.example.org" @node_bar.automatic_attrs[:ipaddress] = "10.0.0.2" @@ -52,15 +52,15 @@ describe Chef::Knife::Ssh do def self.should_return_specified_attributes it "returns an array of the attributes specified on the command line OR config file, if only one is set" do @knife.config[:attribute] = "ipaddress" - @knife.config[:attribute_from_cli] = "ipaddress" + Chef::Config[:knife][:ssh_attribute] = "ipaddress" # this value will be in the config file configure_query([@node_foo, @node_bar]) expect(@knife).to receive(:session_from_list).with([['10.0.0.1', nil], ['10.0.0.2', nil]]) @knife.configure_session end it "returns an array of the attributes specified on the command line even when a config value is set" do - @knife.config[:attribute] = "config_file" # this value will be the config file - @knife.config[:attribute_from_cli] = "ipaddress" # this is the value of the command line via #configure_attribute + Chef::Config[:knife][:ssh_attribute] = "config_file" # this value will be in the config file + @knife.config[:attribute] = "ipaddress" # this is the value of the command line via #configure_attribute configure_query([@node_foo, @node_bar]) expect(@knife).to receive(:session_from_list).with([['10.0.0.1', nil], ['10.0.0.2', nil]]) @knife.configure_session @@ -83,7 +83,6 @@ describe Chef::Knife::Ssh do @node_foo.automatic_attrs[:cloud][:public_hostname] = "ec2-10-0-0-1.compute-1.amazonaws.com" @node_bar.automatic_attrs[:cloud][:public_hostname] = "ec2-10-0-0-2.compute-1.amazonaws.com" end - it "returns an array of cloud public hostnames" do configure_query([@node_foo, @node_bar]) expect(@knife).to receive(:session_from_list).with([ @@ -150,42 +149,40 @@ describe Chef::Knife::Ssh do end end - describe "#configure_attribute" do + describe "#get_ssh_attribute" do + # Order of precedence for ssh target + # 1) command line attribute + # 2) configuration file + # 3) cloud attribute + # 4) fqdn before do Chef::Config[:knife][:ssh_attribute] = nil @knife.config[:attribute] = nil + @node_foo.automatic_attrs[:cloud][:public_hostname] = "ec2-10-0-0-1.compute-1.amazonaws.com" + @node_bar.automatic_attrs[:cloud][:public_hostname] = '' end it "should return fqdn by default" do - @knife.configure_attribute - expect(@knife.config[:attribute]).to eq("fqdn") + expect(@knife.get_ssh_attribute(Chef::Node.new)).to eq("fqdn") end - it "should return the value set in the configuration file" do - Chef::Config[:knife][:ssh_attribute] = "config_file" - @knife.configure_attribute - expect(@knife.config[:attribute]).to eq("config_file") + it "should return cloud.public_hostname attribute if available" do + expect(@knife.get_ssh_attribute(@node_foo)).to eq("cloud.public_hostname") end - it "should return the value set on the command line" do + it "should favor to attribute_from_cli over config file and cloud" do @knife.config[:attribute] = "command_line" - @knife.configure_attribute - expect(@knife.config[:attribute]).to eq("command_line") + Chef::Config[:knife][:ssh_attribute] = "config_file" + expect( @knife.get_ssh_attribute(@node_foo)).to eq("command_line") end - it "should set attribute_from_cli to the value of attribute from the command line" do - @knife.config[:attribute] = "command_line" - @knife.configure_attribute - expect(@knife.config[:attribute]).to eq("command_line") - expect(@knife.config[:attribute_from_cli]).to eq("command_line") + it "should favor config file over cloud and default" do + Chef::Config[:knife][:ssh_attribute] = "config_file" + expect( @knife.get_ssh_attribute(@node_foo)).to eq("config_file") end - it "should prefer the command line over the config file for the value of attribute_from_cli" do - Chef::Config[:knife][:ssh_attribute] = "config_file" - @knife.config[:attribute] = "command_line" - @knife.configure_attribute - expect(@knife.config[:attribute]).to eq("command_line") - expect(@knife.config[:attribute_from_cli]).to eq("command_line") + it "should return fqdn if cloud.hostname is empty" do + expect( @knife.get_ssh_attribute(@node_bar)).to eq("fqdn") end end diff --git a/spec/unit/knife/user_create_spec.rb b/spec/unit/knife/user_create_spec.rb index ad8821cd0e..49d62cc2d7 100644 --- a/spec/unit/knife/user_create_spec.rb +++ b/spec/unit/knife/user_create_spec.rb @@ -1,6 +1,7 @@ # -# Author:: Steven Danna (<steve@opscode.com>) -# Copyright:: Copyright (c) 2012 Opscode, Inc. +# Author:: Steven Danna (<steve@chef.io>) +# Author:: Tyler Cloke (<tyler@chef.io>) +# Copyright:: Copyright (c) 2012, 2015 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,68 +22,193 @@ require 'spec_helper' Chef::Knife::UserCreate.load_deps describe Chef::Knife::UserCreate do + let(:knife) { Chef::Knife::UserCreate.new } + + let(:stderr) { + StringIO.new + } + + let(:stdout) { + StringIO.new + } + before(:each) do - @knife = Chef::Knife::UserCreate.new - - @stdout = StringIO.new - @stderr = StringIO.new - allow(@knife.ui).to receive(:stdout).and_return(@stdout) - allow(@knife.ui).to receive(:stderr).and_return(@stderr) - - @knife.name_args = [ 'a_user' ] - @knife.config[:user_password] = "foobar" - @user = Chef::User.new - @user.name "a_user" - @user_with_private_key = Chef::User.new - @user_with_private_key.name "a_user" - @user_with_private_key.private_key 'private_key' - allow(@user).to receive(:create).and_return(@user_with_private_key) - allow(Chef::User).to receive(:new).and_return(@user) - allow(Chef::User).to receive(:from_hash).and_return(@user) - allow(@knife).to receive(:edit_data).and_return(@user.to_hash) + allow(knife.ui).to receive(:stdout).and_return(stdout) + allow(knife.ui).to receive(:stderr).and_return(stderr) + allow(knife.ui).to receive(:warn) end - it "creates a new user" do - expect(Chef::User).to receive(:new).and_return(@user) - expect(@user).to receive(:create) - @knife.run - expect(@stderr.string).to match /created user.+a_user/i - end + # delete this once OSC11 support is gone + context "when only one name_arg is passed" do + before do + knife.name_args = ['some_user'] + allow(knife).to receive(:run_osc_11_user_create).and_raise(SystemExit) + end + + it "displays the osc warning" do + expect(knife.ui).to receive(:warn).with(knife.osc_11_warning) + expect{ knife.run }.to raise_error(SystemExit) + end + + it "calls knife osc_user create" do + expect(knife).to receive(:run_osc_11_user_create) + expect{ knife.run }.to raise_error(SystemExit) + end - it "sets the password" do - @knife.config[:user_password] = "a_password" - expect(@user).to receive(:password).with("a_password") - @knife.run end - it "exits with an error if password is blank" do - @knife.config[:user_password] = '' - expect { @knife.run }.to raise_error SystemExit - expect(@stderr.string).to match /You must specify a non-blank password/ + context "when USERNAME isn't specified" do + # from spec/support/shared/unit/knife_shared.rb + it_should_behave_like "mandatory field missing" do + let(:name_args) { [] } + let(:fieldname) { 'username' } + end end - it "sets the user name" do - expect(@user).to receive(:name).with("a_user") - @knife.run + # uncomment once OSC11 support is gone, + # pending doesn't work for shared_examples_for by default + # + # context "when DISPLAY_NAME isn't specified" do + # # from spec/support/shared/unit/knife_shared.rb + # it_should_behave_like "mandatory field missing" do + # let(:name_args) { ['some_user'] } + # let(:fieldname) { 'display name' } + # end + # end + + context "when FIRST_NAME isn't specified" do + # from spec/support/shared/unit/knife_shared.rb + it_should_behave_like "mandatory field missing" do + let(:name_args) { ['some_user', 'some_display_name'] } + let(:fieldname) { 'first name' } + end end - it "sets the public key if given" do - @knife.config[:user_key] = "/a/filename" - allow(File).to receive(:read).with(File.expand_path("/a/filename")).and_return("a_key") - expect(@user).to receive(:public_key).with("a_key") - @knife.run + context "when LAST_NAME isn't specified" do + # from spec/support/shared/unit/knife_shared.rb + it_should_behave_like "mandatory field missing" do + let(:name_args) { ['some_user', 'some_display_name', 'some_first_name'] } + let(:fieldname) { 'last name' } + end end - it "allows you to edit the data" do - expect(@knife).to receive(:edit_data).with(@user) - @knife.run + context "when EMAIL isn't specified" do + # from spec/support/shared/unit/knife_shared.rb + it_should_behave_like "mandatory field missing" do + let(:name_args) { ['some_user', 'some_display_name', 'some_first_name', 'some_last_name'] } + let(:fieldname) { 'email' } + end end - it "writes the private key to a file when --file is specified" do - @knife.config[:file] = "/tmp/a_file" - filehandle = double("filehandle") - expect(filehandle).to receive(:print).with('private_key') - expect(File).to receive(:open).with("/tmp/a_file", "w").and_yield(filehandle) - @knife.run + context "when PASSWORD isn't specified" do + # from spec/support/shared/unit/knife_shared.rb + it_should_behave_like "mandatory field missing" do + let(:name_args) { ['some_user', 'some_display_name', 'some_first_name', 'some_last_name', 'some_email'] } + let(:fieldname) { 'password' } + end end + + context "when all mandatory fields are validly specified" do + before do + knife.name_args = ['some_user', 'some_display_name', 'some_first_name', 'some_last_name', 'some_email', 'some_password'] + allow(knife).to receive(:edit_data).and_return(knife.user.to_hash) + allow(knife).to receive(:create_user_from_hash).and_return(knife.user) + end + + before(:each) do + # reset the user field every run + knife.user_field = nil + end + + it "sets all the mandatory fields" do + knife.run + expect(knife.user.username).to eq('some_user') + expect(knife.user.display_name).to eq('some_display_name') + expect(knife.user.first_name).to eq('some_first_name') + expect(knife.user.last_name).to eq('some_last_name') + expect(knife.user.email).to eq('some_email') + expect(knife.user.password).to eq('some_password') + end + + context "when user_key and prevent_keygen are passed" do + before do + knife.config[:user_key] = "some_key" + knife.config[:prevent_keygen] = true + 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 cannot pass --user-key and --prevent-keygen/ + end + end + + context "when --prevent-keygen is passed" do + before do + knife.config[:prevent_keygen] = true + end + + it "does not set user.create_key" do + knife.run + expect(knife.user.create_key).to be_falsey + end + end + + context "when --prevent-keygen is not passed" do + it "sets user.create_key to true" do + knife.run + expect(knife.user.create_key).to be_truthy + end + end + + context "when --user-key is passed" do + before do + knife.config[:user_key] = 'some_key' + allow(File).to receive(:read).and_return('some_key') + allow(File).to receive(:expand_path) + end + + it "sets user.public_key" do + knife.run + expect(knife.user.public_key).to eq('some_key') + end + end + + context "when --user-key is not passed" do + it "does not set user.public_key" do + knife.run + expect(knife.user.public_key).to be_nil + end + end + + context "when a private_key is returned" do + before do + allow(knife).to receive(:create_user_from_hash).and_return(Chef::User.from_hash(knife.user.to_hash.merge({"private_key" => "some_private_key"}))) + end + + context "when --file is passed" do + before do + knife.config[:file] = '/some/path' + end + + it "creates a new file of the path passed" do + filehandle = double('filehandle') + expect(filehandle).to receive(:print).with('some_private_key') + expect(File).to receive(:open).with('/some/path', 'w').and_yield(filehandle) + knife.run + end + end + + context "when --file is not passed" do + it "prints the private key to stdout" do + expect(knife.ui).to receive(:msg).with('some_private_key') + knife.run + end + end + end + + end # when all mandatory fields are validly specified end diff --git a/spec/unit/knife/user_delete_spec.rb b/spec/unit/knife/user_delete_spec.rb index 94cfbf3db1..e49c781358 100644 --- a/spec/unit/knife/user_delete_spec.rb +++ b/spec/unit/knife/user_delete_spec.rb @@ -19,21 +19,47 @@ require 'spec_helper' describe Chef::Knife::UserDelete do + let(:knife) { Chef::Knife::UserDelete.new } + let(:user) { double('user_object') } + let(:stdout) { StringIO.new } + before(:each) do Chef::Knife::UserDelete.load_deps - @knife = Chef::Knife::UserDelete.new - @knife.name_args = [ 'my_user' ] + knife.name_args = [ 'my_user' ] + allow(Chef::User).to receive(:load).and_return(user) + allow(user).to receive(:username).and_return('my_user') + allow(knife.ui).to receive(:stderr).and_return(stdout) + allow(knife.ui).to receive(:stdout).and_return(stdout) + end + + # delete this once OSC11 support is gone + context "when the username field is not supported by the server" do + before do + allow(knife).to receive(:run_osc_11_user_delete).and_raise(SystemExit) + allow(user).to receive(:username).and_return(nil) + end + + it "displays the osc warning" do + expect(knife.ui).to receive(:warn).with(knife.osc_11_warning) + expect{ knife.run }.to raise_error(SystemExit) + end + + it "forwards the command to knife osc_user edit" do + expect(knife).to receive(:run_osc_11_user_delete) + expect{ knife.run }.to raise_error(SystemExit) + end end it 'deletes the user' do - expect(@knife).to receive(:delete_object).with(Chef::User, 'my_user') - @knife.run + #expect(knife).to receive(:delete_object).with(Chef::User, 'my_user') + expect(knife).to receive(:delete_object).with('my_user') + knife.run end it 'prints usage and exits when a user name is not provided' do - @knife.name_args = [] - expect(@knife).to receive(:show_usage) - expect(@knife.ui).to receive(:fatal) - expect { @knife.run }.to raise_error(SystemExit) + knife.name_args = [] + expect(knife).to receive(:show_usage) + expect(knife.ui).to receive(:fatal) + expect { knife.run }.to raise_error(SystemExit) end end diff --git a/spec/unit/knife/user_edit_spec.rb b/spec/unit/knife/user_edit_spec.rb index 0eb75cfa9b..15a7726b20 100644 --- a/spec/unit/knife/user_edit_spec.rb +++ b/spec/unit/knife/user_edit_spec.rb @@ -19,29 +19,48 @@ require 'spec_helper' describe Chef::Knife::UserEdit do + let(:knife) { Chef::Knife::UserEdit.new } + before(:each) do @stderr = StringIO.new @stdout = StringIO.new Chef::Knife::UserEdit.load_deps - @knife = Chef::Knife::UserEdit.new - allow(@knife.ui).to receive(:stderr).and_return(@stderr) - allow(@knife.ui).to receive(:stdout).and_return(@stdout) - @knife.name_args = [ 'my_user' ] - @knife.config[:disable_editing] = true + allow(knife.ui).to receive(:stderr).and_return(@stderr) + allow(knife.ui).to receive(:stdout).and_return(@stdout) + knife.name_args = [ 'my_user' ] + knife.config[:disable_editing] = true + end + + # delete this once OSC11 support is gone + context "when the username field is not supported by the server" do + before do + allow(knife).to receive(:run_osc_11_user_edit).and_raise(SystemExit) + allow(Chef::User).to receive(:load).and_return({"username" => nil}) + end + + it "displays the osc warning" do + expect(knife.ui).to receive(:warn).with(knife.osc_11_warning) + expect{ knife.run }.to raise_error(SystemExit) + end + + it "forwards the command to knife osc_user edit" do + expect(knife).to receive(:run_osc_11_user_edit) + expect{ knife.run }.to raise_error(SystemExit) + end end it 'loads and edits the user' do - data = { :name => "my_user" } + data = { "username" => "my_user" } allow(Chef::User).to receive(:load).with("my_user").and_return(data) - expect(@knife).to receive(:edit_data).with(data).and_return(data) - @knife.run + expect(knife).to receive(:edit_data).with(data).and_return(data) + knife.run end it 'prints usage and exits when a user name is not provided' do - @knife.name_args = [] - expect(@knife).to receive(:show_usage) - expect(@knife.ui).to receive(:fatal) - expect { @knife.run }.to raise_error(SystemExit) + knife.name_args = [] + expect(knife).to receive(:show_usage) + expect(knife.ui).to receive(:fatal) + expect { knife.run }.to raise_error(SystemExit) end end diff --git a/spec/unit/knife/user_list_spec.rb b/spec/unit/knife/user_list_spec.rb index db097a5c16..9990cc802d 100644 --- a/spec/unit/knife/user_list_spec.rb +++ b/spec/unit/knife/user_list_spec.rb @@ -19,14 +19,18 @@ require 'spec_helper' describe Chef::Knife::UserList do + let(:knife) { Chef::Knife::UserList.new } + let(:stdout) { StringIO.new } + before(:each) do Chef::Knife::UserList.load_deps - @knife = Chef::Knife::UserList.new + allow(knife.ui).to receive(:stderr).and_return(stdout) + allow(knife.ui).to receive(:stdout).and_return(stdout) end it 'lists the users' do expect(Chef::User).to receive(:list) - expect(@knife).to receive(:format_list_for_display) - @knife.run + expect(knife).to receive(:format_list_for_display) + knife.run end end diff --git a/spec/unit/knife/user_reregister_spec.rb b/spec/unit/knife/user_reregister_spec.rb index 1268716f40..412a6ec374 100644 --- a/spec/unit/knife/user_reregister_spec.rb +++ b/spec/unit/knife/user_reregister_spec.rb @@ -19,35 +19,56 @@ require 'spec_helper' describe Chef::Knife::UserReregister do - before(:each) do + let(:knife) { Chef::Knife::UserReregister.new } + let(:user_mock) { double('user_mock', :private_key => "private_key") } + let(:stdout) { StringIO.new } + + before do Chef::Knife::UserReregister.load_deps - @knife = Chef::Knife::UserReregister.new - @knife.name_args = [ 'a_user' ] - @user_mock = double('user_mock', :private_key => "private_key") - allow(Chef::User).to receive(:load).and_return(@user_mock) - @stdout = StringIO.new - allow(@knife.ui).to receive(:stdout).and_return(@stdout) + knife.name_args = [ 'a_user' ] + allow(Chef::User).to receive(:load).and_return(user_mock) + allow(knife.ui).to receive(:stdout).and_return(stdout) + allow(knife.ui).to receive(:stderr).and_return(stdout) + allow(user_mock).to receive(:username).and_return('a_user') + end + + # delete this once OSC11 support is gone + context "when the username field is not supported by the server" do + before do + allow(knife).to receive(:run_osc_11_user_reregister).and_raise(SystemExit) + allow(user_mock).to receive(:username).and_return(nil) + end + + it "displays the osc warning" do + expect(knife.ui).to receive(:warn).with(knife.osc_11_warning) + expect{ knife.run }.to raise_error(SystemExit) + end + + it "forwards the command to knife osc_user edit" do + expect(knife).to receive(:run_osc_11_user_reregister) + expect{ knife.run }.to raise_error(SystemExit) + end end it 'prints usage and exits when a user name is not provided' do - @knife.name_args = [] - expect(@knife).to receive(:show_usage) - expect(@knife.ui).to receive(:fatal) - expect { @knife.run }.to raise_error(SystemExit) + knife.name_args = [] + expect(knife).to receive(:show_usage) + expect(knife.ui).to receive(:fatal) + expect { knife.run }.to raise_error(SystemExit) end it 'reregisters the user and prints the key' do - expect(@user_mock).to receive(:reregister).and_return(@user_mock) - @knife.run - expect(@stdout.string).to match( /private_key/ ) + expect(user_mock).to receive(:reregister).and_return(user_mock) + knife.run + expect(stdout.string).to match( /private_key/ ) end it 'writes the private key to a file when --file is specified' do - expect(@user_mock).to receive(:reregister).and_return(@user_mock) - @knife.config[:file] = '/tmp/a_file' + expect(user_mock).to receive(:reregister).and_return(user_mock) + knife.config[:file] = '/tmp/a_file' filehandle = StringIO.new expect(File).to receive(:open).with('/tmp/a_file', 'w').and_yield(filehandle) - @knife.run + knife.run expect(filehandle.string).to eq("private_key") end end diff --git a/spec/unit/knife/user_show_spec.rb b/spec/unit/knife/user_show_spec.rb index f97cbc3f13..43392a3a5c 100644 --- a/spec/unit/knife/user_show_spec.rb +++ b/spec/unit/knife/user_show_spec.rb @@ -19,23 +19,47 @@ require 'spec_helper' describe Chef::Knife::UserShow do - before(:each) do + let(:knife) { Chef::Knife::UserShow.new } + let(:user_mock) { double('user_mock') } + let(:stdout) { StringIO.new } + + before do Chef::Knife::UserShow.load_deps - @knife = Chef::Knife::UserShow.new - @knife.name_args = [ 'my_user' ] - @user_mock = double('user_mock') + knife.name_args = [ 'my_user' ] + allow(user_mock).to receive(:username).and_return('my_user') + allow(knife.ui).to receive(:stderr).and_return(stdout) + allow(knife.ui).to receive(:stdout).and_return(stdout) + end + + # delete this once OSC11 support is gone + context "when the username field is not supported by the server" do + before do + allow(knife).to receive(:run_osc_11_user_show).and_raise(SystemExit) + allow(Chef::User).to receive(:load).with('my_user').and_return(user_mock) + allow(user_mock).to receive(:username).and_return(nil) + end + + it "displays the osc warning" do + expect(knife.ui).to receive(:warn).with(knife.osc_11_warning) + expect{ knife.run }.to raise_error(SystemExit) + end + + it "forwards the command to knife osc_user edit" do + expect(knife).to receive(:run_osc_11_user_show) + expect{ knife.run }.to raise_error(SystemExit) + end end it 'loads and displays the user' do - expect(Chef::User).to receive(:load).with('my_user').and_return(@user_mock) - expect(@knife).to receive(:format_for_display).with(@user_mock) - @knife.run + expect(Chef::User).to receive(:load).with('my_user').and_return(user_mock) + expect(knife).to receive(:format_for_display).with(user_mock) + knife.run end it 'prints usage and exits when a user name is not provided' do - @knife.name_args = [] - expect(@knife).to receive(:show_usage) - expect(@knife.ui).to receive(:fatal) - expect { @knife.run }.to raise_error(SystemExit) + knife.name_args = [] + expect(knife).to receive(:show_usage) + expect(knife.ui).to receive(:fatal) + expect { knife.run }.to raise_error(SystemExit) end end diff --git a/spec/unit/knife_spec.rb b/spec/unit/knife_spec.rb index b748232081..022256f370 100644 --- a/spec/unit/knife_spec.rb +++ b/spec/unit/knife_spec.rb @@ -30,11 +30,20 @@ describe Chef::Knife do let(:knife) { Chef::Knife.new } + let(:config_location) { File.expand_path("~/.chef/config.rb") } + + let(:config_loader) do + instance_double("WorkstationConfigLoader", load: nil, no_config_found?: false, config_location: config_location) + end + before(:each) do Chef::Log.logger = Logger.new(StringIO.new) Chef::Config[:node_name] = "webmonkey.example.com" + allow(Chef::WorkstationConfigLoader).to receive(:new).and_return(config_loader) + allow(config_loader).to receive(:explicit_config_file=) + # Prevent gratuitous code reloading: allow(Chef::Knife).to receive(:load_commands) allow(knife.ui).to receive(:puts) @@ -130,7 +139,8 @@ describe Chef::Knife do "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", 'X-Chef-Version' => Chef::VERSION, "Host"=>"api.opscode.piab", - "X-REMOTE-REQUEST-ID"=>request_id}} + "X-REMOTE-REQUEST-ID"=>request_id, + 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION}} let(:request_id) {"1234"} @@ -251,6 +261,18 @@ describe Chef::Knife do :default => "default-value") end + it "sets the default log_location to STDERR for Chef::Log warnings" do + knife_command = KnifeSpecs::TestYourself.new([]) + knife_command.configure_chef + expect(Chef::Config[:log_location]).to eq(STDERR) + end + + it "sets the default log_level to warn so we can issue Chef::Log.warn" do + knife_command = KnifeSpecs::TestYourself.new([]) + knife_command.configure_chef + expect(Chef::Config[:log_level]).to eql(:warn) + end + it "prefers the default value if no config or command line value is present" do knife_command = KnifeSpecs::TestYourself.new([]) #empty argv knife_command.configure_chef @@ -374,6 +396,22 @@ describe Chef::Knife do expect(stderr.string).to match(%r[Response: nothing to see here]) end + it "formats 406s (non-supported API version error) nicely" do + response = Net::HTTPNotAcceptable.new("1.1", "406", "Not Acceptable") + response.instance_variable_set(:@read, true) # I hate you, net/http. + + # set the header + response["x-ops-server-api-version"] = Chef::JSONCompat.to_json(:min_version => "0", :max_version => "1", :request_version => "10000000") + + allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(:error => "sad trombone")) + allow(knife).to receive(:run).and_raise(Net::HTTPServerException.new("406 Not Acceptable", response)) + + knife.run_with_pretty_exceptions + expect(stderr.string).to include('The request that Knife sent was using API version 10000000') + expect(stderr.string).to include('The Chef server you sent the request to supports a min API verson of 0 and a max API version of 1') + expect(stderr.string).to include('Please either update your Chef client or server to be a compatible set') + end + it "formats 500s nicely" do response = Net::HTTPInternalServerError.new("1.1", "500", "Internal Server Error") response.instance_variable_set(:@read, true) # I hate you, net/http. diff --git a/spec/unit/log/syslog_spec.rb b/spec/unit/log/syslog_spec.rb new file mode 100644 index 0000000000..3db90e50c6 --- /dev/null +++ b/spec/unit/log/syslog_spec.rb @@ -0,0 +1,53 @@ +# +# Author:: SAWANOBORI Yukihiko (<sawanoboriyu@higanworks.com>) +# 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' +require 'chef' + +describe "Chef::Log::Syslog", :unix_only => true do + let(:syslog) { Chef::Log::Syslog.new } + let(:app) { Chef::Application.new } + + before do + Chef::Log.init(MonoLogger.new(syslog)) + @old_log_level = Chef::Log.level + Chef::Log.level = :info + @old_loggers = Chef::Log.loggers + Chef::Log.use_log_devices([syslog]) + end + + after do + Chef::Log.level = @old_log_level + Chef::Log.use_log_devices(@old_loggers) + end + + it "should send message with severity info to syslog." do + expect(syslog).to receive(:info).with("*** Chef 12.4.0.dev.0 ***") + Chef::Log.info("*** Chef 12.4.0.dev.0 ***") + end + + it "should send message with severity warning to syslog." do + expect(syslog).to receive(:warn).with("No config file found or specified on command line, using command line options.") + Chef::Log.warn("No config file found or specified on command line, using command line options.") + end + + it "should fallback into send message with severity info to syslog when wrong format." do + expect(syslog).to receive(:info).with("chef message") + syslog.write("chef message") + end +end diff --git a/spec/unit/log/winevt_spec.rb b/spec/unit/log/winevt_spec.rb new file mode 100644 index 0000000000..867ef55900 --- /dev/null +++ b/spec/unit/log/winevt_spec.rb @@ -0,0 +1,55 @@ +# +# Author:: Jay Mundrawala (jdm@chef.io) +# Author:: SAWANOBORI Yukihiko (<sawanoboriyu@higanworks.com>) +# 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' + +describe Chef::Log::WinEvt do + let(:evtlog) { instance_double("Win32::EventLog")} + let(:winevt) { Chef::Log::WinEvt.new(evtlog) } + let(:app) { Chef::Application.new } + + before do + + Chef::Log.init(MonoLogger.new(winevt)) + @old_log_level = Chef::Log.level + Chef::Log.level = :info + @old_loggers = Chef::Log.loggers + Chef::Log.use_log_devices([winevt]) + end + + after do + Chef::Log.level = @old_log_level + Chef::Log.use_log_devices(@old_loggers) + end + + it "should send message with severity info to Windows Event Log." do + expect(winevt).to receive(:info).with("*** Chef 12.4.0.dev.0 ***") + Chef::Log.info("*** Chef 12.4.0.dev.0 ***") + end + + it "should send message with severity warning to Windows Event Log." do + expect(winevt).to receive(:warn).with("No config file found or specified on command line, using command line options.") + Chef::Log.warn("No config file found or specified on command line, using command line options.") + end + + it "should fallback into send message with severity info to Windows Event Log when wrong format." do + expect(winevt).to receive(:info).with("chef message") + winevt.write("chef message") + end +end diff --git a/spec/unit/lwrp_spec.rb b/spec/unit/lwrp_spec.rb index ec39174da6..34c6f6f1c5 100644 --- a/spec/unit/lwrp_spec.rb +++ b/spec/unit/lwrp_spec.rb @@ -17,20 +17,40 @@ # require 'spec_helper' +require 'tmpdir' +require 'fileutils' +require 'chef/mixin/convert_to_class_name' module LwrpConstScopingConflict end describe "LWRP" do + include Chef::Mixin::ConvertToClassName + before do @original_VERBOSE = $VERBOSE $VERBOSE = nil + Chef::Resource::LWRPBase.class_eval { @loaded_lwrps = {} } end after do $VERBOSE = @original_VERBOSE end + def get_lwrp(name) + Chef::ResourceResolver.resolve(name) + end + + def get_lwrp_provider(name) + old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors] + Chef::Config[:treat_deprecation_warnings_as_errors] = false + begin + Chef::Provider.const_get(convert_to_class_name(name.to_s)) + ensure + Chef::Config[:treat_deprecation_warnings_as_errors] = old_treat_deprecation_warnings_as_errors + end + end + describe "when overriding an existing class" do before :each do allow($stderr).to receive(:write) @@ -43,7 +63,6 @@ describe "LWRP" do expect(Chef::Log).not_to receive(:debug).with(/anymore/) Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) Object.send(:remove_const, 'LwrpFoo') - Chef::Resource.send(:remove_const, 'LwrpFoo') end it "should not skip loading a provider when there's a top level symbol of the same name" do @@ -53,7 +72,6 @@ describe "LWRP" do expect(Chef::Log).not_to receive(:debug).with(/anymore/) Chef::Provider::LWRPBase.build_from_file("lwrp", file, nil) Object.send(:remove_const, 'LwrpBuckPasser') - Chef::Provider.send(:remove_const, 'LwrpBuckPasser') end # @todo: we need a before block to manually remove_const all of the LWRPs that we @@ -67,7 +85,6 @@ describe "LWRP" do Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file| expect(Chef::Log).to receive(:info).with(/Skipping/) - expect(Chef::Log).to receive(:debug).with(/anymore/) Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) end end @@ -79,7 +96,6 @@ describe "LWRP" do Dir[File.expand_path( "lwrp/providers/*", CHEF_SPEC_DATA)].each do |file| expect(Chef::Log).to receive(:info).with(/Skipping/) - expect(Chef::Log).to receive(:debug).with(/anymore/) Chef::Provider::LWRPBase.build_from_file("lwrp", file, nil) end end @@ -90,7 +106,7 @@ describe "LWRP" do Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file| Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) end - first_lwr_foo_class = Chef::Resource::LwrpFoo + first_lwr_foo_class = get_lwrp(:lwrp_foo) expect(Chef::Resource.resource_classes).to include(first_lwr_foo_class) Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file| Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) @@ -106,40 +122,95 @@ describe "LWRP" do end + context "When an LWRP resource in cookbook l-w-r-p is loaded" do + before do + @tmpdir = Dir.mktmpdir("lwrp_test") + resource_path = File.join(@tmpdir, "foo.rb") + IO.write(resource_path, "default_action :create") + provider_path = File.join(@tmpdir, "foo.rb") + IO.write(provider_path, <<-EOM) + action :create do + raise "hi" + end + EOM + end + + it "Can find the resource at l_w_r_p_foo" do + end + end + + context "When an LWRP resource lwrp_foo is loaded" do + before do + @tmpdir = Dir.mktmpdir("lwrp_test") + @lwrp_path = File.join(@tmpdir, "foo.rb") + content = IO.read(File.expand_path("../../data/lwrp/resources/foo.rb", __FILE__)) + IO.write(@lwrp_path, content) + Chef::Resource::LWRPBase.build_from_file("lwrp", @lwrp_path, nil) + @original_resource = Chef::ResourceResolver.resolve(:lwrp_foo) + end + + after do + FileUtils.remove_entry @tmpdir + end + + context "And the LWRP is asked to load again, this time with different code" do + before do + content = IO.read(File.expand_path("../../data/lwrp_override/resources/foo.rb", __FILE__)) + IO.write(@lwrp_path, content) + Chef::Resource::LWRPBase.build_from_file("lwrp", @lwrp_path, nil) + end + + it "Should load the old content, and not the new" do + resource = Chef::ResourceResolver.resolve(:lwrp_foo) + expect(resource).to eq @original_resource + expect(resource.default_action).to eq(:pass_buck) + expect(Chef.method_defined?(:method_created_by_override_lwrp_foo)).to be_falsey + end + end + end + describe "Lightweight Chef::Resource" do before do Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp", "resources", "*"))].each do |file| Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) end + end - Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp_override", "resources", "*"))].each do |file| - Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) - end + it "should load the resource into a properly-named class and emit a warning when it is initialized" do + expect { Chef::Resource::LwrpFoo.new('hi') }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) end - it "should load the resource into a properly-named class" do - expect(Chef::Resource.const_get("LwrpFoo")).to be_kind_of(Class) + it "should be resolvable with Chef::ResourceResolver.resolve(:lwrp_foo)" do + expect(Chef::ResourceResolver.resolve(:lwrp_foo, node: Chef::Node.new)).to eq(get_lwrp(:lwrp_foo)) end it "should set resource_name" do - expect(Chef::Resource::LwrpFoo.new("blah").resource_name).to eql(:lwrp_foo) + expect(get_lwrp(:lwrp_foo).new("blah").resource_name).to eql(:lwrp_foo) + end + + it "should output the resource_name in .to_s" do + expect(get_lwrp(:lwrp_foo).new("blah").to_s).to eq "lwrp_foo[blah]" + end + + it "should have a class that outputs a reasonable string" do + expect(get_lwrp(:lwrp_foo).to_s).to eq "LWRP resource lwrp_foo from cookbook lwrp" end it "should add the specified actions to the allowed_actions array" do - expect(Chef::Resource::LwrpFoo.new("blah").allowed_actions).to include(:pass_buck, :twiddle_thumbs) + expect(get_lwrp(:lwrp_foo).new("blah").allowed_actions).to include(:pass_buck, :twiddle_thumbs) end it "should set the specified action as the default action" do - expect(Chef::Resource::LwrpFoo.new("blah").action).to eq(:pass_buck) + expect(get_lwrp(:lwrp_foo).new("blah").action).to eq(:pass_buck) end it "should create a method for each attribute" do - expect(Chef::Resource::LwrpFoo.new("blah").methods.map{ |m| m.to_sym}).to include(:monkey) + expect(get_lwrp(:lwrp_foo).new("blah").methods.map{ |m| m.to_sym}).to include(:monkey) end it "should build attribute methods that respect validation rules" do - expect { Chef::Resource::LwrpFoo.new("blah").monkey(42) }.to raise_error(ArgumentError) + expect { get_lwrp(:lwrp_foo).new("blah").monkey(42) }.to raise_error(ArgumentError) end it "should have access to the run context and node during class definition" do @@ -151,12 +222,133 @@ describe "LWRP" do Chef::Resource::LWRPBase.build_from_file("lwrp", file, run_context) end - cls = Chef::Resource.const_get("LwrpNodeattr") + cls = get_lwrp(:lwrp_nodeattr) expect(cls.node).to be_kind_of(Chef::Node) expect(cls.run_context).to be_kind_of(Chef::RunContext) expect(cls.node[:penguin_name]).to eql("jackass") end + context "resource class created" do + before do + @old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors] + Chef::Config[:treat_deprecation_warnings_as_errors] = false + end + after do + Chef::Config[:treat_deprecation_warnings_as_errors] = @old_treat_deprecation_warnings_as_errors + end + + it "should load the resource into a properly-named class" do + expect(Chef::Resource::LwrpFoo).to be_kind_of(Class) + expect(Chef::Resource::LwrpFoo <= Chef::Resource::LWRPBase).to be_truthy + end + + it "get_lwrp(:lwrp_foo).new is a Chef::Resource::LwrpFoo" do + lwrp = get_lwrp(:lwrp_foo).new('hi') + expect(lwrp.kind_of?(Chef::Resource::LwrpFoo)).to be_truthy + expect(lwrp.is_a?(Chef::Resource::LwrpFoo)).to be_truthy + expect(get_lwrp(:lwrp_foo) === lwrp).to be_truthy + expect(Chef::Resource::LwrpFoo === lwrp).to be_truthy + end + + it "Chef::Resource::LwrpFoo.new is a get_lwrp(:lwrp_foo)" do + lwrp = Chef::Resource::LwrpFoo.new('hi') + expect(lwrp.kind_of?(get_lwrp(:lwrp_foo))).to be_truthy + expect(lwrp.is_a?(get_lwrp(:lwrp_foo))).to be_truthy + expect(get_lwrp(:lwrp_foo) === lwrp).to be_truthy + expect(Chef::Resource::LwrpFoo === lwrp).to be_truthy + end + + it "works even if LwrpFoo exists in the top level" do + module ::LwrpFoo + end + expect(Chef::Resource::LwrpFoo).not_to eq(::LwrpFoo) + end + + context "with a subclass of get_lwrp(:lwrp_foo)" do + let(:subclass) do + Class.new(get_lwrp(:lwrp_foo)) + end + + it "subclass.new is a subclass" do + lwrp = subclass.new('hi') + expect(lwrp.kind_of?(subclass)).to be_truthy + expect(lwrp.is_a?(subclass)).to be_truthy + expect(subclass === lwrp).to be_truthy + expect(lwrp.class === subclass) + end + it "subclass.new is a Chef::Resource::LwrpFoo" do + lwrp = subclass.new('hi') + expect(lwrp.kind_of?(Chef::Resource::LwrpFoo)).to be_truthy + expect(lwrp.is_a?(Chef::Resource::LwrpFoo)).to be_truthy + expect(Chef::Resource::LwrpFoo === lwrp).to be_truthy + expect(lwrp.class === Chef::Resource::LwrpFoo) + end + it "subclass.new is a get_lwrp(:lwrp_foo)" do + lwrp = subclass.new('hi') + expect(lwrp.kind_of?(get_lwrp(:lwrp_foo))).to be_truthy + expect(lwrp.is_a?(get_lwrp(:lwrp_foo))).to be_truthy + expect(get_lwrp(:lwrp_foo) === lwrp).to be_truthy + expect(lwrp.class === get_lwrp(:lwrp_foo)) + end + it "Chef::Resource::LwrpFoo.new is *not* a subclass" do + lwrp = Chef::Resource::LwrpFoo.new('hi') + expect(lwrp.kind_of?(subclass)).to be_falsey + expect(lwrp.is_a?(subclass)).to be_falsey + expect(subclass === lwrp.class).to be_falsey + expect(subclass === Chef::Resource::LwrpFoo).to be_falsey + end + it "get_lwrp(:lwrp_foo).new is *not* a subclass" do + lwrp = get_lwrp(:lwrp_foo).new('hi') + expect(lwrp.kind_of?(subclass)).to be_falsey + expect(lwrp.is_a?(subclass)).to be_falsey + expect(subclass === lwrp.class).to be_falsey + expect(subclass === get_lwrp(:lwrp_foo)).to be_falsey + end + end + + context "with a subclass of Chef::Resource::LwrpFoo" do + let(:subclass) do + Class.new(Chef::Resource::LwrpFoo) + end + + it "subclass.new is a subclass" do + lwrp = subclass.new('hi') + expect(lwrp.kind_of?(subclass)).to be_truthy + expect(lwrp.is_a?(subclass)).to be_truthy + expect(subclass === lwrp).to be_truthy + expect(lwrp.class === subclass) + end + it "subclass.new is a Chef::Resource::LwrpFoo" do + lwrp = subclass.new('hi') + expect(lwrp.kind_of?(Chef::Resource::LwrpFoo)).to be_truthy + expect(lwrp.is_a?(Chef::Resource::LwrpFoo)).to be_truthy + expect(Chef::Resource::LwrpFoo === lwrp).to be_truthy + expect(lwrp.class === Chef::Resource::LwrpFoo) + end + it "subclass.new is a get_lwrp(:lwrp_foo)" do + lwrp = subclass.new('hi') + expect(lwrp.kind_of?(get_lwrp(:lwrp_foo))).to be_truthy + expect(lwrp.is_a?(get_lwrp(:lwrp_foo))).to be_truthy + expect(get_lwrp(:lwrp_foo) === lwrp).to be_truthy + expect(lwrp.class === get_lwrp(:lwrp_foo)) + end + it "Chef::Resource::LwrpFoo.new is *not* a subclass" do + lwrp = Chef::Resource::LwrpFoo.new('hi') + expect(lwrp.kind_of?(subclass)).to be_falsey + expect(lwrp.is_a?(subclass)).to be_falsey + expect(subclass === lwrp.class).to be_falsey + expect(subclass === Chef::Resource::LwrpFoo).to be_falsey + end + it "get_lwrp(:lwrp_foo).new is *not* a subclass" do + lwrp = get_lwrp(:lwrp_foo).new('hi') + expect(lwrp.kind_of?(subclass)).to be_falsey + expect(lwrp.is_a?(subclass)).to be_falsey + expect(subclass === lwrp.class).to be_falsey + expect(subclass === get_lwrp(:lwrp_foo)).to be_falsey + end + end + end + context "resource_name" do let(:klass) { Class.new(Chef::Resource::LWRPBase) } @@ -175,14 +367,6 @@ describe "LWRP" do expect(klass.resource_name).to eq(:foo) end - context "when creating a new instance" do - it "raises an exception if resource_name is nil" do - expect { - klass.new('blah') - }.to raise_error(Chef::Exceptions::InvalidResourceSpecification) - end - end - context "lazy default values" do let(:klass) do Class.new(Chef::Resource::LWRPBase) do @@ -281,102 +465,146 @@ describe "LWRP" do end describe "Lightweight Chef::Provider" do - before do - @node = Chef::Node.new - @node.automatic[:platform] = :ubuntu - @node.automatic[:platform_version] = '8.10' - @events = Chef::EventDispatch::Dispatcher.new - @run_context = Chef::RunContext.new(@node, Chef::CookbookCollection.new({}), @events) - @runner = Chef::Runner.new(@run_context) - end - before(:each) do - Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp", "resources", "*"))].each do |file| - Chef::Resource::LWRPBase.build_from_file("lwrp", file, @run_context) + let(:node) do + Chef::Node.new.tap do |n| + n.automatic[:platform] = :ubuntu + n.automatic[:platform_version] = '8.10' end + end - Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp_override", "resources", "*"))].each do |file| - Chef::Resource::LWRPBase.build_from_file("lwrp", file, @run_context) - end + let(:events) { Chef::EventDispatch::Dispatcher.new } - Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp", "providers", "*"))].each do |file| - Chef::Provider::LWRPBase.build_from_file("lwrp", file, @run_context) - end + let(:run_context) { Chef::RunContext.new(node, Chef::CookbookCollection.new({}), events) } + + let(:runner) { Chef::Runner.new(run_context) } - Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp_override", "providers", "*"))].each do |file| - Chef::Provider::LWRPBase.build_from_file("lwrp", file, @run_context) + let(:lwrp_cookbok_name) { "lwrp" } + + before do + Chef::Provider::LWRPBase.class_eval { @loaded_lwrps = {} } + end + + before(:each) do + Dir[File.expand_path(File.expand_path("../../data/lwrp/resources/*", __FILE__))].each do |file| + Chef::Resource::LWRPBase.build_from_file(lwrp_cookbok_name, file, run_context) end + Dir[File.expand_path(File.expand_path("../../data/lwrp/providers/*", __FILE__))].each do |file| + Chef::Provider::LWRPBase.build_from_file(lwrp_cookbok_name, file, run_context) + end end it "should properly handle a new_resource reference" do - resource = Chef::Resource::LwrpFoo.new("morpheus") + resource = get_lwrp(:lwrp_foo).new("morpheus", run_context) resource.monkey("bob") - resource.provider(:lwrp_monkey_name_printer) - resource.run_context = @run_context + resource.provider(get_lwrp_provider(:lwrp_monkey_name_printer)) provider = Chef::Platform.provider_for_resource(resource, :twiddle_thumbs) provider.action_twiddle_thumbs end - it "should load the provider into a properly-named class" do - expect(Chef::Provider.const_get("LwrpBuckPasser")).to be_kind_of(Class) - end + context "provider class created" do + before do + @old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors] + Chef::Config[:treat_deprecation_warnings_as_errors] = false + end - it "should create a method for each attribute" do - new_resource = double("new resource").as_null_object - expect(Chef::Provider::LwrpBuckPasser.new(nil, new_resource).methods.map{|m|m.to_sym}).to include(:action_pass_buck) - expect(Chef::Provider::LwrpThumbTwiddler.new(nil, new_resource).methods.map{|m|m.to_sym}).to include(:action_twiddle_thumbs) + after do + Chef::Config[:treat_deprecation_warnings_as_errors] = @old_treat_deprecation_warnings_as_errors + end + + it "should load the provider into a properly-named class" do + expect(Chef::Provider.const_get("LwrpBuckPasser")).to be_kind_of(Class) + expect(Chef::Provider::LwrpBuckPasser <= Chef::Provider::LWRPBase).to be_truthy + end + + it "should create a method for each action" do + expect(get_lwrp_provider(:lwrp_buck_passer).instance_methods).to include(:action_pass_buck) + expect(get_lwrp_provider(:lwrp_thumb_twiddler).instance_methods).to include(:action_twiddle_thumbs) + end + + it "sets itself as a provider for a resource of the same name" do + found_providers = Chef::Platform::ProviderPriorityMap.instance.list_handlers(node, :lwrp_buck_passer) + # we bypass the per-file loading to get the file to load each time, + # which creates the LWRP class repeatedly. New things get prepended to + # the list of providers. + expect(found_providers.first).to eq(get_lwrp_provider(:lwrp_buck_passer)) + end + + context "with a cookbook with an underscore in the name" do + + let(:lwrp_cookbok_name) { "l_w_r_p" } + + it "sets itself as a provider for a resource of the same name" do + found_providers = Chef::Platform::ProviderPriorityMap.instance.list_handlers(node, :l_w_r_p_buck_passer) + expect(found_providers.size).to eq(1) + expect(found_providers.last).to eq(get_lwrp_provider(:l_w_r_p_buck_passer)) + end + end + + context "with a cookbook with a hypen in the name" do + + let(:lwrp_cookbok_name) { "l-w-r-p" } + + it "sets itself as a provider for a resource of the same name" do + incorrect_providers = Chef::Platform::ProviderPriorityMap.instance.list_handlers(node, :'l-w-r-p_buck_passer') + expect(incorrect_providers).to eq([]) + + found_providers = Chef::Platform::ProviderPriorityMap.instance.list_handlers(node, :l_w_r_p_buck_passer) + expect(found_providers.first).to eq(get_lwrp_provider(:l_w_r_p_buck_passer)) + end + end end it "should insert resources embedded in the provider into the middle of the resource collection" do - injector = Chef::Resource::LwrpFoo.new("morpheus", @run_context) + injector = get_lwrp(:lwrp_foo).new("morpheus", run_context) injector.action(:pass_buck) - injector.provider(:lwrp_buck_passer) - dummy = Chef::Resource::ZenMaster.new("keanu reeves", @run_context) + injector.provider(get_lwrp_provider(:lwrp_buck_passer)) + dummy = Chef::Resource::ZenMaster.new("keanu reeves", run_context) dummy.provider(Chef::Provider::Easy) - @run_context.resource_collection.insert(injector) - @run_context.resource_collection.insert(dummy) + run_context.resource_collection.insert(injector) + run_context.resource_collection.insert(dummy) - Chef::Runner.new(@run_context).converge + Chef::Runner.new(run_context).converge - expect(@run_context.resource_collection[0]).to eql(injector) - expect(@run_context.resource_collection[1].name).to eql('prepared_thumbs') - expect(@run_context.resource_collection[2].name).to eql('twiddled_thumbs') - expect(@run_context.resource_collection[3]).to eql(dummy) + expect(run_context.resource_collection[0]).to eql(injector) + expect(run_context.resource_collection[1].name).to eql('prepared_thumbs') + expect(run_context.resource_collection[2].name).to eql('twiddled_thumbs') + expect(run_context.resource_collection[3]).to eql(dummy) end it "should insert embedded resources from multiple providers, including from the last position, properly into the resource collection" do - injector = Chef::Resource::LwrpFoo.new("morpheus", @run_context) + injector = get_lwrp(:lwrp_foo).new("morpheus", run_context) injector.action(:pass_buck) - injector.provider(:lwrp_buck_passer) + injector.provider(get_lwrp_provider(:lwrp_buck_passer)) - injector2 = Chef::Resource::LwrpBar.new("tank", @run_context) + injector2 = get_lwrp(:lwrp_bar).new("tank", run_context) injector2.action(:pass_buck) - injector2.provider(:lwrp_buck_passer_2) + injector2.provider(get_lwrp_provider(:lwrp_buck_passer_2)) - dummy = Chef::Resource::ZenMaster.new("keanu reeves", @run_context) + dummy = Chef::Resource::ZenMaster.new("keanu reeves", run_context) dummy.provider(Chef::Provider::Easy) - @run_context.resource_collection.insert(injector) - @run_context.resource_collection.insert(dummy) - @run_context.resource_collection.insert(injector2) + run_context.resource_collection.insert(injector) + run_context.resource_collection.insert(dummy) + run_context.resource_collection.insert(injector2) - Chef::Runner.new(@run_context).converge + Chef::Runner.new(run_context).converge - expect(@run_context.resource_collection[0]).to eql(injector) - expect(@run_context.resource_collection[1].name).to eql('prepared_thumbs') - expect(@run_context.resource_collection[2].name).to eql('twiddled_thumbs') - expect(@run_context.resource_collection[3]).to eql(dummy) - expect(@run_context.resource_collection[4]).to eql(injector2) - expect(@run_context.resource_collection[5].name).to eql('prepared_eyes') - expect(@run_context.resource_collection[6].name).to eql('dried_paint_watched') + expect(run_context.resource_collection[0]).to eql(injector) + expect(run_context.resource_collection[1].name).to eql('prepared_thumbs') + expect(run_context.resource_collection[2].name).to eql('twiddled_thumbs') + expect(run_context.resource_collection[3]).to eql(dummy) + expect(run_context.resource_collection[4]).to eql(injector2) + expect(run_context.resource_collection[5].name).to eql('prepared_eyes') + expect(run_context.resource_collection[6].name).to eql('dried_paint_watched') end it "should properly handle a new_resource reference" do - resource = Chef::Resource::LwrpFoo.new("morpheus", @run_context) + resource = get_lwrp(:lwrp_foo).new("morpheus", run_context) resource.monkey("bob") - resource.provider(:lwrp_monkey_name_printer) + resource.provider(get_lwrp_provider(:lwrp_monkey_name_printer)) provider = Chef::Platform.provider_for_resource(resource, :twiddle_thumbs) provider.action_twiddle_thumbs @@ -385,9 +613,9 @@ describe "LWRP" do end it "should properly handle an embedded Resource accessing the enclosing Provider's scope" do - resource = Chef::Resource::LwrpFoo.new("morpheus", @run_context) + resource = get_lwrp(:lwrp_foo).new("morpheus", run_context) resource.monkey("bob") - resource.provider(:lwrp_embedded_resource_accesses_providers_scope) + resource.provider(get_lwrp_provider(:lwrp_embedded_resource_accesses_providers_scope)) provider = Chef::Platform.provider_for_resource(resource, :twiddle_thumbs) #provider = @runner.build_provider(resource) @@ -404,15 +632,15 @@ describe "LWRP" do # Side effect of lwrp_inline_compiler provider for testing notifications. $interior_ruby_block_2 = nil # resource type doesn't matter, so make an existing resource type work with provider. - @resource = Chef::Resource::LwrpFoo.new("morpheus", @run_context) + @resource = get_lwrp(:lwrp_foo).new("morpheus", run_context) @resource.allowed_actions << :test @resource.action(:test) - @resource.provider(:lwrp_inline_compiler) + @resource.provider(get_lwrp_provider(:lwrp_inline_compiler)) end it "does not add interior resources to the exterior resource collection" do @resource.run_action(:test) - expect(@run_context.resource_collection).to be_empty + expect(run_context.resource_collection).to be_empty end context "when interior resources are updated" do diff --git a/spec/unit/mixin/api_version_request_handling_spec.rb b/spec/unit/mixin/api_version_request_handling_spec.rb new file mode 100644 index 0000000000..cc5340e424 --- /dev/null +++ b/spec/unit/mixin/api_version_request_handling_spec.rb @@ -0,0 +1,127 @@ +# +# Author:: Tyler Cloke (tyler@chef.io) +# Copyright:: Copyright 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' + +describe Chef::Mixin::ApiVersionRequestHandling do + let(:dummy_class) { Class.new { include Chef::Mixin::ApiVersionRequestHandling } } + let(:object) { dummy_class.new } + + describe ".server_client_api_version_intersection" do + let(:default_supported_client_versions) { [0,1,2] } + + + context "when the response code is not 406" do + let(:response) { OpenStruct.new(:code => '405') } + let(:exception) { Net::HTTPServerException.new("405 Something Else", response) } + + it "returns nil" do + expect(object.server_client_api_version_intersection(exception, default_supported_client_versions)). + to be_nil + end + + end # when the response code is not 406 + + context "when the response code is 406" do + let(:response) { OpenStruct.new(:code => '406') } + let(:exception) { Net::HTTPServerException.new("406 Not Acceptable", response) } + + context "when x-ops-server-api-version header does not exist" do + it "returns nil" do + expect(object.server_client_api_version_intersection(exception, default_supported_client_versions)). + to be_nil + end + end # when x-ops-server-api-version header does not exist + + context "when x-ops-server-api-version header exists" do + let(:min_server_version) { 2 } + let(:max_server_version) { 4 } + let(:return_hash) { + { + "min_version" => min_server_version, + "max_version" => max_server_version + } + } + + before(:each) do + allow(response).to receive(:[]).with('x-ops-server-api-version').and_return(Chef::JSONCompat.to_json(return_hash)) + end + + context "when there is no intersection between client and server versions" do + shared_examples_for "no intersection between client and server versions" do + it "return an array" do + expect(object.server_client_api_version_intersection(exception, supported_client_versions)). + to be_a_kind_of(Array) + end + + it "returns an empty array" do + expect(object.server_client_api_version_intersection(exception, supported_client_versions).length). + to eq(0) + end + + end + + context "when all the versions are higher than the max" do + it_should_behave_like "no intersection between client and server versions" do + let(:supported_client_versions) { [5,6,7] } + end + end + + context "when all the versions are lower than the min" do + it_should_behave_like "no intersection between client and server versions" do + let(:supported_client_versions) { [0,1] } + end + end + + end # when there is no intersection between client and server versions + + context "when there is an intersection between client and server versions" do + context "when multiple versions intersect" do + let(:supported_client_versions) { [1,2,3,4,5] } + + it "includes all of the intersection" do + expect(object.server_client_api_version_intersection(exception, supported_client_versions)). + to eq([2,3,4]) + end + end # when multiple versions intersect + + context "when only the min client version intersects" do + let(:supported_client_versions) { [0,1,2] } + + it "includes the intersection" do + expect(object.server_client_api_version_intersection(exception, supported_client_versions)). + to eq([2]) + end + end # when only the min client version intersects + + context "when only the max client version intersects" do + let(:supported_client_versions) { [4,5,6] } + + it "includes the intersection" do + expect(object.server_client_api_version_intersection(exception, supported_client_versions)). + to eq([4]) + end + end # when only the max client version intersects + + end # when there is an intersection between client and server versions + + end # when x-ops-server-api-version header exists + end # when the response code is 406 + + end # .server_client_api_version_intersection +end # Chef::Mixin::ApiVersionRequestHandling diff --git a/spec/unit/mixin/command_spec.rb b/spec/unit/mixin/command_spec.rb index e198e3addd..050b261256 100644 --- a/spec/unit/mixin/command_spec.rb +++ b/spec/unit/mixin/command_spec.rb @@ -22,7 +22,7 @@ describe Chef::Mixin::Command, :volatile do if windows? - pending("TODO MOVE: this is a platform specific integration test.") + skip("TODO MOVE: this is a platform specific integration test.") else @@ -61,7 +61,6 @@ describe Chef::Mixin::Command, :volatile do it "returns immediately after the first child process exits" do expect {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 diff --git a/spec/unit/mixin/path_sanity_spec.rb b/spec/unit/mixin/path_sanity_spec.rb index ec8e182e3d..3a924b9538 100644 --- a/spec/unit/mixin/path_sanity_spec.rb +++ b/spec/unit/mixin/path_sanity_spec.rb @@ -35,7 +35,7 @@ describe Chef::Mixin::PathSanity do @gem_bindir = '/some/gem/bin' allow(Gem).to receive(:bindir).and_return(@gem_bindir) allow(RbConfig::CONFIG).to receive(:[]).with('bindir').and_return(@ruby_bindir) - allow(Chef::Platform).to receive(:windows?).and_return(false) + allow(ChefConfig).to receive(:windows?).and_return(false) end it "adds all useful PATHs even if environment is an empty hash" do @@ -77,7 +77,7 @@ describe Chef::Mixin::PathSanity do gem_bindir = 'C:\gems\bin' allow(Gem).to receive(:bindir).and_return(gem_bindir) allow(RbConfig::CONFIG).to receive(:[]).with('bindir').and_return(ruby_bindir) - allow(Chef::Platform).to receive(:windows?).and_return(true) + allow(ChefConfig).to receive(:windows?).and_return(true) env = {"PATH" => 'C:\Windows\system32;C:\mr\softie'} @sanity.enforce_path_sanity(env) expect(env["PATH"]).to eq("C:\\Windows\\system32;C:\\mr\\softie;#{ruby_bindir};#{gem_bindir}") diff --git a/spec/unit/mixin/powershell_out_spec.rb b/spec/unit/mixin/powershell_out_spec.rb new file mode 100644 index 0000000000..0fede582fa --- /dev/null +++ b/spec/unit/mixin/powershell_out_spec.rb @@ -0,0 +1,70 @@ +# +# 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' +require 'chef/mixin/powershell_out' + +describe Chef::Mixin::PowershellOut do + let(:shell_out_class) { Class.new { include Chef::Mixin::PowershellOut } } + subject(:object) { shell_out_class.new } + let(:architecture) { "something" } + let(:flags) { + "-NoLogo -NonInteractive -NoProfile -ExecutionPolicy Unrestricted -InputFormat None" + } + + describe "#powershell_out" do + it "runs a command and returns the shell_out object" do + ret = double("Mixlib::ShellOut") + expect(object).to receive(:shell_out).with( + "powershell.exe #{flags} -Command \"Get-Process\"", + {} + ).and_return(ret) + expect(object.powershell_out("Get-Process")).to eql(ret) + end + + it "passes options" do + ret = double("Mixlib::ShellOut") + expect(object).to receive(:shell_out).with( + "powershell.exe #{flags} -Command \"Get-Process\"", + timeout: 600 + ).and_return(ret) + expect(object.powershell_out("Get-Process", timeout: 600)).to eql(ret) + end + end + + describe "#powershell_out!" do + it "runs a command and returns the shell_out object" do + mixlib_shellout = double("Mixlib::ShellOut") + expect(object).to receive(:shell_out).with( + "powershell.exe #{flags} -Command \"Get-Process\"", + {} + ).and_return(mixlib_shellout) + expect(mixlib_shellout).to receive(:error!) + expect(object.powershell_out!("Get-Process")).to eql(mixlib_shellout) + end + + it "passes options" do + mixlib_shellout = double("Mixlib::ShellOut") + expect(object).to receive(:shell_out).with( + "powershell.exe #{flags} -Command \"Get-Process\"", + timeout: 600 + ).and_return(mixlib_shellout) + expect(mixlib_shellout).to receive(:error!) + expect(object.powershell_out!("Get-Process", timeout: 600)).to eql(mixlib_shellout) + end + end +end diff --git a/spec/unit/mixin/template_spec.rb b/spec/unit/mixin/template_spec.rb index f02bd34b8f..6a867b5f9a 100644 --- a/spec/unit/mixin/template_spec.rb +++ b/spec/unit/mixin/template_spec.rb @@ -39,7 +39,7 @@ describe Chef::Mixin::Template, "render_template" do describe "when running on windows" do before do - allow(Chef::Platform).to receive(:windows?).and_return(true) + allow(ChefConfig).to receive(:windows?).and_return(true) end it "should render the templates with windows line endings" do @@ -54,7 +54,7 @@ describe Chef::Mixin::Template, "render_template" do describe "when running on unix" do before do - allow(Chef::Platform).to receive(:windows?).and_return(false) + allow(ChefConfig).to receive(:windows?).and_return(false) end it "should render the templates with unix line endings" do diff --git a/spec/unit/mixin/unformatter_spec.rb b/spec/unit/mixin/unformatter_spec.rb new file mode 100644 index 0000000000..2eae0ac9bb --- /dev/null +++ b/spec/unit/mixin/unformatter_spec.rb @@ -0,0 +1,61 @@ +# +# Author:: Jay Mundrawala (<jdm@chef.io>) +# Copyright:: Copyright (c) 2015 Chef Software +# 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/unformatter' + +class Chef::UnformatterTest + include Chef::Mixin::Unformatter + + def foo + end + +end + +describe Chef::Mixin::Unformatter do + let (:unformatter) { Chef::UnformatterTest.new } + let (:message) { "Test Message" } + + describe "#write" do + context "with a timestamp" do + it "sends foo to itself when the message is of severity foo" do + expect(unformatter).to receive(:foo).with(message) + unformatter.write("[time] foo: #{message}") + end + + it "sends foo to itself when the message is of severity FOO" do + expect(unformatter).to receive(:foo).with(message) + unformatter.write("[time] FOO: #{message}") + end + end + + context "without a timestamp" do + it "sends foo to itself when the message is of severity foo" do + expect(unformatter).to receive(:foo).with(message) + unformatter.write("foo: #{message}") + end + + it "sends foo to itself when the message is of severity FOO" do + expect(unformatter).to receive(:foo).with(message) + unformatter.write("FOO: #{message}") + end + end + + end + +end diff --git a/spec/unit/mixin/uris_spec.rb b/spec/unit/mixin/uris_spec.rb new file mode 100644 index 0000000000..d4985c4f67 --- /dev/null +++ b/spec/unit/mixin/uris_spec.rb @@ -0,0 +1,57 @@ +# +# Author:: Jay Mundrawala (<jdm@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' +require 'chef/mixin/uris' + +class Chef::UrisTest + include Chef::Mixin::Uris +end + +describe Chef::Mixin::Uris do + let (:uris) { Chef::UrisTest.new } + + describe "#uri_scheme?" do + it "matches 'scheme://foo.com'" do + expect(uris.uri_scheme?('scheme://foo.com')).to eq(true) + end + + it "does not match 'c:/foo.com'" do + expect(uris.uri_scheme?('c:/foo.com')).to eq(false) + end + + it "does not match '/usr/bin/foo.com'" do + expect(uris.uri_scheme?('/usr/bin/foo.com')).to eq(false) + end + + it "does not match 'c:/foo.com://bar.com'" do + expect(uris.uri_scheme?('c:/foo.com://bar.com')).to eq(false) + end + end + + describe "#as_uri" do + it "parses a file scheme uri with spaces" do + expect{ uris.as_uri("file:///c:/foo bar.txt") }.not_to raise_exception + end + + it "returns a URI object" do + expect( uris.as_uri("file:///c:/foo bar.txt") ).to be_a(URI) + end + end + +end diff --git a/spec/unit/node_map_spec.rb b/spec/unit/node_map_spec.rb index fe7372961b..9b5ff5e8c6 100644 --- a/spec/unit/node_map_spec.rb +++ b/spec/unit/node_map_spec.rb @@ -134,6 +134,10 @@ describe Chef::NodeMap do end describe "resource back-compat testing" do + before :each do + Chef::Config[:treat_deprecation_warnings_as_errors] = false + end + it "should handle :on_platforms => :all" do node_map.set(:chef_gem, :foo, :on_platforms => :all) allow(node).to receive(:[]).with(:platform).and_return("windows") @@ -152,4 +156,3 @@ describe Chef::NodeMap do end end - diff --git a/spec/unit/osc_user_spec.rb b/spec/unit/osc_user_spec.rb new file mode 100644 index 0000000000..678486a16d --- /dev/null +++ b/spec/unit/osc_user_spec.rb @@ -0,0 +1,276 @@ +# +# Author:: Steven Danna (steve@opscode.com) +# Copyright:: Copyright (c) 2012 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# DEPRECATION NOTE +# This code only remains to support users still operating with +# Open Source Chef Server 11 and should be removed once support +# for OSC 11 ends. New development should occur in user_spec.rb. + +require 'spec_helper' + +require 'chef/osc_user' +require 'tempfile' + +describe Chef::OscUser do + before(:each) do + @user = Chef::OscUser.new + end + + describe "initialize" do + it "should be a Chef::OscUser" do + expect(@user).to be_a_kind_of(Chef::OscUser) + end + end + + describe "name" do + it "should let you set the name to a string" do + expect(@user.name("ops_master")).to eq("ops_master") + end + + it "should return the current name" do + @user.name "ops_master" + expect(@user.name).to eq("ops_master") + end + + # It is not feasible to check all invalid characters. Here are a few + # that we probably care about. + it "should not accept invalid characters" do + # capital letters + expect { @user.name "Bar" }.to raise_error(ArgumentError) + # slashes + expect { @user.name "foo/bar" }.to raise_error(ArgumentError) + # ? + expect { @user.name "foo?" }.to raise_error(ArgumentError) + # & + expect { @user.name "foo&" }.to raise_error(ArgumentError) + end + + + it "should not accept spaces" do + expect { @user.name "ops master" }.to raise_error(ArgumentError) + end + + it "should throw an ArgumentError if you feed it anything but a string" do + expect { @user.name Hash.new }.to raise_error(ArgumentError) + end + end + + describe "admin" do + it "should let you set the admin bit" do + expect(@user.admin(true)).to eq(true) + end + + it "should return the current admin value" do + @user.admin true + expect(@user.admin).to eq(true) + end + + it "should default to false" do + expect(@user.admin).to eq(false) + end + + it "should throw an ArgumentError if you feed it anything but true or false" do + expect { @user.name Hash.new }.to raise_error(ArgumentError) + end + end + + describe "public_key" do + it "should let you set the public key" do + expect(@user.public_key("super public")).to eq("super public") + end + + it "should return the current public key" do + @user.public_key("super public") + expect(@user.public_key).to eq("super public") + end + + it "should throw an ArgumentError if you feed it something lame" do + expect { @user.public_key Hash.new }.to raise_error(ArgumentError) + end + end + + describe "private_key" do + it "should let you set the private key" do + expect(@user.private_key("super private")).to eq("super private") + end + + it "should return the private key" do + @user.private_key("super private") + expect(@user.private_key).to eq("super private") + end + + it "should throw an ArgumentError if you feed it something lame" do + expect { @user.private_key Hash.new }.to raise_error(ArgumentError) + end + end + + describe "when serializing to JSON" do + before(:each) do + @user.name("black") + @user.public_key("crowes") + @json = @user.to_json + end + + it "serializes as a JSON object" do + expect(@json).to match(/^\{.+\}$/) + end + + it "includes the name value" do + expect(@json).to include(%q{"name":"black"}) + end + + it "includes the public key value" do + expect(@json).to include(%{"public_key":"crowes"}) + end + + it "includes the 'admin' flag" do + expect(@json).to include(%q{"admin":false}) + end + + it "includes the private key when present" do + @user.private_key("monkeypants") + expect(@user.to_json).to include(%q{"private_key":"monkeypants"}) + end + + it "does not include the private key if not present" do + expect(@json).not_to include("private_key") + end + + it "includes the password if present" do + @user.password "password" + expect(@user.to_json).to include(%q{"password":"password"}) + end + + it "does not include the password if not present" do + expect(@json).not_to include("password") + end + + include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + let(:jsonable) { @user } + end + end + + describe "when deserializing from JSON" do + before(:each) do + user = { "name" => "mr_spinks", + "public_key" => "turtles", + "private_key" => "pandas", + "password" => "password", + "admin" => true } + @user = Chef::OscUser.from_json(Chef::JSONCompat.to_json(user)) + end + + it "should deserialize to a Chef::OscUser object" do + expect(@user).to be_a_kind_of(Chef::OscUser) + end + + it "preserves the name" do + expect(@user.name).to eq("mr_spinks") + end + + it "preserves the public key" do + expect(@user.public_key).to eq("turtles") + end + + it "preserves the admin status" do + expect(@user.admin).to be_truthy + end + + it "includes the private key if present" do + expect(@user.private_key).to eq("pandas") + end + + it "includes the password if present" do + expect(@user.password).to eq("password") + end + + end + + describe "API Interactions" do + before (:each) do + @user = Chef::OscUser.new + @user.name "foobar" + @http_client = double("Chef::REST mock") + allow(Chef::REST).to receive(:new).and_return(@http_client) + end + + describe "list" do + before(:each) do + Chef::Config[:chef_server_url] = "http://www.example.com" + @osc_response = { "admin" => "http://www.example.com/users/admin"} + @ohc_response = [ { "user" => { "username" => "admin" }} ] + allow(Chef::OscUser).to receive(:load).with("admin").and_return(@user) + @osc_inflated_response = { "admin" => @user } + end + + it "lists all clients on an OSC server" do + allow(@http_client).to receive(:get_rest).with("users").and_return(@osc_response) + expect(Chef::OscUser.list).to eq(@osc_response) + end + + it "inflate all clients on an OSC server" do + allow(@http_client).to receive(:get_rest).with("users").and_return(@osc_response) + expect(Chef::OscUser.list(true)).to eq(@osc_inflated_response) + end + + it "lists all clients on an OHC/OPC server" do + allow(@http_client).to receive(:get_rest).with("users").and_return(@ohc_response) + # We expect that Chef::OscUser.list will give a consistent response + # so OHC API responses should be transformed to OSC-style output. + expect(Chef::OscUser.list).to eq(@osc_response) + end + + it "inflate all clients on an OHC/OPC server" do + allow(@http_client).to receive(:get_rest).with("users").and_return(@ohc_response) + expect(Chef::OscUser.list(true)).to eq(@osc_inflated_response) + end + end + + describe "create" do + it "creates a new user via the API" do + @user.password "password" + expect(@http_client).to receive(:post_rest).with("users", {:name => "foobar", :admin => false, :password => "password"}).and_return({}) + @user.create + end + end + + describe "read" do + it "loads a named user from the API" do + expect(@http_client).to receive(:get_rest).with("users/foobar").and_return({"name" => "foobar", "admin" => true, "public_key" => "pubkey"}) + user = Chef::OscUser.load("foobar") + expect(user.name).to eq("foobar") + expect(user.admin).to eq(true) + expect(user.public_key).to eq("pubkey") + end + end + + describe "update" do + it "updates an existing user on via the API" do + expect(@http_client).to receive(:put_rest).with("users/foobar", {:name => "foobar", :admin => false}).and_return({}) + @user.update + end + end + + describe "destroy" do + it "deletes the specified user via the API" do + expect(@http_client).to receive(:delete_rest).with("users/foobar") + @user.destroy + end + end + end +end diff --git a/spec/unit/platform/query_helpers_spec.rb b/spec/unit/platform/query_helpers_spec.rb index 1dbd07a021..33d4c2c3b7 100644 --- a/spec/unit/platform/query_helpers_spec.rb +++ b/spec/unit/platform/query_helpers_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' describe "Chef::Platform#windows_server_2003?" do it "returns false early when not on windows" do - allow(Chef::Platform).to receive(:windows?).and_return(false) + allow(ChefConfig).to receive(:windows?).and_return(false) expect(Chef::Platform).not_to receive(:require) expect(Chef::Platform.windows_server_2003?).to be_falsey end diff --git a/spec/unit/platform_spec.rb b/spec/unit/platform_spec.rb index e0115bc42a..36325d5411 100644 --- a/spec/unit/platform_spec.rb +++ b/spec/unit/platform_spec.rb @@ -18,29 +18,6 @@ require 'spec_helper' -describe "Chef::Platform supports" do - [ - :freebsd, - :ubuntu, - :debian, - :centos, - :fedora, - :suse, - :opensuse, - :redhat, - :oracle, - :gentoo, - :arch, - :solaris, - :gcel, - :ibm_powerkvm - ].each do |platform| - it "#{platform}" do - expect(Chef::Platform.platforms).to have_key(platform) - end - end -end - describe Chef::Platform do context "while testing with fake data" do @@ -261,41 +238,4 @@ describe Chef::Platform do end - context "while testing the configured platform data" do - - it "should use the solaris package provider on Solaris <11" do - pmap = Chef::Platform.find("Solaris2", "5.9") - expect(pmap[:package]).to eql(Chef::Provider::Package::Solaris) - end - - it "should use the IPS package provider on Solaris 11" do - pmap = Chef::Platform.find("Solaris2", "5.11") - expect(pmap[:package]).to eql(Chef::Provider::Package::Ips) - end - - it "should use the Redhat service provider on SLES11" do - 1.upto(3) do |sp| - pmap = Chef::Platform.find("SUSE", "11.#{sp}") - expect(pmap[:service]).to eql(Chef::Provider::Service::Redhat) - end - end - - it "should use the Systemd service provider on SLES12" do - pmap = Chef::Platform.find("SUSE", "12.0") - expect(pmap[:service]).to eql(Chef::Provider::Service::Systemd) - end - - it "should use the SUSE group provider on SLES11" do - 1.upto(3) do |sp| - pmap = Chef::Platform.find("SUSE", "11.#{sp}") - expect(pmap[:group]).to eql(Chef::Provider::Group::Suse) - end - end - - it "should use the Gpasswd group provider on SLES12" do - pmap = Chef::Platform.find("SUSE", "12.0") - expect(pmap[:group]).to eql(Chef::Provider::Group::Gpasswd) - end - end - end diff --git a/spec/unit/policy_builder/policyfile_spec.rb b/spec/unit/policy_builder/policyfile_spec.rb index e4f7388a1c..5fa00d8f2b 100644 --- a/spec/unit/policy_builder/policyfile_spec.rb +++ b/spec/unit/policy_builder/policyfile_spec.rb @@ -166,13 +166,17 @@ describe Chef::PolicyBuilder::Policyfile do end before do - # TODO: agree on this name and logic. + Chef::Config[:policy_document_native_api] = false Chef::Config[:deployment_group] = "example-policy-stage" allow(policy_builder).to receive(:http_api).and_return(http_api) end describe "when using compatibility mode (policy_document_native_api == false)" do + before do + Chef::Config[:deployment_group] = "example-policy-stage" + end + context "when the deployment group cannot be loaded" do let(:error404) { Net::HTTPServerException.new("404 message", :body) } @@ -389,8 +393,8 @@ describe Chef::PolicyBuilder::Policyfile do let(:example1_cookbook_data) { double("CookbookVersion Hash for example1 cookbook") } let(:example2_cookbook_data) { double("CookbookVersion Hash for example2 cookbook") } - let(:example1_cookbook_object) { double("Chef::CookbookVersion for example1 cookbook") } - let(:example2_cookbook_object) { double("Chef::CookbookVersion for example2 cookbook") } + let(:example1_cookbook_object) { double("Chef::CookbookVersion for example1 cookbook", version: "0.1.2") } + let(:example2_cookbook_object) { double("Chef::CookbookVersion for example2 cookbook", version: "1.2.3") } let(:expected_cookbook_hash) do { "example1" => example1_cookbook_object, "example2" => example2_cookbook_object } diff --git a/spec/unit/provider/deploy/revision_spec.rb b/spec/unit/provider/deploy/revision_spec.rb index 4ca64e3445..caa60878e1 100644 --- a/spec/unit/provider/deploy/revision_spec.rb +++ b/spec/unit/provider/deploy/revision_spec.rb @@ -21,7 +21,7 @@ require 'spec_helper' describe Chef::Provider::Deploy::Revision do before do - allow(Chef::Platform).to receive(:windows?) { false } + allow(ChefConfig).to receive(:windows?) { false } @temp_dir = Dir.mktmpdir Chef::Config[:file_cache_path] = @temp_dir @resource = Chef::Resource::Deploy.new("/my/deploy/dir") diff --git a/spec/unit/provider/deploy_spec.rb b/spec/unit/provider/deploy_spec.rb index c95a9b3d57..63658ac601 100644 --- a/spec/unit/provider/deploy_spec.rb +++ b/spec/unit/provider/deploy_spec.rb @@ -21,7 +21,7 @@ require 'spec_helper' describe Chef::Provider::Deploy do before do - allow(Chef::Platform).to receive(:windows?) { false } + allow(ChefConfig).to receive(:windows?) { false } @release_time = Time.utc( 2004, 8, 15, 16, 23, 42) allow(Time).to receive(:now).and_return(@release_time) @expected_release_dir = "/my/deploy/dir/releases/20040815162342" @@ -622,7 +622,7 @@ describe Chef::Provider::Deploy do gems = @provider.send(:gem_packages) - expect(gems.map { |g| g.action }).to eq([[:install]]) + expect(gems.map { |g| g.action }).to eq([:install]) expect(gems.map { |g| g.name }).to eq(%w{eventmachine}) expect(gems.map { |g| g.version }).to eq(%w{0.12.9}) end diff --git a/spec/unit/provider/directory_spec.rb b/spec/unit/provider/directory_spec.rb index 13c57bfe56..38d6db8320 100644 --- a/spec/unit/provider/directory_spec.rb +++ b/spec/unit/provider/directory_spec.rb @@ -16,173 +16,237 @@ # limitations under the License. # -require 'ostruct' - require 'spec_helper' require 'tmpdir' describe Chef::Provider::Directory do - before(:each) do - @new_resource = Chef::Resource::Directory.new(Dir.tmpdir) - if !windows? - @new_resource.owner(500) - @new_resource.group(500) - @new_resource.mode(0644) + let(:tmp_dir) { Dir.mktmpdir } + let(:new_resource) { Chef::Resource::Directory.new(tmp_dir) } + let(:node) { Chef::Node.new } + let(:events) { Chef::EventDispatch::Dispatcher.new } + let(:run_context) { Chef::RunContext.new(node, {}, events) } + let(:directory) { Chef::Provider::Directory.new(new_resource, run_context) } + + describe "#load_current_resource" do + describe "scanning file security metadata" + describe "on unix", unix_only: true do + describe "when the directory exists" do + let(:dir_stat) { File::Stat.new(tmp_dir) } + let(:expected_uid) { dir_stat.uid } + let(:expected_gid) { dir_stat.gid } + let(:expected_mode) { "0%o" % ( dir_stat.mode & 007777 ) } + let(:expected_pwnam) { Etc.getpwuid(expected_uid).name } + let(:expected_grnam) { Etc.getgrgid(expected_gid).name } + + it "describes the access mode as a String of octal integers" do + directory.load_current_resource + expect(directory.current_resource.mode).to eq(expected_mode) + end + + it "when the new_resource.owner is numeric, describes the owner as a numeric uid" do + new_resource.owner(500) + directory.load_current_resource + expect(directory.current_resource.owner).to eql(expected_uid) + end + + it "when the new_resource.group is numeric, describes the group as a numeric gid" do + new_resource.group(500) + directory.load_current_resource + expect(directory.current_resource.group).to eql(expected_gid) + end + + it "when the new_resource.owner is a string, describes the owner as a string" do + new_resource.owner("foo") + directory.load_current_resource + expect(directory.current_resource.owner).to eql(expected_pwnam) + end + + it "when the new_resource.group is a string, describes the group as a string" do + new_resource.group("bar") + directory.load_current_resource + expect(directory.current_resource.group).to eql(expected_grnam) + end + end end - @node = Chef::Node.new - @events = Chef::EventDispatch::Dispatcher.new - @run_context = Chef::RunContext.new(@node, {}, @events) - @directory = Chef::Provider::Directory.new(@new_resource, @run_context) - end + describe "on windows", windows_only: true do + describe "when the directory exists" do + it "the mode is always nil" do + directory.load_current_resource + expect(directory.current_resource.mode).to be nil + end + + it "the owner is always nil" do + directory.load_current_resource + expect(directory.current_resource.owner).to be nil + end + + it "the group is always nil" do + directory.load_current_resource + expect(directory.current_resource.group).to be nil + end + + it "rights are always nil (incorrectly)" do + directory.load_current_resource + expect(directory.current_resource.rights).to be nil + end + + it "inherits is always nil (incorrectly)" do + directory.load_current_resource + expect(directory.current_resource.inherits).to be nil + end + end + end + describe "when the directory does not exist" do + before do + FileUtils.rmdir tmp_dir + end - describe "scanning file security metadata on windows" do - before do + it "sets the mode, group and owner to nil" do + directory.load_current_resource + expect(directory.current_resource.mode).to eq(nil) + expect(directory.current_resource.group).to eq(nil) + expect(directory.current_resource.owner).to eq(nil) + end end - it "describes the directory's access rights" do - skip - end end - describe "scanning file security metadata on unix" do - before do - allow(Chef::Platform).to receive(:windows?).and_return(false) - end - let(:mock_stat) do - cstats = double("stats") - allow(cstats).to receive(:uid).and_return(500) - allow(cstats).to receive(:gid).and_return(500) - allow(cstats).to receive(:mode).and_return(0755) - cstats - end + describe "#define_resource_requirements" do + describe "on unix", unix_only: true do + it "raises an exception if the user does not exist" do + new_resource.owner("arglebargle_iv") + expect(Etc).to receive(:getpwnam).with("arglebargle_iv").and_raise(ArgumentError) + directory.action = :create + directory.load_current_resource + expect(directory.access_controls).to receive(:define_resource_requirements).and_call_original + directory.define_resource_requirements + expect { directory.process_resource_requirements }.to raise_error(ArgumentError) + end - it "describes the access mode as a String of octal integers" do - allow(File).to receive(:exists?).and_return(true) - expect(File).to receive(:stat).and_return(mock_stat) - @directory.load_current_resource - expect(@directory.current_resource.mode).to eq("0755") + it "raises an exception if the group does not exist" do + new_resource.group("arglebargle_iv") + expect(Etc).to receive(:getgrnam).with("arglebargle_iv").and_raise(ArgumentError) + directory.action = :create + directory.load_current_resource + expect(directory.access_controls).to receive(:define_resource_requirements).and_call_original + directory.define_resource_requirements + expect { directory.process_resource_requirements }.to raise_error(ArgumentError) + end end + end - context "when user and group are specified with UID/GID" do - it "describes the current owner and group as UID and GID" do - allow(File).to receive(:exists?).and_return(true) - expect(File).to receive(:stat).and_return(mock_stat) - @directory.load_current_resource - expect(@directory.current_resource.path).to eql(@new_resource.path) - expect(@directory.current_resource.owner).to eql(500) - expect(@directory.current_resource.group).to eql(500) + describe "#run_action(:create)" do + describe "when the directory exists" do + it "does not create the directory" do + expect(Dir).not_to receive(:mkdir).with(new_resource.path) + directory.run_action(:create) + end + + it "should not set the resource as updated" do + directory.run_action(:create) + expect(new_resource).not_to be_updated end end - context "when user/group are specified with user/group names" do + describe "when the directory does not exist" do + before do + FileUtils.rmdir tmp_dir + end + + it "creates the directory" do + directory.run_action(:create) + expect(File.exist?(tmp_dir)).to be true + end + + it "sets the new resource as updated" do + directory.run_action(:create) + expect(new_resource).to be_updated + end end - end - # Unix only for now. While file security attribute reporting for windows is - # disabled, unix and windows differ in the number of exists? calls that are - # made by the provider. - it "should create a new directory on create, setting updated to true", :unix_only do - @new_resource.path "/tmp/foo" + describe "when the parent directory does not exist" do + before do + new_resource.path "#{tmp_dir}/foobar" + FileUtils.rmdir tmp_dir + end - expect(File).to receive(:exists?).at_least(:once).and_return(false) - expect(File).to receive(:directory?).with("/tmp").and_return(true) - expect(Dir).to receive(:mkdir).with(@new_resource.path).once.and_return(true) + it "raises an exception when recursive is false" do + new_resource.recursive false + expect { directory.run_action(:create) }.to raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist) + end - expect(@directory).to receive(:do_acl_changes) - allow(@directory).to receive(:do_selinux) - @directory.run_action(:create) - expect(@directory.new_resource).to be_updated - end + it "creates the directories when recursive is true" do + new_resource.recursive true + directory.run_action(:create) + expect(new_resource).to be_updated + expect(File.exist?("#{tmp_dir}/foobar")).to be true + end - it "should raise an exception if the parent directory does not exist and recursive is false" do - @new_resource.path "/tmp/some/dir" - @new_resource.recursive false - expect { @directory.run_action(:create) }.to raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist) - end + it "raises an exception when the parent directory is a file and recursive is true" do + FileUtils.touch tmp_dir + new_resource.recursive true + expect { directory.run_action(:create) }.to raise_error + end - # Unix only for now. While file security attribute reporting for windows is - # disabled, unix and windows differ in the number of exists? calls that are - # made by the provider. - it "should create a new directory when parent directory does not exist if recursive is true and permissions are correct", :unix_only do - @new_resource.path "/path/to/dir" - @new_resource.recursive true - expect(File).to receive(:exists?).with(@new_resource.path).ordered.and_return(false) - - expect(File).to receive(:exists?).with('/path/to').ordered.and_return(false) - expect(File).to receive(:exists?).with('/path').ordered.and_return(true) - expect(Chef::FileAccessControl).to receive(:writable?).with('/path').ordered.and_return(true) - expect(File).to receive(:exists?).with(@new_resource.path).ordered.and_return(false) - - expect(FileUtils).to receive(:mkdir_p).with(@new_resource.path).and_return(true) - expect(@directory).to receive(:do_acl_changes) - allow(@directory).to receive(:do_selinux) - @directory.run_action(:create) - expect(@new_resource).to be_updated + it "raises the right exception when the parent directory is a file and recursive is true" do + pending "this seems to return the wrong error" # FIXME + FileUtils.touch tmp_dir + new_resource.recursive true + expect { directory.run_action(:create) }.to raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist) + end + end end + describe "#run_action(:create)" do + describe "when the directory exists" do + it "deletes the directory" do + directory.run_action(:delete) + expect(File.exist?(tmp_dir)).to be false + end - it "should raise an error when creating a directory when parent directory is a file" do - expect(File).to receive(:directory?).and_return(false) - expect(Dir).not_to receive(:mkdir).with(@new_resource.path) - expect { @directory.run_action(:create) }.to raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist) - expect(@directory.new_resource).not_to be_updated - end + it "sets the new resource as updated" do + directory.run_action(:delete) + expect(new_resource).to be_updated + end + end - # Unix only for now. While file security attribute reporting for windows is - # disabled, unix and windows differ in the number of exists? calls that are - # made by the provider. - it "should not create the directory if it already exists", :unix_only do - stub_file_cstats - @new_resource.path "/tmp/foo" - expect(File).to receive(:directory?).at_least(:once).and_return(true) - expect(Chef::FileAccessControl).to receive(:writable?).with("/tmp").and_return(true) - expect(File).to receive(:exists?).at_least(:once).and_return(true) - expect(Dir).not_to receive(:mkdir).with(@new_resource.path) - expect(@directory).to receive(:do_acl_changes) - @directory.run_action(:create) - end + describe "when the directory does not exist" do + before do + FileUtils.rmdir tmp_dir + end - it "should delete the directory if it exists, and is writable with action_delete" do - expect(File).to receive(:directory?).and_return(true) - expect(Chef::FileAccessControl).to receive(:writable?).once.and_return(true) - expect(Dir).to receive(:delete).with(@new_resource.path).once.and_return(true) - @directory.run_action(:delete) - end + it "does not delete the directory" do + expect(Dir).not_to receive(:delete).with(new_resource.path) + directory.run_action(:delete) + end - it "should raise an exception if it cannot delete the directory due to bad permissions" do - allow(File).to receive(:exists?).and_return(true) - allow(Chef::FileAccessControl).to receive(:writable?).and_return(false) - expect { @directory.run_action(:delete) }.to raise_error(RuntimeError) - end + it "sets the new resource as updated" do + directory.run_action(:delete) + expect(new_resource).not_to be_updated + end + end - it "should take no action when deleting a target directory that does not exist" do - @new_resource.path "/an/invalid/path" - allow(File).to receive(:exists?).and_return(false) - expect(Dir).not_to receive(:delete).with(@new_resource.path) - @directory.run_action(:delete) - expect(@directory.new_resource).not_to be_updated - end + describe "when the directory is not writable" do + before do + allow(Chef::FileAccessControl).to receive(:writable?).and_return(false) + end - it "should raise an exception when deleting a directory when target directory is a file" do - stub_file_cstats - @new_resource.path "/an/invalid/path" - allow(File).to receive(:exists?).and_return(true) - expect(File).to receive(:directory?).and_return(false) - expect(Dir).not_to receive(:delete).with(@new_resource.path) - expect { @directory.run_action(:delete) }.to raise_error(RuntimeError) - expect(@directory.new_resource).not_to be_updated - end + it "cannot delete it and raises an exception" do + expect { directory.run_action(:delete) }.to raise_error(RuntimeError) + end + end + + describe "when the target directory is a file" do + before do + FileUtils.rmdir tmp_dir + FileUtils.touch tmp_dir + end - def stub_file_cstats - cstats = double("stats") - allow(cstats).to receive(:uid).and_return(500) - allow(cstats).to receive(:gid).and_return(500) - allow(cstats).to receive(:mode).and_return(0755) - # File.stat is called in: - # - Chef::Provider::File.load_current_resource_attrs - # - Chef::ScanAccessControl via Chef::Provider::File.setup_acl - allow(File).to receive(:stat).and_return(cstats) + it "cannot delete it and raises an exception" do + expect { directory.run_action(:delete) }.to raise_error(RuntimeError) + end + end end end diff --git a/spec/unit/provider/execute_spec.rb b/spec/unit/provider/execute_spec.rb index 51305b6225..1274203ce3 100644 --- a/spec/unit/provider/execute_spec.rb +++ b/spec/unit/provider/execute_spec.rb @@ -39,7 +39,7 @@ describe Chef::Provider::Execute do let(:new_resource) { Chef::Resource::Execute.new("foo_resource", run_context) } before do - allow(Chef::Platform).to receive(:windows?) { false } + allow(ChefConfig).to receive(:windows?) { false } @original_log_level = Chef::Log.level Chef::Log.level = :info allow(STDOUT).to receive(:tty?).and_return(true) diff --git a/spec/unit/provider/ifconfig/debian_spec.rb b/spec/unit/provider/ifconfig/debian_spec.rb index 351e734040..0c02ae9cd4 100644 --- a/spec/unit/provider/ifconfig/debian_spec.rb +++ b/spec/unit/provider/ifconfig/debian_spec.rb @@ -144,11 +144,6 @@ EOF expect(IO.read(tempfile.path)).to eq(expected_string) end - it "should not mark the resource as updated" do - provider.run_action(:add) - pending "superclass ifconfig provider is not idempotent" - expect(new_resource.updated_by_last_action?).to be_falsey - end end context "when the /etc/network/interfaces file does not have the source line" do @@ -280,11 +275,6 @@ another line expect(IO.read(tempfile.path)).to eq(expected_string) end - it "should not mark the resource as updated" do - provider.run_action(:add) - pending "superclass ifconfig provider is not idempotent" - expect(new_resource.updated_by_last_action?).to be_falsey - end end context "when the /etc/network/interfaces file does not have the source line" do diff --git a/spec/unit/provider/package/aix_spec.rb b/spec/unit/provider/package/aix_spec.rb index 5bc861b849..13992cb8d1 100644 --- a/spec/unit/provider/package/aix_spec.rb +++ b/spec/unit/provider/package/aix_spec.rb @@ -36,23 +36,27 @@ describe Chef::Provider::Package::Aix do @bffinfo ="/usr/lib/objrepos:samba.base:3.3.12.0::COMMITTED:I:Samba for AIX: /etc/objrepos:samba.base:3.3.12.0::COMMITTED:I:Samba for AIX:" - @status = double("Status", :stdout => "", :exitstatus => 0) + @empty_status = double("Status", :stdout => "", :exitstatus => 0) end it "should create a current resource with the name of new_resource" do - allow(@provider).to receive(:shell_out).and_return(@status) + status = double("Status", :stdout => @bffinfo, :exitstatus => 0) + expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status) + expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base", timeout: 900).and_return(@empty_status) @provider.load_current_resource expect(@provider.current_resource.name).to eq("samba.base") end it "should set the current resource bff package name to the new resource bff package name" do - allow(@provider).to receive(:shell_out).and_return(@status) + status = double("Status", :stdout => @bffinfo, :exitstatus => 0) + expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status) + expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base", timeout: 900).and_return(@empty_status) @provider.load_current_resource expect(@provider.current_resource.package_name).to eq("samba.base") end it "should raise an exception if a source is supplied but not found" do - allow(@provider).to receive(:shell_out).and_return(@status) + allow(@provider).to receive(:shell_out).and_return(@empty_status) allow(::File).to receive(:exists?).and_return(false) @provider.load_current_resource @provider.define_resource_requirements @@ -61,8 +65,8 @@ describe Chef::Provider::Package::Aix do it "should get the source package version from lslpp if provided" do status = double("Status", :stdout => @bffinfo, :exitstatus => 0) - expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base").and_return(status) - expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base").and_return(@status) + expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status) + expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base", timeout: 900).and_return(@empty_status) @provider.load_current_resource expect(@provider.current_resource.package_name).to eq("samba.base") @@ -73,8 +77,8 @@ describe Chef::Provider::Package::Aix do status = double("Status", :stdout => @bffinfo, :exitstatus => 0) @stdout = StringIO.new(@bffinfo) @stdin, @stderr = StringIO.new, StringIO.new - expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base").and_return(@status) - expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base").and_return(status) + expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status) + expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base", timeout: 900).and_return(status) @provider.load_current_resource expect(@provider.current_resource.version).to eq("3.3.12.0") end @@ -94,12 +98,20 @@ describe Chef::Provider::Package::Aix do end it "should return a current resource with a nil version if the package is not found" do - status = double(:stdout => "", :exitstatus => 0) - expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base").and_return(status) - expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base").and_return(status) + status = double("Status", :stdout => @bffinfo, :exitstatus => 0) + expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status) + expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base", timeout: 900).and_return(@empty_status) @provider.load_current_resource expect(@provider.current_resource.version).to be_nil end + + it "should raise an exception if the source doesn't provide the requested package" do + wrongbffinfo = "/usr/lib/objrepos:openssl.base:0.9.8.2400::COMMITTED:I:Open Secure Socket Layer: +/etc/objrepos:openssl.base:0.9.8.2400::COMMITTED:I:Open Secure Socket Layer:" + status = double("Status", :stdout => wrongbffinfo, :exitstatus => 0) + expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status) + expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Package) + end end describe "candidate_version" do @@ -125,7 +137,7 @@ describe Chef::Provider::Package::Aix do describe "install and upgrade" do it "should run installp -aYF -d with the package source to install" do - expect(@provider).to receive(:shell_out!).with("installp -aYF -d /tmp/samba.base samba.base") + expect(@provider).to receive(:shell_out!).with("installp -aYF -d /tmp/samba.base samba.base", timeout: 900) @provider.install_package("samba.base", "3.3.12.0") end @@ -133,26 +145,26 @@ describe Chef::Provider::Package::Aix do @new_resource = Chef::Resource::Package.new("/tmp/samba.base") @provider = Chef::Provider::Package::Aix.new(@new_resource, @run_context) expect(@new_resource.source).to eq("/tmp/samba.base") - expect(@provider).to receive(:shell_out!).with("installp -aYF -d /tmp/samba.base /tmp/samba.base") + expect(@provider).to receive(:shell_out!).with("installp -aYF -d /tmp/samba.base /tmp/samba.base", timeout: 900) @provider.install_package("/tmp/samba.base", "3.3.12.0") end it "should run installp with -eLogfile option." do allow(@new_resource).to receive(:options).and_return("-e/tmp/installp.log") - expect(@provider).to receive(:shell_out!).with("installp -aYF -e/tmp/installp.log -d /tmp/samba.base samba.base") + expect(@provider).to receive(:shell_out!).with("installp -aYF -e/tmp/installp.log -d /tmp/samba.base samba.base", timeout: 900) @provider.install_package("samba.base", "3.3.12.0") end end describe "remove" do it "should run installp -u samba.base to remove the package" do - expect(@provider).to receive(:shell_out!).with("installp -u samba.base") + expect(@provider).to receive(:shell_out!).with("installp -u samba.base", timeout: 900) @provider.remove_package("samba.base", "3.3.12.0") end it "should run installp -u -e/tmp/installp.log with options -e/tmp/installp.log" do allow(@new_resource).to receive(:options).and_return("-e/tmp/installp.log") - expect(@provider).to receive(:shell_out!).with("installp -u -e/tmp/installp.log samba.base") + expect(@provider).to receive(:shell_out!).with("installp -u -e/tmp/installp.log samba.base", timeout: 900) @provider.remove_package("samba.base", "3.3.12.0") end diff --git a/spec/unit/provider/package/dpkg_spec.rb b/spec/unit/provider/package/dpkg_spec.rb index 3fd86218d2..4974cff934 100644 --- a/spec/unit/provider/package/dpkg_spec.rb +++ b/spec/unit/provider/package/dpkg_spec.rb @@ -51,7 +51,7 @@ describe Chef::Provider::Package::Dpkg do describe 'gets the source package version from dpkg-deb' do def check_version(version) @status = double(:stdout => "wget\t#{version}", :exitstatus => 0) - allow(@provider).to receive(:shell_out).with("dpkg-deb -W #{@new_resource.source}").and_return(@status) + allow(@provider).to receive(:shell_out).with("dpkg-deb -W #{@new_resource.source}", timeout: 900).and_return(@status) @provider.load_current_resource expect(@provider.current_resource.package_name).to eq("wget") expect(@new_resource.version).to eq(version) @@ -106,7 +106,7 @@ Depends: libc6 (>= 2.8~20080505), libssl0.9.8 (>= 0.9.8f-5) Conflicts: wget-ssl DPKG_S status = double(:stdout => stdout, :exitstatus => 1) - allow(@provider).to receive(:shell_out).with("dpkg -s wget").and_return(status) + allow(@provider).to receive(:shell_out).with("dpkg -s wget", timeout: 900).and_return(status) @provider.load_current_resource expect(@provider.current_resource.version).to eq("1.11.4-1ubuntu1") diff --git a/spec/unit/provider/package/freebsd/pkg_spec.rb b/spec/unit/provider/package/freebsd/pkg_spec.rb index f67161930f..d1f5a649bc 100644 --- a/spec/unit/provider/package/freebsd/pkg_spec.rb +++ b/spec/unit/provider/package/freebsd/pkg_spec.rb @@ -77,7 +77,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do it "should return the version number when it is installed" do pkg_info = OpenStruct.new(:stdout => "zsh-4.3.6_7") - expect(@provider).to receive(:shell_out!).with('pkg_info -E "zsh*"', :env => nil, :returns => [0,1]).and_return(pkg_info) + expect(@provider).to receive(:shell_out!).with('pkg_info -E "zsh*"', env: nil, returns: [0,1], timeout: 900).and_return(pkg_info) #@provider.should_receive(:popen4).with('pkg_info -E "zsh*"').and_yield(@pid, @stdin, ["zsh-4.3.6_7"], @stderr).and_return(@status) allow(@provider).to receive(:package_name).and_return("zsh") expect(@provider.current_installed_version).to eq("4.3.6_7") @@ -85,14 +85,14 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do it "does not set the current version number when the package is not installed" do pkg_info = OpenStruct.new(:stdout => "") - expect(@provider).to receive(:shell_out!).with('pkg_info -E "zsh*"', :env => nil, :returns => [0,1]).and_return(pkg_info) + expect(@provider).to receive(:shell_out!).with('pkg_info -E "zsh*"', env: nil, returns: [0,1], timeout: 900).and_return(pkg_info) allow(@provider).to receive(:package_name).and_return("zsh") expect(@provider.current_installed_version).to be_nil end it "should return the port path for a valid port name" do whereis = OpenStruct.new(:stdout => "zsh: /usr/ports/shells/zsh") - expect(@provider).to receive(:shell_out!).with("whereis -s zsh", :env => nil).and_return(whereis) + expect(@provider).to receive(:shell_out!).with("whereis -s zsh", env: nil, timeout: 900).and_return(whereis) #@provider.should_receive(:popen4).with("whereis -s zsh").and_yield(@pid, @stdin, ["zsh: /usr/ports/shells/zsh"], @stderr).and_return(@status) allow(@provider).to receive(:port_name).and_return("zsh") expect(@provider.port_path).to eq("/usr/ports/shells/zsh") @@ -102,7 +102,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do it "should return the ports candidate version when given a valid port path" do allow(@provider).to receive(:port_path).and_return("/usr/ports/shells/zsh") make_v = OpenStruct.new(:stdout => "4.3.6\n", :exitstatus => 0) - expect(@provider).to receive(:shell_out!).with("make -V PORTVERSION", {:cwd=>"/usr/ports/shells/zsh", :returns=>[0, 1], :env=>nil}).and_return(make_v) + expect(@provider).to receive(:shell_out!).with("make -V PORTVERSION", {cwd: "/usr/ports/shells/zsh", returns: [0, 1], env: nil, timeout: 900}).and_return(make_v) expect(@provider.ports_candidate_version).to eq("4.3.6") end @@ -110,7 +110,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do allow(::File).to receive(:exist?).with('/usr/ports/Makefile').and_return(true) allow(@provider).to receive(:port_path).and_return("/usr/ports/shells/zsh") make_v = OpenStruct.new(:stdout => "zsh-4.3.6_7\n", :exitstatus => 0) - expect(@provider).to receive(:shell_out!).with("make -V PKGNAME", {:cwd=>"/usr/ports/shells/zsh", :env=>nil, :returns=>[0, 1]}).and_return(make_v) + expect(@provider).to receive(:shell_out!).with("make -V PKGNAME", {cwd: "/usr/ports/shells/zsh", env: nil, returns: [0, 1], timeout: 900}).and_return(make_v) #@provider.should_receive(:ports_makefile_variable_value).with("PKGNAME").and_return("zsh-4.3.6_7") expect(@provider.package_name).to eq("zsh") end @@ -127,7 +127,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do end it "should run pkg_add -r with the package name" do - expect(@provider).to receive(:shell_out!).with("pkg_add -r zsh", :env => nil).and_return(@cmd_result) + expect(@provider).to receive(:shell_out!).with("pkg_add -r zsh", env: nil, timeout: 900).and_return(@cmd_result) @provider.install_package("zsh", "4.3.6_7") end end @@ -142,7 +142,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do it "should figure out the port path from the package_name using whereis" do whereis = OpenStruct.new(:stdout => "zsh: /usr/ports/shells/zsh") - expect(@provider).to receive(:shell_out!).with("whereis -s zsh", :env=>nil).and_return(whereis) + expect(@provider).to receive(:shell_out!).with("whereis -s zsh", env: nil, timeout: 900).and_return(whereis) expect(@provider.port_path).to eq("/usr/ports/shells/zsh") end @@ -178,7 +178,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do end it "should run pkg_add -r with the package name" do - expect(@provider).to receive(:shell_out!).with("pkg_add -r ruby18-iconv", :env => nil).and_return(@install_result) + expect(@provider).to receive(:shell_out!).with("pkg_add -r ruby18-iconv", env: nil, timeout: 900).and_return(@install_result) @provider.install_package("ruby-iconv", "1.0") end end @@ -193,7 +193,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do end it "should run pkg_delete with the package name and version" do - expect(@provider).to receive(:shell_out!).with("pkg_delete zsh-4.3.6_7", :env => nil).and_return(@pkg_delete) + expect(@provider).to receive(:shell_out!).with("pkg_delete zsh-4.3.6_7", env: nil, timeout: 900).and_return(@pkg_delete) @provider.remove_package("zsh", "4.3.6_7") end end @@ -213,14 +213,14 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do it "should return the port path for a valid port name" do whereis = OpenStruct.new(:stdout => "bonnie++: /usr/ports/benchmarks/bonnie++") - expect(@provider).to receive(:shell_out!).with("whereis -s bonnie++", :env => nil).and_return(whereis) + expect(@provider).to receive(:shell_out!).with("whereis -s bonnie++", env: nil, timeout: 900).and_return(whereis) allow(@provider).to receive(:port_name).and_return("bonnie++") expect(@provider.port_path).to eq("/usr/ports/benchmarks/bonnie++") end it "should return the version number when it is installed" do pkg_info = OpenStruct.new(:stdout => "bonnie++-1.96") - expect(@provider).to receive(:shell_out!).with('pkg_info -E "bonnie++*"', :env => nil, :returns => [0,1]).and_return(pkg_info) + expect(@provider).to receive(:shell_out!).with('pkg_info -E "bonnie++*"', env: nil, returns: [0,1], timeout: 900).and_return(pkg_info) allow(@provider).to receive(:package_name).and_return("bonnie++") expect(@provider.current_installed_version).to eq("1.96") end @@ -253,7 +253,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do allow(@provider).to receive(:latest_link_name).and_return("perl") cmd = OpenStruct.new(:status => true) - expect(@provider).to receive(:shell_out!).with("pkg_add -r perl", :env => nil).and_return(cmd) + expect(@provider).to receive(:shell_out!).with("pkg_add -r perl", env: nil, timeout: 900).and_return(cmd) @provider.install_package("perl5.8", "5.8.8_1") end @@ -267,7 +267,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do allow(@provider).to receive(:latest_link_name).and_return("mysql50-server") cmd = OpenStruct.new(:status => true) - expect(@provider).to receive(:shell_out!).with("pkg_add -r mysql50-server", :env=>nil).and_return(cmd) + expect(@provider).to receive(:shell_out!).with("pkg_add -r mysql50-server", env: nil, timeout: 900).and_return(cmd) @provider.install_package("mysql50-server", "5.0.45_1") end end diff --git a/spec/unit/provider/package/freebsd/pkgng_spec.rb b/spec/unit/provider/package/freebsd/pkgng_spec.rb index 0c1e89c7ab..59215f855b 100644 --- a/spec/unit/provider/package/freebsd/pkgng_spec.rb +++ b/spec/unit/provider/package/freebsd/pkgng_spec.rb @@ -71,7 +71,7 @@ describe Chef::Provider::Package::Freebsd::Port do end it "should query pkg database" do - expect(@provider).to receive(:shell_out!).with('pkg info "zsh"', :env => nil, :returns => [0,70]).and_return(@pkg_info) + expect(@provider).to receive(:shell_out!).with('pkg info "zsh"', env: nil, returns: [0,70], timeout: 900).and_return(@pkg_info) expect(@provider.current_installed_version).to eq("3.1.7") end end @@ -80,14 +80,14 @@ describe Chef::Provider::Package::Freebsd::Port do describe "determining candidate version" do it "should query repository" do pkg_query = OpenStruct.new(:stdout => "5.0.5\n", :exitstatus => 0) - expect(@provider).to receive(:shell_out!).with("pkg rquery '%v' zsh", :env => nil).and_return(pkg_query) + expect(@provider).to receive(:shell_out!).with("pkg rquery '%v' zsh", env: nil, timeout: 900).and_return(pkg_query) expect(@provider.candidate_version).to eq("5.0.5") end it "should query specified repository when given option" do @provider.new_resource.options('-r LocalMirror') # This requires LocalMirror repo configuration. pkg_query = OpenStruct.new(:stdout => "5.0.3\n", :exitstatus => 0) - expect(@provider).to receive(:shell_out!).with("pkg rquery -r LocalMirror '%v' zsh", :env => nil).and_return(pkg_query) + expect(@provider).to receive(:shell_out!).with("pkg rquery -r LocalMirror '%v' zsh", env: nil, timeout: 900).and_return(pkg_query) expect(@provider.candidate_version).to eq("5.0.3") end @@ -106,7 +106,7 @@ describe Chef::Provider::Package::Freebsd::Port do it "should handle package source from file" do @provider.new_resource.source("/nas/pkg/repo/zsh-5.0.1.txz") expect(@provider).to receive(:shell_out!). - with("pkg add /nas/pkg/repo/zsh-5.0.1.txz", :env => { 'LC_ALL' => nil }). + with("pkg add /nas/pkg/repo/zsh-5.0.1.txz", env: { 'LC_ALL' => nil }, timeout: 900). and_return(@install_result) @provider.install_package("zsh", "5.0.1") end @@ -114,21 +114,21 @@ describe Chef::Provider::Package::Freebsd::Port do it "should handle package source over ftp or http" do @provider.new_resource.source("http://repo.example.com/zsh-5.0.1.txz") expect(@provider).to receive(:shell_out!). - with("pkg add http://repo.example.com/zsh-5.0.1.txz", :env => { 'LC_ALL' => nil }). + with("pkg add http://repo.example.com/zsh-5.0.1.txz", env: { 'LC_ALL' => nil }, timeout: 900). and_return(@install_result) @provider.install_package("zsh", "5.0.1") end it "should handle a package name" do expect(@provider).to receive(:shell_out!). - with("pkg install -y zsh", :env => { 'LC_ALL' => nil }).and_return(@install_result) + with("pkg install -y zsh", env: { 'LC_ALL' => nil }, timeout: 900).and_return(@install_result) @provider.install_package("zsh", "5.0.1") end it "should handle a package name with a specified repo" do @provider.new_resource.options('-r LocalMirror') # This requires LocalMirror repo configuration. expect(@provider).to receive(:shell_out!). - with("pkg install -y -r LocalMirror zsh", :env => { 'LC_ALL' => nil }).and_return(@install_result) + with("pkg install -y -r LocalMirror zsh", env: { 'LC_ALL' => nil }, timeout: 900).and_return(@install_result) @provider.install_package("zsh", "5.0.1") end end @@ -141,14 +141,14 @@ describe Chef::Provider::Package::Freebsd::Port do it "should call pkg delete" do expect(@provider).to receive(:shell_out!). - with("pkg delete -y zsh-5.0.1", :env => nil).and_return(@install_result) + with("pkg delete -y zsh-5.0.1", env: nil, timeout: 900).and_return(@install_result) @provider.remove_package("zsh", "5.0.1") end it "should not include repo option in pkg delete" do @provider.new_resource.options('-r LocalMirror') # This requires LocalMirror repo configuration. expect(@provider).to receive(:shell_out!). - with("pkg delete -y zsh-5.0.1", :env => nil).and_return(@install_result) + with("pkg delete -y zsh-5.0.1", env: nil, timeout: 900).and_return(@install_result) @provider.remove_package("zsh", "5.0.1") end end diff --git a/spec/unit/provider/package/freebsd/port_spec.rb b/spec/unit/provider/package/freebsd/port_spec.rb index 2e32e88f97..4b23575740 100644 --- a/spec/unit/provider/package/freebsd/port_spec.rb +++ b/spec/unit/provider/package/freebsd/port_spec.rb @@ -72,7 +72,7 @@ describe Chef::Provider::Package::Freebsd::Port do it "should check 'pkg_info' if system uses pkg_* tools" do allow(@new_resource).to receive(:supports_pkgng?) expect(@new_resource).to receive(:supports_pkgng?).and_return(false) - expect(@provider).to receive(:shell_out!).with('pkg_info -E "zsh*"', :env => nil, :returns => [0,1]).and_return(@pkg_info) + expect(@provider).to receive(:shell_out!).with('pkg_info -E "zsh*"', env: nil, returns: [0,1], timeout: 900).and_return(@pkg_info) expect(@provider.current_installed_version).to eq("3.1.7") end @@ -80,8 +80,8 @@ describe Chef::Provider::Package::Freebsd::Port do pkg_enabled = OpenStruct.new(:stdout => "yes\n") [1000016, 1000000, 901503, 902506, 802511].each do |__freebsd_version| @node.automatic_attrs[:os_version] = __freebsd_version - expect(@new_resource).to receive(:shell_out!).with('make -V WITH_PKGNG', :env => nil).and_return(pkg_enabled) - expect(@provider).to receive(:shell_out!).with('pkg info "zsh"', :env => nil, :returns => [0,70]).and_return(@pkg_info) + expect(@new_resource).to receive(:shell_out!).with('make -V WITH_PKGNG', env: nil).and_return(pkg_enabled) + expect(@provider).to receive(:shell_out!).with('pkg info "zsh"', env: nil, returns: [0,70], timeout: 900).and_return(@pkg_info) expect(@provider.current_installed_version).to eq("3.1.7") end end @@ -89,7 +89,7 @@ describe Chef::Provider::Package::Freebsd::Port do it "should check 'pkg info' if the freebsd version is greater than or equal to 1000017" do __freebsd_version = 1000017 @node.automatic_attrs[:os_version] = __freebsd_version - expect(@provider).to receive(:shell_out!).with('pkg info "zsh"', :env => nil, :returns => [0,70]).and_return(@pkg_info) + expect(@provider).to receive(:shell_out!).with('pkg info "zsh"', env: nil, returns: [0,70], timeout: 900).and_return(@pkg_info) expect(@provider.current_installed_version).to eq("3.1.7") end end @@ -102,7 +102,7 @@ describe Chef::Provider::Package::Freebsd::Port do it "should return candidate version if port exists" do allow(::File).to receive(:exist?).with('/usr/ports/Makefile').and_return(true) allow(@provider).to receive(:port_dir).and_return('/usr/ports/shells/zsh') - expect(@provider).to receive(:shell_out!).with("make -V PORTVERSION", :cwd => "/usr/ports/shells/zsh", :env => nil, :returns => [0,1]). + expect(@provider).to receive(:shell_out!).with("make -V PORTVERSION", cwd: "/usr/ports/shells/zsh", env: nil, returns: [0,1], timeout: 900). and_return(@port_version) expect(@provider.candidate_version).to eq("5.0.5") end @@ -127,13 +127,13 @@ describe Chef::Provider::Package::Freebsd::Port do it "should query system for path given just a name" do whereis = OpenStruct.new(:stdout => "zsh: /usr/ports/shells/zsh\n") - expect(@provider).to receive(:shell_out!).with("whereis -s zsh", :env => nil).and_return(whereis) + expect(@provider).to receive(:shell_out!).with("whereis -s zsh", env: nil, timeout: 900).and_return(whereis) expect(@provider.port_dir).to eq("/usr/ports/shells/zsh") end it "should raise exception if not found" do whereis = OpenStruct.new(:stdout => "zsh:\n") - expect(@provider).to receive(:shell_out!).with("whereis -s zsh", :env => nil).and_return(whereis) + expect(@provider).to receive(:shell_out!).with("whereis -s zsh", env: nil, timeout: 900).and_return(whereis) expect { @provider.port_dir }.to raise_error(Chef::Exceptions::Package, "Could not find port with the name zsh") end end diff --git a/spec/unit/provider/package/ips_spec.rb b/spec/unit/provider/package/ips_spec.rb index 342ac4c040..ad69dffb10 100644 --- a/spec/unit/provider/package/ips_spec.rb +++ b/spec/unit/provider/package/ips_spec.rb @@ -65,28 +65,28 @@ PKG_STATUS context "when loading current resource" do it "should create a current resource with the name of the new_resource" do - expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local_output) - expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote_output) + expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output) + expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output) expect(Chef::Resource::Package).to receive(:new).and_return(@current_resource) @provider.load_current_resource end it "should set the current resources package name to the new resources package name" do - expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local_output) - expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote_output) + expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output) + expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output) @provider.load_current_resource expect(@current_resource.package_name).to eq(@new_resource.package_name) end it "should run pkg info with the package name" do - expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local_output) - expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote_output) + expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output) + expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output) @provider.load_current_resource end it "should set the installed version to nil on the current resource if package state is not installed" do - expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local_output) - expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote_output) + expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output) + expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output) @provider.load_current_resource expect(@current_resource.version).to be_nil end @@ -108,27 +108,27 @@ Packaging Date: October 19, 2011 09:14:50 AM Size: 8.07 MB FMRI: pkg://solaris/crypto/gnupg@2.0.17,5.11-0.175.0.0.0.2.537:20111019T091450Z INSTALLED - expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local) - expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote_output) + expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local) + expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output) @provider.load_current_resource expect(@current_resource.version).to eq("2.0.17") end it "should return the current resource" do - expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local_output) - expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote_output) + expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output) + expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output) expect(@provider.load_current_resource).to eql(@current_resource) end end context "when installing a package" do it "should run pkg install with the package name and version" do - expect(@provider).to receive(:shell_out).with("pkg install -q crypto/gnupg@2.0.17") + expect(@provider).to receive(:shell_out).with("pkg install -q crypto/gnupg@2.0.17", timeout: 900) @provider.install_package("crypto/gnupg", "2.0.17") end it "should run pkg install with the package name and version and options if specified" do - expect(@provider).to receive(:shell_out).with("pkg --no-refresh install -q crypto/gnupg@2.0.17") + expect(@provider).to receive(:shell_out).with("pkg --no-refresh install -q crypto/gnupg@2.0.17", timeout: 900) allow(@new_resource).to receive(:options).and_return("--no-refresh") @provider.install_package("crypto/gnupg", "2.0.17") end @@ -147,8 +147,8 @@ Packaging Date: April 1, 2012 05:55:52 PM Size: 2.57 MB FMRI: pkg://omnios/security/sudo@1.8.4.1,5.11-0.151002:20120401T175552Z PKG_STATUS - expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local_output) - expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote) + expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output) + expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote) @provider.load_current_resource expect(@current_resource.version).to be_nil expect(@provider.candidate_version).to eql("1.8.4.1") @@ -188,8 +188,8 @@ Packaging Date: October 19, 2011 09:14:50 AM FMRI: pkg://solaris/crypto/gnupg@2.0.18,5.11-0.175.0.0.0.2.537:20111019T091450Z REMOTE - expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local) - expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote) + expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local) + expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote) expect(@provider).to receive(:install_package).exactly(0).times @provider.run_action(:install) end @@ -200,7 +200,7 @@ REMOTE end it "should run pkg install with the --accept flag" do - expect(@provider).to receive(:shell_out).with("pkg install -q --accept crypto/gnupg@2.0.17") + expect(@provider).to receive(:shell_out).with("pkg install -q --accept crypto/gnupg@2.0.17", timeout: 900) @provider.install_package("crypto/gnupg", "2.0.17") end end @@ -208,19 +208,19 @@ REMOTE context "when upgrading a package" do it "should run pkg install with the package name and version" do - expect(@provider).to receive(:shell_out).with("pkg install -q crypto/gnupg@2.0.17") + expect(@provider).to receive(:shell_out).with("pkg install -q crypto/gnupg@2.0.17", timeout: 900) @provider.upgrade_package("crypto/gnupg", "2.0.17") end end context "when uninstalling a package" do it "should run pkg uninstall with the package name and version" do - expect(@provider).to receive(:shell_out!).with("pkg uninstall -q crypto/gnupg@2.0.17") + expect(@provider).to receive(:shell_out!).with("pkg uninstall -q crypto/gnupg@2.0.17", timeout: 900) @provider.remove_package("crypto/gnupg", "2.0.17") end it "should run pkg uninstall with the package name and version and options if specified" do - expect(@provider).to receive(:shell_out!).with("pkg --no-refresh uninstall -q crypto/gnupg@2.0.17") + expect(@provider).to receive(:shell_out!).with("pkg --no-refresh uninstall -q crypto/gnupg@2.0.17", timeout: 900) allow(@new_resource).to receive(:options).and_return("--no-refresh") @provider.remove_package("crypto/gnupg", "2.0.17") end diff --git a/spec/unit/provider/package/macports_spec.rb b/spec/unit/provider/package/macports_spec.rb index 9822fb3928..eef84113b4 100644 --- a/spec/unit/provider/package/macports_spec.rb +++ b/spec/unit/provider/package/macports_spec.rb @@ -105,7 +105,7 @@ EOF it "should run the port install command with the correct version" do expect(@current_resource).to receive(:version).and_return("4.1.6") @provider.current_resource = @current_resource - expect(@provider).to receive(:shell_out!).with("port install zsh @4.2.7") + expect(@provider).to receive(:shell_out!).with("port install zsh @4.2.7", timeout: 900) @provider.install_package("zsh", "4.2.7") end @@ -122,7 +122,7 @@ EOF expect(@current_resource).to receive(:version).and_return("4.1.6") @provider.current_resource = @current_resource allow(@new_resource).to receive(:options).and_return("-f") - expect(@provider).to receive(:shell_out!).with("port -f install zsh @4.2.7") + expect(@provider).to receive(:shell_out!).with("port -f install zsh @4.2.7", timeout: 900) @provider.install_package("zsh", "4.2.7") end @@ -130,36 +130,36 @@ EOF describe "purge_package" do it "should run the port uninstall command with the correct version" do - expect(@provider).to receive(:shell_out!).with("port uninstall zsh @4.2.7") + expect(@provider).to receive(:shell_out!).with("port uninstall zsh @4.2.7", timeout: 900) @provider.purge_package("zsh", "4.2.7") end it "should purge the currently active version if no explicit version is passed in" do - expect(@provider).to receive(:shell_out!).with("port uninstall zsh") + expect(@provider).to receive(:shell_out!).with("port uninstall zsh", timeout: 900) @provider.purge_package("zsh", nil) end it "should add options to the port command when specified" do allow(@new_resource).to receive(:options).and_return("-f") - expect(@provider).to receive(:shell_out!).with("port -f uninstall zsh @4.2.7") + expect(@provider).to receive(:shell_out!).with("port -f uninstall zsh @4.2.7", timeout: 900) @provider.purge_package("zsh", "4.2.7") end end describe "remove_package" do it "should run the port deactivate command with the correct version" do - expect(@provider).to receive(:shell_out!).with("port deactivate zsh @4.2.7") + expect(@provider).to receive(:shell_out!).with("port deactivate zsh @4.2.7", timeout: 900) @provider.remove_package("zsh", "4.2.7") end it "should remove the currently active version if no explicit version is passed in" do - expect(@provider).to receive(:shell_out!).with("port deactivate zsh") + expect(@provider).to receive(:shell_out!).with("port deactivate zsh", timeout: 900) @provider.remove_package("zsh", nil) end it "should add options to the port command when specified" do allow(@new_resource).to receive(:options).and_return("-f") - expect(@provider).to receive(:shell_out!).with("port -f deactivate zsh @4.2.7") + expect(@provider).to receive(:shell_out!).with("port -f deactivate zsh @4.2.7", timeout: 900) @provider.remove_package("zsh", "4.2.7") end end @@ -169,7 +169,7 @@ EOF expect(@current_resource).to receive(:version).at_least(:once).and_return("4.1.6") @provider.current_resource = @current_resource - expect(@provider).to receive(:shell_out!).with("port upgrade zsh @4.2.7") + expect(@provider).to receive(:shell_out!).with("port upgrade zsh @4.2.7", timeout: 900) @provider.upgrade_package("zsh", "4.2.7") end @@ -195,7 +195,7 @@ EOF expect(@current_resource).to receive(:version).at_least(:once).and_return("4.1.6") @provider.current_resource = @current_resource - expect(@provider).to receive(:shell_out!).with("port -f upgrade zsh @4.2.7") + expect(@provider).to receive(:shell_out!).with("port -f upgrade zsh @4.2.7", timeout: 900) @provider.upgrade_package("zsh", "4.2.7") end diff --git a/spec/unit/provider/package/openbsd_spec.rb b/spec/unit/provider/package/openbsd_spec.rb index b0cdb9969d..8407f83785 100644 --- a/spec/unit/provider/package/openbsd_spec.rb +++ b/spec/unit/provider/package/openbsd_spec.rb @@ -50,25 +50,13 @@ describe Chef::Provider::Package::Openbsd do context 'when there is a single candidate' do - context 'when installing from source' do - it 'should run the installation command' do - pending('Installing from source is not supported yet') - # This is a consequence of load_current_resource being called before define_resource_requirements - # It can be deleted once an implementation is provided - allow(provider).to receive(:shell_out!).with("pkg_info -I \"#{name}\"", anything()).and_return( - instance_double('shellout', :stdout => "#{name}-#{version}\n")) - new_resource.source('/some/path/on/disk.tgz') - provider.run_action(:install) - end - end - context 'when source is not provided' do it 'should run the installation command' do expect(provider).to receive(:shell_out!).with("pkg_info -I \"#{name}\"", anything()).and_return( instance_double('shellout', :stdout => "#{name}-#{version}\n")) expect(provider).to receive(:shell_out!).with( "pkg_add -r #{name}-#{version}", - {:env => {"PKG_PATH" => "http://ftp.OpenBSD.org/pub/OpenBSD/5.5/packages/amd64/"}} + {:env => {"PKG_PATH" => "http://ftp.OpenBSD.org/pub/OpenBSD/5.5/packages/amd64/"}, timeout: 900} ) {OpenStruct.new :status => true} provider.run_action(:install) end @@ -100,21 +88,12 @@ describe Chef::Provider::Package::Openbsd do instance_double('shellout', :stdout => "#{name}-#{version}-#{flavor}\n")) expect(provider).to receive(:shell_out!).with( "pkg_add -r #{name}-#{version}-#{flavor}", - {:env => {"PKG_PATH" => "http://ftp.OpenBSD.org/pub/OpenBSD/5.5/packages/amd64/"}} + {env: {"PKG_PATH" => "http://ftp.OpenBSD.org/pub/OpenBSD/5.5/packages/amd64/"}, timeout: 900} ) {OpenStruct.new :status => true} provider.run_action(:install) end end - context 'if a version is specified' do - it 'runs the installation command' do - pending('Specifying both a version and flavor is not supported') - new_resource.version(version) - allow(provider).to receive(:shell_out!).with(/pkg_info -e/, anything()).and_return(instance_double('shellout', :stdout => '')) - allow(provider).to receive(:candidate_version).and_return("#{package_name}-#{version}-#{flavor}") - provider.run_action(:install) - end - end end context 'if a version is specified' do @@ -125,7 +104,7 @@ describe Chef::Provider::Package::Openbsd do new_resource.version("#{version}-#{flavor_b}") expect(provider).to receive(:shell_out!).with( "pkg_add -r #{name}-#{version}-#{flavor_b}", - {:env => {"PKG_PATH" => "http://ftp.OpenBSD.org/pub/OpenBSD/5.5/packages/amd64/"}} + {env: {"PKG_PATH" => "http://ftp.OpenBSD.org/pub/OpenBSD/5.5/packages/amd64/"}, timeout: 900} ) {OpenStruct.new :status => true} provider.run_action(:install) end @@ -144,11 +123,10 @@ describe Chef::Provider::Package::Openbsd do end it "should run the command to delete the installed package" do expect(@provider).to receive(:shell_out!).with( - "pkg_delete #{@name}", :env=>nil + "pkg_delete #{@name}", env: nil, timeout: 900 ) {OpenStruct.new :status => true} @provider.remove_package(@name, nil) end end end - diff --git a/spec/unit/provider/package/pacman_spec.rb b/spec/unit/provider/package/pacman_spec.rb index 3b8848c41b..fcb9f8a86c 100644 --- a/spec/unit/provider/package/pacman_spec.rb +++ b/spec/unit/provider/package/pacman_spec.rb @@ -51,7 +51,7 @@ ERR end it "should run pacman query with the package name" do - expect(@provider).to receive(:shell_out).with("pacman -Qi #{@new_resource.package_name}").and_return(@status) + expect(@provider).to receive(:shell_out).with("pacman -Qi #{@new_resource.package_name}", {timeout: 900}).and_return(@status) @provider.load_current_resource end @@ -152,12 +152,12 @@ PACMAN_CONF describe Chef::Provider::Package::Pacman, "install_package" do it "should run pacman install with the package name and version" do - expect(@provider).to receive(:shell_out!).with("pacman --sync --noconfirm --noprogressbar nano") + expect(@provider).to receive(:shell_out!).with("pacman --sync --noconfirm --noprogressbar nano", {timeout: 900}) @provider.install_package("nano", "1.0") end it "should run pacman install with the package name and version and options if specified" do - expect(@provider).to receive(:shell_out!).with("pacman --sync --noconfirm --noprogressbar --debug nano") + expect(@provider).to receive(:shell_out!).with("pacman --sync --noconfirm --noprogressbar --debug nano", {timeout: 900}) allow(@new_resource).to receive(:options).and_return("--debug") @provider.install_package("nano", "1.0") @@ -173,12 +173,12 @@ PACMAN_CONF describe Chef::Provider::Package::Pacman, "remove_package" do it "should run pacman remove with the package name" do - expect(@provider).to receive(:shell_out!).with("pacman --remove --noconfirm --noprogressbar nano") + expect(@provider).to receive(:shell_out!).with("pacman --remove --noconfirm --noprogressbar nano", {timeout: 900}) @provider.remove_package("nano", "1.0") end it "should run pacman remove with the package name and options if specified" do - expect(@provider).to receive(:shell_out!).with("pacman --remove --noconfirm --noprogressbar --debug nano") + expect(@provider).to receive(:shell_out!).with("pacman --remove --noconfirm --noprogressbar --debug nano", {timeout: 900}) allow(@new_resource).to receive(:options).and_return("--debug") @provider.remove_package("nano", "1.0") diff --git a/spec/unit/provider/package/rpm_spec.rb b/spec/unit/provider/package/rpm_spec.rb index 411afd3755..e0e45d0b4f 100644 --- a/spec/unit/provider/package/rpm_spec.rb +++ b/spec/unit/provider/package/rpm_spec.rb @@ -23,183 +23,394 @@ describe Chef::Provider::Package::Rpm do let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } + + let(:package_source) { "/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" } + + let(:package_name) { "ImageMagick-c++" } + let(:new_resource) do - Chef::Resource::Package.new("ImageMagick-c++").tap do |resource| - resource.source "/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" + Chef::Resource::Package.new(package_name).tap do |resource| + resource.source(package_source) end end - let(:exitstatus) { 0 } - let(:stdout) { String.new('') } - let(:status) { double('Process::Status', exitstatus: exitstatus, stdout: stdout) } + + # `rpm -qp [stuff] $source` + let(:rpm_qp_status) { instance_double('Mixlib::ShellOut', exitstatus: rpm_qp_exitstatus, stdout: rpm_qp_stdout) } + + # `rpm -q [stuff] $package_name` + let(:rpm_q_status) { instance_double('Mixlib::ShellOut', exitstatus: rpm_q_exitstatus, stdout: rpm_q_stdout) } before(:each) do - allow(::File).to receive(:exists?).and_return(true) - allow(provider).to receive(:shell_out!).and_return(status) + allow(::File).to receive(:exists?).with("PLEASE STUB File.exists? EXACTLY").and_return(true) + + # Ensure all shell out usage is stubbed with exact arguments + allow(provider).to receive(:shell_out!).with("PLEASE STUB YOUR SHELLOUT CALLS").and_return(nil) + allow(provider).to receive(:shell_out).with("PLEASE STUB YOUR SHELLOUT CALLS").and_return(nil) end - describe "when determining the current state of the package" do - it "should create a current resource with the name of new_resource" do - provider.load_current_resource - expect(provider.current_resource.name).to eq("ImageMagick-c++") - end + describe "when the package source is not valid" do - it "should set the current reource package name to the new resource package name" do - provider.load_current_resource - expect(provider.current_resource.package_name).to eq('ImageMagick-c++') - end + context "when source is not defiend" do + let(:new_resource) { Chef::Resource::Package.new("ImageMagick-c++") } - it "should raise an exception if a source is supplied but not found" do - allow(::File).to receive(:exists?).and_return(false) - expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package) + it "should raise an exception when attempting any action" do + expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package) + end end - context "installation exists" do - let(:stdout) { "ImageMagick-c++ 6.5.4.7-7.el6_5" } + context "when the source is a file that doesn't exist" do - it "should get the source package version from rpm if provided" do - expect(provider).to receive(:shell_out!).with("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm").and_return(status) - expect(provider).to receive(:shell_out).with("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' ImageMagick-c++").and_return(status) - provider.load_current_resource - expect(provider.current_resource.package_name).to eq("ImageMagick-c++") - expect(provider.new_resource.version).to eq("6.5.4.7-7.el6_5") + it "should raise an exception when attempting any action" do + allow(::File).to receive(:exists?).with(package_source).and_return(false) + expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package) end + end - it "should return the current version installed if found by rpm" do - expect(provider).to receive(:shell_out!).with("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm").and_return(status) - expect(provider).to receive(:shell_out).with("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' ImageMagick-c++").and_return(status) - provider.load_current_resource - expect(provider.current_resource.version).to eq("6.5.4.7-7.el6_5") + context "when the source is an unsupported URI scheme" do + + let(:package_source) { "foobar://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" } + + it "should raise an exception if an uri formed source is non-supported scheme" do + allow(::File).to receive(:exists?).with(package_source).and_return(false) + + # verify let bindings are as we expect + expect(new_resource.source).to eq("foobar://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") + expect(provider.load_current_resource).to be_nil + expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package) end end - context "source is uri formed" do - before(:each) do - allow(::File).to receive(:exists?).and_return(false) + end + + describe "when the package source is valid" do + + before do + expect(provider).to receive(:shell_out!). + with("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{package_source}", timeout: 900). + and_return(rpm_qp_status) + + expect(provider).to receive(:shell_out). + with("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{package_name}", timeout: 900). + and_return(rpm_q_status) + end + + context "when rpm fails when querying package installed state" do + + before do + allow(::File).to receive(:exists?).with(package_source).and_return(true) end - %w(http HTTP https HTTPS ftp FTP).each do |scheme| - it "should accept uri formed source (#{scheme})" do - new_resource.source "#{scheme}://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" - expect(provider.load_current_resource).not_to be_nil + let(:rpm_qp_stdout) { "ImageMagick-c++ 6.5.4.7-7.el6_5" } + let(:rpm_q_stdout) { "" } + + let(:rpm_qp_exitstatus) { 0 } + let(:rpm_q_exitstatus) { -1 } + + it "raises an exception when attempting any action" do + expected_message = "Unable to determine current version due to RPM failure." + + expect { provider.run_action(:install) }.to raise_error do |error| + expect(error).to be_a_kind_of(Chef::Exceptions::Package) + expect(error.to_s).to include(expected_message) end end + end + + + context "when the package is installed" do + + let(:rpm_qp_stdout) { "ImageMagick-c++ 6.5.4.7-7.el6_5" } + let(:rpm_q_stdout) { "ImageMagick-c++ 6.5.4.7-7.el6_5" } + + let(:rpm_qp_exitstatus) { 0 } + let(:rpm_q_exitstatus) { 0 } - %w(file FILE).each do |scheme| - it "should accept uri formed source (#{scheme})" do - new_resource.source "#{scheme}:///ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" - expect(provider.load_current_resource).not_to be_nil + let(:action) { :install } + + context "when the source is a file system path" do + + before do + allow(::File).to receive(:exists?).with(package_source).and_return(true) + + provider.action = action + + provider.load_current_resource + provider.define_resource_requirements + provider.process_resource_requirements end - end - it "should raise an exception if an uri formed source is non-supported scheme" do - new_resource.source "foobar://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" - expect(provider.load_current_resource).to be_nil - expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package) - end - end + it "should get the source package version from rpm if provided" do + expect(provider.current_resource.package_name).to eq("ImageMagick-c++") + expect(provider.new_resource.version).to eq("6.5.4.7-7.el6_5") + end - context "source is not defiend" do - let(:new_resource) { Chef::Resource::Package.new("ImageMagick-c++") } + it "should return the current version installed if found by rpm" do + expect(provider.current_resource.version).to eq("6.5.4.7-7.el6_5") + end + + describe "action install" do + + context "when at the desired version already" do + it "does nothing when the correct version is installed" do + expect(provider).to_not receive(:shell_out!).with("rpm -i /tmp/imagemagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) + + provider.action_install + end + end + + context "when a newer version is desired" do + + let(:rpm_q_stdout) { "imagemagick-c++ 0.5.4.7-7.el6_5" } + + it "runs rpm -u with the package source to upgrade" do + expect(provider).to receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) + provider.action_install + end + end + + context "when an older version is desired" do + let(:new_resource) do + Chef::Resource::RpmPackage.new(package_name).tap do |r| + r.source(package_source) + r.allow_downgrade(true) + end + end + + let(:rpm_q_stdout) { "imagemagick-c++ 21.4-19.el6_5" } + + it "should run rpm -u --oldpackage with the package source to downgrade" do + expect(provider).to receive(:shell_out!).with("rpm -U --oldpackage /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) + provider.action_install + end + + end + + end + + describe "action upgrade" do + + let(:action) { :upgrade } + + context "when at the desired version already" do + it "does nothing when the correct version is installed" do + expect(provider).to_not receive(:shell_out!).with("rpm -i /tmp/imagemagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) + + provider.action_upgrade + end + end + + context "when a newer version is desired" do + + let(:rpm_q_stdout) { "imagemagick-c++ 0.5.4.7-7.el6_5" } + + it "runs rpm -u with the package source to upgrade" do + expect(provider).to receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) + provider.action_upgrade + end + end + + context "when an older version is desired" do + let(:new_resource) do + Chef::Resource::RpmPackage.new(package_name).tap do |r| + r.source(package_source) + r.allow_downgrade(true) + end + end + + let(:rpm_q_stdout) { "imagemagick-c++ 21.4-19.el6_5" } + + it "should run rpm -u --oldpackage with the package source to downgrade" do + expect(provider).to receive(:shell_out!).with("rpm -U --oldpackage /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) + provider.action_upgrade + end + + end + end + + describe "action :remove" do + + let(:action) { :remove } + + it "should remove the package" do + expect(provider).to receive(:shell_out!).with("rpm -e ImageMagick-c++-6.5.4.7-7.el6_5", timeout: 900) + provider.action_remove + end + end + + + context "when the package name contains a tilde (chef#3503)" do + + let(:package_name) { "supermarket" } + + let(:package_source) { "/tmp/supermarket-1.10.1~alpha.0-1.el5.x86_64.rpm" } + + let(:rpm_qp_stdout) { "supermarket 1.10.1~alpha.0-1.el5" } + let(:rpm_q_stdout) { "supermarket 1.10.1~alpha.0-1.el5" } + + let(:rpm_qp_exitstatus) { 0 } + let(:rpm_q_exitstatus) { 0 } + + it "should correctly determine the candidate version and installed version" do + expect(provider.current_resource.package_name).to eq("supermarket") + expect(provider.new_resource.version).to eq("1.10.1~alpha.0-1.el5") + end + end - it "should raise an exception if the source is not set but we are installing" do - expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package) end - end - context "installation does not exist" do - let(:stdout) { String.new("package openssh-askpass is not installed") } - let(:exitstatus) { -1 } - let(:new_resource) do - Chef::Resource::Package.new("openssh-askpass").tap do |resource| - resource.source "openssh-askpass" + context "when the source is given as an URI" do + before(:each) do + allow(::File).to receive(:exists?).with(package_source).and_return(false) + + provider.action = action + + provider.load_current_resource + provider.define_resource_requirements + provider.process_resource_requirements + end + + %w(http HTTP https HTTPS ftp FTP file FILE).each do |scheme| + + context "when the source URI uses protocol scheme '#{scheme}'" do + + let(:package_source) { "#{scheme}://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" } + + it "should get the source package version from rpm if provided" do + expect(provider.current_resource.package_name).to eq("ImageMagick-c++") + expect(provider.new_resource.version).to eq("6.5.4.7-7.el6_5") + end + + it "should return the current version installed if found by rpm" do + expect(provider.current_resource.version).to eq("6.5.4.7-7.el6_5") + end + + end end + end - it "should raise an exception if rpm fails to run" do - allow(provider).to receive(:shell_out).and_return(status) - expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package) + end + + context "when the package is not installed" do + + let(:package_name) { "openssh-askpass" } + + let(:package_source) { "/tmp/openssh-askpass-1.2.3-4.el6_5.x86_64.rpm" } + + let(:rpm_qp_stdout) { "openssh-askpass 1.2.3-4.el6_5" } + let(:rpm_q_stdout) { "package openssh-askpass is not installed" } + + let(:rpm_qp_exitstatus) { 0 } + let(:rpm_q_exitstatus) { 0 } + + let(:action) { :install } + + before do + allow(File).to receive(:exists?).with(package_source).and_return(true) + + provider.action = action + + provider.load_current_resource + provider.define_resource_requirements + provider.process_resource_requirements end it "should not detect the package name as version when not installed" do - expect(provider).to receive(:shell_out!).with("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' openssh-askpass").and_return(status) - expect(provider).to receive(:shell_out).with("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' openssh-askpass").and_return(status) - provider.load_current_resource expect(provider.current_resource.version).to be_nil end - end - end - describe "after the current resource is loaded" do - let(:current_resource) { Chef::Resource::Package.new("ImageMagick-c++") } - let(:provider) do - Chef::Provider::Package::Rpm.new(new_resource, run_context).tap do |provider| - provider.current_resource = current_resource - end - end + context "when the package name contains a tilde (chef#3503)" do - describe "when installing or upgrading" do - it "should run rpm -i with the package source to install" do - expect(provider).to receive(:shell_out!).with("rpm -i /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") - provider.install_package("ImageMagick-c++", "6.5.4.7-7.el6_5") - end + let(:package_name) { "supermarket" } - it "should run rpm -U with the package source to upgrade" do - current_resource.version("21.4-19.el5") - expect(provider).to receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") - provider.upgrade_package("ImageMagick-c++", "6.5.4.7-7.el6_5") - end + let(:package_source) { "/tmp/supermarket-1.10.1~alpha.0-1.el5.x86_64.rpm" } - it "should install package if missing and set to upgrade" do - current_resource.version("ImageMagick-c++") - expect(provider).to receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") - provider.upgrade_package("ImageMagick-c++", "6.5.4.7-7.el6_5") - end + let(:rpm_qp_stdout) { "supermarket 1.10.1~alpha.0-1.el5" } + let(:rpm_q_stdout) { "package supermarket is not installed" } - context "allowing downgrade" do - let(:new_resource) { Chef::Resource::RpmPackage.new("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") } - let(:current_resource) { Chef::Resource::RpmPackage.new("ImageMagick-c++") } + let(:rpm_qp_exitstatus) { 0 } + let(:rpm_q_exitstatus) { 0 } - it "should run rpm -U --oldpackage with the package source to downgrade" do - new_resource.allow_downgrade(true) - current_resource.version("21.4-19.el5") - expect(provider).to receive(:shell_out!).with("rpm -U --oldpackage /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") - provider.upgrade_package("ImageMagick-c++", "6.5.4.7-7.el6_5") + it "should correctly determine the candidate version" do + expect(provider.new_resource.version).to eq("1.10.1~alpha.0-1.el5") end end - context "installing when the name is a path" do - let(:new_resource) { Chef::Resource::Package.new("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") } - let(:current_resource) { Chef::Resource::Package.new("ImageMagick-c++") } + describe "managing the package" do + + describe "action install" do + + it "installs the package" do + expect(provider).to receive(:shell_out!).with("rpm -i #{package_source}", timeout: 900) - it "should install from a path when the package is a path and the source is nil" do - expect(new_resource.source).to eq("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") - provider.current_resource = current_resource - expect(provider).to receive(:shell_out!).with("rpm -i /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") - provider.install_package("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", "6.5.4.7-7.el6_5") + provider.action_install + end + + context "when custom resource options are given" do + it "installs with custom options specified in the resource" do + new_resource.options("--dbpath /var/lib/rpm") + expect(provider).to receive(:shell_out!).with("rpm --dbpath /var/lib/rpm -i #{package_source}", timeout: 900) + provider.action_install + end + end end - it "should uprgrade from a path when the package is a path and the source is nil" do - expect(new_resource.source).to eq("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") - current_resource.version("21.4-19.el5") - provider.current_resource = current_resource - expect(provider).to receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") - provider.upgrade_package("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", "6.5.4.7-7.el6_5") + describe "action upgrade" do + + let(:action) { :upgrade } + + it "installs the package" do + expect(provider).to receive(:shell_out!).with("rpm -i #{package_source}", timeout: 900) + + provider.action_upgrade + end + end + + describe "when removing the package" do + + let(:action) { :remove } + + it "should do nothing" do + expect(provider).to_not receive(:shell_out!).with("rpm -e ImageMagick-c++-6.5.4.7-7.el6_5", timeout: 900) + provider.action_remove + end end - end - it "installs with custom options specified in the resource" do - provider.candidate_version = '11' - new_resource.options("--dbpath /var/lib/rpm") - expect(provider).to receive(:shell_out!).with("rpm --dbpath /var/lib/rpm -i /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") - provider.install_package(new_resource.name, provider.candidate_version) end + + end + end - describe "when removing the package" do - it "should run rpm -e to remove the package" do - expect(provider).to receive(:shell_out!).with("rpm -e ImageMagick-c++-6.5.4.7-7.el6_5") - provider.remove_package("ImageMagick-c++", "6.5.4.7-7.el6_5") - end + context "when the resource name is the path to the package" do + + let(:new_resource) do + # When we pass a source in as the name, then #initialize in the + # provider will call File.exists?. Because of the ordering in our + # let() bindings and such, we have to set the stub here and not in a + # before block. + allow(::File).to receive(:exists?).with(package_source).and_return(true) + Chef::Resource::Package.new("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") + end + + let(:current_resource) { Chef::Resource::Package.new("ImageMagick-c++") } + + it "should install from a path when the package is a path and the source is nil" do + expect(new_resource.source).to eq("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") + provider.current_resource = current_resource + expect(provider).to receive(:shell_out!).with("rpm -i /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) + provider.install_package("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", "6.5.4.7-7.el6_5") + end + + it "should uprgrade from a path when the package is a path and the source is nil" do + expect(new_resource.source).to eq("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") + current_resource.version("21.4-19.el5") + provider.current_resource = current_resource + expect(provider).to receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) + provider.upgrade_package("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", "6.5.4.7-7.el6_5") end end + + end + diff --git a/spec/unit/provider/package/rubygems_spec.rb b/spec/unit/provider/package/rubygems_spec.rb index 380572499c..67ffb7bb9e 100644 --- a/spec/unit/provider/package/rubygems_spec.rb +++ b/spec/unit/provider/package/rubygems_spec.rb @@ -107,38 +107,6 @@ describe Chef::Provider::Package::Rubygems::CurrentGemEnvironment do expect(@gem_env.candidate_version_from_remote(Gem::Dependency.new('rspec', '>= 0'))).to eq(Gem::Version.new('1.3.0')) end - context "when rubygems was upgraded from 1.8->2.0" do - # https://github.com/rubygems/rubygems/issues/404 - # tl;dr rubygems 1.8 and 2.0 can both be in the load path, which means that - # require "rubygems/format" will load even though rubygems 2.0 doesn't have - # that file. - - before do - if defined?(Gem::Format) - # tests are running under rubygems 1.8, or 2.0 upgraded from 1.8 - @remove_gem_format = false - else - Gem.const_set(:Format, Object.new) - @remove_gem_format = true - end - allow(Gem::Package).to receive(:respond_to?).and_call_original - allow(Gem::Package).to receive(:respond_to?).with(:open).and_return(false) - end - - after do - if @remove_gem_format - Gem.send(:remove_const, :Format) - end - end - - it "finds a matching gem candidate version on rubygems 2.0+ with some rubygems 1.8 code loaded" do - package = double("Gem::Package", :spec => "a gemspec from package") - expect(Gem::Package).to receive(:new).with("/path/to/package.gem").and_return(package) - expect(@gem_env.spec_from_file("/path/to/package.gem")).to eq("a gemspec from package") - end - - end - it "gives the candidate version as nil if none is found" do dep = Gem::Dependency.new('rspec', '>= 0') latest = [] @@ -222,8 +190,6 @@ describe Chef::Provider::Package::Rubygems::AlternateGemEnvironment do end it "uses the cached result for gem paths when available" do - gem_env_output = ['/path/to/gems', '/another/path/to/gems'].join(File::PATH_SEPARATOR) - shell_out_result = OpenStruct.new(:stdout => gem_env_output) expect(@gem_env).not_to receive(:shell_out!) expected = ['/path/to/gems', '/another/path/to/gems'] Chef::Provider::Package::Rubygems::AlternateGemEnvironment.gempath_cache['/usr/weird/bin/gem']= expected @@ -261,7 +227,7 @@ describe Chef::Provider::Package::Rubygems::AlternateGemEnvironment do else `which gem`.strip end - pending("cant find your gem executable") if path_to_gem.empty? + skip("cant find your gem executable") if path_to_gem.empty? gem_env = Chef::Provider::Package::Rubygems::AlternateGemEnvironment.new(path_to_gem) expected = ['rspec-core', Gem::Version.new(RSpec::Core::Version::STRING)] actual = gem_env.installed_versions(Gem::Dependency.new('rspec-core', nil)).map { |s| [s.name, s.version] } @@ -542,7 +508,7 @@ describe Chef::Provider::Package::Rubygems do it "installs the gem by shelling out when options are provided as a String" do @new_resource.options('-i /alt/install/location') expected ="gem install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\" -i /alt/install/location" - expect(@provider).to receive(:shell_out!).with(expected, :env => nil) + expect(@provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900) @provider.run_action(:install) expect(@new_resource).to be_updated_by_last_action end @@ -551,7 +517,7 @@ describe Chef::Provider::Package::Rubygems do @new_resource.gem_binary('/foo/bar') @new_resource.source('http://mirror.ops.rhcloud.com/mirror/ruby') expected ="/foo/bar install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\" --source=#{@new_resource.source} --source=https://rubygems.org" - expect(@provider).to receive(:shell_out!).with(expected, :env => nil) + expect(@provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900) @provider.run_action(:install) expect(@new_resource).to be_updated_by_last_action end @@ -561,7 +527,7 @@ describe Chef::Provider::Package::Rubygems do @new_resource.source('http://mirror.ops.rhcloud.com/mirror/ruby') @new_resource.clear_sources(true) expected ="/foo/bar install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\" --clear-sources --source=#{@new_resource.source}" - expect(@provider).to receive(:shell_out!).with(expected, :env => nil) + expect(@provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900) @provider.run_action(:install) expect(@new_resource).to be_updated_by_last_action end @@ -572,7 +538,7 @@ describe Chef::Provider::Package::Rubygems do it "installs the gem by shelling out when options are provided but no version is given" do @new_resource.options('-i /alt/install/location') expected ="gem install rspec-core -q --no-rdoc --no-ri -v \"#{@provider.candidate_version}\" -i /alt/install/location" - expect(@provider).to receive(:shell_out!).with(expected, :env => nil) + expect(@provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900) @provider.run_action(:install) expect(@new_resource).to be_updated_by_last_action end @@ -618,7 +584,7 @@ describe Chef::Provider::Package::Rubygems do describe "in an alternate gem environment" do it "installs the gem by shelling out to gem install" do @new_resource.gem_binary('/usr/weird/bin/gem') - expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\"", :env=>nil) + expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\"", env: nil, timeout: 900) @provider.run_action(:install) expect(@new_resource).to be_updated_by_last_action end @@ -627,7 +593,7 @@ describe Chef::Provider::Package::Rubygems do @new_resource.gem_binary('/usr/weird/bin/gem') @new_resource.source(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') @new_resource.version('>= 0') - expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", :env=>nil) + expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", env: nil, timeout: 900) @provider.run_action(:install) expect(@new_resource).to be_updated_by_last_action end @@ -639,7 +605,7 @@ describe Chef::Provider::Package::Rubygems do @new_resource.gem_binary('/usr/weird/bin/gem') @new_resource.version('>= 0') expect(@new_resource.source).to eq(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem') - expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", :env=>nil) + expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", env: nil, timeout: 900) @provider.run_action(:install) expect(@new_resource).to be_updated_by_last_action end @@ -678,7 +644,7 @@ describe Chef::Provider::Package::Rubygems do it "uninstalls via the gem command when options are given as a String" do @new_resource.options('-i /alt/install/location') - expect(@provider).to receive(:shell_out!).with("gem uninstall rspec -q -x -I -a -i /alt/install/location", :env=>nil) + expect(@provider).to receive(:shell_out!).with("gem uninstall rspec -q -x -I -a -i /alt/install/location", env: nil, timeout: 900) @provider.action_remove end @@ -692,7 +658,7 @@ describe Chef::Provider::Package::Rubygems do describe "in an alternate gem environment" do it "uninstalls via the gem command" do @new_resource.gem_binary('/usr/weird/bin/gem') - expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem uninstall rspec -q -x -I -a", :env=>nil) + expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem uninstall rspec -q -x -I -a", env: nil, timeout: 900) @provider.action_remove end end diff --git a/spec/unit/provider/package/smartos_spec.rb b/spec/unit/provider/package/smartos_spec.rb index db39589b85..8f2d2bb8ea 100644 --- a/spec/unit/provider/package/smartos_spec.rb +++ b/spec/unit/provider/package/smartos_spec.rb @@ -29,45 +29,45 @@ describe Chef::Provider::Package::SmartOS, "load_current_resource" do @current_resource = Chef::Resource::Package.new("varnish") - @status = double("Status", :exitstatus => 0) - @provider = Chef::Provider::Package::SmartOS.new(@new_resource, @run_context) - allow(Chef::Resource::Package).to receive(:new).and_return(@current_resource) - @stdin = StringIO.new - @stdout = "varnish-2.1.5nb2\n" - @stderr = StringIO.new - @pid = 10 - @shell_out = OpenStruct.new(:stdout => @stdout, :stdin => @stdin, :stderr => @stderr, :status => @status, :exitstatus => 0) + @status = double("Status", :exitstatus => 0) + @provider = Chef::Provider::Package::SmartOS.new(@new_resource, @run_context) + allow(Chef::Resource::Package).to receive(:new).and_return(@current_resource) + @stdin = StringIO.new + @stdout = "varnish-2.1.5nb2\n" + @stderr = StringIO.new + @pid = 10 + @shell_out = OpenStruct.new(:stdout => @stdout, :stdin => @stdin, :stderr => @stderr, :status => @status, :exitstatus => 0) end - describe "when loading current resource" do + describe "when loading current resource" do it "should create a current resource with the name of the new_resource" do - expect(@provider).to receive(:shell_out!).and_return(@shell_out) - expect(Chef::Resource::Package).to receive(:new).and_return(@current_resource) - @provider.load_current_resource + expect(@provider).to receive(:shell_out!).and_return(@shell_out) + expect(Chef::Resource::Package).to receive(:new).and_return(@current_resource) + @provider.load_current_resource end - it "should set the current resource package name" do - expect(@provider).to receive(:shell_out!).and_return(@shell_out) - expect(@current_resource).to receive(:package_name).with(@new_resource.package_name) - @provider.load_current_resource - end + it "should set the current resource package name" do + expect(@provider).to receive(:shell_out!).and_return(@shell_out) + expect(@current_resource).to receive(:package_name).with(@new_resource.package_name) + @provider.load_current_resource + end - it "should set the installed version if it is installed" do - expect(@provider).to receive(:shell_out!).and_return(@shell_out) - @provider.load_current_resource - expect(@current_resource.version).to eq("2.1.5nb2") - end + it "should set the installed version if it is installed" do + expect(@provider).to receive(:shell_out!).and_return(@shell_out) + @provider.load_current_resource + expect(@current_resource.version).to eq("2.1.5nb2") + end - it "should set the installed version to nil if it's not installed" do - out = OpenStruct.new(:stdout => nil) - expect(@provider).to receive(:shell_out!).and_return(out) - @provider.load_current_resource - expect(@current_resource.version).to eq(nil) - end + it "should set the installed version to nil if it's not installed" do + out = OpenStruct.new(:stdout => nil) + expect(@provider).to receive(:shell_out!).and_return(out) + @provider.load_current_resource + expect(@current_resource.version).to eq(nil) + end - end + end describe "candidate_version" do it "should return the candidate_version variable if already setup" do @@ -76,27 +76,37 @@ describe Chef::Provider::Package::SmartOS, "load_current_resource" do @provider.candidate_version end - it "should lookup the candidate_version if the variable is not already set" do + it "should lookup the candidate_version if the variable is not already set (pkgin separated by spaces)" do search = double() expect(search).to receive(:each_line). - and_yield("something-varnish-1.1.1 something varnish like\n"). - and_yield("varnish-2.3.4 actual varnish\n") + and_yield("something-varnish-1.1.1 something varnish like\n"). + and_yield("varnish-2.3.4 actual varnish\n") @shell_out = double('shell_out!', :stdout => search) - expect(@provider).to receive(:shell_out!).with('/opt/local/bin/pkgin se varnish', :env => nil, :returns => [0,1]).and_return(@shell_out) + expect(@provider).to receive(:shell_out!).with('/opt/local/bin/pkgin', 'se', 'varnish', :env => nil, :returns => [0,1], :timeout=>900).and_return(@shell_out) + expect(@provider.candidate_version).to eq("2.3.4") + end + + it "should lookup the candidate_version if the variable is not already set (pkgin separated by semicolons)" do + search = double() + expect(search).to receive(:each_line). + and_yield("something-varnish-1.1.1;;something varnish like\n"). + and_yield("varnish-2.3.4;;actual varnish\n") + @shell_out = double('shell_out!', :stdout => search) + expect(@provider).to receive(:shell_out!).with('/opt/local/bin/pkgin', 'se', 'varnish', :env => nil, :returns => [0,1], :timeout=>900).and_return(@shell_out) expect(@provider.candidate_version).to eq("2.3.4") end end - describe "when manipulating a resource" do + describe "when manipulating a resource" do - it "run pkgin and install the package" do - out = OpenStruct.new(:stdout => nil) - expect(@provider).to receive(:shell_out!).with("/opt/local/sbin/pkg_info -E \"varnish*\"", {:env => nil, :returns=>[0,1]}).and_return(@shell_out) - expect(@provider).to receive(:shell_out!).with("/opt/local/bin/pkgin -y install varnish-2.1.5nb2", {:env=>nil}).and_return(out) + it "run pkgin and install the package" do + out = OpenStruct.new(:stdout => nil) + expect(@provider).to receive(:shell_out!).with("/opt/local/sbin/pkg_info", "-E", "varnish*", {:env => nil, :returns=>[0,1], :timeout=>900}).and_return(@shell_out) + expect(@provider).to receive(:shell_out!).with("/opt/local/bin/pkgin", "-y", "install", "varnish-2.1.5nb2", {:env=>nil, :timeout=>900}).and_return(out) @provider.load_current_resource @provider.install_package("varnish", "2.1.5nb2") - end + end - end + end end diff --git a/spec/unit/provider/package/solaris_spec.rb b/spec/unit/provider/package/solaris_spec.rb index c348d665e8..ae6c96da00 100644 --- a/spec/unit/provider/package/solaris_spec.rb +++ b/spec/unit/provider/package/solaris_spec.rb @@ -71,8 +71,8 @@ PKGINFO it "should get the source package version from pkginfo if provided" do status = double(:stdout => @pkginfo, :exitstatus => 0) - expect(@provider).to receive(:shell_out).with("pkginfo -l -d /tmp/bash.pkg SUNWbash").and_return(status) - expect(@provider).to receive(:shell_out).with("pkginfo -l SUNWbash").and_return(@status) + expect(@provider).to receive(:shell_out).with("pkginfo -l -d /tmp/bash.pkg SUNWbash", { timeout: 900 }).and_return(status) + expect(@provider).to receive(:shell_out).with("pkginfo -l SUNWbash", { timeout: 900 }).and_return(@status) @provider.load_current_resource expect(@provider.current_resource.package_name).to eq("SUNWbash") @@ -81,8 +81,8 @@ PKGINFO it "should return the current version installed if found by pkginfo" do status = double(:stdout => @pkginfo, :exitstatus => 0) - expect(@provider).to receive(:shell_out).with("pkginfo -l -d /tmp/bash.pkg SUNWbash").and_return(@status) - expect(@provider).to receive(:shell_out).with("pkginfo -l SUNWbash").and_return(status) + expect(@provider).to receive(:shell_out).with("pkginfo -l -d /tmp/bash.pkg SUNWbash", { timeout: 900 }).and_return(@status) + expect(@provider).to receive(:shell_out).with("pkginfo -l SUNWbash", { timeout: 900 }).and_return(status) @provider.load_current_resource expect(@provider.current_resource.version).to eq("11.10.0,REV=2005.01.08.05.16") end @@ -101,8 +101,8 @@ PKGINFO end it "should return a current resource with a nil version if the package is not found" do - expect(@provider).to receive(:shell_out).with("pkginfo -l -d /tmp/bash.pkg SUNWbash").and_return(@status) - expect(@provider).to receive(:shell_out).with("pkginfo -l SUNWbash").and_return(@status) + expect(@provider).to receive(:shell_out).with("pkginfo -l -d /tmp/bash.pkg SUNWbash", { timeout: 900 }).and_return(@status) + expect(@provider).to receive(:shell_out).with("pkginfo -l SUNWbash", { timeout: 900 }).and_return(@status) @provider.load_current_resource expect(@provider.current_resource.version).to be_nil end @@ -132,7 +132,7 @@ PKGINFO describe "install and upgrade" do it "should run pkgadd -n -d with the package source to install" do - expect(@provider).to receive(:shell_out!).with("pkgadd -n -d /tmp/bash.pkg all") + expect(@provider).to receive(:shell_out!).with("pkgadd -n -d /tmp/bash.pkg all", { timeout: 900 }) @provider.install_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16") end @@ -140,26 +140,26 @@ PKGINFO @new_resource = Chef::Resource::Package.new("/tmp/bash.pkg") @provider = Chef::Provider::Package::Solaris.new(@new_resource, @run_context) expect(@new_resource.source).to eq("/tmp/bash.pkg") - expect(@provider).to receive(:shell_out!).with("pkgadd -n -d /tmp/bash.pkg all") + expect(@provider).to receive(:shell_out!).with("pkgadd -n -d /tmp/bash.pkg all", { timeout: 900 }) @provider.install_package("/tmp/bash.pkg", "11.10.0,REV=2005.01.08.05.16") end it "should run pkgadd -n -a /tmp/myadmin -d with the package options -a /tmp/myadmin" do allow(@new_resource).to receive(:options).and_return("-a /tmp/myadmin") - expect(@provider).to receive(:shell_out!).with("pkgadd -n -a /tmp/myadmin -d /tmp/bash.pkg all") + expect(@provider).to receive(:shell_out!).with("pkgadd -n -a /tmp/myadmin -d /tmp/bash.pkg all", { timeout: 900 }) @provider.install_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16") end end describe "remove" do it "should run pkgrm -n to remove the package" do - expect(@provider).to receive(:shell_out!).with("pkgrm -n SUNWbash") + expect(@provider).to receive(:shell_out!).with("pkgrm -n SUNWbash", { timeout: 900 }) @provider.remove_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16") end it "should run pkgrm -n -a /tmp/myadmin with options -a /tmp/myadmin" do allow(@new_resource).to receive(:options).and_return("-a /tmp/myadmin") - expect(@provider).to receive(:shell_out!).with("pkgrm -n -a /tmp/myadmin SUNWbash") + expect(@provider).to receive(:shell_out!).with("pkgrm -n -a /tmp/myadmin SUNWbash", { timeout: 900 }) @provider.remove_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16") end diff --git a/spec/unit/provider/package/windows_spec.rb b/spec/unit/provider/package/windows_spec.rb index d402113d72..e5acc87694 100644 --- a/spec/unit/provider/package/windows_spec.rb +++ b/spec/unit/provider/package/windows_spec.rb @@ -19,50 +19,129 @@ require 'spec_helper' describe Chef::Provider::Package::Windows, :windows_only do + before(:each) do + allow(Chef::Util::PathHelper).to receive(:windows?).and_return(true) + allow(Chef::FileCache).to receive(:create_cache_path).with("package/").and_return(cache_path) + end + let(:node) { double('Chef::Node') } let(:events) { double('Chef::Events').as_null_object } # mock all the methods let(:run_context) { double('Chef::RunContext', :node => node, :events => events) } - let(:new_resource) { Chef::Resource::WindowsPackage.new("calculator.msi") } + let(:resource_source) { 'calculator.msi' } + let(:new_resource) { Chef::Resource::WindowsPackage.new(resource_source) } let(:provider) { Chef::Provider::Package::Windows.new(new_resource, run_context) } + let(:cache_path) { 'c:\\cache\\' } describe "load_current_resource" do - before(:each) do - allow(Chef::Util::PathHelper).to receive(:validate_path) - allow(provider).to receive(:package_provider).and_return(double('package_provider', + shared_examples "a local file" do + before(:each) do + allow(Chef::Util::PathHelper).to receive(:validate_path) + allow(provider).to receive(:package_provider).and_return(double('package_provider', :installed_version => "1.0", :package_version => "2.0")) - end + end - it "creates a current resource with the name of the new resource" do - provider.load_current_resource - expect(provider.current_resource).to be_a(Chef::Resource::WindowsPackage) - expect(provider.current_resource.name).to eql("calculator.msi") - end + it "creates a current resource with the name of the new resource" do + provider.load_current_resource + expect(provider.current_resource).to be_a(Chef::Resource::WindowsPackage) + expect(provider.current_resource.name).to eql(resource_source) + end + + it "sets the current version if the package is installed" do + provider.load_current_resource + expect(provider.current_resource.version).to eql("1.0") + end - it "sets the current version if the package is installed" do - provider.load_current_resource - expect(provider.current_resource.version).to eql("1.0") + it "sets the version to be installed" do + provider.load_current_resource + expect(provider.new_resource.version).to eql("2.0") + end end - it "sets the version to be installed" do - provider.load_current_resource - expect(provider.new_resource.version).to eql("2.0") + context "when the source is a uri" do + let(:resource_source) { 'https://foo.bar/calculator.msi' } + + context "when the source has not been downloaded" do + before(:each) do + allow(provider).to receive(:downloadable_file_missing?).and_return(true) + end + it "sets the current version to unknown" do + provider.load_current_resource + expect(provider.current_resource.version).to eql("unknown") + end + end + + context "when the source has been downloaded" do + before(:each) do + allow(provider).to receive(:downloadable_file_missing?).and_return(false) + end + it_behaves_like "a local file" + end + + context "when remote_file_attributes are provided" do + let (:remote_file_attributes) { {:path => 'C:\\foobar.msi'} } + before(:each) do + new_resource.remote_file_attributes(remote_file_attributes) + end + + it 'should override the attributes of the remote file resource used' do + expect(::File).to receive(:exists?).with(remote_file_attributes[:path]) + provider.load_current_resource + end + + end end - it "checks that the source path is valid" do - expect(Chef::Util::PathHelper).to receive(:validate_path) - provider.load_current_resource + context "when source is a local file" do + it_behaves_like "a local file" end end describe "package_provider" do - it "sets the package provider to MSI if the the installer type is :msi" do - allow(provider).to receive(:installer_type).and_return(:msi) - expect(provider.package_provider).to be_a(Chef::Provider::Package::Windows::MSI) + shared_examples "a local file" do + it "checks that the source path is valid" do + expect(Chef::Util::PathHelper).to receive(:validate_path) + provider.package_provider + end + + it "sets the package provider to MSI if the the installer type is :msi" do + allow(provider).to receive(:installer_type).and_return(:msi) + expect(provider.package_provider).to be_a(Chef::Provider::Package::Windows::MSI) + end + + it "raises an error if the installer_type is unknown" do + allow(provider).to receive(:installer_type).and_return(:apt_for_windows) + expect { provider.package_provider }.to raise_error + end + end + + context "when the source is a uri" do + let(:resource_source) { 'https://foo.bar/calculator.msi' } + + context "when the source has not been downloaded" do + before(:each) do + allow(provider).to receive(:should_download?).and_return(true) + end + + it "should create a package provider with source pointing at the local file" do + expect(Chef::Provider::Package::Windows::MSI).to receive(:new) do |r| + expect(r.source).to eq("#{cache_path}#{::File.basename(resource_source)}") + end + provider.package_provider + end + + it_behaves_like "a local file" + end + + context "when the source has been downloaded" do + before(:each) do + allow(provider).to receive(:should_download?).and_return(false) + end + it_behaves_like "a local file" + end end - it "raises an error if the installer_type is unknown" do - allow(provider).to receive(:installer_type).and_return(:apt_for_windows) - expect { provider.package_provider }.to raise_error + context "when source is a local file" do + it_behaves_like "a local file" end end diff --git a/spec/unit/provider/package/yum_spec.rb b/spec/unit/provider/package/yum_spec.rb index 865dce23fa..e878b92621 100644 --- a/spec/unit/provider/package/yum_spec.rb +++ b/spec/unit/provider/package/yum_spec.rb @@ -17,6 +17,7 @@ # require 'spec_helper' +require 'securerandom' describe Chef::Provider::Package::Yum do before(:each) do @@ -122,6 +123,26 @@ describe Chef::Provider::Package::Yum do expect(@provider.arch).to eq("noarch") end + describe "when version constraint in package_name" do + it "should set package_version if no existing package_name is found and new_package_name is available" do + @new_resource = Chef::Resource::Package.new('cups = 1.2.4-11.18.el5_2.3') + @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) + allow(@yum_cache).to receive(:package_available?) { |pkg| pkg == 'cups' ? true : false } + allow(@yum_cache).to receive(:packages_from_require) do |pkg| + [Chef::Provider::Package::Yum::RPMDbPackage.new("cups", "1.2.4-11.18.el5_2.3", "noarch", [], false, true, "base"), + Chef::Provider::Package::Yum::RPMDbPackage.new("cups", "1.2.4-11.18.el5_2.2", "noarch", [], false, true, "base"),] + end + expect(Chef::Log).to receive(:debug).exactly(1).times.with(%r{checking yum info}) + expect(Chef::Log).to receive(:debug).exactly(1).times.with(%r{installed version}) + expect(Chef::Log).to receive(:debug).exactly(1).times.with(%r{matched 2 packages,}) + @provider.load_current_resource + expect(@provider.new_resource.package_name).to eq("cups") + expect(@provider.new_resource.version).to eq("1.2.4-11.18.el5_2.3") + expect(@provider.send(:new_version_array)).to eq(["1.2.4-11.18.el5_2.3"]) + expect(@provider.send(:package_name_array)).to eq(["cups"]) + end + end + it "should not set the arch when an existing package_name is found" do @new_resource = Chef::Resource::YumPackage.new('testing.beta3') @yum_cache = double( @@ -1659,6 +1680,14 @@ describe Chef::Provider::Package::Yum::YumCache do end end + let(:yum_exe) { + StringIO.new("#!/usr/bin/python\n\naldsjfa\ldsajflkdsjf\lajsdfj") + } + + let(:bin_exe) { + StringIO.new(SecureRandom.random_bytes) + } + before(:each) do @stdin = double("STDIN", :nil_object => true) @stdout = double("STDOUT", :nil_object => true) @@ -1704,12 +1733,19 @@ file: file://///etc/yum.repos.d/CentOS-Base.repo, line: 12 'qeqwewe\n' EOF @status = double("Status", :exitstatus => 0, :stdin => @stdin, :stdout => @stdout_good, :stderr => @stderr) - # new singleton each time Chef::Provider::Package::Yum::YumCache.reset_instance @yc = Chef::Provider::Package::Yum::YumCache.instance # load valid data allow(@yc).to receive(:shell_out!).and_return(@status) + allow_any_instance_of(described_class).to receive(:which).with("yum").and_return("/usr/bin/yum") + allow(::File).to receive(:open).with("/usr/bin/yum", "r") do |&block| + res = block.call(yum_exe) + # a bit of a hack. rewind this since it seem that no matter what + # I do, we get the same StringIO objects on multiple calls to + # ::File.open + yum_exe.rewind; res + end end describe "initialize" do @@ -1726,6 +1762,24 @@ EOF end end + describe "python_bin" do + it "should return the default python if an error occurs" do + allow(::File).to receive(:open).with("/usr/bin/yum", "r").and_raise(StandardError) + expect(@yc.python_bin).to eq("/usr/bin/python") + end + + it "should return the default python if the yum-executable doesn't start with #!" do + allow(::File).to receive(:open).with("/usr/bin/yum", "r") { |&b| r = b.call(bin_exe); bin_exe.rewind; r} + expect(@yc.python_bin).to eq("/usr/bin/python") + end + + it "should return the interpreter for yum" do + other = StringIO.new("#!/usr/bin/super_python\n\nlasjdfdsaljf\nlasdjfs") + allow(::File).to receive(:open).with("/usr/bin/yum", "r") { |&b| r = b.call(other); other.rewind; r} + expect(@yc.python_bin).to eq("/usr/bin/super_python") + end + end + describe "refresh" do it "should implicitly call yum-dump.py only once by default after being instantiated" do expect(@yc).to receive(:shell_out!).once @@ -2041,6 +2095,36 @@ describe "Chef::Provider::Package::Yum - Multi" do it "should return the current resouce" do expect(@provider.load_current_resource).to eql(@provider.current_resource) end + + describe "when version constraint in package_name" do + it "should set package_version if no existing package_name is found and new_package_name is available" do + @new_resource = Chef::Resource::Package.new(['cups = 1.2.4-11.18.el5_2.3', 'emacs = 24.4']) + @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) + allow(@yum_cache).to receive(:package_available?) { |pkg| %w(cups emacs).include?(pkg) ? true : false } + allow(@yum_cache).to receive(:candidate_version) do |pkg| + if pkg == 'cups' + "1.2.4-11.18.el5_2.3" + elsif pkg == 'emacs' + "24.4" + end + end + allow(@yum_cache).to receive(:packages_from_require) do |pkg| + if pkg.name == 'cups' + [Chef::Provider::Package::Yum::RPMDbPackage.new("cups", "1.2.4-11.18.el5_2.3", "noarch", [], false, true, "base")] + elsif pkg.name == 'emacs' + [Chef::Provider::Package::Yum::RPMDbPackage.new("emacs", "24.4", "noarch", [], false, true, "base")] + end + end + expect(Chef::Log).to receive(:debug).exactly(2).times.with(%r{matched 1 package,}) + expect(Chef::Log).to receive(:debug).exactly(1).times.with(%r{candidate version: \["1.2.4-11.18.el5_2.3", "24.4"\]}) + expect(Chef::Log).to receive(:debug).at_least(2).times.with(%r{checking yum info}) + @provider.load_current_resource + expect(@provider.new_resource.package_name).to eq(["cups", "emacs"]) + expect(@provider.new_resource.version).to eq(["1.2.4-11.18.el5_2.3", "24.4"]) + expect(@provider.send(:package_name_array)).to eq(["cups", "emacs"]) + expect(@provider.send(:new_version_array)).to eq(["1.2.4-11.18.el5_2.3", "24.4"]) + end + end end describe "when installing a package" do @@ -2076,5 +2160,31 @@ describe "Chef::Provider::Package::Yum - Multi" do allow(@new_resource).to receive(:options).and_return("--disablerepo epmd") @provider.install_package(["cups", "vim"], ["1.2.4-11.19.el5", '1.0']) end + + it "should run yum install with the package name and version when name has arch" do + @new_resource = Chef::Resource::Package.new(['cups.x86_64', 'vim']) + @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) + allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1) + + # Inside of load_current_resource() we'll call parse_arch for cups, + # and we need to craft the right response. The default mock setup above + # will just return valid versions all the time which won't work for this + # test. + allow(@yum_cache).to receive(:installed_version).with('cups', 'x86_64').and_return('XXXX') + allow(@yum_cache).to receive(:candidate_version).with('cups', 'x86_64').and_return('1.2.4-11.18.el5') + allow(@yum_cache).to receive(:installed_version).with('cups.x86_64').and_return(nil) + allow(@yum_cache).to receive(:candidate_version).with('cups.x86_64').and_return(nil) + + # Normal mock's for the idempotency check + allow(@yum_cache).to receive(:installed_version).with('cups', nil).and_return('1.2.4-11.18.el5') + allow(@yum_cache).to receive(:installed_version).with('vim', nil).and_return('0.9') + + @provider.load_current_resource + expect(@provider).to receive(:yum_command).with( + "yum -d0 -e0 -y install cups-1.2.4-11.19.el5.x86_64 vim-1.0" + ) + @provider.install_package(["cups", "vim"], ["1.2.4-11.19.el5", '1.0']) + end + end end diff --git a/spec/unit/provider/package/zypper_spec.rb b/spec/unit/provider/package/zypper_spec.rb index 706ad722dd..18ff739bc6 100644 --- a/spec/unit/provider/package/zypper_spec.rb +++ b/spec/unit/provider/package/zypper_spec.rb @@ -19,126 +19,150 @@ require 'spec_helper' describe Chef::Provider::Package::Zypper do + let!(:new_resource) { Chef::Resource::ZypperPackage.new("cups") } + + let!(:current_resource) { Chef::Resource::ZypperPackage.new("cups") } + + let(:provider) do + node = Chef::Node.new + events = Chef::EventDispatch::Dispatcher.new + run_context = Chef::RunContext.new(node, {}, events) + Chef::Provider::Package::Zypper.new(new_resource, run_context) + end + + let(:status) { double(:stdout => "\n", :exitstatus => 0) } + before(:each) do - @node = Chef::Node.new - @events = Chef::EventDispatch::Dispatcher.new - @run_context = Chef::RunContext.new(@node, {}, @events) - @new_resource = Chef::Resource::Package.new("cups") - - @current_resource = Chef::Resource::Package.new("cups") - - @provider = Chef::Provider::Package::Zypper.new(@new_resource, @run_context) - allow(Chef::Resource::Package).to receive(:new).and_return(@current_resource) - @status = double(:stdout => "\n", :exitstatus => 0) - allow(@provider).to receive(:shell_out).and_return(@status) - allow(@provider).to receive(:`).and_return("2.0") + allow(Chef::Resource::Package).to receive(:new).and_return(current_resource) + allow(provider).to receive(:shell_out).and_return(status) + allow(provider).to receive(:`).and_return("2.0") + end + + def shell_out_expectation(command, options=nil) + options ||= { timeout: 900 } + expect(provider).to receive(:shell_out).with(command, options) + end + + def shell_out_expectation!(command, options=nil) + options ||= { timeout: 900 } + expect(provider).to receive(:shell_out!).with(command, options) end describe "when loading the current package state" do it "should create a current resource with the name of the new_resource" do - expect(Chef::Resource::Package).to receive(:new).and_return(@current_resource) - @provider.load_current_resource + expect(Chef::Resource::Package).to receive(:new).with(new_resource.name).and_return(current_resource) + provider.load_current_resource end it "should set the current resources package name to the new resources package name" do - expect(@current_resource).to receive(:package_name).with(@new_resource.package_name) - @provider.load_current_resource + expect(current_resource).to receive(:package_name).with(new_resource.package_name) + provider.load_current_resource end it "should run zypper info with the package name" do - expect(@provider).to receive(:shell_out).with("zypper --non-interactive info #{@new_resource.package_name}").and_return(@status) - @provider.load_current_resource + shell_out_expectation( + "zypper --non-interactive info #{new_resource.package_name}" + ).and_return(status) + provider.load_current_resource end it "should set the installed version to nil on the current resource if zypper info installed version is (none)" do - allow(@provider).to receive(:shell_out).and_return(@status) - expect(@current_resource).to receive(:version).with(nil).and_return(true) - @provider.load_current_resource + allow(provider).to receive(:shell_out).and_return(status) + expect(current_resource).to receive(:version).with(nil).and_return(true) + provider.load_current_resource end it "should set the installed version if zypper info has one" do status = double(:stdout => "Version: 1.0\nInstalled: Yes\n", :exitstatus => 0) - allow(@provider).to receive(:shell_out).and_return(status) - expect(@current_resource).to receive(:version).with("1.0").and_return(true) - @provider.load_current_resource + allow(provider).to receive(:shell_out).and_return(status) + expect(current_resource).to receive(:version).with("1.0").and_return(true) + provider.load_current_resource end it "should set the candidate version if zypper info has one" do status = double(:stdout => "Version: 1.0\nInstalled: No\nStatus: out-of-date (version 0.9 installed)", :exitstatus => 0) - allow(@provider).to receive(:shell_out).and_return(status) - @provider.load_current_resource - expect(@provider.candidate_version).to eql("1.0") + allow(provider).to receive(:shell_out).and_return(status) + provider.load_current_resource + expect(provider.candidate_version).to eql("1.0") end it "should raise an exception if zypper info fails" do - expect(@status).to receive(:exitstatus).and_return(1) - expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Package) + expect(status).to receive(:exitstatus).and_return(1) + expect { provider.load_current_resource }.to raise_error(Chef::Exceptions::Package) end it "should not raise an exception if zypper info succeeds" do - expect(@status).to receive(:exitstatus).and_return(0) - expect { @provider.load_current_resource }.not_to raise_error + expect(status).to receive(:exitstatus).and_return(0) + expect { provider.load_current_resource }.not_to raise_error end it "should return the current resouce" do - expect(@provider.load_current_resource).to eql(@current_resource) + expect(provider.load_current_resource).to eql(current_resource) end end describe "install_package" do it "should run zypper install with the package name and version" do allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true) - expect(@provider).to receive(:shell_out!).with( - "zypper --non-interactive install --auto-agree-with-licenses emacs=1.0") - @provider.install_package("emacs", "1.0") + shell_out_expectation!( + "zypper --non-interactive install --auto-agree-with-licenses emacs=1.0" + ) + provider.install_package("emacs", "1.0") end it "should run zypper install without gpg checks" do allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false) - expect(@provider).to receive(:shell_out!).with( + shell_out_expectation!( "zypper --non-interactive --no-gpg-checks install "+ - "--auto-agree-with-licenses emacs=1.0") - @provider.install_package("emacs", "1.0") + "--auto-agree-with-licenses emacs=1.0" + ) + provider.install_package("emacs", "1.0") end it "should warn about gpg checks on zypper install" do expect(Chef::Log).to receive(:warn).with( - /All packages will be installed without gpg signature checks/) - expect(@provider).to receive(:shell_out!).with( + /All packages will be installed without gpg signature checks/ + ) + shell_out_expectation!( "zypper --non-interactive --no-gpg-checks install "+ - "--auto-agree-with-licenses emacs=1.0") - @provider.install_package("emacs", "1.0") + "--auto-agree-with-licenses emacs=1.0" + ) + provider.install_package("emacs", "1.0") end end describe "upgrade_package" do it "should run zypper update with the package name and version" do allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true) - expect(@provider).to receive(:shell_out!).with( - "zypper --non-interactive install --auto-agree-with-licenses emacs=1.0") - @provider.upgrade_package("emacs", "1.0") + shell_out_expectation!( + "zypper --non-interactive install --auto-agree-with-licenses emacs=1.0" + ) + provider.upgrade_package("emacs", "1.0") end it "should run zypper update without gpg checks" do allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false) - expect(@provider).to receive(:shell_out!).with( + shell_out_expectation!( "zypper --non-interactive --no-gpg-checks install "+ - "--auto-agree-with-licenses emacs=1.0") - @provider.upgrade_package("emacs", "1.0") + "--auto-agree-with-licenses emacs=1.0" + ) + provider.upgrade_package("emacs", "1.0") end it "should warn about gpg checks on zypper upgrade" do expect(Chef::Log).to receive(:warn).with( - /All packages will be installed without gpg signature checks/) - expect(@provider).to receive(:shell_out!).with( + /All packages will be installed without gpg signature checks/ + ) + shell_out_expectation!( "zypper --non-interactive --no-gpg-checks install "+ - "--auto-agree-with-licenses emacs=1.0") - @provider.upgrade_package("emacs", "1.0") + "--auto-agree-with-licenses emacs=1.0" + ) + provider.upgrade_package("emacs", "1.0") end it "should run zypper upgrade without gpg checks" do - expect(@provider).to receive(:shell_out!).with( + shell_out_expectation!( "zypper --non-interactive --no-gpg-checks install "+ - "--auto-agree-with-licenses emacs=1.0") - - @provider.upgrade_package("emacs", "1.0") + "--auto-agree-with-licenses emacs=1.0" + ) + provider.upgrade_package("emacs", "1.0") end end @@ -147,83 +171,94 @@ describe Chef::Provider::Package::Zypper do context "when package version is not explicitly specified" do it "should run zypper remove with the package name" do allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true) - expect(@provider).to receive(:shell_out!).with( - "zypper --non-interactive remove emacs") - @provider.remove_package("emacs", nil) + shell_out_expectation!( + "zypper --non-interactive remove emacs" + ) + provider.remove_package("emacs", nil) end end context "when package version is explicitly specified" do it "should run zypper remove with the package name" do allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true) - expect(@provider).to receive(:shell_out!).with( - "zypper --non-interactive remove emacs=1.0") - @provider.remove_package("emacs", "1.0") + shell_out_expectation!( + "zypper --non-interactive remove emacs=1.0" + ) + provider.remove_package("emacs", "1.0") end it "should run zypper remove without gpg checks" do allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false) - expect(@provider).to receive(:shell_out!).with( - "zypper --non-interactive --no-gpg-checks remove emacs=1.0") - @provider.remove_package("emacs", "1.0") + shell_out_expectation!( + "zypper --non-interactive --no-gpg-checks remove emacs=1.0" + ) + provider.remove_package("emacs", "1.0") end it "should warn about gpg checks on zypper remove" do expect(Chef::Log).to receive(:warn).with( - /All packages will be installed without gpg signature checks/) - expect(@provider).to receive(:shell_out!).with( - "zypper --non-interactive --no-gpg-checks remove emacs=1.0") - - @provider.remove_package("emacs", "1.0") + /All packages will be installed without gpg signature checks/ + ) + shell_out_expectation!( + "zypper --non-interactive --no-gpg-checks remove emacs=1.0" + ) + provider.remove_package("emacs", "1.0") end end end describe "purge_package" do it "should run remove_package with the name and version" do - expect(@provider).to receive(:shell_out!).with( - "zypper --non-interactive --no-gpg-checks remove --clean-deps emacs=1.0") - @provider.purge_package("emacs", "1.0") + shell_out_expectation!( + "zypper --non-interactive --no-gpg-checks remove --clean-deps emacs=1.0" + ) + provider.purge_package("emacs", "1.0") end it "should run zypper purge without gpg checks" do allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false) - expect(@provider).to receive(:shell_out!).with( - "zypper --non-interactive --no-gpg-checks remove --clean-deps emacs=1.0") - @provider.purge_package("emacs", "1.0") + shell_out_expectation!( + "zypper --non-interactive --no-gpg-checks remove --clean-deps emacs=1.0" + ) + provider.purge_package("emacs", "1.0") end it "should warn about gpg checks on zypper purge" do expect(Chef::Log).to receive(:warn).with( - /All packages will be installed without gpg signature checks/) - expect(@provider).to receive(:shell_out!).with( - "zypper --non-interactive --no-gpg-checks remove --clean-deps emacs=1.0") - @provider.purge_package("emacs", "1.0") + /All packages will be installed without gpg signature checks/ + ) + shell_out_expectation!( + "zypper --non-interactive --no-gpg-checks remove --clean-deps emacs=1.0" + ) + provider.purge_package("emacs", "1.0") end end describe "on an older zypper" do before(:each) do - allow(@provider).to receive(:`).and_return("0.11.6") + allow(provider).to receive(:`).and_return("0.11.6") end describe "install_package" do it "should run zypper install with the package name and version" do - expect(@provider).to receive(:shell_out!).with( - "zypper --no-gpg-checks install --auto-agree-with-licenses -y emacs") - @provider.install_package("emacs", "1.0") + shell_out_expectation!( + "zypper --no-gpg-checks install --auto-agree-with-licenses -y emacs" + ) + provider.install_package("emacs", "1.0") end end describe "upgrade_package" do it "should run zypper update with the package name and version" do - expect(@provider).to receive(:shell_out!).with( - "zypper --no-gpg-checks install --auto-agree-with-licenses -y emacs") - @provider.upgrade_package("emacs", "1.0") + shell_out_expectation!( + "zypper --no-gpg-checks install --auto-agree-with-licenses -y emacs" + ) + provider.upgrade_package("emacs", "1.0") end end describe "remove_package" do it "should run zypper remove with the package name" do - expect(@provider).to receive(:shell_out!).with( - "zypper --no-gpg-checks remove -y emacs") - @provider.remove_package("emacs", "1.0") + shell_out_expectation!( + "zypper --no-gpg-checks remove -y emacs" + ) + provider.remove_package("emacs", "1.0") end end end diff --git a/spec/unit/provider/package_spec.rb b/spec/unit/provider/package_spec.rb index 1633d18f9d..432d968906 100644 --- a/spec/unit/provider/package_spec.rb +++ b/spec/unit/provider/package_spec.rb @@ -37,6 +37,12 @@ describe Chef::Provider::Package do allow(@provider).to receive(:install_package).and_return(true) end + it "raises a Chef::Exceptions::InvalidResourceSpecification if both multipackage and source are provided" do + @new_resource.package_name(['a', 'b']) + @new_resource.source('foo') + expect { @provider.run_action(:install) }.to raise_error(Chef::Exceptions::InvalidResourceSpecification) + end + it "should raise a Chef::Exceptions::Package if no version is specified, and no candidate is available" do @provider.candidate_version = nil expect { @provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package) @@ -698,4 +704,38 @@ describe "Chef::Provider::Package - Multi" do expect(@new_resource).not_to be_updated_by_last_action end end + + describe "shell_out helpers" do + [ :shell_out_with_timeout, :shell_out_with_timeout! ].each do |method| + stubbed_method = method == :shell_out_with_timeout! ? :shell_out! : :shell_out + [ %w{command arg1 arg2}, "command arg1 arg2" ].each do |command| + it "#{method} defaults to 900 seconds" do + expect(@provider).to receive(stubbed_method).with(*command, timeout: 900) + @provider.send(method, *command) + end + it "#{method} overrides the default timeout with its options" do + expect(@provider).to receive(stubbed_method).with(*command, timeout: 1) + @provider.send(method, *command, timeout: 1) + end + it "#{method} overrides both timeouts with the new_resource.timeout" do + @new_resource.timeout(99) + expect(@provider).to receive(stubbed_method).with(*command, timeout: 99) + @provider.send(method, *command, timeout: 1) + end + it "#{method} defaults to 900 seconds and preserves options" do + expect(@provider).to receive(stubbed_method).with(*command, env: nil, timeout: 900) + @provider.send(method, *command, env: nil) + end + it "#{method} overrides the default timeout with its options and preserves options" do + expect(@provider).to receive(stubbed_method).with(*command, timeout: 1, env: nil) + @provider.send(method, *command, timeout: 1, env: nil) + end + it "#{method} overrides both timeouts with the new_resource.timeout and preseves options" do + @new_resource.timeout(99) + expect(@provider).to receive(stubbed_method).with(*command, timeout: 99, env: nil) + @provider.send(method, *command, timeout: 1, env: nil) + end + end + end + end end diff --git a/spec/unit/provider/powershell_spec.rb b/spec/unit/provider/powershell_spec.rb index 60dbcf80b0..855c18af9b 100644 --- a/spec/unit/provider/powershell_spec.rb +++ b/spec/unit/provider/powershell_spec.rb @@ -19,20 +19,62 @@ require 'spec_helper' describe Chef::Provider::PowershellScript, "action_run" do - before(:each) do - @node = Chef::Node.new + let(:powershell_version) { nil } + let(:node) { + node = Chef::Node.new + node.default["kernel"] = Hash.new + node.default["kernel"][:machine] = :x86_64.to_s + if ! powershell_version.nil? + node.default[:languages] = { :powershell => { :version => powershell_version } } + end + node + } - @node.default["kernel"] = Hash.new - @node.default["kernel"][:machine] = :x86_64.to_s + let(:provider) { + empty_events = Chef::EventDispatch::Dispatcher.new + run_context = Chef::RunContext.new(node, {}, empty_events) + new_resource = Chef::Resource::PowershellScript.new('run some powershell code', run_context) + Chef::Provider::PowershellScript.new(new_resource, run_context) + } - @run_context = Chef::RunContext.new(@node, {}, @events) - @new_resource = Chef::Resource::PowershellScript.new('run some powershell code', @run_context) + context 'when setting interpreter flags' do + it "should set the -File flag as the last flag" do + expect(provider.flags.split(' ').pop).to eq("-File") + end - @provider = Chef::Provider::PowershellScript.new(@new_resource, @run_context) - end + let(:execution_policy_flag) do + execution_policy_index = 0 + provider_flags = provider.flags.split(' ') + execution_policy_specified = false - it "should set the -File flag as the last flag" do - expect(@provider.flags.split(' ').pop).to eq("-File") - end + provider_flags.find do | value | + execution_policy_index += 1 + execution_policy_specified = value.downcase == '-ExecutionPolicy'.downcase + end + + execution_policy = execution_policy_specified ? provider_flags[execution_policy_index] : nil + end + context 'when running with an unspecified PowerShell version' do + let(:powershell_version) { nil } + it "should set the -ExecutionPolicy flag to 'Unrestricted' by default" do + expect(execution_policy_flag.downcase).to eq('unrestricted'.downcase) + end + end + + { '2.0' => 'Unrestricted', + '2.5' => 'Unrestricted', + '3.0' => 'Bypass', + '3.6' => 'Bypass', + '4.0' => 'Bypass', + '5.0' => 'Bypass' }.each do | version_policy | + let(:powershell_version) { version_policy[0].to_f } + context "when running PowerShell version #{version_policy[0]}" do + let(:powershell_version) { version_policy[0].to_f } + it "should set the -ExecutionPolicy flag to '#{version_policy[1]}'" do + expect(execution_policy_flag.downcase).to eq(version_policy[1].downcase) + end + end + end + end end diff --git a/spec/unit/provider/remote_directory_spec.rb b/spec/unit/provider/remote_directory_spec.rb index 4434714ebc..99e2fe285c 100644 --- a/spec/unit/provider/remote_directory_spec.rb +++ b/spec/unit/provider/remote_directory_spec.rb @@ -194,8 +194,8 @@ describe Chef::Provider::RemoteDirectory do expect(::File.exist?(symlinked_dir_path)).to be_falsey expect(::File.exist?(tmp_dir)).to be_truthy - rescue Chef::Exceptions::Win32APIError => e - pending "This must be run as an Administrator to create symlinks" + rescue Chef::Exceptions::Win32APIError + skip "This must be run as an Administrator to create symlinks" end end end diff --git a/spec/unit/provider/remote_file/fetcher_spec.rb b/spec/unit/provider/remote_file/fetcher_spec.rb index c049848fbf..8bd3b7c625 100644 --- a/spec/unit/provider/remote_file/fetcher_spec.rb +++ b/spec/unit/provider/remote_file/fetcher_spec.rb @@ -24,6 +24,26 @@ describe Chef::Provider::RemoteFile::Fetcher do let(:new_resource) { double("new resource") } let(:fetcher_instance) { double("fetcher") } + describe "when passed a network share" do + before do + expect(Chef::Provider::RemoteFile::NetworkFile).to receive(:new).and_return(fetcher_instance) + end + + context "when host is a name" do + let(:source) { "\\\\foohost\\fooshare\\Foo.tar.gz" } + it "returns a network file fetcher" do + expect(described_class.for_resource(source, new_resource, current_resource)).to eq(fetcher_instance) + end + end + + context "when host is an ip" do + let(:source) { "\\\\127.0.0.1\\fooshare\\Foo.tar.gz" } + it "returns a network file fetcher" do + expect(described_class.for_resource(source, new_resource, current_resource)).to eq(fetcher_instance) + end + end + end + describe "when passed an http url" do let(:uri) { double("uri", :scheme => "http" ) } before do @@ -72,4 +92,3 @@ describe Chef::Provider::RemoteFile::Fetcher do end end - diff --git a/spec/unit/provider/remote_file/local_file_spec.rb b/spec/unit/provider/remote_file/local_file_spec.rb index b33d82f624..575996a540 100644 --- a/spec/unit/provider/remote_file/local_file_spec.rb +++ b/spec/unit/provider/remote_file/local_file_spec.rb @@ -25,26 +25,45 @@ describe Chef::Provider::RemoteFile::LocalFile do let(:new_resource) { Chef::Resource::RemoteFile.new("local file backend test (new_resource)") } let(:current_resource) { Chef::Resource::RemoteFile.new("local file backend test (current_resource)") } subject(:fetcher) { Chef::Provider::RemoteFile::LocalFile.new(uri, new_resource, current_resource) } - - context "when parsing source path" do + + context "when parsing source path on windows" do + + before do + allow(Chef::Platform).to receive(:windows?).and_return(true) + end + describe "when given local unix path" do let(:uri) { URI.parse("file:///nyan_cat.png") } it "returns a correct unix path" do - expect(fetcher.fix_windows_path(uri.path)).to eq("/nyan_cat.png") + expect(fetcher.source_path).to eq("/nyan_cat.png") end end describe "when given local windows path" do let(:uri) { URI.parse("file:///z:/windows/path/file.txt") } it "returns a valid windows local path" do - expect(fetcher.fix_windows_path(uri.path)).to eq("z:/windows/path/file.txt") + expect(fetcher.source_path).to eq("z:/windows/path/file.txt") + end + end + + describe "when given local windows path with spaces" do + let(:uri) { URI.parse(URI.escape("file:///z:/windows/path/foo & bar.txt")) } + it "returns a valid windows local path" do + expect(fetcher.source_path).to eq("z:/windows/path/foo & bar.txt") end end describe "when given unc windows path" do let(:uri) { URI.parse("file:////server/share/windows/path/file.txt") } it "returns a valid windows unc path" do - expect(fetcher.fix_windows_path(uri.path)).to eq("//server/share/windows/path/file.txt") + expect(fetcher.source_path).to eq("//server/share/windows/path/file.txt") + end + end + + describe "when given unc windows path with spaces" do + let(:uri) { URI.parse(URI.escape("file:////server/share/windows/path/foo & bar.txt")) } + it "returns a valid windows unc path" do + expect(fetcher.source_path).to eq("//server/share/windows/path/foo & bar.txt") end end end @@ -73,7 +92,7 @@ describe Chef::Provider::RemoteFile::LocalFile do it "stages the local file to a temporary file" do expect(Chef::FileContentManagement::Tempfile).to receive(:new).with(new_resource).and_return(chef_tempfile) expect(::FileUtils).to receive(:cp).with(uri.path, tempfile.path) - expect(tempfile).to receive(:close) + expect(tempfile).to receive(:close) result = fetcher.fetch expect(result).to eq(tempfile) diff --git a/spec/unit/provider/remote_file/network_file_spec.rb b/spec/unit/provider/remote_file/network_file_spec.rb new file mode 100644 index 0000000000..3666a47468 --- /dev/null +++ b/spec/unit/provider/remote_file/network_file_spec.rb @@ -0,0 +1,45 @@ +# +# Author:: Jay Mundrawala (<jdm@chef.io>) +# Copyright:: Copyright (c) 2015 Chef Software +# 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::Provider::RemoteFile::NetworkFile do + + let(:source) { "\\\\foohost\\fooshare\\Foo.tar.gz" } + + let(:new_resource) { Chef::Resource::RemoteFile.new("network file (new_resource)") } + let(:current_resource) { Chef::Resource::RemoteFile.new("network file (current_resource)") } + subject(:fetcher) { Chef::Provider::RemoteFile::NetworkFile.new(source, new_resource, current_resource) } + + describe "when fetching the object" do + + let(:tempfile) { double("Tempfile", :path => "/tmp/foo/bar/Foo.tar.gz", :close => nil) } + let(:chef_tempfile) { double("Chef::FileContentManagement::Tempfile", :tempfile => tempfile) } + + it "stages the local file to a temporary file" do + expect(Chef::FileContentManagement::Tempfile).to receive(:new).with(new_resource).and_return(chef_tempfile) + expect(::FileUtils).to receive(:cp).with(source, tempfile.path) + expect(tempfile).to receive(:close) + + result = fetcher.fetch + expect(result).to eq(tempfile) + end + + end + +end diff --git a/spec/unit/provider/service/aix_service_spec.rb b/spec/unit/provider/service/aix_service_spec.rb index 796661145b..a0c8bb3407 100644 --- a/spec/unit/provider/service/aix_service_spec.rb +++ b/spec/unit/provider/service/aix_service_spec.rb @@ -51,22 +51,35 @@ describe Chef::Provider::Service::Aix do end it "current resource is running" do - expect(@provider).to receive(:shell_out!).with("lssrc -a | grep -w chef").and_return(@status) - expect(@provider).to receive(:is_resource_group?).with(["chef chef 12345 active"]) + expect(@provider).to receive(:shell_out!).with("lssrc -s chef").and_return(@status) + expect(@provider).to receive(:is_resource_group?).and_return false @provider.load_current_resource expect(@current_resource.running).to be_truthy end end - context "when the service is inoprative" do + context "when the service is inoperative" do before do @status = double("Status", :exitstatus => 0, :stdout => "chef chef inoperative\n") end it "current resource is not running" do - expect(@provider).to receive(:shell_out!).with("lssrc -a | grep -w chef").and_return(@status) - expect(@provider).to receive(:is_resource_group?).with(["chef chef inoperative"]) + expect(@provider).to receive(:shell_out!).with("lssrc -s chef").and_return(@status) + expect(@provider).to receive(:is_resource_group?).and_return false + + @provider.load_current_resource + expect(@current_resource.running).to be_falsey + end + end + + context "when there is no such service" do + before do + @status = double("Status", :exitstatus => 1, :stdout => "0513-085 The chef Subsystem is not on file.\n") + end + it "current resource is not running" do + expect(@provider).to receive(:shell_out!).with("lssrc -s chef").and_return(@status) + expect(@provider).to receive(:is_resource_group?).and_return false @provider.load_current_resource expect(@current_resource.running).to be_falsey @@ -75,13 +88,13 @@ describe Chef::Provider::Service::Aix do end describe "is resource group" do - context "when there are mutiple subsystems associated with group" do + context "when there are multiple subsystems associated with group" do before do @status = double("Status", :exitstatus => 0, :stdout => "chef1 chef 12345 active\nchef2 chef 12334 active\nchef3 chef inoperative") end it "service is a group" do - expect(@provider).to receive(:shell_out!).with("lssrc -a | grep -w chef").and_return(@status) + expect(@provider).to receive(:shell_out!).with("lssrc -g chef").and_return(@status) @provider.load_current_resource expect(@provider.instance_eval("@is_resource_group")).to be_truthy end @@ -93,19 +106,21 @@ describe Chef::Provider::Service::Aix do end it "service is a group" do - expect(@provider).to receive(:shell_out!).with("lssrc -a | grep -w chef").and_return(@status) + expect(@provider).to receive(:shell_out!).with("lssrc -g chef").and_return(@status) @provider.load_current_resource expect(@provider.instance_eval("@is_resource_group")).to be_truthy end end - context "when there service is a subsytem" do + context "when the service is a subsystem" do before do - @status = double("Status", :exitstatus => 0, :stdout => "chef chef123 inoperative\n") + @group_status = double("Status", :exitstatus => 1, :stdout => "0513-086 The chef Group is not on file.\n") + @service_status = double("Status", :exitstatus => 0, :stdout => "chef chef inoperative\n") end it "service is a subsystem" do - expect(@provider).to receive(:shell_out!).with("lssrc -a | grep -w chef").and_return(@status) + expect(@provider).to receive(:shell_out!).with("lssrc -g chef").and_return(@group_status) + expect(@provider).to receive(:shell_out!).with("lssrc -s chef").and_return(@service_status) @provider.load_current_resource expect(@provider.instance_eval("@is_resource_group")).to be_falsey end diff --git a/spec/unit/provider/service/freebsd_service_spec.rb b/spec/unit/provider/service/freebsd_service_spec.rb index 5a55425d87..cfc28c94d5 100644 --- a/spec/unit/provider/service/freebsd_service_spec.rb +++ b/spec/unit/provider/service/freebsd_service_spec.rb @@ -189,18 +189,6 @@ PS_SAMPLE expect(provider.status_load_success).to be_nil end - context "when ps command is nil" do - before do - node.automatic_attrs[:command] = {:ps => nil} - end - - it "should set running to nil" do - pending "superclass raises no conversion of nil to string which seems broken" - provider.determine_current_status! - expect(current_resource.running).to be_nil - end - end - context "when ps is empty string" do before do node.automatic_attrs[:command] = {:ps => ""} diff --git a/spec/unit/provider/user/dscl_spec.rb b/spec/unit/provider/user/dscl_spec.rb index 5ea037d944..32d0812d8c 100644 --- a/spec/unit/provider/user/dscl_spec.rb +++ b/spec/unit/provider/user/dscl_spec.rb @@ -24,7 +24,7 @@ require 'mixlib/shellout' describe Chef::Provider::User::Dscl do before do - allow(Chef::Platform).to receive(:windows?) { false } + allow(ChefConfig).to receive(:windows?) { false } end let(:node) { node = Chef::Node.new diff --git a/spec/unit/provider/user_spec.rb b/spec/unit/provider/user_spec.rb index 381168647b..2345ce18fb 100644 --- a/spec/unit/provider/user_spec.rb +++ b/spec/unit/provider/user_spec.rb @@ -143,8 +143,8 @@ describe Chef::Provider::User do begin require 'rubygems' require 'shadow' - rescue LoadError => e - pending "ruby-shadow gem not installed for dynamic load test" + rescue LoadError + skip "ruby-shadow gem not installed for dynamic load test" true else false @@ -161,7 +161,7 @@ describe Chef::Provider::User do unless shadow_lib_unavail? context "and we have the ruby-shadow gem" do - pending "and we are not root (rerun this again as root)", :requires_unprivileged_user => true + skip "and we are not root (rerun this again as root)", :requires_unprivileged_user => true context "and we are root", :requires_root => true do it "should pass assertions when ruby-shadow can be loaded" do diff --git a/spec/unit/provider_resolver_spec.rb b/spec/unit/provider_resolver_spec.rb index 718eebfdf4..e18d69bc19 100644 --- a/spec/unit/provider_resolver_spec.rb +++ b/spec/unit/provider_resolver_spec.rb @@ -19,9 +19,13 @@ require 'spec_helper' require 'chef/mixin/convert_to_class_name' require 'chef/provider_resolver' +require 'chef/platform/service_helpers' include Chef::Mixin::ConvertToClassName +# Open up Provider so we can write things down easier in here +#module Chef::Provider + describe Chef::ProviderResolver do let(:node) do @@ -46,6 +50,55 @@ describe Chef::ProviderResolver do let(:resource) { double(Chef::Resource, provider: provider, resource_name: resource_name) } + before do + allow(resource).to receive(:is_a?).with(Chef::Resource).and_return(true) + end + + def self.on_platform(platform, *tags, + platform_version: '11.0.1', + platform_family: nil, + os: nil, + &block) + Array(platform).each do |platform| + Array(platform_version).each do |platform_version| + on_one_platform(platform, platform_version, platform_family || platform, os || platform_family || platform, *tags, &block) + end + end + end + + def self.on_one_platform(platform, platform_version, platform_family, os, *tags, &block) + describe "on #{platform} #{platform_version}, platform_family: #{platform_family}, os: #{os}", *tags do + let(:os) { os } + let(:platform) { platform } + let(:platform_family) { platform_family } + let(:platform_version) { platform_version } + + define_singleton_method(:os) { os } + define_singleton_method(:platform) { platform } + define_singleton_method(:platform_family) { platform_family } + define_singleton_method(:platform_version) { platform_version } + + instance_eval(&block) + end + end + + def self.expect_providers(**providers) + providers.each do |name, provider| + describe name.to_s do + let(:resource_name) { name } + if provider + it "resolves to a #{provider}" do + expect(resolved_provider).to eql(provider) + end + else + it "Fails to resolve (since #{name.inspect} is unsupported on #{platform} #{platform_version})" do + expect { resolved_provider }.to raise_error /Cannot find a provider/ + end + end + end + end + end + describe "resolving service resource" do def stub_service_providers(*services) services ||= [] @@ -60,7 +113,6 @@ describe Chef::ProviderResolver do end before do - expect(provider_resolver).not_to receive(:maybe_chef_platform_lookup) allow(resource).to receive(:service_name).and_return("ntp") end @@ -297,389 +349,477 @@ describe Chef::ProviderResolver do end end - describe "on Ubuntu 14.10" do - let(:os) { "linux" } - let(:platform) { "ubuntu" } - let(:platform_family) { "debian" } - let(:platform_version) { "14.04" } - + on_platform "ubuntu", platform_version: "14.10", platform_family: "debian", os: "linux" do it_behaves_like "an ubuntu platform with upstart, update-rc.d and systemd" end - describe "on Ubuntu 14.04" do - let(:os) { "linux" } - let(:platform) { "ubuntu" } - let(:platform_family) { "debian" } - let(:platform_version) { "14.04" } - + on_platform "ubuntu", platform_version: "14.04", platform_family: "debian", os: "linux" do it_behaves_like "an ubuntu platform with upstart and update-rc.d" end - describe "on Ubuntu 10.04" do - let(:os) { "linux" } - let(:platform) { "ubuntu" } - let(:platform_family) { "debian" } - let(:platform_version) { "10.04" } - + on_platform "ubuntu", platform_version: "10.04", platform_family: "debian", os: "linux" do it_behaves_like "an ubuntu platform with upstart and update-rc.d" end # old debian uses the Debian provider (does not have insserv or upstart, or update-rc.d???) - describe "on Debian 4.0" do - let(:os) { "linux" } - let(:platform) { "debian" } - let(:platform_family) { "debian" } - let(:platform_version) { "4.0" } - + on_platform "debian", platform_version: "4.0", os: "linux" do #it_behaves_like "a debian platform using the debian provider" end # Debian replaced the debian provider with insserv in the FIXME:VERSION distro - describe "on Debian 7.0" do - let(:os) { "linux" } - let(:platform) { "debian" } - let(:platform_family) { "debian" } - let(:platform_version) { "7.0" } - + on_platform "debian", platform_version: "7.0", os: "linux" do it_behaves_like "a debian platform using the insserv provider" end - %w{solaris2 openindiana opensolaris nexentacore omnios smartos}.each do |platform| - describe "on #{platform}" do - let(:os) { "solaris2" } - let(:platform) { platform } - let(:platform_family) { platform } - let(:platform_version) { "5.11" } - - it "returns a Solaris provider" do - stub_service_providers - stub_service_configs - expect(resolved_provider).to eql(Chef::Provider::Service::Solaris) - end + on_platform %w{solaris2 openindiana opensolaris nexentacore omnios smartos}, os: "solaris2", platform_version: "5.11" do + it "returns a Solaris provider" do + stub_service_providers + stub_service_configs + expect(resolved_provider).to eql(Chef::Provider::Service::Solaris) + end - it "always returns a Solaris provider" do - # no matter what we stub on the next two lines we should get a Solaris provider - stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd) - stub_service_configs(:initd, :upstart, :xinetd, :user_local_etc_rcd, :systemd) - expect(resolved_provider).to eql(Chef::Provider::Service::Solaris) - end + it "always returns a Solaris provider" do + # no matter what we stub on the next two lines we should get a Solaris provider + stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd) + stub_service_configs(:initd, :upstart, :xinetd, :user_local_etc_rcd, :systemd) + expect(resolved_provider).to eql(Chef::Provider::Service::Solaris) end end - %w{mswin mingw32 windows}.each do |platform| - describe "on #{platform}" do - let(:os) { "windows" } - let(:platform) { platform } - let(:platform_family) { "windows" } - let(:platform_version) { "5.11" } - - it "returns a Windows provider" do - stub_service_providers - stub_service_configs - expect(resolved_provider).to eql(Chef::Provider::Service::Windows) - end + on_platform %w{mswin mingw32 windows}, platform_family: "windows", platform_version: "5.11" do + it "returns a Windows provider" do + stub_service_providers + stub_service_configs + expect(resolved_provider).to eql(Chef::Provider::Service::Windows) + end - it "always returns a Windows provider" do - # no matter what we stub on the next two lines we should get a Windows provider - stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd) - stub_service_configs(:initd, :upstart, :xinetd, :user_local_etc_rcd, :systemd) - expect(resolved_provider).to eql(Chef::Provider::Service::Windows) - end + it "always returns a Windows provider" do + # no matter what we stub on the next two lines we should get a Windows provider + stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd) + stub_service_configs(:initd, :upstart, :xinetd, :user_local_etc_rcd, :systemd) + expect(resolved_provider).to eql(Chef::Provider::Service::Windows) end end - %w{mac_os_x mac_os_x_server}.each do |platform| - describe "on #{platform}" do - let(:os) { "darwin" } - let(:platform) { platform } - let(:platform_family) { "mac_os_x" } - let(:platform_version) { "10.9.2" } - - it "returns a Macosx provider" do - stub_service_providers - stub_service_configs - expect(resolved_provider).to eql(Chef::Provider::Service::Macosx) - end + on_platform %w{mac_os_x mac_os_x_server}, os: "darwin", platform_family: "mac_os_x", platform_version: "10.9.2" do + it "returns a Macosx provider" do + stub_service_providers + stub_service_configs + expect(resolved_provider).to eql(Chef::Provider::Service::Macosx) + end - it "always returns a Macosx provider" do - # no matter what we stub on the next two lines we should get a Macosx provider - stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd) - stub_service_configs(:initd, :upstart, :xinetd, :user_local_etc_rcd, :systemd) - expect(resolved_provider).to eql(Chef::Provider::Service::Macosx) - end + it "always returns a Macosx provider" do + # no matter what we stub on the next two lines we should get a Macosx provider + stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd) + stub_service_configs(:initd, :upstart, :xinetd, :user_local_etc_rcd, :systemd) + expect(resolved_provider).to eql(Chef::Provider::Service::Macosx) end end - %w{freebsd netbsd}.each do |platform| - describe "on #{platform}" do - let(:os) { platform } - let(:platform) { platform } - let(:platform_family) { platform } - let(:platform_version) { "10.0-RELEASE" } - - it "returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do - stub_service_providers - stub_service_configs(:usr_local_etc_rcd) - expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) - end + on_platform %w(freebsd netbsd), platform_version: '3.1.4' do + it "returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do + stub_service_providers + stub_service_configs(:usr_local_etc_rcd) + expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) + end - it "returns a Freebsd provider if it finds the /etc/rc.d initscript" do - stub_service_providers - stub_service_configs(:etc_rcd) - expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) - end + it "returns a Freebsd provider if it finds the /etc/rc.d initscript" do + stub_service_providers + stub_service_configs(:etc_rcd) + expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) + end - it "always returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do - # should only care about :usr_local_etc_rcd stub in the service configs - stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd) - stub_service_configs(:usr_local_etc_rcd, :initd, :upstart, :xinetd, :systemd) - expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) - end + it "always returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do + # should only care about :usr_local_etc_rcd stub in the service configs + stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd) + stub_service_configs(:usr_local_etc_rcd, :initd, :upstart, :xinetd, :systemd) + expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) + end - it "always returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do - # should only care about :etc_rcd stub in the service configs - stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd) - stub_service_configs(:etc_rcd, :initd, :upstart, :xinetd, :systemd) - expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) - end + it "always returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do + # should only care about :etc_rcd stub in the service configs + stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd) + stub_service_configs(:etc_rcd, :initd, :upstart, :xinetd, :systemd) + expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) + end - it "foo" do - stub_service_providers - stub_service_configs - expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) - end + it "foo" do + stub_service_providers + stub_service_configs + expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) end end end - describe "for the package provider" do - let(:resource_name) { :package } - - before do - expect(provider_resolver).not_to receive(:maybe_chef_platform_lookup) - end - - %w{mac_os_x mac_os_x_server}.each do |platform| - describe "on #{platform}" do - let(:os) { "darwin" } - let(:platform) { platform } - let(:platform_family) { "mac_os_x" } - let(:platform_version) { "10.9.2" } - - - it "returns a Chef::Provider::Package::Homebrew provider" do - expect(resolved_provider).to eql(Chef::Provider::Package::Homebrew) - end - end - end - end + PROVIDERS = + { + bash: Chef::Provider::Script, + breakpoint: Chef::Provider::Breakpoint, + chef_gem: Chef::Provider::Package::Rubygems, + cookbook_file: Chef::Provider::CookbookFile, + csh: Chef::Provider::Script, + deploy: Chef::Provider::Deploy::Timestamped, + deploy_revision: Chef::Provider::Deploy::Revision, + directory: Chef::Provider::Directory, + easy_install_package: Chef::Provider::Package::EasyInstall, + erl_call: Chef::Provider::ErlCall, + execute: Chef::Provider::Execute, + file: Chef::Provider::File, + gem_package: Chef::Provider::Package::Rubygems, + git: Chef::Provider::Git, + group: Chef::Provider::Group::Gpasswd, + homebrew_package: Chef::Provider::Package::Homebrew, + http_request: Chef::Provider::HttpRequest, + ifconfig: Chef::Provider::Ifconfig, + link: Chef::Provider::Link, + log: Chef::Provider::Log::ChefLog, + macports_package: Chef::Provider::Package::Macports, + mdadm: Chef::Provider::Mdadm, + mount: Chef::Provider::Mount::Mount, + perl: Chef::Provider::Script, + portage_package: Chef::Provider::Package::Portage, + python: Chef::Provider::Script, + remote_directory: Chef::Provider::RemoteDirectory, + route: Chef::Provider::Route, + rpm_package: Chef::Provider::Package::Rpm, + ruby: Chef::Provider::Script, + ruby_block: Chef::Provider::RubyBlock, + script: Chef::Provider::Script, + subversion: Chef::Provider::Subversion, + template: Chef::Provider::Template, + timestamped_deploy: Chef::Provider::Deploy::Timestamped, + user: Chef::Provider::User::Useradd, + whyrun_safe_ruby_block: Chef::Provider::WhyrunSafeRubyBlock, + + # We want to check that these are unsupported: + apt_package: nil, + bff_package: nil, + dsc_script: nil, + dpkg_package: nil, + ips_package: nil, + pacman_package: nil, + paludis_package: nil, + rpm_package: nil, + smartos_package: nil, + solaris_package: nil, + yum_package: nil, + windows_package: nil, + windows_service: nil, + + "linux" => { + apt_package: Chef::Provider::Package::Apt, + dpkg_package: Chef::Provider::Package::Dpkg, + pacman_package: Chef::Provider::Package::Pacman, + paludis_package: Chef::Provider::Package::Paludis, + rpm_package: Chef::Provider::Package::Rpm, + yum_package: Chef::Provider::Package::Yum, + + "debian" => { + ifconfig: Chef::Provider::Ifconfig::Debian, + package: Chef::Provider::Package::Apt, +# service: Chef::Provider::Service::Debian, + + "debian" => { + "7.0" => { + }, + "6.0" => { + ifconfig: Chef::Provider::Ifconfig, +# service: Chef::Provider::Service::Insserv, + }, + "5.0" => { + ifconfig: Chef::Provider::Ifconfig, + }, + }, + "gcel" => { + "3.1.4" => { + ifconfig: Chef::Provider::Ifconfig, + }, + }, + "linaro" => { + "3.1.4" => { + ifconfig: Chef::Provider::Ifconfig, + }, + }, + "linuxmint" => { + "3.1.4" => { + ifconfig: Chef::Provider::Ifconfig, +# service: Chef::Provider::Service::Upstart, + }, + }, + "raspbian" => { + "3.1.4" => { + ifconfig: Chef::Provider::Ifconfig, + }, + }, + "ubuntu" => { + "11.10" => { + }, + "10.04" => { + ifconfig: Chef::Provider::Ifconfig, + }, + }, + }, + + "arch" => { + package: Chef::Provider::Package::Pacman, + + "arch" => { + "3.1.4" => { + } + }, + }, + + "freebsd" => { + group: Chef::Provider::Group::Pw, + user: Chef::Provider::User::Pw, + + "freebsd" => { + "3.1.4" => { + }, + }, + }, + "suse" => { + group: Chef::Provider::Group::Gpasswd, + "suse" => { + "12.0" => { + }, + %w(11.1 11.2 11.3) => { + group: Chef::Provider::Group::Suse, + }, + }, + "opensuse" => { +# service: Chef::Provider::Service::Redhat, + package: Chef::Provider::Package::Zypper, + group: Chef::Provider::Group::Usermod, + "12.3" => { + }, + "12.2" => { + group: Chef::Provider::Group::Suse, + }, + }, + }, + + "gentoo" => { + package: Chef::Provider::Package::Portage, + portage_package: Chef::Provider::Package::Portage, +# service: Chef::Provider::Service::Gentoo, + + "gentoo" => { + "3.1.4" => { + }, + }, + }, + + "rhel" => { +# service: Chef::Provider::Service::Systemd, + package: Chef::Provider::Package::Yum, + ifconfig: Chef::Provider::Ifconfig::Redhat, + + %w(amazon xcp xenserver ibm_powerkvm cloudlinux parallels) => { + "3.1.4" => { +# service: Chef::Provider::Service::Redhat, + }, + }, + %w(redhat centos scientific oracle) => { + "7.0" => { + }, + "6.0" => { +# service: Chef::Provider::Service::Redhat, + }, + }, + "fedora" => { + "15.0" => { + }, + "14.0" => { +# service: Chef::Provider::Service::Redhat, + }, + }, + }, - provider_mapping = { - "mac_os_x" => { - :package => Chef::Provider::Package::Homebrew, - :user => Chef::Provider::User::Dscl, - :group => Chef::Provider::Group::Dscl, - }, - "mac_os_x_server" => { - :package => Chef::Provider::Package::Homebrew, - :user => Chef::Provider::User::Dscl, - :group => Chef::Provider::Group::Dscl, - }, - "mswin" => { - :env => Chef::Provider::Env::Windows, - :user => Chef::Provider::User::Windows, - :group => Chef::Provider::Group::Windows, - :mount => Chef::Provider::Mount::Windows, - :batch => Chef::Provider::Batch, - :powershell_script => Chef::Provider::PowershellScript, }, - "mingw32" => { - :env => Chef::Provider::Env::Windows, - :user => Chef::Provider::User::Windows, - :group => Chef::Provider::Group::Windows, - :mount => Chef::Provider::Mount::Windows, - :batch => Chef::Provider::Batch, - :powershell_script => Chef::Provider::PowershellScript, + + "darwin" => { + %w(mac_os_x mac_os_x_server) => { + group: Chef::Provider::Group::Dscl, + package: Chef::Provider::Package::Homebrew, + user: Chef::Provider::User::Dscl, + + "mac_os_x" => { + "10.9.2" => { + }, + }, + }, }, + "windows" => { - :env => Chef::Provider::Env::Windows, - :user => Chef::Provider::User::Windows, - :group => Chef::Provider::Group::Windows, - :mount => Chef::Provider::Mount::Windows, - :batch => Chef::Provider::Batch, - :powershell_script => Chef::Provider::PowershellScript, + batch: Chef::Provider::Batch, + dsc_script: Chef::Provider::DscScript, + env: Chef::Provider::Env::Windows, + group: Chef::Provider::Group::Windows, + mount: Chef::Provider::Mount::Windows, + package: Chef::Provider::Package::Windows, + powershell_script: Chef::Provider::PowershellScript, + service: Chef::Provider::Service::Windows, + user: Chef::Provider::User::Windows, + windows_package: Chef::Provider::Package::Windows, + windows_service: Chef::Provider::Service::Windows, + + "windows" => { + %w(mswin mingw32 windows) => { + "10.9.2" => { + }, + }, + }, }, + "aix" => { - :cron => Chef::Provider::Cron::Aix, - }, - "netbsd"=> { - :group => Chef::Provider::Group::Groupmod, - }, - "openbsd" => { - :group => Chef::Provider::Group::Usermod, - :package => Chef::Provider::Package::Openbsd, + bff_package: Chef::Provider::Package::Aix, + cron: Chef::Provider::Cron::Aix, + group: Chef::Provider::Group::Aix, + ifconfig: Chef::Provider::Ifconfig::Aix, + mount: Chef::Provider::Mount::Aix, + package: Chef::Provider::Package::Aix, + rpm_package: Chef::Provider::Package::Rpm, + user: Chef::Provider::User::Aix, +# service: Chef::Provider::Service::Aix, + + "aix" => { + "aix" => { + "5.6" => { + }, + }, + }, }, - } - - def self.do_platform(platform_hash) - platform_hash.each do |resource, provider| - describe "for #{resource}" do - let(:resource_name) { resource } - - it "resolves to a #{provider}" do - expect(resolved_provider).to eql(provider) - end - end - end - end - - describe "individual platform mappings" do - let(:resource_name) { :user } - - before do - expect(provider_resolver).not_to receive(:maybe_chef_platform_lookup) - end - - %w{mac_os_x mac_os_x_server}.each do |platform| - describe "on #{platform}" do - let(:os) { "darwin" } - let(:platform) { platform } - let(:platform_family) { "mac_os_x" } - let(:platform_version) { "10.9.2" } - do_platform(provider_mapping[platform]) - end - end - - %w{mswin mingw32 windows}.each do |platform| - describe "on #{platform}" do - let(:os) { "windows" } - let(:platform) { platform } - let(:platform_family) { "windows" } - let(:platform_version) { "10.9.2" } + "hpux" => { + "hpux" => { + "hpux" => { + "3.1.4" => { + group: Chef::Provider::Group::Usermod + } + } + } + }, - do_platform(provider_mapping[platform]) - end - end + "netbsd" => { + "netbsd" => { + "netbsd" => { + "3.1.4" => { + group: Chef::Provider::Group::Groupmod, + }, + }, + }, + }, - describe "on AIX" do - let(:os) { "aix" } - let(:platform) { "aix" } - let(:platform_family) { "aix" } - let(:platform_version) { "6.2" } + "openbsd" => { + group: Chef::Provider::Group::Usermod, + package: Chef::Provider::Package::Openbsd, + + "openbsd" => { + "openbsd" => { + "3.1.4" => { + }, + }, + }, + }, - do_platform(provider_mapping['aix']) - end + "solaris2" => { + group: Chef::Provider::Group::Usermod, + ips_package: Chef::Provider::Package::Ips, + package: Chef::Provider::Package::Ips, + mount: Chef::Provider::Mount::Solaris, + solaris_package: Chef::Provider::Package::Solaris, + + "smartos" => { + smartos_package: Chef::Provider::Package::SmartOS, + package: Chef::Provider::Package::SmartOS, + + "smartos" => { + "3.1.4" => { + }, + }, + }, + + "solaris2" => { + "nexentacore" => { + "3.1.4" => { + package: Chef::Provider::Package::Solaris, + }, + }, + "omnios" => { + "3.1.4" => { + user: Chef::Provider::User::Solaris, + } + }, + "openindiana" => { + "3.1.4" => { + }, + }, + "opensolaris" => { + "3.1.4" => { + }, + }, + "solaris2" => { + user: Chef::Provider::User::Solaris, + "5.11" => { + }, + "5.9" => { + package: Chef::Provider::Package::Solaris, + }, + }, + }, - %w{netbsd openbsd}.each do |platform| - describe "on #{platform}" do - let(:os) { platform } - let(:platform) { platform } - let(:platform_family) { platform } - let(:platform_version) { "10.0-RELEASE" } + }, - do_platform(provider_mapping[platform]) - end - end - end + "solaris" => { + "solaris" => { + "solaris" => { + "3.1.4" => { + }, + }, + }, + }, - describe "resolving static providers" do - def resource_class(resource) - Chef::Resource.const_get(convert_to_class_name(resource.to_s)) - end - static_mapping = { - apt_package: Chef::Provider::Package::Apt, - bash: Chef::Provider::Script, - bff_package: Chef::Provider::Package::Aix, - breakpoint: Chef::Provider::Breakpoint, - chef_gem: Chef::Provider::Package::Rubygems, - cookbook_file: Chef::Provider::CookbookFile, - csh: Chef::Provider::Script, - deploy: Chef::Provider::Deploy::Timestamped, - deploy_revision: Chef::Provider::Deploy::Revision, - directory: Chef::Provider::Directory, - dpkg_package: Chef::Provider::Package::Dpkg, - dsc_script: Chef::Provider::DscScript, - easy_install_package: Chef::Provider::Package::EasyInstall, - erl_call: Chef::Provider::ErlCall, - execute: Chef::Provider::Execute, - file: Chef::Provider::File, - gem_package: Chef::Provider::Package::Rubygems, - git: Chef::Provider::Git, - homebrew_package: Chef::Provider::Package::Homebrew, - http_request: Chef::Provider::HttpRequest, - ips_package: Chef::Provider::Package::Ips, - link: Chef::Provider::Link, - log: Chef::Provider::Log::ChefLog, - macports_package: Chef::Provider::Package::Macports, - mdadm: Chef::Provider::Mdadm, - pacman_package: Chef::Provider::Package::Pacman, - paludis_package: Chef::Provider::Package::Paludis, - perl: Chef::Provider::Script, - portage_package: Chef::Provider::Package::Portage, - python: Chef::Provider::Script, - remote_directory: Chef::Provider::RemoteDirectory, - route: Chef::Provider::Route, - rpm_package: Chef::Provider::Package::Rpm, - ruby: Chef::Provider::Script, - ruby_block: Chef::Provider::RubyBlock, - script: Chef::Provider::Script, - smartos_package: Chef::Provider::Package::SmartOS, - solaris_package: Chef::Provider::Package::Solaris, - subversion: Chef::Provider::Subversion, - template: Chef::Provider::Template, - timestamped_deploy: Chef::Provider::Deploy::Timestamped, - whyrun_safe_ruby_block: Chef::Provider::WhyrunSafeRubyBlock, - windows_package: Chef::Provider::Package::Windows, - windows_service: Chef::Provider::Service::Windows, - yum_package: Chef::Provider::Package::Yum, + "exherbo" => { + "exherbo" => { + "exherbo" => { + "3.1.4" => { + package: Chef::Provider::Package::Paludis + } + } } + } + } - describe "on Ubuntu 14.04" do - let(:os) { "linux" } - let(:platform) { "ubuntu" } - let(:platform_family) { "debian" } - let(:platform_version) { "14.04" } - - supported_providers = [ - :apt_package, :bash, :breakpoint, :chef_gem, :cookbook_file, :csh, :deploy, - :deploy_revision, :directory, :dpkg_package, :easy_install_package, :erl_call, - :execute, :file, :gem_package, :git, :homebrew_package, :http_request, :link, - :log, :macports_package, :pacman_package, :paludis_package, :perl, :python, - :remote_directory, :route, :rpm_package, :ruby, :ruby_block, :script, :subversion, - :template, :timestamped_deploy, :whyrun_safe_ruby_block, :yum_package, - ] - - supported_providers.each do |static_resource| - static_provider = static_mapping[static_resource] - context "when the resource is a #{static_resource}" do - let(:resource) { double(Chef::Resource, provider: nil, resource_name: static_resource) } - let(:action) { :start } # in reality this doesn't matter much - it "should resolve to a #{static_provider} provider" do - expect(provider_resolver).not_to receive(:maybe_chef_platform_lookup) - expect(resolved_provider).to eql(static_provider) + def self.create_provider_tests(providers, test, expected, filter) + expected = expected.merge(providers.select { |key, value| key.is_a?(Symbol) }) + providers.each do |key, value| + if !key.is_a?(Symbol) + next_test = test.merge({ filter => key }) + next_filter = + case filter + when :os + :platform_family + when :platform_family + :platform + when :platform + :platform_version + when :platform_version + nil + else + raise "Hash too deep; only os, platform_family, platform and platform_version supported" end - end + create_provider_tests(value, next_test, expected, next_filter) end - - unsupported_providers = [ - :bff_package, :dsc_script, :ips_package, :smartos_package, - :solaris_package, :windows_package, :windows_service, - ] - - unsupported_providers.each do |static_resource| - static_provider = static_mapping[static_resource] - context "when the resource is a #{static_resource}" do - let(:resource) { double(Chef::Resource, provider: nil, resource_name: static_resource) } - let(:action) { :start } # in reality this doesn't matter much - it "should fall back into the old provider mapper code and hooks" do - retval = Object.new - expect(provider_resolver).to receive(:maybe_chef_platform_lookup).and_return(retval) - expect(resolved_provider).to equal(retval) - end - end + end + # If there is no filter, we're as deep as we need to go + if !filter + on_platform test.delete(:platform), test do + expect_providers(expected) end end end + + create_provider_tests(PROVIDERS, {}, {}, :os) end diff --git a/spec/unit/provider_spec.rb b/spec/unit/provider_spec.rb index 5a21b094d0..d7a34bc21b 100644 --- a/spec/unit/provider_spec.rb +++ b/spec/unit/provider_spec.rb @@ -49,6 +49,13 @@ class ConvergeActionDemonstrator < Chef::Provider end end +class CheckResourceSemanticsDemonstrator < ConvergeActionDemonstrator + def check_resource_semantics! + raise Chef::Exceptions::InvalidResourceSpecification.new("check_resource_semantics!") + end +end + + describe Chef::Provider do before(:each) do @cookbook_collection = Chef::CookbookCollection.new([]) @@ -89,6 +96,10 @@ describe Chef::Provider do expect(@provider.send(:whyrun_supported?)).to eql(false) end + it "should do nothing for check_resource_semantics! by default" do + expect { @provider.check_resource_semantics! }.not_to raise_error + end + it "should return true for action_nothing" do expect(@provider.action_nothing).to eql(true) end @@ -176,6 +187,15 @@ describe Chef::Provider do expect(@resource).not_to be_updated_by_last_action end end + + describe "and the resource is invalid" do + let(:provider) { CheckResourceSemanticsDemonstrator.new(@resource, @run_context) } + + it "fails with InvalidResourceSpecification when run" do + expect { provider.run_action(:foo) }.to raise_error(Chef::Exceptions::InvalidResourceSpecification) + end + + end end end diff --git a/spec/unit/recipe_spec.rb b/spec/unit/recipe_spec.rb index 7442f4477e..ee98e63c1f 100644 --- a/spec/unit/recipe_spec.rb +++ b/spec/unit/recipe_spec.rb @@ -83,7 +83,7 @@ describe Chef::Recipe do it "should require a name argument" do expect { recipe.cat - }.to raise_error(ArgumentError, "You must supply a name when declaring a cat resource") + }.to raise_error(ArgumentError) end it "should allow regular errors (not NameErrors) to pass unchanged" do @@ -121,7 +121,7 @@ describe Chef::Recipe do it "locate resource for particular platform" do ShaunTheSheep = Class.new(Chef::Resource) - ShaunTheSheep.provides :laughter, :on_platforms => ["television"] + ShaunTheSheep.provides :laughter, :platform => ["television"] node.automatic[:platform] = "television" node.automatic[:platform_version] = "123" res = recipe.laughter "timmy" @@ -141,9 +141,7 @@ describe Chef::Recipe do before do node.automatic[:platform] = "nbc_sports" Sounders = Class.new(Chef::Resource) - Sounders.provides :football, platform: "nbc_sports" TottenhamHotspur = Class.new(Chef::Resource) - TottenhamHotspur.provides :football, platform: "nbc_sports" end after do @@ -151,16 +149,12 @@ describe Chef::Recipe do Object.send(:remove_const, :TottenhamHotspur) end - it "warns if resolution of the two resources is ambiguous" do - expect(Chef::Log).to receive(:warn).at_least(:once).with(/Ambiguous resource precedence/) - res1 = recipe.football "club world cup" - expect(res1.name).to eql("club world cup") - # the class of res1 is not defined. - end - - it "selects one if it is given priority" do + it "selects one if it is the last declared" do expect(Chef::Log).not_to receive(:warn) - Chef::Platform::ResourcePriorityMap.instance.send(:priority, :football, TottenhamHotspur, platform: "nbc_sports") + + Sounders.provides :football, platform: "nbc_sports" + TottenhamHotspur.provides :football, platform: "nbc_sports" + res1 = recipe.football "club world cup" expect(res1.name).to eql("club world cup") expect(res1).to be_a_kind_of(TottenhamHotspur) @@ -168,7 +162,10 @@ describe Chef::Recipe do it "selects the other one if it is given priority" do expect(Chef::Log).not_to receive(:warn) - Chef::Platform::ResourcePriorityMap.instance.send(:priority, :football, Sounders, platform: "nbc_sports") + + TottenhamHotspur.provides :football, platform: "nbc_sports" + Sounders.provides :football, platform: "nbc_sports" + res1 = recipe.football "club world cup" expect(res1.name).to eql("club world cup") expect(res1).to be_a_kind_of(Sounders) @@ -408,7 +405,7 @@ describe Chef::Recipe do end it "does not copy the action from the first resource" do - expect(original_resource.action).to eq([:score]) + expect(original_resource.action).to eq(:score) expect(duplicated_resource.action).to eq(:nothing) end @@ -504,7 +501,7 @@ describe Chef::Recipe do recipe.from_file(File.join(CHEF_SPEC_DATA, "recipes", "test.rb")) res = recipe.resources(:file => "/etc/nsswitch.conf") expect(res.name).to eql("/etc/nsswitch.conf") - expect(res.action).to eql([:create]) + expect(res.action).to eql(:create) expect(res.owner).to eql("root") expect(res.group).to eql("root") expect(res.mode).to eql(0644) @@ -577,6 +574,36 @@ describe Chef::Recipe do expect(cookbook_collection[:openldap]).not_to receive(:load_recipe).with("default", run_context) openldap_recipe.include_recipe "::default" end + + it "will not load a recipe twice when called first from an LWRP provider" do + openldap_recipe = Chef::Recipe.new("openldap", "test", run_context) + expect(node).to receive(:loaded_recipe).with(:openldap, "default").exactly(:once) + allow(run_context).to receive(:unreachable_cookbook?).with(:openldap).and_return(false) + expect(cookbook_collection[:openldap]).to receive(:load_recipe).with("default", run_context) + openldap_recipe.include_recipe "::default" + expect(cookbook_collection[:openldap]).not_to receive(:load_recipe).with("default", run_context) + openldap_recipe.openldap_includer("do it").run_action(:run) + end + + it "will not load a recipe twice when called last from an LWRP provider" do + openldap_recipe = Chef::Recipe.new("openldap", "test", run_context) + expect(node).to receive(:loaded_recipe).with(:openldap, "default").exactly(:once) + allow(run_context).to receive(:unreachable_cookbook?).with(:openldap).and_return(false) + expect(cookbook_collection[:openldap]).to receive(:load_recipe).with("default", run_context) + openldap_recipe.openldap_includer("do it").run_action(:run) + expect(cookbook_collection[:openldap]).not_to receive(:load_recipe).with("default", run_context) + openldap_recipe.include_recipe "::default" + end + + it "will not load a recipe twice when called both times from an LWRP provider" do + openldap_recipe = Chef::Recipe.new("openldap", "test", run_context) + expect(node).to receive(:loaded_recipe).with(:openldap, "default").exactly(:once) + allow(run_context).to receive(:unreachable_cookbook?).with(:openldap).and_return(false) + expect(cookbook_collection[:openldap]).to receive(:load_recipe).with("default", run_context) + openldap_recipe.openldap_includer("do it").run_action(:run) + expect(cookbook_collection[:openldap]).not_to receive(:load_recipe).with("default", run_context) + openldap_recipe.openldap_includer("do it").run_action(:run) + end end describe "tags" do diff --git a/spec/unit/resource/batch_spec.rb b/spec/unit/resource/batch_spec.rb index 4a056b8735..b8c2897f42 100644 --- a/spec/unit/resource/batch_spec.rb +++ b/spec/unit/resource/batch_spec.rb @@ -25,6 +25,7 @@ describe Chef::Resource::Batch do node.default["kernel"] = Hash.new node.default["kernel"][:machine] = :x86_64.to_s + node.automatic[:os] = 'windows' run_context = Chef::RunContext.new(node, nil, nil) diff --git a/spec/unit/resource/breakpoint_spec.rb b/spec/unit/resource/breakpoint_spec.rb index ed1f3ebcd5..9c867ebcc7 100644 --- a/spec/unit/resource/breakpoint_spec.rb +++ b/spec/unit/resource/breakpoint_spec.rb @@ -37,7 +37,7 @@ describe Chef::Resource::Breakpoint do end it "defaults to the break action" do - expect(@breakpoint.action).to eq("break") + expect(@breakpoint.action).to eq(:break) end it "names itself after the line number of the file where it's created" do diff --git a/spec/unit/resource/erl_call_spec.rb b/spec/unit/resource/erl_call_spec.rb index 8ec182665f..008d27372a 100644 --- a/spec/unit/resource/erl_call_spec.rb +++ b/spec/unit/resource/erl_call_spec.rb @@ -35,7 +35,7 @@ describe Chef::Resource::ErlCall do end it "should have a default action of run" do - expect(@resource.action).to eql("run") + expect(@resource.action).to eql(:run) end it "should accept run as an action" do diff --git a/spec/unit/resource/file_spec.rb b/spec/unit/resource/file_spec.rb index db52e35004..dd20f5f03a 100644 --- a/spec/unit/resource/file_spec.rb +++ b/spec/unit/resource/file_spec.rb @@ -29,7 +29,7 @@ describe Chef::Resource::File do end it "should have a default action of 'create'" do - expect(@resource.action).to eql("create") + expect(@resource.action).to eql(:create) end it "should have a default content of nil" do diff --git a/spec/unit/resource/ifconfig_spec.rb b/spec/unit/resource/ifconfig_spec.rb index ea5282acd5..e3e1f6daa2 100644 --- a/spec/unit/resource/ifconfig_spec.rb +++ b/spec/unit/resource/ifconfig_spec.rb @@ -47,21 +47,23 @@ describe Chef::Resource::Ifconfig do end end - shared_examples "being a platform using the default ifconfig provider" do |platform, version| + shared_examples "being a platform based on an old Debian" do |platform, version| before do + @node.automatic_attrs[:os] = 'linux' + @node.automatic_attrs[:platform_family] = 'debian' @node.automatic_attrs[:platform] = platform @node.automatic_attrs[:platform_version] = version end it "should use an ordinary Provider::Ifconfig as a provider for #{platform} #{version}" do - expect(@resource.provider_for_action(:add)).to be_a_kind_of(Chef::Provider::Ifconfig) - expect(@resource.provider_for_action(:add)).not_to be_a_kind_of(Chef::Provider::Ifconfig::Debian) - expect(@resource.provider_for_action(:add)).not_to be_a_kind_of(Chef::Provider::Ifconfig::Redhat) + expect(@resource.provider_for_action(:add).class).to eq(Chef::Provider::Ifconfig) end end shared_examples "being a platform based on RedHat" do |platform, version| before do + @node.automatic_attrs[:os] = 'linux' + @node.automatic_attrs[:platform_family] = 'rhel' @node.automatic_attrs[:platform] = platform @node.automatic_attrs[:platform_version] = version end @@ -73,6 +75,8 @@ describe Chef::Resource::Ifconfig do shared_examples "being a platform based on a recent Debian" do |platform, version| before do + @node.automatic_attrs[:os] = 'linux' + @node.automatic_attrs[:platform_family] = 'debian' @node.automatic_attrs[:platform] = platform @node.automatic_attrs[:platform_version] = version end @@ -87,7 +91,7 @@ describe Chef::Resource::Ifconfig do end describe "when it is an old Debian platform" do - it_should_behave_like "being a platform using the default ifconfig provider", "debian", "6.0" + it_should_behave_like "being a platform based on an old Debian", "debian", "6.0" end describe "when it is a new Debian platform" do @@ -95,7 +99,7 @@ describe Chef::Resource::Ifconfig do end describe "when it is an old Ubuntu platform" do - it_should_behave_like "being a platform using the default ifconfig provider", "ubuntu", "11.04" + it_should_behave_like "being a platform based on an old Debian", "ubuntu", "11.04" end describe "when it is a new Ubuntu platform" do diff --git a/spec/unit/resource/powershell_spec.rb b/spec/unit/resource/powershell_spec.rb index c263172ae6..2505c4a3d7 100644 --- a/spec/unit/resource/powershell_spec.rb +++ b/spec/unit/resource/powershell_spec.rb @@ -25,6 +25,7 @@ describe Chef::Resource::PowershellScript do node.default["kernel"] = Hash.new node.default["kernel"][:machine] = :x86_64.to_s + node.automatic[:os] = 'windows' run_context = Chef::RunContext.new(node, nil, nil) diff --git a/spec/unit/resource/remote_file_spec.rb b/spec/unit/resource/remote_file_spec.rb index 3731d1aee2..0a379ff574 100644 --- a/spec/unit/resource/remote_file_spec.rb +++ b/spec/unit/resource/remote_file_spec.rb @@ -39,6 +39,11 @@ describe Chef::Resource::RemoteFile do expect(Chef::Platform.find_provider(:noplatform, 'noversion', @resource)).to eq(Chef::Provider::RemoteFile) end + it "says its provider is RemoteFile when the source is a network share" do + @resource.source("\\\\fakey\\fakerton\\fake.txt") + expect(@resource.provider).to eq(Chef::Provider::RemoteFile) + expect(Chef::Platform.find_provider(:noplatform, 'noversion', @resource)).to eq(Chef::Provider::RemoteFile) + end describe "source" do it "does not have a default value for 'source'" do @@ -50,6 +55,16 @@ describe Chef::Resource::RemoteFile do expect(@resource.source).to eql([ "http://opscode.com/" ]) end + it "should accept a windows network share source" do + @resource.source "\\\\fakey\\fakerton\\fake.txt" + expect(@resource.source).to eql([ "\\\\fakey\\fakerton\\fake.txt" ]) + end + + it 'should accept file URIs with spaces' do + @resource.source("file:///C:/foo bar") + expect(@resource.source).to eql(["file:///C:/foo bar"]) + end + it "should accept a delayed evalutator (string) for the remote file source" do @resource.source Chef::DelayedEvaluator.new {"http://opscode.com/"} expect(@resource.source).to eql([ "http://opscode.com/" ]) diff --git a/spec/unit/resource/route_spec.rb b/spec/unit/resource/route_spec.rb index ec1d369932..ffb9304511 100644 --- a/spec/unit/resource/route_spec.rb +++ b/spec/unit/resource/route_spec.rb @@ -35,7 +35,7 @@ describe Chef::Resource::Route do end it "should have a default action of 'add'" do - expect(@resource.action).to eql([:add]) + expect(@resource.action).to eql(:add) end it "should accept add or delete for action" do diff --git a/spec/unit/resource/ruby_block_spec.rb b/spec/unit/resource/ruby_block_spec.rb index 9f19fecd4f..5d83f7e367 100644 --- a/spec/unit/resource/ruby_block_spec.rb +++ b/spec/unit/resource/ruby_block_spec.rb @@ -31,7 +31,7 @@ describe Chef::Resource::RubyBlock do end it "should have a default action of 'create'" do - expect(@resource.action).to eql("run") + expect(@resource.action).to eql(:run) end it "should have a resource name of :ruby_block" do @@ -46,7 +46,7 @@ describe Chef::Resource::RubyBlock do it "allows the action to be 'create'" do @resource.action :create - expect(@resource.action).to eq([:create]) + expect(@resource.action).to eq(:create) end describe "when it has been initialized with block code" do diff --git a/spec/unit/resource/template_spec.rb b/spec/unit/resource/template_spec.rb index df5ca94b8a..2fd951b72d 100644 --- a/spec/unit/resource/template_spec.rb +++ b/spec/unit/resource/template_spec.rb @@ -98,7 +98,7 @@ describe Chef::Resource::Template do context "on windows", :windows_only do # according to Chef::Resource::File, windows state attributes are rights + deny_rights - pending "it describes its state" + skip "it describes its state" end it "returns the file path as its identity" do diff --git a/spec/unit/resource/timestamped_deploy_spec.rb b/spec/unit/resource/timestamped_deploy_spec.rb index eca6c570d4..4ebfdaf059 100644 --- a/spec/unit/resource/timestamped_deploy_spec.rb +++ b/spec/unit/resource/timestamped_deploy_spec.rb @@ -23,11 +23,10 @@ describe Chef::Resource::TimestampedDeploy, "initialize" do static_provider_resolution( resource: Chef::Resource::TimestampedDeploy, provider: Chef::Provider::Deploy::Timestamped, - name: :deploy, + name: :timestamped_deploy, action: :deploy, os: 'linux', platform_family: 'rhel', ) end - diff --git a/spec/unit/resource/windows_package_spec.rb b/spec/unit/resource/windows_package_spec.rb index 1e02f2449b..6aa5d357ea 100644 --- a/spec/unit/resource/windows_package_spec.rb +++ b/spec/unit/resource/windows_package_spec.rb @@ -63,9 +63,9 @@ describe Chef::Resource::WindowsPackage, "initialize" do end it "coverts a source to an absolute path" do - allow(::File).to receive(:absolute_path).and_return("c:\\Files\\frost.msi") + allow(::File).to receive(:absolute_path).and_return("c:\\files\\frost.msi") resource.source("frost.msi") - expect(resource.source).to eql "c:\\Files\\frost.msi" + expect(resource.source).to eql "c:\\files\\frost.msi" end it "converts slashes to backslashes in the source path" do @@ -78,4 +78,18 @@ describe Chef::Resource::WindowsPackage, "initialize" do # it's a little late to stub out File.absolute_path expect(resource.source).to include("solitaire.msi") end + + it "supports the checksum attribute" do + resource.checksum('somechecksum') + expect(resource.checksum).to eq('somechecksum') + end + + context 'when a URL is used' do + let(:resource_source) { 'https://foo.bar/solitare.msi' } + let(:resource) { Chef::Resource::WindowsPackage.new(resource_source) } + + it "should return the source unmodified" do + expect(resource.source).to eq(resource_source) + end + end end diff --git a/spec/unit/resource/windows_service_spec.rb b/spec/unit/resource/windows_service_spec.rb index 45a295c24e..8866cad1bf 100644 --- a/spec/unit/resource/windows_service_spec.rb +++ b/spec/unit/resource/windows_service_spec.rb @@ -44,6 +44,6 @@ describe Chef::Resource::WindowsService, "initialize" do it "allows the action to be 'configure_startup'" do resource.action :configure_startup - expect(resource.action).to eq([:configure_startup]) + expect(resource.action).to eq(:configure_startup) end end diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb index 6b2d6c89d3..8ba45d9350 100644 --- a/spec/unit/resource_spec.rb +++ b/spec/unit/resource_spec.rb @@ -21,10 +21,6 @@ require 'spec_helper' -class ResourceTestHarness < Chef::Resource - provider_base Chef::Provider::Package -end - describe Chef::Resource do before(:each) do @cookbook_repo_path = File.join(CHEF_SPEC_DATA, 'cookbooks') @@ -35,6 +31,18 @@ describe Chef::Resource do @resource = Chef::Resource.new("funk", @run_context) end + it "should mixin shell_out" do + expect(@resource.respond_to?(:shell_out)).to be true + end + + it "should mixin shell_out!" do + expect(@resource.respond_to?(:shell_out!)).to be true + end + + it "should mixin shell_out_with_systems_locale" do + expect(@resource.respond_to?(:shell_out_with_systems_locale)).to be true + end + describe "when inherited" do it "adds an entry to a list of subclasses" do @@ -324,6 +332,71 @@ describe Chef::Resource do end end + describe "self.resource_name" do + context "When resource_name is not set" do + it "and there are no provides lines, resource_name is nil" do + c = Class.new(Chef::Resource) do + end + + r = c.new('hi') + r.declared_type = :d + expect(c.resource_name).to be_nil + expect(r.resource_name).to be_nil + expect(r.declared_type).to eq :d + end + it "and there are no provides lines, @resource_name is used" do + c = Class.new(Chef::Resource) do + def initialize(*args, &block) + @resource_name = :blah + super + end + end + + r = c.new('hi') + r.declared_type = :d + expect(c.resource_name).to be_nil + expect(r.resource_name).to eq :blah + expect(r.declared_type).to eq :d + end + end + + it "resource_name without provides is honored" do + c = Class.new(Chef::Resource) do + resource_name 'blah' + end + + r = c.new('hi') + r.declared_type = :d + expect(c.resource_name).to eq :blah + expect(r.resource_name).to eq :blah + expect(r.declared_type).to eq :d + end + it "setting class.resource_name with 'resource_name = blah' overrides declared_type" do + c = Class.new(Chef::Resource) do + provides :self_resource_name_test_2 + end + c.resource_name = :blah + + r = c.new('hi') + r.declared_type = :d + expect(c.resource_name).to eq :blah + expect(r.resource_name).to eq :blah + expect(r.declared_type).to eq :d + end + it "setting class.resource_name with 'resource_name blah' overrides declared_type" do + c = Class.new(Chef::Resource) do + resource_name :blah + provides :self_resource_name_test_3 + end + + r = c.new('hi') + r.declared_type = :d + expect(c.resource_name).to eq :blah + expect(r.resource_name).to eq :blah + expect(r.declared_type).to eq :d + end + end + describe "is" do it "should return the arguments passed with 'is'" do zm = Chef::Resource::ZenMaster.new("coffee") @@ -447,8 +520,21 @@ describe Chef::Resource do expect(Chef::Resource.provider_base).to eq(Chef::Provider) end - it "allows the base provider to be overriden by a " do - expect(ResourceTestHarness.provider_base).to eq(Chef::Provider::Package) + it "allows the base provider to be overridden" do + Chef::Config.treat_deprecation_warnings_as_errors(false) + class OverrideProviderBaseTest < Chef::Resource + provider_base Chef::Provider::Package + end + + expect(OverrideProviderBaseTest.provider_base).to eq(Chef::Provider::Package) + end + + it "warns when setting provider_base" do + expect { + class OverrideProviderBaseTest2 < Chef::Resource + provider_base Chef::Provider::Package + end + }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) end end @@ -709,57 +795,73 @@ describe Chef::Resource do end it 'adds mappings for a single platform' do - expect(Chef::Resource::Klz.node_map).to receive(:set).with( - :dinobot, true, { platform: ['autobots'] } + expect(Chef).to receive(:set_resource_priority_array).with( + :dinobot, Chef::Resource::Klz, { platform: ['autobots'] } ) klz.provides :dinobot, platform: ['autobots'] end it 'adds mappings for multiple platforms' do - expect(Chef::Resource::Klz.node_map).to receive(:set).with( - :energy, true, { platform: ['autobots', 'decepticons']} + expect(Chef).to receive(:set_resource_priority_array).with( + :energy, Chef::Resource::Klz, { platform: ['autobots', 'decepticons']} ) klz.provides :energy, platform: ['autobots', 'decepticons'] end it 'adds mappings for all platforms' do - expect(Chef::Resource::Klz.node_map).to receive(:set).with( - :tape_deck, true, {} + expect(Chef).to receive(:set_resource_priority_array).with( + :tape_deck, Chef::Resource::Klz, {} ) klz.provides :tape_deck end end - describe "lookups from the platform map" do - let(:klz1) { Class.new(Chef::Resource) } - let(:klz2) { Class.new(Chef::Resource) } + describe "resource_for_node" do + describe "lookups from the platform map" do + let(:klz1) { Class.new(Chef::Resource) } + + before(:each) do + Chef::Resource::Klz1 = klz1 + @node = Chef::Node.new + @node.name("bumblebee") + @node.automatic[:platform] = "autobots" + @node.automatic[:platform_version] = "6.1" + Object.const_set('Soundwave', klz1) + klz1.provides :soundwave + end - before(:each) do - Chef::Resource::Klz1 = klz1 - Chef::Resource::Klz2 = klz2 - @node = Chef::Node.new - @node.name("bumblebee") - @node.automatic[:platform] = "autobots" - @node.automatic[:platform_version] = "6.1" - Object.const_set('Soundwave', klz1) - klz2.provides :dinobot, :on_platforms => ['autobots'] - Object.const_set('Grimlock', klz2) - end + after(:each) do + Object.send(:remove_const, :Soundwave) + Chef::Resource.send(:remove_const, :Klz1) + end - after(:each) do - Object.send(:remove_const, :Soundwave) - Object.send(:remove_const, :Grimlock) - Chef::Resource.send(:remove_const, :Klz1) - Chef::Resource.send(:remove_const, :Klz2) + it "returns a resource by short_name if nothing else matches" do + expect(Chef::Resource.resource_for_node(:soundwave, @node)).to eql(klz1) + end end - describe "resource_for_node" do - it "returns a resource by short_name and node" do - expect(Chef::Resource.resource_for_node(:dinobot, @node)).to eql(Grimlock) + describe "lookups from the platform map" do + let(:klz2) { Class.new(Chef::Resource) } + + before(:each) do + Chef::Resource::Klz2 = klz2 + @node = Chef::Node.new + @node.name("bumblebee") + @node.automatic[:platform] = "autobots" + @node.automatic[:platform_version] = "6.1" + klz2.provides :dinobot, :platform => ['autobots'] + Object.const_set('Grimlock', klz2) + klz2.provides :grimlock end - it "returns a resource by short_name if nothing else matches" do - expect(Chef::Resource.resource_for_node(:soundwave, @node)).to eql(Soundwave) + + after(:each) do + Object.send(:remove_const, :Grimlock) + Chef::Resource.send(:remove_const, :Klz2) + end + + it "returns a resource by short_name and node" do + expect(Chef::Resource.resource_for_node(:dinobot, @node)).to eql(klz2) end end diff --git a/spec/unit/rest_spec.rb b/spec/unit/rest_spec.rb index 85c9e3df8f..3b04981610 100644 --- a/spec/unit/rest_spec.rb +++ b/spec/unit/rest_spec.rb @@ -69,8 +69,8 @@ describe Chef::REST do rest end - let(:standard_read_headers) {{"Accept"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID"=>request_id}} - let(:standard_write_headers) {{"Accept"=>"application/json", "Content-Type"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID"=>request_id}} + let(:standard_read_headers) {{"Accept"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID"=>request_id, 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION}} + let(:standard_write_headers) {{"Accept"=>"application/json", "Content-Type"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID"=>request_id, 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION}} before(:each) do Chef::Log.init(log_stringio) @@ -277,19 +277,6 @@ describe Chef::REST do rest end - let(:base_headers) do - { - 'Accept' => 'application/json', - 'X-Chef-Version' => Chef::VERSION, - 'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE, - 'X-REMOTE-REQUEST-ID' => request_id - } - end - - let (:req_with_body_headers) do - base_headers.merge("Content-Type" => "application/json", "Content-Length" => '13') - end - before(:each) do Chef::Config[:ssl_client_cert] = nil Chef::Config[:ssl_client_key] = nil @@ -304,7 +291,8 @@ describe Chef::REST do 'X-Chef-Version' => Chef::VERSION, 'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE, 'Host' => host_header, - 'X-REMOTE-REQUEST-ID' => request_id + 'X-REMOTE-REQUEST-ID' => request_id, + 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION } end @@ -548,7 +536,7 @@ describe Chef::REST do end end end - end + end # as JSON API requests context "when streaming downloads to a tempfile" do let!(:tempfile) { Tempfile.open("chef-rspec-rest_spec-line-@{__LINE__}--") } @@ -586,7 +574,8 @@ describe Chef::REST do 'X-Chef-Version' => Chef::VERSION, 'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE, 'Host' => host_header, - 'X-REMOTE-REQUEST-ID'=> request_id + 'X-REMOTE-REQUEST-ID'=> request_id, + 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION } expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", expected_headers).and_return(request_mock) rest.streaming_request(url, {}) @@ -597,7 +586,8 @@ describe Chef::REST do 'X-Chef-Version' => Chef::VERSION, 'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE, 'Host' => host_header, - 'X-REMOTE-REQUEST-ID'=> request_id + 'X-REMOTE-REQUEST-ID'=> request_id, + 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION } expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", expected_headers).and_return(request_mock) rest.streaming_request(url, {}) @@ -695,7 +685,7 @@ describe Chef::REST do expect(block_called).to be_truthy end end - end + end # when making REST requests context "when following redirects" do let(:rest) do diff --git a/spec/unit/role_spec.rb b/spec/unit/role_spec.rb index 5421b5a7b3..f120ca6da6 100644 --- a/spec/unit/role_spec.rb +++ b/spec/unit/role_spec.rb @@ -21,7 +21,7 @@ require 'chef/role' describe Chef::Role do before(:each) do - allow(Chef::Platform).to receive(:windows?) { false } + allow(ChefConfig).to receive(:windows?) { false } @role = Chef::Role.new @role.name("ops_master") end diff --git a/spec/unit/run_context_spec.rb b/spec/unit/run_context_spec.rb index d656111a7d..e20ba63b72 100644 --- a/spec/unit/run_context_spec.rb +++ b/spec/unit/run_context_spec.rb @@ -53,6 +53,37 @@ describe Chef::RunContext do expect(run_context.node).to eq(node) end + it "loads up node[:cookbooks]" do + expect(run_context.node[:cookbooks]).to eql( + { + "circular-dep1" => { + "version" => "0.0.0", + }, + "circular-dep2" => { + "version" => "0.0.0", + }, + "dependency1" => { + "version" => "0.0.0", + }, + "dependency2" => { + "version" => "0.0.0", + }, + "no-default-attr" => { + "version" => "0.0.0", + }, + "test" => { + "version" => "0.0.0", + }, + "test-with-circular-deps" => { + "version" => "0.0.0", + }, + "test-with-deps" => { + "version" => "0.0.0", + }, + } + ) + end + describe "loading cookbooks for a run list" do before do @@ -159,4 +190,45 @@ describe Chef::RunContext do expect(run_context.reboot_requested?).to be_falsey end end + + describe "notifications" do + let(:notification) { Chef::Resource::Notification.new(nil, nil, notifying_resource) } + + shared_context "notifying resource is a Chef::Resource" do + let(:notifying_resource) { Chef::Resource.new("gerbil") } + + it "should be keyed off the resource name" do + run_context.send(setter, notification) + expect(run_context.send(getter, notifying_resource)).to eq([notification]) + end + end + + shared_context "notifying resource is a subclass of Chef::Resource" do + let(:declared_type) { :alpaca } + let(:notifying_resource) { + r = Class.new(Chef::Resource).new("guinea pig") + r.declared_type = declared_type + r + } + + it "should be keyed off the resource declared key" do + run_context.send(setter, notification) + expect(run_context.send(getter, notifying_resource)).to eq([notification]) + end + end + + describe "of the immediate kind" do + let(:setter) { :notifies_immediately } + let(:getter) { :immediate_notifications } + include_context "notifying resource is a Chef::Resource" + include_context "notifying resource is a subclass of Chef::Resource" + end + + describe "of the delayed kind" do + let(:setter) { :notifies_delayed } + let(:getter) { :delayed_notifications } + include_context "notifying resource is a Chef::Resource" + include_context "notifying resource is a subclass of Chef::Resource" + end + end end diff --git a/spec/unit/runner_spec.rb b/spec/unit/runner_spec.rb index b30f818da1..82e57e068c 100644 --- a/spec/unit/runner_spec.rb +++ b/spec/unit/runner_spec.rb @@ -273,8 +273,8 @@ describe Chef::Runner do expected_message =<<-E Multiple failures occurred: -* FailureProvider::ChefClientFail occurred in delayed notification: [explode] (dynamically defined) had an error: FailureProvider::ChefClientFail: chef had an error of some sort -* FailureProvider::ChefClientFail occurred in delayed notification: [explode again] (dynamically defined) had an error: FailureProvider::ChefClientFail: chef had an error of some sort +* FailureProvider::ChefClientFail occurred in delayed notification: failure_resource[explode] (dynamically defined) had an error: FailureProvider::ChefClientFail: chef had an error of some sort +* FailureProvider::ChefClientFail occurred in delayed notification: failure_resource[explode again] (dynamically defined) had an error: FailureProvider::ChefClientFail: chef had an error of some sort E expect(exception.message).to eq(expected_message) diff --git a/spec/unit/shell_spec.rb b/spec/unit/shell_spec.rb index acbb1891e7..379043a017 100644 --- a/spec/unit/shell_spec.rb +++ b/spec/unit/shell_spec.rb @@ -43,7 +43,7 @@ describe Shell do before do Shell.irb_conf = {} allow(Shell::ShellSession.instance).to receive(:reset!) - allow(Chef::Platform).to receive(:windows?).and_return(false) + allow(ChefConfig).to receive(:windows?).and_return(false) allow(Chef::Util::PathHelper).to receive(:home).and_return('/home/foo') end @@ -71,7 +71,7 @@ describe Shell do Shell.irb_conf[:IRB_RC].call(conf) expect(conf.prompt_c).to eq("chef > ") expect(conf.return_format).to eq(" => %s \n") - expect(conf.prompt_i).to eq("chef > ") + expect(conf.prompt_i).to eq("chef (#{Chef::VERSION})> ") expect(conf.prompt_n).to eq("chef ?> ") expect(conf.prompt_s).to eq("chef%l> ") expect(conf.use_tracer).to eq(false) @@ -85,7 +85,7 @@ describe Shell do conf.main = Chef::Recipe.new(nil,nil,Chef::RunContext.new(Chef::Node.new, {}, events)) Shell.irb_conf[:IRB_RC].call(conf) expect(conf.prompt_c).to eq("chef:recipe > ") - expect(conf.prompt_i).to eq("chef:recipe > ") + expect(conf.prompt_i).to eq("chef:recipe (#{Chef::VERSION})> ") expect(conf.prompt_n).to eq("chef:recipe ?> ") expect(conf.prompt_s).to eq("chef:recipe%l> ") end @@ -97,7 +97,7 @@ describe Shell do conf.main = Chef::Node.new Shell.irb_conf[:IRB_RC].call(conf) expect(conf.prompt_c).to eq("chef:attributes > ") - expect(conf.prompt_i).to eq("chef:attributes > ") + expect(conf.prompt_i).to eq("chef:attributes (#{Chef::VERSION})> ") expect(conf.prompt_n).to eq("chef:attributes ?> ") expect(conf.prompt_s).to eq("chef:attributes%l> ") end diff --git a/spec/unit/user_spec.rb b/spec/unit/user_spec.rb index d451531b16..57822df7e3 100644 --- a/spec/unit/user_spec.rb +++ b/spec/unit/user_spec.rb @@ -26,98 +26,141 @@ describe Chef::User do @user = Chef::User.new end + shared_examples_for "string fields with no contraints" do + it "should let you set the public key" do + expect(@user.send(method, "some_string")).to eq("some_string") + end + + it "should return the current public key" do + @user.send(method, "some_string") + expect(@user.send(method)).to eq("some_string") + end + + it "should throw an ArgumentError if you feed it something lame" do + expect { @user.send(method, Hash.new) }.to raise_error(ArgumentError) + end + end + + shared_examples_for "boolean fields with no constraints" do + it "should let you set the field" do + expect(@user.send(method, true)).to eq(true) + end + + it "should return the current field value" do + @user.send(method, true) + expect(@user.send(method)).to eq(true) + end + + it "should return the false value when false" do + @user.send(method, false) + expect(@user.send(method)).to eq(false) + end + + it "should throw an ArgumentError if you feed it anything but true or false" do + expect { @user.send(method, Hash.new) }.to raise_error(ArgumentError) + end + end + describe "initialize" do it "should be a Chef::User" do expect(@user).to be_a_kind_of(Chef::User) end end - describe "name" do - it "should let you set the name to a string" do - expect(@user.name("ops_master")).to eq("ops_master") + describe "username" do + it "should let you set the username to a string" do + expect(@user.username("ops_master")).to eq("ops_master") end - it "should return the current name" do - @user.name "ops_master" - expect(@user.name).to eq("ops_master") + it "should return the current username" do + @user.username "ops_master" + expect(@user.username).to eq("ops_master") end # It is not feasible to check all invalid characters. Here are a few # that we probably care about. it "should not accept invalid characters" do # capital letters - expect { @user.name "Bar" }.to raise_error(ArgumentError) + expect { @user.username "Bar" }.to raise_error(ArgumentError) # slashes - expect { @user.name "foo/bar" }.to raise_error(ArgumentError) + expect { @user.username "foo/bar" }.to raise_error(ArgumentError) # ? - expect { @user.name "foo?" }.to raise_error(ArgumentError) + expect { @user.username "foo?" }.to raise_error(ArgumentError) # & - expect { @user.name "foo&" }.to raise_error(ArgumentError) + expect { @user.username "foo&" }.to raise_error(ArgumentError) end it "should not accept spaces" do - expect { @user.name "ops master" }.to raise_error(ArgumentError) + expect { @user.username "ops master" }.to raise_error(ArgumentError) end it "should throw an ArgumentError if you feed it anything but a string" do - expect { @user.name Hash.new }.to raise_error(ArgumentError) + expect { @user.username Hash.new }.to raise_error(ArgumentError) end end - describe "admin" do - it "should let you set the admin bit" do - expect(@user.admin(true)).to eq(true) - end - - it "should return the current admin value" do - @user.admin true - expect(@user.admin).to eq(true) + describe "boolean fields" do + describe "create_key" do + it_should_behave_like "boolean fields with no constraints" do + let(:method) { :create_key } + end end + end - it "should default to false" do - expect(@user.admin).to eq(false) + describe "string fields" do + describe "public_key" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :public_key } + end end - it "should throw an ArgumentError if you feed it anything but true or false" do - expect { @user.name Hash.new }.to raise_error(ArgumentError) + describe "private_key" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :private_key } + end end - end - describe "public_key" do - it "should let you set the public key" do - expect(@user.public_key("super public")).to eq("super public") + describe "display_name" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :display_name } + end end - it "should return the current public key" do - @user.public_key("super public") - expect(@user.public_key).to eq("super public") + describe "first_name" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :first_name } + end end - it "should throw an ArgumentError if you feed it something lame" do - expect { @user.public_key Hash.new }.to raise_error(ArgumentError) + describe "middle_name" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :middle_name } + end end - end - describe "private_key" do - it "should let you set the private key" do - expect(@user.private_key("super private")).to eq("super private") + describe "last_name" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :last_name } + end end - it "should return the private key" do - @user.private_key("super private") - expect(@user.private_key).to eq("super private") + describe "email" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :email } + end end - it "should throw an ArgumentError if you feed it something lame" do - expect { @user.private_key Hash.new }.to raise_error(ArgumentError) + describe "password" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :password } + end end end describe "when serializing to JSON" do before(:each) do - @user.name("black") - @user.public_key("crowes") + @user.username("black") @json = @user.to_json end @@ -125,16 +168,62 @@ describe Chef::User do expect(@json).to match(/^\{.+\}$/) end - it "includes the name value" do - expect(@json).to include(%q{"name":"black"}) + it "includes the username value" do + expect(@json).to include(%q{"username":"black"}) + end + + it "includes the display name when present" do + @user.display_name("get_displayed") + expect(@user.to_json).to include(%{"display_name":"get_displayed"}) + end + + it "does not include the display name if not present" do + expect(@json).not_to include("display_name") end - it "includes the public key value" do - expect(@json).to include(%{"public_key":"crowes"}) + it "includes the first name when present" do + @user.first_name("char") + expect(@user.to_json).to include(%{"first_name":"char"}) end - it "includes the 'admin' flag" do - expect(@json).to include(%q{"admin":false}) + it "does not include the first name if not present" do + expect(@json).not_to include("first_name") + end + + it "includes the middle name when present" do + @user.middle_name("man") + expect(@user.to_json).to include(%{"middle_name":"man"}) + end + + it "does not include the middle name if not present" do + expect(@json).not_to include("middle_name") + end + + it "includes the last name when present" do + @user.last_name("der") + expect(@user.to_json).to include(%{"last_name":"der"}) + end + + it "does not include the last name if not present" do + expect(@json).not_to include("last_name") + end + + it "includes the email when present" do + @user.email("charmander@pokemon.poke") + expect(@user.to_json).to include(%{"email":"charmander@pokemon.poke"}) + end + + it "does not include the email if not present" do + expect(@json).not_to include("email") + end + + it "includes the public key when present" do + @user.public_key("crowes") + expect(@user.to_json).to include(%{"public_key":"crowes"}) + end + + it "does not include the public key if not present" do + expect(@json).not_to include("public_key") end it "includes the private key when present" do @@ -162,11 +251,18 @@ describe Chef::User do describe "when deserializing from JSON" do before(:each) do - user = { "name" => "mr_spinks", + user = { + "username" => "mr_spinks", + "display_name" => "displayed", + "first_name" => "char", + "middle_name" => "man", + "last_name" => "der", + "email" => "charmander@pokemon.poke", + "password" => "password", "public_key" => "turtles", "private_key" => "pandas", - "password" => "password", - "admin" => true } + "create_key" => false + } @user = Chef::User.from_json(Chef::JSONCompat.to_json(user)) end @@ -174,32 +270,275 @@ describe Chef::User do expect(@user).to be_a_kind_of(Chef::User) end - it "preserves the name" do - expect(@user.name).to eq("mr_spinks") + it "preserves the username" do + expect(@user.username).to eq("mr_spinks") end - it "preserves the public key" do - expect(@user.public_key).to eq("turtles") + it "preserves the display name if present" do + expect(@user.display_name).to eq("displayed") end - it "preserves the admin status" do - expect(@user.admin).to be_truthy + it "preserves the first name if present" do + expect(@user.first_name).to eq("char") end - it "includes the private key if present" do - expect(@user.private_key).to eq("pandas") + it "preserves the middle name if present" do + expect(@user.middle_name).to eq("man") + end + + it "preserves the last name if present" do + expect(@user.last_name).to eq("der") + end + + it "preserves the email if present" do + expect(@user.email).to eq("charmander@pokemon.poke") end it "includes the password if present" do expect(@user.password).to eq("password") end + it "preserves the public key if present" do + expect(@user.public_key).to eq("turtles") + end + + it "includes the private key if present" do + expect(@user.private_key).to eq("pandas") + end + + it "includes the create key status if not nil" do + expect(@user.create_key).to be_falsey + end end + describe "Versioned API Interactions" do + let(:response_406) { OpenStruct.new(:code => '406') } + let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) } + + before (:each) do + @user = Chef::User.new + allow(@user).to receive(:chef_root_rest_v0).and_return(double('chef rest root v0 object')) + allow(@user).to receive(:chef_root_rest_v1).and_return(double('chef rest root v1 object')) + end + + describe "update" do + before do + # populate all fields that are valid between V0 and V1 + @user.username "some_username" + @user.display_name "some_display_name" + @user.first_name "some_first_name" + @user.middle_name "some_middle_name" + @user.last_name "some_last_name" + @user.email "some_email" + @user.password "some_password" + end + + let(:payload) { + { + :username => "some_username", + :display_name => "some_display_name", + :first_name => "some_first_name", + :middle_name => "some_middle_name", + :last_name => "some_last_name", + :email => "some_email", + :password => "some_password" + } + } + + context "when server API V1 is valid on the Chef Server receiving the request" do + context "when the user submits valid data" do + it "properly updates the user" do + expect(@user.chef_root_rest_v1).to receive(:put).with("users/some_username", payload).and_return({}) + @user.update + end + end + end + + context "when server API V1 is not valid on the Chef Server receiving the request" do + let(:payload) { + { + :username => "some_username", + :display_name => "some_display_name", + :first_name => "some_first_name", + :middle_name => "some_middle_name", + :last_name => "some_last_name", + :email => "some_email", + :password => "some_password", + :public_key => "some_public_key" + } + } + + before do + @user.public_key "some_public_key" + allow(@user.chef_root_rest_v1).to receive(:put) + end + + context "when the server returns a 400" do + let(:response_400) { OpenStruct.new(:code => '400') } + let(:exception_400) { Net::HTTPServerException.new("400 Bad Request", response_400) } + + context "when the 400 was due to public / private key fields no longer being supported" do + let(:response_body_400) { '{"error":["Since Server API v1, all keys must be updated via the keys endpoint. "]}' } + + before do + allow(response_400).to receive(:body).and_return(response_body_400) + allow(@user.chef_root_rest_v1).to receive(:put).and_raise(exception_400) + end + + it "proceeds with the V0 PUT since it can handle public / private key fields" do + expect(@user.chef_root_rest_v0).to receive(:put).with("users/some_username", payload).and_return({}) + @user.update + end + + it "does not call server_client_api_version_intersection, since we know to proceed with V0 in this case" do + expect(@user).to_not receive(:server_client_api_version_intersection) + allow(@user.chef_root_rest_v0).to receive(:put).and_return({}) + @user.update + end + end # when the 400 was due to public / private key fields + + context "when the 400 was NOT due to public / private key fields no longer being supported" do + let(:response_body_400) { '{"error":["Some other error. "]}' } + + before do + allow(response_400).to receive(:body).and_return(response_body_400) + allow(@user.chef_root_rest_v1).to receive(:put).and_raise(exception_400) + end + + it "will not proceed with the V0 PUT since the original bad request was not key related" do + expect(@user.chef_root_rest_v0).to_not receive(:put).with("users/some_username", payload) + expect { @user.update }.to raise_error(exception_400) + end + + it "raises the original error" do + expect { @user.update }.to raise_error(exception_400) + end + + end + end # when the server returns a 400 + + context "when the server returns a 406" do + # from spec/support/shared/unit/api_versioning.rb + it_should_behave_like "version handling" do + let(:object) { @user } + let(:method) { :update } + let(:http_verb) { :put } + let(:rest_v1) { @user.chef_root_rest_v1 } + end + + context "when the server supports API V0" do + before do + allow(@user).to receive(:server_client_api_version_intersection).and_return([0]) + allow(@user.chef_root_rest_v1).to receive(:put).and_raise(exception_406) + end + + it "properly updates the user" do + expect(@user.chef_root_rest_v0).to receive(:put).with("users/some_username", payload).and_return({}) + @user.update + end + end # when the server supports API V0 + end # when the server returns a 406 + + end # when server API V1 is not valid on the Chef Server receiving the request + end # update + + describe "create" do + let(:payload) { + { + :username => "some_username", + :display_name => "some_display_name", + :first_name => "some_first_name", + :last_name => "some_last_name", + :email => "some_email", + :password => "some_password" + } + } + before do + @user.username "some_username" + @user.display_name "some_display_name" + @user.first_name "some_first_name" + @user.last_name "some_last_name" + @user.email "some_email" + @user.password "some_password" + end + + # from spec/support/shared/unit/user_and_client_shared.rb + it_should_behave_like "user or client create" do + let(:object) { @user } + let(:error) { Chef::Exceptions::InvalidUserAttribute } + let(:rest_v0) { @user.chef_root_rest_v0 } + let(:rest_v1) { @user.chef_root_rest_v1 } + let(:url) { "users" } + end + + context "when handling API V1" do + it "creates a new user via the API with a middle_name when it exists" do + @user.middle_name "some_middle_name" + expect(@user.chef_root_rest_v1).to receive(:post).with("users", payload.merge({:middle_name => "some_middle_name"})).and_return({}) + @user.create + end + end # when server API V1 is valid on the Chef Server receiving the request + + context "when API V1 is not supported by the server" do + # from spec/support/shared/unit/api_versioning.rb + it_should_behave_like "version handling" do + let(:object) { @user } + let(:method) { :create } + let(:http_verb) { :post } + let(:rest_v1) { @user.chef_root_rest_v1 } + end + end + + context "when handling API V0" do + before do + allow(@user).to receive(:server_client_api_version_intersection).and_return([0]) + allow(@user.chef_root_rest_v1).to receive(:post).and_raise(exception_406) + end + + it "creates a new user via the API with a middle_name when it exists" do + @user.middle_name "some_middle_name" + expect(@user.chef_root_rest_v0).to receive(:post).with("users", payload.merge({:middle_name => "some_middle_name"})).and_return({}) + @user.create + end + end # when server API V1 is not valid on the Chef Server receiving the request + + end # create + + # DEPRECATION + # This can be removed after API V0 support is gone + describe "reregister" do + let(:payload) { + { + "username" => "some_username", + } + } + + before do + @user.username "some_username" + end + + context "when server API V0 is valid on the Chef Server receiving the request" do + it "creates a new object via the API" do + expect(@user.chef_root_rest_v0).to receive(:put).with("users/#{@user.username}", payload.merge({"private_key" => true})).and_return({}) + @user.reregister + end + end # when server API V0 is valid on the Chef Server receiving the request + + context "when server API V0 is not supported by the Chef Server" do + # from spec/support/shared/unit/api_versioning.rb + it_should_behave_like "user and client reregister" do + let(:object) { @user } + let(:rest_v0) { @user.chef_root_rest_v0 } + end + end # when server API V0 is not supported by the Chef Server + end # reregister + + end # Versioned API Interactions + describe "API Interactions" do before (:each) do @user = Chef::User.new - @user.name "foobar" + @user.username "foobar" @http_client = double("Chef::REST mock") allow(Chef::REST).to receive(:new).and_return(@http_client) end @@ -213,57 +552,31 @@ describe Chef::User do @osc_inflated_response = { "admin" => @user } end - it "lists all clients on an OSC server" do - allow(@http_client).to receive(:get_rest).with("users").and_return(@osc_response) - expect(Chef::User.list).to eq(@osc_response) - end - - it "inflate all clients on an OSC server" do - allow(@http_client).to receive(:get_rest).with("users").and_return(@osc_response) - expect(Chef::User.list(true)).to eq(@osc_inflated_response) - end - it "lists all clients on an OHC/OPC server" do - allow(@http_client).to receive(:get_rest).with("users").and_return(@ohc_response) + allow(@http_client).to receive(:get).with("users").and_return(@ohc_response) # We expect that Chef::User.list will give a consistent response # so OHC API responses should be transformed to OSC-style output. expect(Chef::User.list).to eq(@osc_response) end it "inflate all clients on an OHC/OPC server" do - allow(@http_client).to receive(:get_rest).with("users").and_return(@ohc_response) + allow(@http_client).to receive(:get).with("users").and_return(@ohc_response) expect(Chef::User.list(true)).to eq(@osc_inflated_response) end end - describe "create" do - it "creates a new user via the API" do - @user.password "password" - expect(@http_client).to receive(:post_rest).with("users", {:name => "foobar", :admin => false, :password => "password"}).and_return({}) - @user.create - end - end - describe "read" do it "loads a named user from the API" do - expect(@http_client).to receive(:get_rest).with("users/foobar").and_return({"name" => "foobar", "admin" => true, "public_key" => "pubkey"}) + expect(@http_client).to receive(:get).with("users/foobar").and_return({"username" => "foobar", "admin" => true, "public_key" => "pubkey"}) user = Chef::User.load("foobar") - expect(user.name).to eq("foobar") - expect(user.admin).to eq(true) + expect(user.username).to eq("foobar") expect(user.public_key).to eq("pubkey") end end - describe "update" do - it "updates an existing user on via the API" do - expect(@http_client).to receive(:put_rest).with("users/foobar", {:name => "foobar", :admin => false}).and_return({}) - @user.update - end - end - describe "destroy" do it "deletes the specified user via the API" do - expect(@http_client).to receive(:delete_rest).with("users/foobar") + expect(@http_client).to receive(:delete).with("users/foobar") @user.destroy end end diff --git a/spec/unit/util/path_helper_spec.rb b/spec/unit/util/path_helper_spec.rb deleted file mode 100644 index 23db9587a6..0000000000 --- a/spec/unit/util/path_helper_spec.rb +++ /dev/null @@ -1,255 +0,0 @@ -# -# Author:: Bryan McLellan <btm@loftninjas.org> -# Copyright:: Copyright (c) 2014 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/util/path_helper' -require 'spec_helper' - -describe Chef::Util::PathHelper do - PathHelper = Chef::Util::PathHelper - - [ false, true ].each do |is_windows| - context "on #{is_windows ? "windows" : "unix"}" do - before(:each) do - allow(Chef::Platform).to receive(:windows?).and_return(is_windows) - end - - describe "join" do - it "joins components when some end with separators" do - expected = PathHelper.cleanpath("/foo/bar/baz") - expected = "C:#{expected}" if is_windows - expect(PathHelper.join(is_windows ? 'C:\\foo\\' : "/foo/", "bar", "baz")).to eq(expected) - end - - it "joins components when some end and start with separators" do - expected = PathHelper.cleanpath("/foo/bar/baz") - expected = "C:#{expected}" if is_windows - expect(PathHelper.join(is_windows ? 'C:\\foo\\' : "/foo/", "bar/", "/baz")).to eq(expected) - end - - it "joins components that don't end in separators" do - expected = PathHelper.cleanpath("/foo/bar/baz") - expected = "C:#{expected}" if is_windows - expect(PathHelper.join(is_windows ? 'C:\\foo' : "/foo", "bar", "baz")).to eq(expected) - end - - it "joins starting with '' resolve to absolute paths" do - expect(PathHelper.join('', 'a', 'b')).to eq("#{PathHelper.path_separator}a#{PathHelper.path_separator}b") - end - - it "joins ending with '' add a / to the end" do - expect(PathHelper.join('a', 'b', '')).to eq("a#{PathHelper.path_separator}b#{PathHelper.path_separator}") - end - - if is_windows - it "joins components on Windows when some end with unix separators" do - expect(PathHelper.join('C:\\foo/', "bar", "baz")).to eq('C:\\foo\\bar\\baz') - end - end - end - - if is_windows - it "path_separator is \\" do - expect(PathHelper.path_separator).to eq('\\') - end - else - it "path_separator is /" do - expect(PathHelper.path_separator).to eq('/') - end - end - - if is_windows - it "cleanpath changes slashes into backslashes and leaves backslashes alone" do - expect(PathHelper.cleanpath('/a/b\\c/d/')).to eq('\\a\\b\\c\\d') - end - it "cleanpath does not remove leading double backslash" do - expect(PathHelper.cleanpath('\\\\a/b\\c/d/')).to eq('\\\\a\\b\\c\\d') - end - else - it "cleanpath removes extra slashes alone" do - expect(PathHelper.cleanpath('/a///b/c/d/')).to eq('/a/b/c/d') - end - end - - describe "dirname" do - it "dirname('abc') is '.'" do - expect(PathHelper.dirname('abc')).to eq('.') - end - it "dirname('/') is '/'" do - expect(PathHelper.dirname(PathHelper.path_separator)).to eq(PathHelper.path_separator) - end - it "dirname('a/b/c') is 'a/b'" do - expect(PathHelper.dirname(PathHelper.join('a', 'b', 'c'))).to eq(PathHelper.join('a', 'b')) - end - it "dirname('a/b/c/') is 'a/b'" do - expect(PathHelper.dirname(PathHelper.join('a', 'b', 'c', ''))).to eq(PathHelper.join('a', 'b')) - end - it "dirname('/a/b/c') is '/a/b'" do - expect(PathHelper.dirname(PathHelper.join('', 'a', 'b', 'c'))).to eq(PathHelper.join('', 'a', 'b')) - end - end - end - end - - describe "validate_path" do - context "on windows" do - before(:each) do - # pass by default - allow(Chef::Platform).to receive(:windows?).and_return(true) - allow(PathHelper).to receive(:printable?).and_return(true) - allow(PathHelper).to receive(:windows_max_length_exceeded?).and_return(false) - end - - it "returns the path if the path passes the tests" do - expect(PathHelper.validate_path("C:\\ThisIsRigged")).to eql("C:\\ThisIsRigged") - end - - it "does not raise an error if everything looks great" do - expect { PathHelper.validate_path("C:\\cool path\\dude.exe") }.not_to raise_error - end - - it "raises an error if the path has invalid characters" do - allow(PathHelper).to receive(:printable?).and_return(false) - expect { PathHelper.validate_path("Newline!\n") }.to raise_error(Chef::Exceptions::ValidationFailed) - end - - it "Adds the \\\\?\\ prefix if the path exceeds MAX_LENGTH and does not have it" do - long_path = "C:\\" + "a" * 250 + "\\" + "b" * 250 - prefixed_long_path = "\\\\?\\" + long_path - allow(PathHelper).to receive(:windows_max_length_exceeded?).and_return(true) - expect(PathHelper.validate_path(long_path)).to eql(prefixed_long_path) - end - end - end - - describe "windows_max_length_exceeded?" do - it "returns true if the path is too long (259 + NUL) for the API" do - expect(PathHelper.windows_max_length_exceeded?("C:\\" + "a" * 250 + "\\" + "b" * 6)).to be_truthy - end - - it "returns false if the path is not too long (259 + NUL) for the standard API" do - expect(PathHelper.windows_max_length_exceeded?("C:\\" + "a" * 250 + "\\" + "b" * 5)).to be_falsey - end - - it "returns false if the path is over 259 characters but uses the \\\\?\\ prefix" do - expect(PathHelper.windows_max_length_exceeded?("\\\\?\\C:\\" + "a" * 250 + "\\" + "b" * 250)).to be_falsey - end - end - - describe "printable?" do - it "returns true if the string contains no non-printable characters" do - expect(PathHelper.printable?("C:\\Program Files (x86)\\Microsoft Office\\Files.lst")).to be_truthy - end - - it "returns true when given 'abc' in unicode" do - expect(PathHelper.printable?("\u0061\u0062\u0063")).to be_truthy - end - - it "returns true when given japanese unicode" do - expect(PathHelper.printable?("\uff86\uff87\uff88")).to be_truthy - end - - it "returns false if the string contains a non-printable character" do - expect(PathHelper.printable?("\my files\work\notes.txt")).to be_falsey - end - - # This isn't necessarily a requirement, but here to be explicit about functionality. - it "returns false if the string contains a newline or tab" do - expect(PathHelper.printable?("\tThere's no way,\n\t *no* way,\n\t that you came from my loins.\n")).to be_falsey - end - end - - describe "canonical_path" do - context "on windows", :windows_only do - it "returns an absolute path with backslashes instead of slashes" do - expect(PathHelper.canonical_path("\\\\?\\C:/windows/win.ini")).to eq("\\\\?\\c:\\windows\\win.ini") - end - - it "adds the \\\\?\\ prefix if it is missing" do - expect(PathHelper.canonical_path("C:/windows/win.ini")).to eq("\\\\?\\c:\\windows\\win.ini") - end - - it "returns a lowercase path" do - expect(PathHelper.canonical_path("\\\\?\\C:\\CASE\\INSENSITIVE")).to eq("\\\\?\\c:\\case\\insensitive") - end - end - - context "not on windows", :unix_only do - it "returns a canonical path" do - expect(PathHelper.canonical_path("/etc//apache.d/sites-enabled/../sites-available/default")).to eq("/etc/apache.d/sites-available/default") - end - end - end - - describe "paths_eql?" do - it "returns true if the paths are the same" do - allow(PathHelper).to receive(:canonical_path).with("bandit").and_return("c:/bandit/bandit") - allow(PathHelper).to receive(:canonical_path).with("../bandit/bandit").and_return("c:/bandit/bandit") - expect(PathHelper.paths_eql?("bandit", "../bandit/bandit")).to be_truthy - end - - it "returns false if the paths are different" do - allow(PathHelper).to receive(:canonical_path).with("bandit").and_return("c:/Bo/Bandit") - allow(PathHelper).to receive(:canonical_path).with("../bandit/bandit").and_return("c:/bandit/bandit") - expect(PathHelper.paths_eql?("bandit", "../bandit/bandit")).to be_falsey - end - end - - describe "escape_glob" do - it "escapes characters reserved by glob" do - path = "C:\\this\\*path\\[needs]\\escaping?" - escaped_path = "C:\\\\this\\\\\\*path\\\\\\[needs\\]\\\\escaping\\?" - expect(PathHelper.escape_glob(path)).to eq(escaped_path) - end - - context "when given more than one argument" do - it "joins, cleanpaths, and escapes characters reserved by glob" do - args = ["this/*path", "[needs]", "escaping?"] - escaped_path = if windows? - "this\\\\\\*path\\\\\\[needs\\]\\\\escaping\\?" - else - "this/\\*path/\\[needs\\]/escaping\\?" - end - expect(PathHelper).to receive(:join).with(*args).and_call_original - expect(PathHelper).to receive(:cleanpath).and_call_original - expect(PathHelper.escape_glob(*args)).to eq(escaped_path) - end - end - end - - describe "all_homes" do - before do - stub_const('ENV', env) - allow(Chef::Platform).to receive(:windows?).and_return(is_windows) - end - - context "on windows" do - let (:is_windows) { true } - end - - context "on unix" do - let (:is_windows) { false } - - context "when HOME is not set" do - let (:env) { {} } - it "returns an empty array" do - expect(PathHelper.all_homes).to eq([]) - end - end - end - end -end |