summaryrefslogtreecommitdiff
path: root/spec/unit/client_spec.rb
diff options
context:
space:
mode:
authorNoah Kantrowitz <noah@coderanger.net>2015-05-29 18:03:37 -0700
committerNoah Kantrowitz <noah@coderanger.net>2015-05-29 18:03:37 -0700
commit0bf9fda2b7224db4005aeda9cf2efb00ba6fb51b (patch)
tree8a20227ac9cacb776af3eb14daaa0080f629271a /spec/unit/client_spec.rb
parentdec356f59e9aaa37e30aee49b0e23ce1e3f41a12 (diff)
parentbcb812deffae37b15513bfcff448b95c7be4b265 (diff)
downloadchef-0bf9fda2b7224db4005aeda9cf2efb00ba6fb51b.tar.gz
Merge branch 'master' into nameless
Diffstat (limited to 'spec/unit/client_spec.rb')
-rw-r--r--spec/unit/client_spec.rb432
1 files changed, 58 insertions, 374 deletions
diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb
index d8c4ede796..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], 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,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)
-
- expect_any_instance_of(Chef::ResourceReporter).to receive(:run_failed)
- expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_failed)
- end
- end
+ describe "when converge completes successfully" do
+ include_context "a client run"
+ include_context "converge completed"
- 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)
+ 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
- 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
-
+ describe "when audit phase completed" do
+ include_context "audit phase completed"
+ include_examples "a completed run"
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)
@@ -779,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
@@ -833,7 +514,10 @@ describe Chef::Client do
it "should run exception handlers on early fail" do
expect(subject).to receive(:run_failed)
- expect { subject.run }.to raise_error(NoMethodError)
+ 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