diff options
-rw-r--r-- | lib/chef/exceptions.rb | 2 | ||||
-rw-r--r-- | spec/unit/audit/audit_event_proxy_spec.rb | 4 | ||||
-rw-r--r-- | spec/unit/client_spec.rb | 194 | ||||
-rw-r--r-- | spec/unit/dsl/audit_spec.rb | 43 | ||||
-rw-r--r-- | spec/unit/exceptions_spec.rb | 2 |
5 files changed, 222 insertions, 23 deletions
diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb index ef957ec502..f710266530 100644 --- a/lib/chef/exceptions.rb +++ b/lib/chef/exceptions.rb @@ -403,7 +403,7 @@ class Chef attr_reader :wrapped_errors def initialize(*errors) errors = errors.select {|e| !e.nil?} - output = "Found #{errors.size} errors, they are stored in the backtrace\n" + output = "Found #{errors.size} errors, they are stored in the backtrace" @wrapped_errors = errors super output end diff --git a/spec/unit/audit/audit_event_proxy_spec.rb b/spec/unit/audit/audit_event_proxy_spec.rb index 1fddde43f1..2c4a0a1b9a 100644 --- a/spec/unit/audit/audit_event_proxy_spec.rb +++ b/spec/unit/audit/audit_event_proxy_spec.rb @@ -204,7 +204,7 @@ describe Chef::Audit::AuditEventProxy do # Metadata fields let(:described_class) { double("Serverspec::Type::Port", - :class => "Serverspec::Type::Port") } + :class => "Serverspec::Type::Port", :name => resource_name) } # Control data fields let(:resource_type) { "Port" } @@ -262,7 +262,7 @@ describe Chef::Audit::AuditEventProxy do # Metadata parts let(:described_class) { double("Serverspec::Type::File", - :class => "Serverspec::Type::File") } + :class => "Serverspec::Type::File", :name => resource_name) } # Example group parts let(:parent_group) { diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb index f38dee634d..4f6d8a0b82 100644 --- a/spec/unit/client_spec.rb +++ b/spec/unit/client_spec.rb @@ -187,12 +187,12 @@ describe Chef::Client do end describe "a full client run" do - shared_examples_for "a successful 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) { double("Chef::Audit::Runner") } + let(:audit_runner) { instance_double("Chef::Audit::Runner", :failed? => false) } let(:api_client_exists?) { false } @@ -205,7 +205,11 @@ describe Chef::Client do # --Client.register # Make sure Client#register thinks the client key doesn't # exist, so it tries to register and create one. - expect(File).to receive(:exists?).with(Chef::Config[:client_key]).exactly(1).times.and_return(api_client_exists?) + 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. @@ -219,7 +223,7 @@ describe Chef::Client do # previous step. expect(Chef::REST).to receive(:new). with(Chef::Config[:chef_server_url], fqdn, Chef::Config[:client_key]). - exactly(1). + exactly(:once). and_return(http_node_load) # --Client#build_node @@ -247,18 +251,12 @@ describe Chef::Client do # --Client#converge expect(Chef::Runner).to receive(:new).and_return(runner) expect(runner).to receive(:converge).and_return(true) - - # --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) end def stub_for_audit - # --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) + # -- 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 @@ -277,11 +275,21 @@ describe Chef::Client do # 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 stub_const("Chef::Client::STDOUT_FD", stdout) stub_const("Chef::Client::STDERR_FD", stderr) @@ -294,8 +302,12 @@ describe Chef::Client do stub_for_node_save stub_for_run end + end - it "runs ohai, sets up authentication, loads node state, synchronizes policy, and converges" do + shared_examples_for "a successful client run" do + include_context "a client run" + + it "runs ohai, sets up authentication, loads node state, synchronizes policy, converges, and runs audits" do # This is what we're testing. client.run @@ -305,16 +317,12 @@ describe Chef::Client do end end - describe "when running chef-client without fork" do - include_examples "a successful client run" end describe "when the client key already exists" do - let(:api_client_exists?) { true } - include_examples "a successful client run" end @@ -353,7 +361,6 @@ describe Chef::Client do end describe "when a permanent run list is passed as an option" do - include_examples "a successful client run" do let(:new_runlist) { "recipe[new_run_list_recipe]" } @@ -383,6 +390,155 @@ describe Chef::Client do 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) + + expect_any_instance_of(Chef::ResourceReporter).to receive(:run_failed) + expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_failed) + 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 + end + end + + describe "when the audit phase fails" do + context "with an exception" 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 "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 + 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 + + # 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 + 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) + 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") + end + end + end + end diff --git a/spec/unit/dsl/audit_spec.rb b/spec/unit/dsl/audit_spec.rb new file mode 100644 index 0000000000..38707127f0 --- /dev/null +++ b/spec/unit/dsl/audit_spec.rb @@ -0,0 +1,43 @@ + +require 'spec_helper' +require 'chef/dsl/audit' + +class AuditDSLTester < Chef::Recipe + include Chef::DSL::Audit +end + +class BadAuditDSLTester + include Chef::DSL::Audit +end + +describe Chef::DSL::Audit do + let(:auditor) { AuditDSLTester.new("cookbook_name", "recipe_name", run_context) } + let(:run_context) { instance_double(Chef::RunContext, :audits => audits, :cookbook_collection => cookbook_collection) } + let(:audits) { {} } + let(:cookbook_collection) { {} } + + it "raises an error when a block of audits is not provided" do + expect{ auditor.controls "name" }.to raise_error(Chef::Exceptions::NoAuditsProvided) + end + + it "raises an error when no audit name is given" do + expect{ auditor.controls do end }.to raise_error(Chef::Exceptions::AuditNameMissing) + end + + context "audits already populated" do + let(:audits) { {"unique" => {} } } + + it "raises an error if the audit name is a duplicate" do + expect { auditor.controls "unique" do end }.to raise_error(Chef::Exceptions::AuditControlGroupDuplicate) + end + end + + context "included in a class without recipe DSL" do + let(:auditor) { BadAuditDSLTester.new } + + it "fails because it relies on the recipe DSL existing" do + expect { auditor.controls "unique" do end }.to raise_error(NoMethodError, /undefined method `cookbook_name'/) + end + end + +end diff --git a/spec/unit/exceptions_spec.rb b/spec/unit/exceptions_spec.rb index 165c11446b..d35ecc8ec8 100644 --- a/spec/unit/exceptions_spec.rb +++ b/spec/unit/exceptions_spec.rb @@ -85,7 +85,7 @@ describe Chef::Exceptions do describe Chef::Exceptions::RunFailedWrappingError do shared_examples "RunFailedWrappingError expectations" do it "should initialize with a default message" do - expect(e.message).to eq("Found #{num_errors} errors, they are stored in the backtrace\n") + expect(e.message).to eq("Found #{num_errors} errors, they are stored in the backtrace") end it "should provide a modified backtrace when requested" do |