diff options
author | Claire McQuin <claire@getchef.com> | 2015-05-11 16:12:13 -0700 |
---|---|---|
committer | Claire McQuin <claire@getchef.com> | 2015-05-21 10:02:33 -0700 |
commit | 69603a28129741fbc549f53496b9efdd459f9bfb (patch) | |
tree | 12164fde6b57d62b88b9064987ed29e9b0a36ffb | |
parent | f521bafc0c3e9f44148c2cf0c1c6d79c4cde1c62 (diff) | |
download | chef-69603a28129741fbc549f53496b9efdd459f9bfb.tar.gz |
Use shared contexts and shared examples to minimize code duplication.
-rw-r--r-- | spec/support/shared/context/client.rb | 275 | ||||
-rw-r--r-- | spec/support/shared/examples/client.rb | 30 | ||||
-rw-r--r-- | spec/unit/client_spec.rb | 491 |
3 files changed, 378 insertions, 418 deletions
diff --git a/spec/support/shared/context/client.rb b/spec/support/shared/context/client.rb new file mode 100644 index 0000000000..aa1f473aae --- /dev/null +++ b/spec/support/shared/context/client.rb @@ -0,0 +1,275 @@ + +require 'spec_helper' + +# Stubs a basic client object +shared_context "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 +end + +# Stubs a client for a client run. +# Requires a client object be defined in the scope of this included context. +# e.g.: +# describe "some functionality" do +# include_context "client" +# include_context "a client run" +# ... +# end +shared_context "a client run" do + let(:stdout) { StringIO.new } + let(:stderr) { StringIO.new } + + let(:api_client_exists?) { false } + let(:enable_fork) { false } + + let(:http_cookbook_sync) { double("Chef::REST (cookbook sync)") } + let(:http_node_load) { double("Chef::REST (node)") } + let(:http_node_save) { double("Chef::REST (node save)") } + + let(:runner) { instance_double("Chef::Runner") } + let(:audit_runner) { instance_double("Chef::Audit::Runner", :failed? => false) } + + def stub_for_register + # --Client.register + # Make sure Client#register thinks the client key doesn't + # exist, so it tries to register and create one. + allow(File).to receive(:exists?).and_call_original + expect(File).to receive(:exists?). + with(Chef::Config[:client_key]). + exactly(:once). + and_return(api_client_exists?) + + unless api_client_exists? + # Client.register will register with the validation client name. + expect_any_instance_of(Chef::ApiClient::Registration).to receive(:run) + end + end + + def stub_for_node_load + # Client.register will then turn around create another + # Chef::REST object, this time with the client key it got from the + # previous step. + expect(Chef::REST).to receive(:new). + with(Chef::Config[:chef_server_url], fqdn, Chef::Config[:client_key]). + exactly(:once). + and_return(http_node_load) + + # --Client#build_node + # looks up the node, which we will return, then later saves it. + expect(Chef::Node).to receive(:find_or_create).with(fqdn).and_return(node) + + # --ResourceReporter#node_load_completed + # gets a run id from the server for storing resource history + # (has its own tests, so stubbing it here.) + expect_any_instance_of(Chef::ResourceReporter).to receive(:node_load_completed) + end + + def stub_for_sync_cookbooks + # --Client#setup_run_context + # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync + # + expect_any_instance_of(Chef::CookbookSynchronizer).to receive(:sync_cookbooks) + expect(Chef::REST).to receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_cookbook_sync) + expect(http_cookbook_sync).to receive(:post). + with("environments/_default/cookbook_versions", {:run_list => []}). + and_return({}) + end + + def stub_for_converge + # define me + end + + def stub_for_audit + # define me + end + + def stub_for_node_save + # define me + end + + def stub_for_run + # define me + end + + before do + Chef::Config[:client_fork] = enable_fork + Chef::Config[:cache_path] = windows? ? 'C:\chef' : '/var/chef' + Chef::Config[:why_run] = false + Chef::Config[:audit_mode] = :enabled + + stub_const("Chef::Client::STDOUT_FD", stdout) + stub_const("Chef::Client::STDERR_FD", stderr) + + stub_for_register + stub_for_node_load + stub_for_sync_cookbooks + stub_for_converge + stub_for_audit + stub_for_node_save + + expect_any_instance_of(Chef::RunLock).to receive(:acquire) + expect_any_instance_of(Chef::RunLock).to receive(:save_pid) + expect_any_instance_of(Chef::RunLock).to receive(:release) + + # Post conditions: check that node has been filled in correctly + expect(client).to receive(:run_started) + + stub_for_run + end +end + +shared_context "converge completed" do + def stub_for_converge + # --Client#converge + expect(Chef::Runner).to receive(:new).and_return(runner) + expect(runner).to receive(:converge).and_return(true) + end + + def stub_for_node_save + allow(node).to receive(:data_for_save).and_return(node.for_json) + + # --Client#save_updated_node + expect(Chef::REST).to receive(:new).with(Chef::Config[:chef_server_url], fqdn, Chef::Config[:client_key], validate_utf8: false).and_return(http_node_save) + expect(http_node_save).to receive(:put_rest).with("nodes/#{fqdn}", node.for_json).and_return(true) + end +end + +shared_context "converge failed" do + let(:converge_error) do + err = Chef::Exceptions::UnsupportedAction.new("Action unsupported") + err.set_backtrace([ "/path/recipe.rb:15", "/path/recipe.rb:12" ]) + err + end + + def stub_for_converge + expect(Chef::Runner).to receive(:new).and_return(runner) + expect(runner).to receive(:converge).and_raise(converge_error) + end + + def stub_for_node_save + expect(client).to_not receive(:save_updated_node) + end +end + +shared_context "audit phase completed" do + def stub_for_audit + # -- Client#run_audits + expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner) + expect(audit_runner).to receive(:run).and_return(true) + expect(client.events).to receive(:audit_phase_complete) + end +end + +shared_context "audit phase failed with error" do + let(:audit_error) do + err = RuntimeError.new("Unexpected audit error") + err.set_backtrace([ "/path/recipe.rb:57", "/path/recipe.rb:55" ]) + err + end + + def stub_for_audit + expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner) + expect(audit_runner).to receive(:run).and_raise(audit_error) + expect(client.events).to receive(:audit_phase_failed).with(audit_error) + end +end + +shared_context "audit phase completed with failed controls" do + let(:audit_runner) { instance_double("Chef::Audit::Runner", :failed? => true, + :num_failed => 1, :num_total => 3) } + + let(:audit_error) do + err = Chef::Exceptions::AuditsFailed.new(audit_runner.num_failed, audit_runner.num_total) + err.set_backtrace([ "/path/recipe.rb:108", "/path/recipe.rb:103" ]) + err + end + + def stub_for_audit + expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner) + expect(audit_runner).to receive(:run) + expect(Chef::Exceptions::AuditsFailed).to receive(:new).with( + audit_runner.num_failed, audit_runner.num_total + ).and_return(audit_error) + expect(client.events).to receive(:audit_phase_failed).with(audit_error) + end +end + +shared_context "run completed" do + def stub_for_run + expect(client).to receive(:run_completed_successfully) + + # --ResourceReporter#run_completed + # updates the server with the resource history + # (has its own tests, so stubbing it here.) + expect_any_instance_of(Chef::ResourceReporter).to receive(:run_completed) + # --AuditReporter#run_completed + # posts the audit data to server. + # (has its own tests, so stubbing it here.) + expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_completed) + end +end + +shared_context "run failed" do + def stub_for_run + expect(client).to receive(:run_failed) + + # --ResourceReporter#run_completed + # updates the server with the resource history + # (has its own tests, so stubbing it here.) + expect_any_instance_of(Chef::ResourceReporter).to receive(:run_failed) + # --AuditReporter#run_completed + # posts the audit data to server. + # (has its own tests, so stubbing it here.) + expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_failed) + end + + before do + expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError) + end +end diff --git a/spec/support/shared/examples/client.rb b/spec/support/shared/examples/client.rb new file mode 100644 index 0000000000..17eda195b8 --- /dev/null +++ b/spec/support/shared/examples/client.rb @@ -0,0 +1,30 @@ + +require 'spec_helper' +require 'spec/support/shared/context/client' + +shared_examples "a completed run" do + include_context "run completed" + + it "runs ohai, sets up authentication, loads node state, synchronizes policy, converges, and runs audits" do + # This is what we're testing. + expect(client.run).to be true + + # fork is stubbed, so we can see the outcome of the run + expect(node.automatic_attrs[:platform]).to eq("example-platform") + expect(node.automatic_attrs[:platform_version]).to eq("example-platform-1.0") + end +end + +shared_examples "a failed run" do + include_context "run failed" + + it "skips node save and raises the error in a wrapping error" do + expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error| + expect(error.wrapped_errors.size).to eq(run_errors.size) + run_errors.each do |run_error| + expect(error.wrapped_errors).to include(run_error) + expect(error.backtrace).to include(*run_error.backtrace) + end + end + end +end diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb index 2f03b23e32..301f64ddf1 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], fqdn, Chef::Config[:client_key], validate_utf8: false).and_return(http_node_save) - expect(http_node_save).to receive(:put_rest).with("nodes/#{fqdn}", node.for_json).and_return(true) - end - - 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,272 +232,93 @@ 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 - context "when converge errors" do - include_context "a client run" do - - let(:converge_error) do - err = Chef::Exceptions::UnsupportedAction.new("Action unsupported") - err.set_backtrace([ "/path/recipe.rb:15", "/path/recipe.rb:12" ]) - err - end - - def stub_for_converge - expect(Chef::Runner).to receive(:new).and_return(runner) - expect(runner).to receive(:converge).and_raise(converge_error) - end + describe "when converge completes successfully" do + include_context "a client run" + include_context "converge completed" - def stub_for_node_save - expect(client).to_not receive(:save_updated_node) + describe "when audit phase errors" do + include_context "audit phase failed with error" + include_examples "a failed run" do + let(:run_errors) { [audit_error] } end + 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 + describe "when audit phase completed" do + include_context "audit phase completed" + include_examples "a completed run" + end - before do - expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError) - end + describe "when audit phase completed with failed controls" do + include_context "audit phase completed with failed controls" - context "without audit phase errors" do - it "skips node save and raises the converge error in a wrapping 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).to include(converge_error) - expect(error.backtrace).to include(*converge_error.backtrace) - end + context "with :audit_as_warning true" do + before do + Chef::Config[:audit_as_warning] = true end + + include_examples "a completed run" end - context "when audit phase errors" do - context "with failed audits" do - - let(:audit_error) do - err = Chef::Exceptions::AuditsFailed.new(num_failed, num_total) - err.set_backtrace([ "/path/recipe.rb:57", "/path/recipe.rb:55" ]) - err - end - - let(:num_failed) { 1 } - - let(:num_total) { 5 } - - def stub_for_audit - expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner) - expect(audit_runner).to receive(:run) - expect(audit_runner).to receive(:failed?).and_return(true) - expect(Chef::Exceptions::AuditsFailed).to receive(:new).with(num_failed, num_total).and_return(audit_error) - allow(audit_runner).to receive(:num_failed).and_return(num_failed) - allow(audit_runner).to receive(:num_total).and_return(num_total) - end - - context "with audit_as_warning true" do - before do - Chef::Config[:audit_as_warning] = true - end - - it "skips node save and raises the converge error in a wrapping 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(converge_error) - end - end - end - - context "with audit_as_warning false" do - - before do - Chef::Config[:audit_as_warning] = false - end - - it "skips node save and raises converge and audit errors in a wrapping error" do - expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error| - expect(error.wrapped_errors.size).to eq(2) - expect(error.wrapped_errors).to include(converge_error) - expect(error.wrapped_errors).to include(audit_error) - expect(error.backtrace).to include(*converge_error.backtrace) - expect(error.backtrace).to include(*audit_error.backtrace) - end - end - end + context "with :audit_as_warning false" do + before do + Chef::Config[:audit_as_warning] = false end - context "with unexpected error" do - - let(:audit_error) do - err = RuntimeError.new("Unexpected audit error") - err.set_backtrace([ "/path/recipe.rb:57", "/path/recipe.rb:55" ]) - err - end - - def stub_for_audit - expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner) - expect(audit_runner).to receive(:run).and_raise(audit_error) - end - - context "with audit_as_warning true" do - it "skips node save and raises converge and audit errors in a wrapping error" do - expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error| - expect(error.wrapped_errors.size).to eq(2) - expect(error.wrapped_errors).to include(converge_error) - expect(error.wrapped_errors).to include(audit_error) - expect(error.backtrace).to include(*converge_error.backtrace) - expect(error.backtrace).to include(*audit_error.backtrace) - end - end - end - - context "with audit_as_warning false" do - it "skips node save and raises converge and audit errors in a wrapping error" do - expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error| - expect(error.wrapped_errors.size).to eq(2) - expect(error.wrapped_errors).to include(converge_error) - expect(error.wrapped_errors).to include(audit_error) - expect(error.backtrace).to include(*converge_error.backtrace) - expect(error.backtrace).to include(*audit_error.backtrace) - end - end - end + include_examples "a failed run" do + let(:run_errors) { [audit_error] } end end end end - context "when audit phase errors" do - include_context "a client run" do - - 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) + describe "when converge errors" do + include_context "a client run" + include_context "converge failed" - # Post conditions: check that node has been filled in correctly - expect(client).to receive(:run_started) + 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 - context "with failed audits" do - - let(:audit_error) do - err = Chef::Exceptions::AuditsFailed.new(num_failed, num_total) - err.set_backtrace([ "/path/recipe.rb:57", "/path/recipe.rb:55" ]) - err - end - - let(:num_failed) { 1 } - - let(:num_total) { 5 } - - def stub_for_audit - expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner) - expect(audit_runner).to receive(:run) - expect(audit_runner).to receive(:failed?).and_return(true) - expect(Chef::Exceptions::AuditsFailed).to receive(:new).with(num_failed, num_total).and_return(audit_error) - allow(audit_runner).to receive(:num_failed).and_return(num_failed) - allow(audit_runner).to receive(:num_total).and_return(num_total) - end - - context "with audit_as_warning true" do - before do - Chef::Config[:audit_as_warning] = true - end - - it "saves the node and completes the run successfully" do - expect(client).to receive(:run_completed_successfully) - expect_any_instance_of(Chef::ResourceReporter).to receive(:run_completed) - expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_completed) - client.run - end - end - - context "with audit_as_warning false" do - - before do - Chef::Config[:audit_as_warning] = false - expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError) - end - - it "saves the node and raises audit error in a wrapping error" do - 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) - - expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error| - expect(error.wrapped_errors.size).to eq(1) - expect(error.wrapped_errors).to include(audit_error) - expect(error.backtrace).to include(*audit_error.backtrace) - end - end - end + describe "when audit phase completed" do + include_context "audit phase completed" + include_examples "a failed run" do + let(:run_errors) { [converge_error] } end + end - context "with unexpected error" do + describe "when audit phase completed with failed controls" do + include_context "audit phase completed with failed controls" - let(:audit_error) do - err = RuntimeError.new("Unexpected audit error") - err.set_backtrace([ "/path/recipe.rb:57", "/path/recipe.rb:55" ]) - err + context "with :audit_as_warning true" do + before do + Chef::Config[:audit_as_warning] = true end - def stub_for_audit - expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner) - expect(audit_runner).to receive(:run).and_raise(audit_error) + include_examples "a failed run" do + let(:run_errors) { [converge_error] } end + end + context "with :audit_as_warning false" do before do - expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError) - end - - context "with audit_as_warning true" do - it "saves the node and raises audit error in a wrapping error" do - 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) - - expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error| - expect(error.wrapped_errors.size).to eq(1) - expect(error.wrapped_errors).to include(audit_error) - expect(error.backtrace).to include(*audit_error.backtrace) - end - end + Chef::Config[:audit_as_warning] = false end - context "with audit_as_warning false" do - it "skips node save and raises converge and audit errors in a wrapping error" do - 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) - - expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error| - expect(error.wrapped_errors.size).to eq(1) - expect(error.wrapped_errors).to include(audit_error) - expect(error.backtrace).to include(*audit_error.backtrace) - end - end + include_examples "a failed run" do + let(:run_errors) { [converge_error, audit_error] } end 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) @@ -837,6 +491,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 |