summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaire McQuin <mcquin@users.noreply.github.com>2014-12-11 14:14:24 -0800
committerClaire McQuin <mcquin@users.noreply.github.com>2014-12-11 14:14:24 -0800
commitb90bd89058a3db6be7971e4f78d18c7ed1294fa8 (patch)
treee0547151474e97ee55620c68e18465fdaac68acb
parente7b78812aed1c1958fe1a263f1a448bba203bc3c (diff)
parent9e59d5a89d39fa46278ee655d5d9af66fb827c1f (diff)
downloadchef-b90bd89058a3db6be7971e4f78d18c7ed1294fa8.tar.gz
Merge pull request #2533 from opscode/mcquin/unit-tests
[WIP] Audit mode specs
-rw-r--r--lib/chef/exceptions.rb2
-rw-r--r--spec/unit/audit/audit_event_proxy_spec.rb4
-rw-r--r--spec/unit/client_spec.rb194
-rw-r--r--spec/unit/dsl/audit_spec.rb43
-rw-r--r--spec/unit/exceptions_spec.rb2
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