summaryrefslogtreecommitdiff
path: root/spec/unit
diff options
context:
space:
mode:
Diffstat (limited to 'spec/unit')
-rw-r--r--spec/unit/api_client/registration_spec.rb147
-rw-r--r--spec/unit/api_client_spec.rb46
-rw-r--r--spec/unit/application/client_spec.rb49
-rw-r--r--spec/unit/client_spec.rb574
-rw-r--r--spec/unit/cookbook/chefignore_spec.rb10
-rw-r--r--spec/unit/cookbook/metadata_spec.rb46
-rw-r--r--spec/unit/cookbook/syntax_check_spec.rb29
-rw-r--r--spec/unit/cookbook_spec.rb10
-rw-r--r--spec/unit/dsl/reboot_pending_spec.rb100
-rw-r--r--spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb56
-rw-r--r--spec/unit/http/simple_spec.rb32
-rw-r--r--spec/unit/http/validate_content_length_spec.rb187
-rw-r--r--spec/unit/knife/bootstrap_spec.rb17
-rw-r--r--spec/unit/knife/client_bulk_delete_spec.rb161
-rw-r--r--spec/unit/knife/client_create_spec.rb30
-rw-r--r--spec/unit/knife/client_delete_spec.rb45
-rw-r--r--spec/unit/knife/cookbook_upload_spec.rb269
-rw-r--r--spec/unit/knife/core/bootstrap_context_spec.rb12
-rw-r--r--spec/unit/knife/core/ui_spec.rb147
-rw-r--r--spec/unit/knife/node_run_list_add_spec.rb25
-rw-r--r--spec/unit/knife/ssh_spec.rb23
-rw-r--r--spec/unit/knife/ssl_check_spec.rb187
-rw-r--r--spec/unit/knife/ssl_fetch_spec.rb151
-rw-r--r--spec/unit/knife_spec.rb55
-rw-r--r--spec/unit/mixin/deep_merge_spec.rb17
-rw-r--r--spec/unit/node/attribute_spec.rb7
-rw-r--r--spec/unit/node/immutable_collections_spec.rb61
-rw-r--r--spec/unit/node_spec.rb9
-rw-r--r--spec/unit/platform/query_helpers_spec.rb32
-rw-r--r--spec/unit/platform_spec.rb368
-rw-r--r--spec/unit/policy_builder/expand_node_object_spec.rb2
-rw-r--r--spec/unit/provider/cron_spec.rb176
-rw-r--r--spec/unit/provider/group_spec.rb5
-rw-r--r--spec/unit/provider/ifconfig/debian_spec.rb275
-rw-r--r--spec/unit/provider/mount/mount_spec.rb25
-rw-r--r--spec/unit/provider/ohai_spec.rb5
-rw-r--r--spec/unit/provider/package/dpkg_spec.rb4
-rw-r--r--spec/unit/provider/package/windows/msi_spec.rb60
-rw-r--r--spec/unit/provider/package/windows_spec.rb80
-rw-r--r--spec/unit/provider/service/macosx_spec.rb40
-rw-r--r--spec/unit/provider/service/solaris_smf_service_spec.rb45
-rw-r--r--spec/unit/recipe_spec.rb4
-rw-r--r--spec/unit/resource/conditional_spec.rb25
-rw-r--r--spec/unit/resource/cron_spec.rb9
-rw-r--r--spec/unit/resource/powershell_spec.rb87
-rw-r--r--spec/unit/resource/subversion_spec.rb5
-rw-r--r--spec/unit/resource/windows_package_spec.rb74
-rw-r--r--spec/unit/resource_reporter_spec.rb2
-rw-r--r--spec/unit/resource_spec.rb24
-rw-r--r--spec/unit/rest_spec.rb66
-rw-r--r--spec/unit/run_context/cookbook_compiler_spec.rb12
-rw-r--r--spec/unit/run_context_spec.rb7
-rw-r--r--spec/unit/util/editor_spec.rb152
-rw-r--r--spec/unit/util/file_edit_spec.rb38
54 files changed, 3408 insertions, 716 deletions
diff --git a/spec/unit/api_client/registration_spec.rb b/spec/unit/api_client/registration_spec.rb
index 845c217f72..15a9c30482 100644
--- a/spec/unit/api_client/registration_spec.rb
+++ b/spec/unit/api_client/registration_spec.rb
@@ -22,16 +22,45 @@ require 'tempfile'
require 'chef/api_client/registration'
describe Chef::ApiClient::Registration do
+
let(:key_location) do
make_tmpname("client-registration-key")
end
- let(:registration) { Chef::ApiClient::Registration.new("silent-bob", key_location) }
+ let(:client_name) { "silent-bob" }
+
+ subject(:registration) { Chef::ApiClient::Registration.new(client_name, key_location) }
- let :private_key_data do
+ let(:private_key_data) do
File.open(Chef::Config[:validation_key], "r") {|f| f.read.chomp }
end
+ let(:http_mock) { double("Chef::REST mock") }
+
+ let(:expected_post_data) do
+ { :name => client_name, :admin => false }
+ end
+
+ let(:expected_put_data) do
+ { :name => client_name, :admin => false, :private_key => true }
+ end
+
+ let(:server_v10_response) do
+ {"uri" => "https://chef.local/clients/#{client_name}",
+ "private_key" => "--begin rsa key etc--"}
+ end
+
+ # Server v11 includes `json_class` on all replies
+ let(:server_v11_response) do
+ response = Chef::ApiClient.new
+ response.name(client_name)
+ response.private_key("--begin rsa key etc--")
+ response
+ end
+
+ let(:response_409) { Net::HTTPConflict.new("1.1", "409", "Conflict") }
+ let(:exception_409) { Net::HTTPServerException.new("409 conflict", response_409) }
+
before do
Chef::Config[:validation_client_name] = "test-validator"
Chef::Config[:validation_key] = File.expand_path('ssl/private_key.pem', CHEF_SPEC_DATA)
@@ -39,8 +68,6 @@ describe Chef::ApiClient::Registration do
after do
File.unlink(key_location) if File.exist?(key_location)
- Chef::Config[:validation_client_name] = nil
- Chef::Config[:validation_key] = nil
end
it "has an HTTP client configured with validator credentials" do
@@ -50,57 +77,107 @@ describe Chef::ApiClient::Registration do
end
describe "when creating/updating the client on the server" do
- let(:http_mock) { double("Chef::REST mock") }
-
before do
registration.stub(:http_api).and_return(http_mock)
end
it "creates a new ApiClient on the server using the validator identity" do
- response = {"uri" => "https://chef.local/clients/silent-bob",
- "private_key" => "--begin rsa key etc--"}
http_mock.should_receive(:post).
- with("clients", :name => 'silent-bob', :admin => false).
- and_return(response)
- registration.create_or_update.should == response
+ with("clients", expected_post_data).
+ and_return(server_v10_response)
+ registration.create_or_update.should == server_v10_response
registration.private_key.should == "--begin rsa key etc--"
end
context "and the client already exists on a Chef 10 server" do
it "requests a new key from the server and saves it" do
- response = {"name" => "silent-bob", "private_key" => "--begin rsa key etc--" }
-
- response_409 = Net::HTTPConflict.new("1.1", "409", "Conflict")
- exception_409 = Net::HTTPServerException.new("409 conflict", response_409)
-
- http_mock.should_receive(:post).and_raise(exception_409)
+ http_mock.should_receive(:post).with("clients", expected_post_data).
+ and_raise(exception_409)
http_mock.should_receive(:put).
- with("clients/silent-bob", :name => 'silent-bob', :admin => false, :private_key => true).
- and_return(response)
- registration.create_or_update.should == response
+ with("clients/#{client_name}", expected_put_data).
+ and_return(server_v10_response)
+ registration.create_or_update.should == server_v10_response
registration.private_key.should == "--begin rsa key etc--"
end
end
context "and the client already exists on a Chef 11 server" do
it "requests a new key from the server and saves it" do
- response = Chef::ApiClient.new
- response.name("silent-bob")
- response.private_key("--begin rsa key etc--")
-
- response_409 = Net::HTTPConflict.new("1.1", "409", "Conflict")
- exception_409 = Net::HTTPServerException.new("409 conflict", response_409)
-
http_mock.should_receive(:post).and_raise(exception_409)
http_mock.should_receive(:put).
- with("clients/silent-bob", :name => 'silent-bob', :admin => false, :private_key => true).
- and_return(response)
- registration.create_or_update.should == response
+ with("clients/#{client_name}", expected_put_data).
+ and_return(server_v11_response)
+ registration.create_or_update.should == server_v11_response
registration.private_key.should == "--begin rsa key etc--"
end
end
end
+ context "when local key generation is enabled", :nofocus do
+ let(:generated_private_key_pem) { IO.read(File.expand_path('ssl/private_key.pem', CHEF_SPEC_DATA)) }
+ let(:generated_private_key) { OpenSSL::PKey::RSA.new(generated_private_key_pem) }
+ let(:generated_public_key) { generated_private_key.public_key }
+
+ let(:expected_post_data) do
+ { :name => client_name, :admin => false, :public_key => generated_public_key.to_pem }
+ end
+
+ let(:expected_put_data) do
+ { :name => client_name, :admin => false, :public_key => generated_public_key.to_pem }
+ end
+
+ let(:create_with_pkey_response) do
+ {
+ "uri" => "",
+ "public_key" => generated_public_key.to_pem
+ }
+ end
+
+ let(:update_with_pkey_response) do
+ {"name"=>client_name,
+ "admin"=>false,
+ "public_key"=> generated_public_key,
+ "validator"=>false,
+ "private_key"=>false,
+ "clientname"=>client_name}
+ end
+
+
+ before do
+ registration.stub(:http_api).and_return(http_mock)
+ Chef::Config.local_key_generation = true
+ OpenSSL::PKey::RSA.should_receive(:generate).with(2048).and_return(generated_private_key)
+ end
+
+ it "posts a locally generated public key to the server to create a client" do
+ http_mock.should_receive(:post).
+ with("clients", expected_post_data).
+ and_return(create_with_pkey_response)
+ registration.create_or_update.should == create_with_pkey_response
+ registration.private_key.should == generated_private_key_pem
+ end
+
+ it "puts a locally generated public key to the server to update a client" do
+ http_mock.should_receive(:post).
+ with("clients", expected_post_data).
+ and_raise(exception_409)
+ http_mock.should_receive(:put).
+ with("clients/#{client_name}", expected_put_data).
+ and_return(update_with_pkey_response)
+ registration.create_or_update.should == update_with_pkey_response
+ registration.private_key.should == generated_private_key_pem
+ end
+
+ it "writes the generated private key to disk" do
+ http_mock.should_receive(:post).
+ with("clients", expected_post_data).
+ and_return(create_with_pkey_response)
+ registration.run
+ IO.read(key_location).should == generated_private_key_pem
+ end
+
+ end
+
describe "when writing the private key to disk" do
before do
registration.stub(:private_key).and_return('--begin rsa key etc--')
@@ -125,16 +202,12 @@ describe Chef::ApiClient::Registration do
describe "when registering a client" do
- let(:http_mock) { double("Chef::REST mock") }
-
before do
registration.stub(:http_api).and_return(http_mock)
end
it "creates the client on the server and writes the key" do
- response = {"uri" => "http://chef.local/clients/silent-bob",
- "private_key" => "--begin rsa key etc--" }
- http_mock.should_receive(:post).ordered.and_return(response)
+ http_mock.should_receive(:post).ordered.and_return(server_v10_response)
registration.run
IO.read(key_location).should == "--begin rsa key etc--"
end
@@ -149,9 +222,7 @@ describe Chef::ApiClient::Registration do
http_mock.should_receive(:post).ordered.and_raise(exception_500) # 4
http_mock.should_receive(:post).ordered.and_raise(exception_500) # 5
- response = {"uri" => "http://chef.local/clients/silent-bob",
- "private_key" => "--begin rsa key etc--" }
- http_mock.should_receive(:post).ordered.and_return(response)
+ http_mock.should_receive(:post).ordered.and_return(server_v10_response)
registration.run
IO.read(key_location).should == "--begin rsa key etc--"
end
diff --git a/spec/unit/api_client_spec.rb b/spec/unit/api_client_spec.rb
index 4ccd64bafe..8657fa59a8 100644
--- a/spec/unit/api_client_spec.rb
+++ b/spec/unit/api_client_spec.rb
@@ -164,6 +164,52 @@ describe Chef::ApiClient do
end
+ describe "when loading from JSON" do
+ before do
+ end
+
+ before(:each) do
+ client = {
+ "name" => "black",
+ "clientname" => "black",
+ "public_key" => "crowes",
+ "private_key" => "monkeypants",
+ "admin" => true,
+ "validator" => true,
+ "json_class" => "Chef::ApiClient"
+ }
+ @http_client = double("Chef::REST mock")
+ Chef::REST.stub(:new).and_return(@http_client)
+ @http_client.should_receive(:get).with("clients/black").and_return(client)
+ @client = Chef::ApiClient.load(client['name'])
+ end
+
+ it "should deserialize to a Chef::ApiClient object" do
+ @client.should be_a_kind_of(Chef::ApiClient)
+ end
+
+ it "preserves the name" do
+ @client.name.should == "black"
+ end
+
+ it "preserves the public key" do
+ @client.public_key.should == "crowes"
+ end
+
+ it "preserves the admin status" do
+ @client.admin.should be_a_kind_of(Chef::TrueClass)
+ end
+
+ it "preserves the 'validator' status" do
+ @client.validator.should be_a_kind_of(Chef::TrueClass)
+ end
+
+ it "includes the private key if present" do
+ @client.private_key.should == "monkeypants"
+ end
+
+ end
+
describe "with correctly configured API credentials" do
before do
Chef::Config[:node_name] = "silent-bob"
diff --git a/spec/unit/application/client_spec.rb b/spec/unit/application/client_spec.rb
index f84932073f..8b4ea6a077 100644
--- a/spec/unit/application/client_spec.rb
+++ b/spec/unit/application/client_spec.rb
@@ -127,7 +127,7 @@ describe Chef::Application::Client, "configure_chef" do
end
describe Chef::Application::Client, "run_application", :unix_only do
- before do
+ before(:each) do
@pipe = IO.pipe
@app = Chef::Application::Client.new
@app.stub(:run_chef_client) do
@@ -147,4 +147,51 @@ describe Chef::Application::Client, "run_application", :unix_only do
IO.select([@pipe[0]], nil, nil, 0).should_not be_nil
@pipe[0].gets.should == "finished\n"
end
+
+ describe "when splay is set" do
+ before do
+ Chef::Config[:splay] = 10
+ Chef::Config[:interval] = 10
+
+ run_count = 0
+
+ # uncomment to debug failures...
+ # Chef::Log.init($stderr)
+ # Chef::Log.level = :debug
+
+ @app.stub(:run_chef_client) do
+
+ run_count += 1
+ if run_count > 3
+ exit 0
+ end
+
+ # If everything is fine, sending USR1 to self should prevent
+ # app to go into splay sleep forever.
+ Process.kill("USR1", Process.pid)
+ end
+
+ number_of_sleep_calls = 0
+
+ # This is a very complicated way of writing
+ # @app.should_receive(:sleep).once.
+ # We have to do it this way because the main loop of
+ # Chef::Application::Client swallows most exceptions, and we need to be
+ # able to expose our expectation failures to the parent process in the test.
+ @app.stub(:sleep) do |arg|
+ number_of_sleep_calls += 1
+ if number_of_sleep_calls > 1
+ exit 127
+ end
+ end
+ end
+
+ it "shouldn't sleep when sent USR1" do
+ pid = fork do
+ @app.run_application
+ end
+ _pid, result = Process.waitpid2(pid)
+ result.exitstatus.should == 0
+ end
+ end
end
diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb
index 58a643d403..9688cce2f4 100644
--- a/spec/unit/client_spec.rb
+++ b/spec/unit/client_spec.rb
@@ -24,29 +24,54 @@ require 'chef/run_context'
require 'chef/rest'
require 'rbconfig'
-shared_examples_for Chef::Client do
+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)
+ ohai_system.stub(:[]) 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::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
- @hostname = "hostname"
- @fqdn = "hostname.example.org"
- Chef::Config[:node_name] = @fqdn
- ohai_data = { :fqdn => @fqdn,
- :hostname => @hostname,
- :platform => 'example-platform',
- :platform_version => 'example-platform-1.0',
- :data => {} }
- ohai_data.stub(:all_plugins).and_return(true)
- ohai_data.stub(:data).and_return(ohai_data)
- Ohai::System.stub(:new).and_return(ohai_data)
-
- @node = Chef::Node.new
- @node.name(@fqdn)
- @node.chef_environment("_default")
-
- @client = Chef::Client.new
- @client.node = @node
+ #Chef::Config[:node_name] = fqdn
+ Ohai::System.stub(:new).and_return(ohai_system)
end
describe "authentication protocol selection" do
@@ -58,7 +83,7 @@ shared_examples_for Chef::Client do
it "does not force the authentication protocol to 1.1" do
Chef::Config[:node_name] = ("f" * 90)
# ugly that this happens as a side effect of a getter :(
- @client.node_name
+ client.node_name
Chef::Config[:authentication_protocol_version].should == "1.0"
end
end
@@ -67,7 +92,7 @@ shared_examples_for Chef::Client do
it "sets the authentication protocol to version 1.1" do
Chef::Config[:node_name] = ("f" * 91)
# ugly that this happens as a side effect of a getter :(
- @client.node_name
+ client.node_name
Chef::Config[:authentication_protocol_version].should == "1.1"
end
end
@@ -75,9 +100,6 @@ shared_examples_for Chef::Client do
describe "configuring output formatters" do
context "when no formatter has been configured" do
- before do
- @client = Chef::Client.new
- end
context "and STDOUT is a TTY" do
before do
@@ -85,7 +107,7 @@ shared_examples_for Chef::Client do
end
it "configures the :doc formatter" do
- @client.formatters_for_run.should == [[:doc]]
+ client.formatters_for_run.should == [[:doc]]
end
context "and force_logger is set" do
@@ -95,7 +117,7 @@ shared_examples_for Chef::Client do
it "configures the :null formatter" do
Chef::Config[:force_logger].should be_true
- @client.formatters_for_run.should == [[:null]]
+ client.formatters_for_run.should == [[:null]]
end
end
@@ -108,7 +130,7 @@ shared_examples_for Chef::Client do
end
it "configures the :null formatter" do
- @client.formatters_for_run.should == [[:null]]
+ client.formatters_for_run.should == [[:null]]
end
context "and force_formatter is set" do
@@ -116,7 +138,7 @@ shared_examples_for Chef::Client do
Chef::Config[:force_formatter] = true
end
it "it configures the :doc formatter" do
- @client.formatters_for_run.should == [[:doc]]
+ client.formatters_for_run.should == [[:doc]]
end
end
end
@@ -126,16 +148,15 @@ shared_examples_for Chef::Client do
context "when a formatter is configured" do
context "with no output path" do
before do
- @client = Chef::Client.new
Chef::Config.add_formatter(:min)
end
it "does not configure a default formatter" do
- @client.formatters_for_run.should == [[:min, nil]]
+ client.formatters_for_run.should == [[:min, nil]]
end
it "configures the formatter for STDOUT/STDERR" do
- configured_formatters = @client.configure_formatters
+ configured_formatters = client.configure_formatters
min_formatter = configured_formatters[0]
min_formatter.output.out.should == STDOUT
min_formatter.output.err.should == STDERR
@@ -144,7 +165,6 @@ shared_examples_for Chef::Client do
context "with an output path" do
before do
- @client = Chef::Client.new
@tmpout = Tempfile.open("rspec-for-client-formatter-selection-#{Process.pid}")
Chef::Config.add_formatter(:min, @tmpout.path)
end
@@ -155,7 +175,7 @@ shared_examples_for Chef::Client do
end
it "configures the formatter for the file path" do
- configured_formatters = @client.configure_formatters
+ configured_formatters = client.configure_formatters
min_formatter = configured_formatters[0]
min_formatter.output.out.path.should == @tmpout.path
min_formatter.output.err.path.should == @tmpout.path
@@ -165,93 +185,216 @@ shared_examples_for Chef::Client do
end
end
- describe "run" do
-
- it "should identify the node and run ohai, then register the client" do
- mock_chef_rest_for_node = double("Chef::REST (node)")
- mock_chef_rest_for_cookbook_sync = double("Chef::REST (cookbook sync)")
- mock_chef_rest_for_node_save = double("Chef::REST (node save)")
- mock_chef_runner = double("Chef::Runner")
-
- # --Client.register
- # Make sure Client#register thinks the client key doesn't
- # exist, so it tries to register and create one.
- File.should_receive(:exists?).with(Chef::Config[:client_key]).exactly(1).times.and_return(false)
-
- # Client.register will register with the validation client name.
- Chef::ApiClient::Registration.any_instance.should_receive(:run)
- # Client.register will then turn around create another
- # Chef::REST object, this time with the client key it got from the
- # previous step.
- Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url], @fqdn, Chef::Config[:client_key]).exactly(1).and_return(mock_chef_rest_for_node)
-
- # --Client#build_node
- # looks up the node, which we will return, then later saves it.
- Chef::Node.should_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.)
- Chef::ResourceReporter.any_instance.should_receive(:node_load_completed)
-
- # --ResourceReporter#run_completed
- # updates the server with the resource history
- # (has its own tests, so stubbing it here.)
- Chef::ResourceReporter.any_instance.should_receive(:run_completed)
- # --Client#setup_run_context
- # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync
- #
- Chef::CookbookSynchronizer.any_instance.should_receive(:sync_cookbooks)
- Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url]).and_return(mock_chef_rest_for_cookbook_sync)
- mock_chef_rest_for_cookbook_sync.should_receive(:post).with("environments/_default/cookbook_versions", {:run_list => []}).and_return({})
-
- # --Client#converge
- Chef::Runner.should_receive(:new).and_return(mock_chef_runner)
- mock_chef_runner.should_receive(:converge).and_return(true)
-
- # --Client#save_updated_node
- Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url]).and_return(mock_chef_rest_for_node_save)
- mock_chef_rest_for_node_save.should_receive(:put_rest).with("nodes/#{@fqdn}", @node).and_return(true)
-
- Chef::RunLock.any_instance.should_receive(:acquire)
- Chef::RunLock.any_instance.should_receive(:save_pid)
- Chef::RunLock.any_instance.should_receive(:release)
-
- # Post conditions: check that node has been filled in correctly
- @client.should_receive(:run_started)
- @client.should_receive(:run_completed_successfully)
+ describe "a full client run" do
+ shared_examples_for "a successful 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") }
- if(Chef::Config[:client_fork] && !windows?)
- require 'stringio'
- if(Chef::Config[:pipe_node])
- pipe_sim = StringIO.new
- pipe_sim.should_receive(:close).exactly(4).and_return(nil)
- res = ''
- pipe_sim.should_receive(:puts) do |string|
- res.replace(string)
- end
- pipe_sim.should_receive(:gets).and_return(res)
- IO.should_receive(:pipe).and_return([pipe_sim, pipe_sim])
- IO.should_receive(:select).and_return(true)
+ 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.
+ File.should_receive(:exists?).with(Chef::Config[:client_key]).exactly(1).times.and_return(api_client_exists?)
+
+ unless api_client_exists?
+ # Client.register will register with the validation client name.
+ Chef::ApiClient::Registration.any_instance.should_receive(:run)
end
- proc_ret = Class.new.new
- proc_ret.should_receive(:success?).and_return(true)
- Process.should_receive(:waitpid2).and_return([1, proc_ret])
- @client.should_receive(:exit).and_return(nil)
- @client.should_receive(:fork) do |&block|
- block.call
+ 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.
+ Chef::REST.should_receive(:new).
+ with(Chef::Config[:chef_server_url], fqdn, Chef::Config[:client_key]).
+ exactly(1).
+ and_return(http_node_load)
+
+ # --Client#build_node
+ # looks up the node, which we will return, then later saves it.
+ Chef::Node.should_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.)
+ Chef::ResourceReporter.any_instance.should_receive(:node_load_completed)
+ end
+
+ def stub_for_sync_cookbooks
+ # --Client#setup_run_context
+ # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync
+ #
+ Chef::CookbookSynchronizer.any_instance.should_receive(:sync_cookbooks)
+ Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_cookbook_sync)
+ http_cookbook_sync.should_receive(:post).
+ with("environments/_default/cookbook_versions", {:run_list => []}).
+ and_return({})
+ end
+
+ def stub_for_converge
+ # --Client#converge
+ Chef::Runner.should_receive(:new).and_return(runner)
+ runner.should_receive(:converge).and_return(true)
+
+ # --ResourceReporter#run_completed
+ # updates the server with the resource history
+ # (has its own tests, so stubbing it here.)
+ Chef::ResourceReporter.any_instance.should_receive(:run_completed)
+ end
+
+ def stub_for_node_save
+ # --Client#save_updated_node
+ Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_node_save)
+ http_node_save.should_receive(:put_rest).with("nodes/#{fqdn}", node).and_return(true)
+ end
+
+ def stub_for_run
+ Chef::RunLock.any_instance.should_receive(:acquire)
+ Chef::RunLock.any_instance.should_receive(:save_pid)
+ Chef::RunLock.any_instance.should_receive(:release)
+
+ # Post conditions: check that node has been filled in correctly
+ client.should_receive(:run_started)
+ client.should_receive(:run_completed_successfully)
+ end
+
+ before do
+ Chef::Config[:client_fork] = enable_fork
+
+ 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_node_save
+ stub_for_run
+ end
+
+ it "runs ohai, sets up authentication, loads node state, synchronizes policy, and converges" do
+ # This is what we're testing.
+ client.run
+
+ # fork is stubbed, so we can see the outcome of the run
+ node.automatic_attrs[:platform].should == "example-platform"
+ node.automatic_attrs[:platform_version].should == "example-platform-1.0"
+ end
+ end
+
+
+ describe "when running chef-client without fork" do
+
+ include_examples "a successful client run"
+ end
+
+ describe "when running chef-client with forking enabled", :unix_only do
+ include_examples "a successful client run" do
+ let(:process_status) do
+ double("Process::Status")
+ end
+
+ let(:enable_fork) { true }
+
+ before do
+ Process.should_receive(:waitpid2).and_return([1, process_status])
+
+ process_status.should_receive(:success?).and_return(true)
+ client.should_receive(:exit).and_return(nil)
+ client.should_receive(:fork).and_yield
end
end
- # This is what we're testing.
- @client.run
+ end
+
+ describe "when the client key already exists" do
+
+ let(:api_client_exists?) { true }
+
+ include_examples "a successful client run"
+ end
+
+ describe "when an override run list is given" do
+ let(:client_opts) { {:override_runlist => "recipe[override_recipe]"} }
- if(!Chef::Config[:client_fork] || Chef::Config[:pipe_node])
- @node.automatic_attrs[:platform].should == "example-platform"
- @node.automatic_attrs[:platform_version].should == "example-platform-1.0"
+ it "should permit spaces in overriding run list" do
+ Chef::Client.new(nil, :override_runlist => 'role[a], role[b]')
+ end
+
+ describe "when running the client" do
+ include_examples "a successful client run" do
+
+ before do
+ # Client will try to compile and run override_recipe
+ Chef::RunContext::CookbookCompiler.any_instance.should_receive(:compile)
+ end
+
+ def stub_for_sync_cookbooks
+ # --Client#setup_run_context
+ # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync
+ #
+ Chef::CookbookSynchronizer.any_instance.should_receive(:sync_cookbooks)
+ Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_cookbook_sync)
+ http_cookbook_sync.should_receive(:post).
+ with("environments/_default/cookbook_versions", {:run_list => ["override_recipe"]}).
+ and_return({})
+ end
+
+ def stub_for_node_save
+ # Expect NO node save
+ node.should_not_receive(:save)
+ end
+ end
end
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]" }
+ let(:client_opts) { {:runlist => new_runlist} }
+
+ def stub_for_sync_cookbooks
+ # --Client#setup_run_context
+ # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync
+ #
+ Chef::CookbookSynchronizer.any_instance.should_receive(:sync_cookbooks)
+ Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_cookbook_sync)
+ http_cookbook_sync.should_receive(:post).
+ with("environments/_default/cookbook_versions", {:run_list => ["new_run_list_recipe"]}).
+ and_return({})
+ end
+
+ before do
+ # Client will try to compile and run the new_run_list_recipe, but we
+ # do not create a fixture for this.
+ Chef::RunContext::CookbookCompiler.any_instance.should_receive(:compile)
+ end
+
+ it "sets the new run list on the node" do
+ client.run
+ node.run_list.should == Chef::RunList.new(new_runlist)
+ 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)
Chef::RunLock.stub(:new).and_return(@run_lock)
@@ -260,64 +403,64 @@ shared_examples_for Chef::Client do
Chef::EventDispatch::Dispatcher.stub(:new).and_return(@events)
# @events is created on Chef::Client.new, so we need to recreate it after mocking
- @client = Chef::Client.new
- @client.stub(:load_node).and_raise(Exception)
+ client = Chef::Client.new
+ client.stub(:load_node).and_raise(Exception)
@run_lock.should_receive(:release)
if(Chef::Config[:client_fork] && !windows?)
- @client.should_receive(:fork) do |&block|
+ client.should_receive(:fork) do |&block|
block.call
end
end
- lambda { @client.run }.should raise_error(Exception)
+ lambda { client.run }.should raise_error(Exception)
end
+ end
- describe "when notifying other objects of the status of the chef run" do
- before do
- Chef::Client.clear_notifications
- Chef::Node.stub(:find_or_create).and_return(@node)
- @node.stub(:save)
- @client.load_node
- @client.build_node
- end
-
- it "notifies observers that the run has started" do
- notified = false
- Chef::Client.when_run_starts do |run_status|
- run_status.node.should == @node
- notified = true
- end
+ describe "when notifying other objects of the status of the chef run" do
+ before do
+ Chef::Client.clear_notifications
+ Chef::Node.stub(:find_or_create).and_return(node)
+ node.stub(:save)
+ client.load_node
+ client.build_node
+ end
- @client.run_started
- notified.should be_true
+ it "notifies observers that the run has started" do
+ notified = false
+ Chef::Client.when_run_starts do |run_status|
+ run_status.node.should == node
+ notified = true
end
- it "notifies observers that the run has completed successfully" do
- notified = false
- Chef::Client.when_run_completes_successfully do |run_status|
- run_status.node.should == @node
- notified = true
- end
+ client.run_started
+ notified.should be_true
+ end
- @client.run_completed_successfully
- notified.should be_true
+ it "notifies observers that the run has completed successfully" do
+ notified = false
+ Chef::Client.when_run_completes_successfully do |run_status|
+ run_status.node.should == node
+ notified = true
end
- it "notifies observers that the run failed" do
- notified = false
- Chef::Client.when_run_fails do |run_status|
- run_status.node.should == @node
- notified = true
- end
+ client.run_completed_successfully
+ notified.should be_true
+ end
- @client.run_failed
- notified.should be_true
+ it "notifies observers that the run failed" do
+ notified = false
+ Chef::Client.when_run_fails do |run_status|
+ run_status.node.should == node
+ notified = true
end
+
+ client.run_failed
+ notified.should be_true
end
end
describe "build_node" do
it "should expand the roles and recipes for the node" do
- @node.run_list << "role[role_containing_cookbook1]"
+ node.run_list << "role[role_containing_cookbook1]"
role_containing_cookbook1 = Chef::Role.new
role_containing_cookbook1.name("role_containing_cookbook1")
role_containing_cookbook1.run_list << "cookbook1"
@@ -329,37 +472,33 @@ shared_examples_for Chef::Client do
Chef::REST.should_receive(:new).and_return(mock_chef_rest)
# check pre-conditions.
- @node[:roles].should be_nil
- @node[:recipes].should be_nil
+ node[:roles].should be_nil
+ node[:recipes].should be_nil
- @client.policy_builder.stub(:node).and_return(@node)
+ client.policy_builder.stub(:node).and_return(node)
# chefspec and possibly others use the return value of this method
- @client.build_node.should == @node
+ client.build_node.should == node
# check post-conditions.
- @node[:roles].should_not be_nil
- @node[:roles].length.should == 1
- @node[:roles].should include("role_containing_cookbook1")
- @node[:recipes].should_not be_nil
- @node[:recipes].length.should == 1
- @node[:recipes].should include("cookbook1")
+ node[:roles].should_not be_nil
+ node[:roles].length.should == 1
+ node[:roles].should include("role_containing_cookbook1")
+ node[:recipes].should_not be_nil
+ node[:recipes].length.should == 1
+ node[:recipes].should include("cookbook1")
end
end
describe "windows_admin_check" do
- before do
- @client = Chef::Client.new
- end
-
context "platform is not windows" do
before do
Chef::Platform.stub(:windows?).and_return(false)
end
it "shouldn't be called" do
- @client.should_not_receive(:has_admin_privileges?)
- @client.do_windows_admin_check
+ client.should_not_receive(:has_admin_privileges?)
+ client.do_windows_admin_check
end
end
@@ -369,91 +508,46 @@ shared_examples_for Chef::Client do
end
it "should be called" do
- @client.should_receive(:has_admin_privileges?)
- @client.do_windows_admin_check
+ client.should_receive(:has_admin_privileges?)
+ client.do_windows_admin_check
end
context "admin privileges exist" do
before do
- @client.should_receive(:has_admin_privileges?).and_return(true)
+ client.should_receive(:has_admin_privileges?).and_return(true)
end
it "should not log a warning message" do
Chef::Log.should_not_receive(:warn)
- @client.do_windows_admin_check
+ client.do_windows_admin_check
end
context "fatal admin check is configured" do
it "should not raise an exception" do
- @client.do_windows_admin_check.should_not raise_error
+ client.do_windows_admin_check #should not raise
end
end
end
context "admin privileges doesn't exist" do
before do
- @client.should_receive(:has_admin_privileges?).and_return(false)
+ client.should_receive(:has_admin_privileges?).and_return(false)
end
it "should log a warning message" do
Chef::Log.should_receive(:warn)
- @client.do_windows_admin_check
+ client.do_windows_admin_check
end
context "fatal admin check is configured" do
it "should raise an exception" do
- @client.do_windows_admin_check.should_not raise_error
+ client.do_windows_admin_check # should not raise
end
end
end
end
end
- describe "when a run list override is provided" do
- before do
- @node = Chef::Node.new
- @node.name(@fqdn)
- @node.chef_environment("_default")
- @node.automatic_attrs[:platform] = "example-platform"
- @node.automatic_attrs[:platform_version] = "example-platform-1.0"
- end
-
- it "should permit spaces in overriding run list" do
- @client = Chef::Client.new(nil, :override_runlist => 'role[a], role[b]')
- end
-
- it "should override the run list and skip the final node save" do
- @client = Chef::Client.new(nil, :override_runlist => 'role[test_role]')
- @client.node = @node
-
- @node.run_list << "role[role_containing_cookbook1]"
-
- override_role = Chef::Role.new
- override_role.name 'test_role'
- override_role.run_list << 'cookbook1'
-
- original_runlist = @node.run_list.dup
-
- mock_chef_rest = double("Chef::REST")
- mock_chef_rest.should_receive(:get_rest).with("roles/test_role").and_return(override_role)
- Chef::REST.should_receive(:new).and_return(mock_chef_rest)
-
- @node.should_not_receive(:save)
-
- @client.policy_builder.stub(:node).and_return(@node)
- @client.policy_builder.build_node
-
- @node[:roles].should_not be_nil
- @node[:roles].should eql(['test_role'])
- @node[:recipes].should eql(['cookbook1'])
-
- @client.save_updated_node
-
- @node.run_list.should == original_runlist
-
- end
- end
-
describe "assert_cookbook_path_not_empty" do
before do
Chef::Config[:solo] = true
@@ -462,24 +556,46 @@ shared_examples_for Chef::Client do
context "when any directory of cookbook_path contains no cookbook" do
it "raises CookbookNotFound error" do
expect do
- @client.send(:assert_cookbook_path_not_empty, nil)
+ client.send(:assert_cookbook_path_not_empty, nil)
end.to raise_error(Chef::Exceptions::CookbookNotFound, 'None of the cookbook paths set in Chef::Config[:cookbook_path], ["/path/to/invalid/cookbook_path"], contain any cookbooks')
end
end
end
-end
+ describe "setting node name" do
+ context "when machinename, hostname and fqdn are all set" do
+ it "favors the fqdn" do
+ expect(client.node_name).to eql(fqdn)
+ end
+ end
-describe Chef::Client do
- Chef::Config[:client_fork] = false
- it_behaves_like Chef::Client
-end
+ context "when fqdn is missing" do
+ # ohai 7 should always have machinename == return of hostname
+ let(:fqdn) { nil }
+ it "favors the machinename" do
+ expect(client.node_name).to eql(machinename)
+ end
+ end
-describe "Chef::Client Forked" do
- before do
- Chef::Config[:client_fork] = true
- end
+ context "when fqdn and machinename are missing" do
+ # ohai 6 will not have machinename, return the short hostname
+ let(:fqdn) { nil }
+ let(:machinename) { nil }
+ it "falls back to hostname" do
+ expect(client.node_name).to eql(hostname)
+ end
+ end
+
+ context "when they're all missing" do
+ let(:machinename) { nil }
+ let(:hostname) { nil }
+ let(:fqdn) { nil }
- it_behaves_like Chef::Client
+ it "throws an exception" do
+ expect { client.node_name }.to raise_error(Chef::Exceptions::CannotDetermineNodeName)
+ end
+ end
+ end
end
+
diff --git a/spec/unit/cookbook/chefignore_spec.rb b/spec/unit/cookbook/chefignore_spec.rb
index aacb60c012..e529a6d05a 100644
--- a/spec/unit/cookbook/chefignore_spec.rb
+++ b/spec/unit/cookbook/chefignore_spec.rb
@@ -36,4 +36,14 @@ describe Chef::Cookbook::Chefignore do
@chefignore.ignored?('recipes/ignoreme.rb').should be_true
@chefignore.ignored?('recipes/dontignoreme.rb').should be_false
end
+
+ context "when using the single cookbook pattern" do
+ before do
+ @chefignore = Chef::Cookbook::Chefignore.new(File.join(CHEF_SPEC_DATA, 'standalone_cookbook'))
+ end
+
+ it "loads the globs in the chefignore file" do
+ @chefignore.ignores.should =~ %w[recipes/ignoreme.rb ignored vendor/bundle/*]
+ end
+ end
end
diff --git a/spec/unit/cookbook/metadata_spec.rb b/spec/unit/cookbook/metadata_spec.rb
index cba2aff5da..88c4a1a5f5 100644
--- a/spec/unit/cookbook/metadata_spec.rb
+++ b/spec/unit/cookbook/metadata_spec.rb
@@ -402,7 +402,7 @@ describe Chef::Cookbook::Metadata do
@meta.attributes["db/mysql/databases"][:recipes].should == []
end
- it "should allow the default value to be a string, array, or hash" do
+ it "should allow the default value to be a string, array, hash, boolean or numeric" do
lambda {
@meta.attribute("db/mysql/databases", :default => [])
}.should_not raise_error
@@ -413,10 +413,54 @@ describe Chef::Cookbook::Metadata do
@meta.attribute("db/mysql/databases", :default => "alice in chains")
}.should_not raise_error
lambda {
+ @meta.attribute("db/mysql/databases", :default => 1337)
+ }.should_not raise_error
+ lambda {
+ @meta.attribute("db/mysql/databases", :default => true)
+ }.should_not raise_error
+ lambda {
@meta.attribute("db/mysql/databases", :required => :not_gonna_do_it)
}.should raise_error(ArgumentError)
end
+ it "should limit the types allowed in the choice array" do
+ options = {
+ :type => "string",
+ :choice => [ "test1", "test2" ],
+ :default => "test1"
+ }
+ lambda {
+ @meta.attribute("test_cookbook/test", options)
+ }.should_not raise_error
+
+ options = {
+ :type => "boolean",
+ :choice => [ true, false ],
+ :default => true
+ }
+ lambda {
+ @meta.attribute("test_cookbook/test", options)
+ }.should_not raise_error
+
+ options = {
+ :type => "numeric",
+ :choice => [ 1337, 420 ],
+ :default => 1337
+ }
+ lambda {
+ @meta.attribute("test_cookbook/test", options)
+ }.should_not raise_error
+
+ options = {
+ :type => "numeric",
+ :choice => [ true, "false" ],
+ :default => false
+ }
+ lambda {
+ @meta.attribute("test_cookbook/test", options)
+ }.should raise_error
+ end
+
it "should error if default used with calculated" do
lambda {
attrs = {
diff --git a/spec/unit/cookbook/syntax_check_spec.rb b/spec/unit/cookbook/syntax_check_spec.rb
index 85d6950a45..a674f6ab40 100644
--- a/spec/unit/cookbook/syntax_check_spec.rb
+++ b/spec/unit/cookbook/syntax_check_spec.rb
@@ -24,11 +24,21 @@ describe Chef::Cookbook::SyntaxCheck do
let(:cookbook_path) { File.join(CHEF_SPEC_DATA, 'cookbooks', 'openldap') }
let(:syntax_check) { Chef::Cookbook::SyntaxCheck.new(cookbook_path) }
+ let(:open_ldap_cookbook_files) {
+ %w{ attributes/default.rb
+ attributes/smokey.rb
+ definitions/client.rb
+ definitions/server.rb
+ metadata.rb
+ recipes/default.rb
+ recipes/gigantor.rb
+ recipes/one.rb }.map{ |f| File.join(cookbook_path, f) }
+}
+
before do
Chef::Log.logger = Logger.new(StringIO.new)
Chef::Log.level = :warn # suppress "Syntax OK" messages
-
@attr_files = %w{default.rb smokey.rb}.map { |f| File.join(cookbook_path, 'attributes', f) }
@defn_files = %w{client.rb server.rb}.map { |f| File.join(cookbook_path, 'definitions', f)}
@recipes = %w{default.rb gigantor.rb one.rb}.map { |f| File.join(cookbook_path, 'recipes', f) }
@@ -48,6 +58,23 @@ describe Chef::Cookbook::SyntaxCheck do
Chef::Config[:cookbook_path] = File.dirname(cookbook_path)
syntax_check = Chef::Cookbook::SyntaxCheck.for_cookbook(:openldap)
syntax_check.cookbook_path.should == cookbook_path
+ syntax_check.ruby_files.sort.should == open_ldap_cookbook_files.sort
+ end
+
+ it "creates a syntax checker given the cookbook name and cookbook_path" do
+ syntax_check = Chef::Cookbook::SyntaxCheck.for_cookbook(:openldap, File.join(CHEF_SPEC_DATA, 'cookbooks'))
+ syntax_check.cookbook_path.should == cookbook_path
+ syntax_check.ruby_files.sort.should == open_ldap_cookbook_files.sort
+ end
+
+ context "when using a standalone cookbook" do
+ let(:cookbook_path) { File.join(CHEF_SPEC_DATA, 'standalone_cookbook') }
+
+ it "creates a syntax checker given the cookbook name and cookbook_path for a standalone cookbook" do
+ syntax_check = Chef::Cookbook::SyntaxCheck.for_cookbook(:standalone_cookbook, CHEF_SPEC_DATA)
+ syntax_check.cookbook_path.should == cookbook_path
+ syntax_check.ruby_files.should == [File.join(cookbook_path, 'recipes/default.rb')]
+ end
end
describe "when first created" do
diff --git a/spec/unit/cookbook_spec.rb b/spec/unit/cookbook_spec.rb
index ca4f4adc08..9bcea97d98 100644
--- a/spec/unit/cookbook_spec.rb
+++ b/spec/unit/cookbook_spec.rb
@@ -68,16 +68,6 @@ describe Chef::CookbookVersion do
@cookbook.preferred_filename(@node, :files, 'a-filename', 'the-checksum').should be_nil
end
- it "should allow you to include a fully-qualified recipe using the DSL" do
- # DSL method include_recipe allows multiple arguments, so extract the first
- @node.should_receive(:loaded_recipe).with(:openldap, "gigantor")
- recipe = @run_context.include_recipe("openldap::gigantor").first
-
- recipe.recipe_name.should == "gigantor"
- recipe.cookbook_name.should == :openldap
- @run_context.resource_collection[0].name.should == "blanket"
- end
-
it "should raise an ArgumentException if you try to load a bad recipe name" do
lambda { @cookbook.load_recipe("doesnt_exist", @node) }.should raise_error(ArgumentError)
end
diff --git a/spec/unit/dsl/reboot_pending_spec.rb b/spec/unit/dsl/reboot_pending_spec.rb
new file mode 100644
index 0000000000..8576ae168a
--- /dev/null
+++ b/spec/unit/dsl/reboot_pending_spec.rb
@@ -0,0 +1,100 @@
+#
+# 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/dsl/reboot_pending"
+require "spec_helper"
+
+describe Chef::DSL::RebootPending do
+ describe "reboot_pending?" do
+ describe "in isoloation" do
+ let(:recipe) { Object.new.extend(Chef::DSL::RebootPending) }
+
+ before do
+ recipe.stub(:platform?).and_return(false)
+ end
+
+ context "platform is windows" do
+ before do
+ recipe.stub(:platform?).with('windows').and_return(true)
+ recipe.stub(:registry_key_exists?).and_return(false)
+ recipe.stub(:registry_value_exists?).and_return(false)
+ end
+
+ it 'should return true if "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations" exists' do
+ recipe.stub(:registry_value_exists?).with('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager', { :name => 'PendingFileRenameOperations' }).and_return(true)
+ expect(recipe.reboot_pending?).to be_true
+ end
+
+ it 'should return true if "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" exists' do
+ recipe.stub(:registry_key_exists?).with('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired').and_return(true)
+ expect(recipe.reboot_pending?).to be_true
+ end
+
+ it 'should return true if key "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired" exists' do
+ recipe.stub(:registry_key_exists?).with('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired').and_return(true)
+ expect(recipe.reboot_pending?).to be_true
+ end
+
+ it 'should return true if value "HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile" contains specific data' do
+ recipe.stub(:registry_key_exists?).with('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').and_return(true)
+ recipe.stub(:registry_get_values).with('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').and_return(
+ [{:name => "Flags", :type => :dword, :data => 3}])
+ expect(recipe.reboot_pending?).to be_true
+ end
+ end
+
+ context "platform is ubuntu" do
+ before do
+ recipe.stub(:platform?).with('ubuntu').and_return(true)
+ end
+
+ it 'should return true if /var/run/reboot-required exists' do
+ File.stub(:exists?).with('/var/run/reboot-required').and_return(true)
+ expect(recipe.reboot_pending?).to be_true
+ end
+
+ it 'should return false if /var/run/reboot-required does not exist' do
+ File.stub(:exists?).with('/var/run/reboot-required').and_return(false)
+ expect(recipe.reboot_pending?).to be_false
+ end
+ end
+
+ context "platform is not supported" do
+ it 'should raise an exception' do
+ recipe.stub_chain(:node, :[]).with(:platform).and_return('msdos')
+ expect { recipe.reboot_pending? }.to raise_error(Chef::Exceptions::UnsupportedPlatform)
+ end
+ end
+ end # describe in isolation
+
+ describe "in a recipe" do
+ it "responds to reboot_pending?" do
+ # Chef::Recipe.new(cookbook_name, recipe_name, run_context(node, cookbook_collection, events))
+ recipe = Chef::Recipe.new(nil,nil,Chef::RunContext.new(Chef::Node.new, {}, nil))
+ expect(recipe).to respond_to(:reboot_pending?)
+ end
+ end # describe in a recipe
+
+ describe "in a resource" do
+ it "responds to reboot_pending?" do
+ resource = Chef::Resource::new("Crackerjack::Timing", nil)
+ expect(resource).to respond_to(:reboot_pending?)
+ end
+ end # describe in a resource
+ end
+end
diff --git a/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb b/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb
new file mode 100644
index 0000000000..a016cbfeb8
--- /dev/null
+++ b/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb
@@ -0,0 +1,56 @@
+#
+# Author:: Adam Edwards (<adamed@getchef.com>)
+# 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::GuardInterpreter::ResourceGuardInterpreter do
+ before(:each) do
+ node = Chef::Node.new
+
+ node.default["kernel"] = Hash.new
+ node.default["kernel"][:machine] = :x86_64.to_s
+
+ run_context = Chef::RunContext.new(node, nil, nil)
+
+ @resource = Chef::Resource.new("powershell_unit_test", run_context)
+ @resource.stub(:run_action)
+ @resource.stub(:updated).and_return(true)
+ end
+
+ describe "when evaluating a guard resource" do
+ let(:resource) { @resource }
+
+ it "should allow guard interpreter to be set to Chef::Resource::Script" do
+ resource.guard_interpreter(:script)
+ allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false)
+ resource.only_if("echo hi")
+ end
+
+ it "should allow guard interpreter to be set to Chef::Resource::PowershellScript derived indirectly from Chef::Resource::Script" do
+ resource.guard_interpreter(:powershell_script)
+ allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false)
+ resource.only_if("echo hi")
+ end
+
+ it "should raise an exception if guard_interpreter is set to a resource not derived from Chef::Resource::Script" do
+ resource.guard_interpreter(:file)
+ expect { resource.only_if("echo hi") }.to raise_error ArgumentError
+ end
+ end
+end
+
diff --git a/spec/unit/http/simple_spec.rb b/spec/unit/http/simple_spec.rb
new file mode 100644
index 0000000000..b33ef1d553
--- /dev/null
+++ b/spec/unit/http/simple_spec.rb
@@ -0,0 +1,32 @@
+#
+# Author:: Serdar Sutay (<serdar@opscode.com>)
+# 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::HTTP::Simple do
+ it "should have content length validation middleware after compressor middleware" do
+ client = Chef::HTTP::Simple.new("dummy.com")
+ middlewares = client.instance_variable_get(:@middlewares)
+ content_length = middlewares.find_index { |e| e.is_a? Chef::HTTP::ValidateContentLength }
+ decompressor = middlewares.find_index { |e| e.is_a? Chef::HTTP::Decompressor }
+
+ content_length.should_not be_nil
+ decompressor.should_not be_nil
+ (decompressor < content_length).should be_true
+ end
+end
diff --git a/spec/unit/http/validate_content_length_spec.rb b/spec/unit/http/validate_content_length_spec.rb
new file mode 100644
index 0000000000..091f2b0757
--- /dev/null
+++ b/spec/unit/http/validate_content_length_spec.rb
@@ -0,0 +1,187 @@
+#
+# Author:: Serdar Sutay (<serdar@opscode.com>)
+# 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'
+require 'stringio'
+
+describe Chef::HTTP::ValidateContentLength do
+ class TestClient < Chef::HTTP
+ use Chef::HTTP::ValidateContentLength
+ end
+
+ let(:method) { "GET" }
+ let(:url) { "http://dummy.com" }
+ let(:headers) { {} }
+ let(:data) { false }
+
+ let(:request) { }
+ let(:return_value) { "200" }
+
+ # Test Variables
+ let(:request_type) { :streaming }
+ let(:content_length_value) { 23 }
+ let(:streaming_length) { 23 }
+ let(:response_body) { "Thanks for checking in." }
+ let(:response_headers) {
+ {
+ "content-length" => content_length_value
+ }
+ }
+
+ let(:response) {
+ m = double('HttpResponse', :body => response_body)
+ m.stub(:[]) do |key|
+ response_headers[key]
+ end
+
+ m
+ }
+
+ let(:middleware) {
+ client = TestClient.new(url)
+ client.middlewares[0]
+ }
+
+ def run_content_length_validation
+ stream_handler = middleware.stream_response_handler(response)
+ middleware.handle_request(method, url, headers, data)
+
+ case request_type
+ when :streaming
+ # First stream the data
+ data_length = streaming_length
+ while data_length > 0
+ chunk_size = data_length > 10 ? 10 : data_length
+ stream_handler.handle_chunk(double("Chunk", :bytesize => chunk_size))
+ data_length -= chunk_size
+ end
+
+ # Finally call stream complete
+ middleware.handle_stream_complete(response, request, return_value)
+ when :direct
+ middleware.handle_response(response, request, return_value)
+ else
+ raise "Unknown request_type: #{request_type}"
+ end
+ end
+
+ let(:debug_stream) { StringIO.new }
+ let(:debug_output) { debug_stream.string }
+
+ before(:each) {
+ Chef::Log.level = :debug
+ Chef::Log.stub(:debug) do |message|
+ debug_stream.puts message
+ end
+ }
+
+ describe "without response body" do
+ let(:request_type) { :direct }
+ let(:response_body) { "Thanks for checking in." }
+
+ it "shouldn't raise error" do
+ lambda { run_content_length_validation }.should_not raise_error
+ end
+ end
+
+ describe "without Content-Length header" do
+ let(:response_headers) { { } }
+
+ [ "direct", "streaming" ].each do |req_type|
+ describe "when running #{req_type} request" do
+ let(:request_type) { req_type.to_sym }
+
+ it "should skip validation and log for debug" do
+ run_content_length_validation
+ debug_output.should include("HTTP server did not include a Content-Length header in response")
+ end
+ end
+ end
+ end
+
+ describe "with correct Content-Length header" do
+ [ "direct", "streaming" ].each do |req_type|
+ describe "when running #{req_type} request" do
+ let(:request_type) { req_type.to_sym }
+
+ it "should validate correctly" do
+ run_content_length_validation
+ debug_output.should include("Content-Length validated correctly.")
+ end
+ end
+ end
+ end
+
+ describe "with wrong Content-Length header" do
+ let(:content_length_value) { 25 }
+ [ "direct", "streaming" ].each do |req_type|
+ describe "when running #{req_type} request" do
+ let(:request_type) { req_type.to_sym }
+
+ it "should raise ContentLengthMismatch error" do
+ lambda { run_content_length_validation }.should raise_error(Chef::Exceptions::ContentLengthMismatch)
+ end
+ end
+ end
+ end
+
+ describe "when download is interrupted" do
+ let(:streaming_length) { 12 }
+
+ it "should raise ContentLengthMismatch error" do
+ lambda { run_content_length_validation }.should raise_error(Chef::Exceptions::ContentLengthMismatch)
+ end
+ end
+
+ describe "when Transfer-Encoding & Content-Length is set" do
+ let(:response_headers) {
+ {
+ "content-length" => content_length_value,
+ "transfer-encoding" => "chunked"
+ }
+ }
+
+ [ "direct", "streaming" ].each do |req_type|
+ describe "when running #{req_type} request" do
+ let(:request_type) { req_type.to_sym }
+
+ it "should skip validation and log for debug" do
+ run_content_length_validation
+ debug_output.should include("Transfer-Encoding header is set, skipping Content-Length check.")
+ end
+ end
+ end
+ end
+
+ describe "when client is being reused" do
+ before do
+ run_content_length_validation
+ debug_output.should include("Content-Length validated correctly.")
+ end
+
+ it "should reset internal counter" do
+ middleware.instance_variable_get(:@content_length_counter).should be_nil
+ end
+
+ it "should validate correctly second time" do
+ run_content_length_validation
+ debug_output.should include("Content-Length validated correctly.")
+ end
+ end
+
+end
diff --git a/spec/unit/knife/bootstrap_spec.rb b/spec/unit/knife/bootstrap_spec.rb
index b055cadcee..501e678a2b 100644
--- a/spec/unit/knife/bootstrap_spec.rb
+++ b/spec/unit/knife/bootstrap_spec.rb
@@ -124,12 +124,21 @@ describe Chef::Knife::Bootstrap do
end
describe "specifying no_proxy with various entries" do
- subject(:knife) { described_class.new }
- let(:options){ ["--bootstrap-no-proxy", setting] }
+ subject(:knife) do
+ k = described_class.new
+ k.instance_variable_set("@template_file", template_file)
+ k.parse_options(options)
+ k.merge_configs
+ k
+ end
+
+ # Include a data bag secret in the options to prevent Bootstrap from
+ # attempting to access /etc/chef/encrypted_data_bag_secret, which
+ # can fail when the file exists but can't be accessed by the user
+ # running the tests.
+ let(:options){ ["--bootstrap-no-proxy", setting, "-s", "foo"] }
let(:template_file) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "no_proxy.erb")) }
let(:rendered_template) do
- knife.instance_variable_set("@template_file", template_file)
- knife.parse_options(options)
template_string = knife.read_template
knife.render_template(template_string)
end
diff --git a/spec/unit/knife/client_bulk_delete_spec.rb b/spec/unit/knife/client_bulk_delete_spec.rb
index bedd4911c5..7df7d02e9b 100644
--- a/spec/unit/knife/client_bulk_delete_spec.rb
+++ b/spec/unit/knife/client_bulk_delete_spec.rb
@@ -19,60 +19,145 @@
require 'spec_helper'
describe Chef::Knife::ClientBulkDelete do
- before(:each) do
- Chef::Log.logger = Logger.new(StringIO.new)
-
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::ClientBulkDelete.new
- @knife.name_args = ["."]
- @stdout = StringIO.new
- @knife.ui.stub(:stdout).and_return(@stdout)
- @knife.ui.stub(:confirm).and_return(true)
- @clients = Hash.new
- %w{tim dan stephen}.each do |client_name|
+ let(:stdout_io) { StringIO.new }
+ let(:stdout) {stdout_io.string}
+
+ let(:knife) {
+ k = Chef::Knife::ClientBulkDelete.new
+ k.name_args = name_args
+ k.config = option_args
+ k.ui.stub(:stdout).and_return(stdout_io)
+ k.ui.stub(:confirm).and_return(knife_confirm)
+ k.ui.stub(:confirm_without_exit).and_return(knife_confirm)
+ k
+ }
+
+ let(:name_args) { [ "." ] }
+ let(:option_args) { {} }
+
+ let(:knife_confirm) { true }
+
+ let(:nonvalidator_client_names) { %w{tim dan stephen} }
+ let(:nonvalidator_clients) {
+ clients = Hash.new
+
+ nonvalidator_client_names.each do |client_name|
client = Chef::ApiClient.new()
client.name(client_name)
client.stub(:destroy).and_return(true)
- @clients[client_name] = client
+ clients[client_name] = client
+ end
+
+ clients
+ }
+
+ let(:validator_client_names) { %w{myorg-validator} }
+ let(:validator_clients) {
+ clients = Hash.new
+
+ validator_client_names.each do |validator_client_name|
+ validator_client = Chef::ApiClient.new()
+ validator_client.name(validator_client_name)
+ validator_client.stub(:validator).and_return(true)
+ validator_client.stub(:destroy).and_return(true)
+ clients[validator_client_name] = validator_client
end
- Chef::ApiClient.stub(:list).and_return(@clients)
+
+ clients
+ }
+
+ let(:client_names) { nonvalidator_client_names + validator_client_names}
+ let(:clients) {
+ nonvalidator_clients.merge(validator_clients)
+ }
+
+ before(:each) do
+ Chef::ApiClient.stub(:list).and_return(clients)
end
describe "run" do
+ describe "without a regex" do
+ let(:name_args) { [ ] }
- it "should get the list of the clients" do
- Chef::ApiClient.should_receive(:list).and_return(@clients)
- @knife.run
+ it "should exit if the regex is not provided" do
+ lambda { knife.run }.should raise_error(SystemExit)
+ end
end
- it "should print the clients you are about to delete" do
- @knife.run
- @stdout.string.should match(/#{@knife.ui.list(@clients.keys.sort, :columns_down)}/)
- end
+ describe "with any clients" do
+ it "should get the list of the clients" do
+ Chef::ApiClient.should_receive(:list)
+ knife.run
+ end
- it "should confirm you really want to delete them" do
- @knife.ui.should_receive(:confirm)
- @knife.run
- end
+ it "should print the name of the clients" do
+ knife.run
+ client_names.each do |client_name|
+ stdout.should include(client_name)
+ end
+ end
- it "should delete each client" do
- @clients.each_value do |c|
- c.should_receive(:destroy)
+ it "should confirm you really want to delete them" do
+ knife.ui.should_receive(:confirm)
+ knife.run
end
- @knife.run
- end
- it "should only delete clients that match the regex" do
- @knife.name_args = ["tim"]
- @clients["tim"].should_receive(:destroy)
- @clients["stephen"].should_not_receive(:destroy)
- @clients["dan"].should_not_receive(:destroy)
- @knife.run
+ describe "without --delete-validators" do
+ it "should mention that validator clients wont be deleted" do
+ knife.run
+ stdout.should include("Following clients are validators and will not be deleted.")
+ info = stdout.index "Following clients are validators and will not be deleted."
+ val = stdout.index "myorg-validator"
+ (val > info).should be_true
+ end
+
+ it "should only delete nonvalidator clients" do
+ nonvalidator_clients.each_value do |c|
+ c.should_receive(:destroy)
+ end
+
+ validator_clients.each_value do |c|
+ c.should_not_receive(:destroy)
+ end
+
+ knife.run
+ end
+ end
+
+ describe "with --delete-validators" do
+ let(:option_args) { {:delete_validators => true} }
+
+ it "should mention that validator clients will be deleted" do
+ knife.run
+ stdout.should include("The following validators will be deleted")
+ end
+
+ it "should confirm twice" do
+ knife.ui.should_receive(:confirm).once
+ knife.ui.should_receive(:confirm_without_exit).once
+ knife.run
+ end
+
+ it "should delete all clients" do
+ clients.each_value do |c|
+ c.should_receive(:destroy)
+ end
+
+ knife.run
+ end
+ end
end
- it "should exit if the regex is not provided" do
- @knife.name_args = []
- lambda { @knife.run }.should raise_error(SystemExit)
+ describe "with some clients" do
+ let(:name_args) { [ "^ti" ] }
+
+ it "should only delete clients that match the regex" do
+ clients["tim"].should_receive(:destroy)
+ clients["stephen"].should_not_receive(:destroy)
+ clients["dan"].should_not_receive(:destroy)
+ clients["myorg-validator"].should_not_receive(:destroy)
+ knife.run
+ end
end
end
end
diff --git a/spec/unit/knife/client_create_spec.rb b/spec/unit/knife/client_create_spec.rb
index 69c55ba015..897cee8974 100644
--- a/spec/unit/knife/client_create_spec.rb
+++ b/spec/unit/knife/client_create_spec.rb
@@ -25,7 +25,9 @@ describe Chef::Knife::ClientCreate do
Chef::Config[:node_name] = "webmonkey.example.com"
@knife = Chef::Knife::ClientCreate.new
@knife.config = {
- :file => nil
+ :file => nil,
+ :admin => false,
+ :validator => false
}
@knife.name_args = [ "adam" ]
@client = Chef::ApiClient.new
@@ -49,6 +51,16 @@ describe Chef::Knife::ClientCreate do
@knife.run
end
+ it "by default it is not an admin" do
+ @client.should_receive(:admin).with(false)
+ @knife.run
+ end
+
+ it "by default it is not a validator" do
+ @client.should_receive(:validator).with(false)
+ @knife.run
+ end
+
it "should allow you to edit the data" do
@knife.should_receive(:edit_data).with(@client)
@knife.run
@@ -70,5 +82,21 @@ describe Chef::Knife::ClientCreate do
end
end
+ describe "with -a or --admin" do
+ it "should create an admin client" do
+ @knife.config[:admin] = true
+ @client.should_receive(:admin).with(true)
+ @knife.run
+ end
+ end
+
+ describe "with --validator" do
+ it "should create an validator client" do
+ @knife.config[:validator] = true
+ @client.should_receive(:validator).with(true)
+ @knife.run
+ end
+ end
+
end
end
diff --git a/spec/unit/knife/client_delete_spec.rb b/spec/unit/knife/client_delete_spec.rb
index 9ebccbae15..01b49b3d7c 100644
--- a/spec/unit/knife/client_delete_spec.rb
+++ b/spec/unit/knife/client_delete_spec.rb
@@ -21,12 +21,16 @@ require 'spec_helper'
describe Chef::Knife::ClientDelete do
before(:each) do
@knife = Chef::Knife::ClientDelete.new
+ # defaults
+ @knife.config = {
+ :delete_validators => false
+ }
@knife.name_args = [ 'adam' ]
end
describe 'run' do
it 'should delete the client' do
- @knife.should_receive(:delete_object).with(Chef::ApiClient, 'adam')
+ @knife.should_receive(:delete_object).with(Chef::ApiClient, 'adam', 'client')
@knife.run
end
@@ -37,4 +41,43 @@ describe Chef::Knife::ClientDelete do
lambda { @knife.run }.should raise_error(SystemExit)
end
end
+
+ describe 'with a validator' do
+ before(:each) do
+ Chef::Knife::UI.stub(:confirm).and_return(true)
+ @knife.stub(:confirm).and_return(true)
+ @client = Chef::ApiClient.new
+ Chef::ApiClient.should_receive(:load).and_return(@client)
+ end
+
+ it 'should delete non-validator client if --force is not set' do
+ @knife.config[:delete_validators] = false
+ @client.should_receive(:destroy).and_return(@client)
+ @knife.should_receive(:msg)
+
+ @knife.run
+ end
+
+ it 'should delete non-validator client if --force is set' do
+ @knife.config[:delete_validators] = true
+ @client.should_receive(:destroy).and_return(@client)
+ @knife.should_receive(:msg)
+
+ @knife.run
+ end
+
+ it 'should not delete validator client if --force is not set' do
+ @client.validator(true)
+ @knife.ui.should_receive(:fatal)
+ lambda { @knife.run}.should raise_error(SystemExit)
+ end
+
+ it 'should delete validator client if --force is set' do
+ @knife.config[:delete_validators] = true
+ @client.should_receive(:destroy).and_return(@client)
+ @knife.should_receive(:msg)
+
+ @knife.run
+ end
+ end
end
diff --git a/spec/unit/knife/cookbook_upload_spec.rb b/spec/unit/knife/cookbook_upload_spec.rb
index 65331b3952..5c7a4c1125 100644
--- a/spec/unit/knife/cookbook_upload_spec.rb
+++ b/spec/unit/knife/cookbook_upload_spec.rb
@@ -23,178 +23,271 @@ require 'chef/cookbook_uploader'
require 'timeout'
describe Chef::Knife::CookbookUpload do
- before(:each) do
- @knife = Chef::Knife::CookbookUpload.new
- @knife.name_args = ['test_cookbook']
+ let(:cookbook) { Chef::CookbookVersion.new('test_cookbook') }
+
+ let(:cookbooks_by_name) do
+ {cookbook.name => cookbook}
+ end
- @cookbook = Chef::CookbookVersion.new('test_cookbook')
+ let(:cookbook_loader) do
+ cookbook_loader = cookbooks_by_name.dup
+ cookbook_loader.stub(:merged_cookbooks).and_return([])
+ cookbook_loader.stub(:load_cookbooks).and_return(cookbook_loader)
+ cookbook_loader
+ end
+
+ let(:cookbook_uploader) { double(:upload_cookbooks => nil) }
- @cookbook_loader = {}
- @cookbook_loader.stub(:[]).and_return(@cookbook)
- @cookbook_loader.stub(:merged_cookbooks).and_return([])
- @cookbook_loader.stub(:load_cookbooks).and_return(@cookbook_loader)
- Chef::CookbookLoader.stub(:new).and_return(@cookbook_loader)
+ let(:output) { StringIO.new }
+
+ let(:name_args) { ['test_cookbook'] }
- @output = StringIO.new
- @knife.ui.stub(:stdout).and_return(@output)
- @knife.ui.stub(:stderr).and_return(@output)
+ let(:knife) do
+ k = Chef::Knife::CookbookUpload.new
+ k.name_args = name_args
+ k.ui.stub(:stdout).and_return(output)
+ k.ui.stub(:stderr).and_return(output)
+ k
+ end
+
+ before(:each) do
+ Chef::CookbookLoader.stub(:new).and_return(cookbook_loader)
end
describe 'with --concurrency' do
it 'should upload cookbooks with predefined concurrency' do
- @cookbook_uploader = double(:upload_cookbooks => nil)
Chef::CookbookVersion.stub(:list_all_versions).and_return({})
- @knife.config[:concurrency] = 3
- @test_cookbook = Chef::CookbookVersion.new('test_cookbook')
- @cookbook_loader.stub(:each).and_yield("test_cookbook", @test_cookbook)
- @cookbook_loader.stub(:cookbook_names).and_return(["test_cookbook"])
+ knife.config[:concurrency] = 3
+ test_cookbook = Chef::CookbookVersion.new('test_cookbook')
+ cookbook_loader.stub(:each).and_yield("test_cookbook", test_cookbook)
+ cookbook_loader.stub(:cookbook_names).and_return(["test_cookbook"])
Chef::CookbookUploader.should_receive(:new).with( kind_of(Array), kind_of(Array),
{:force=>nil, :concurrency => 3}).and_return(double("Chef::CookbookUploader", :upload_cookbooks=> true))
- @knife.run
+ knife.run
end
end
describe 'run' do
before(:each) do
- @cookbook_uploader = double(:upload_cookbooks => nil)
- Chef::CookbookUploader.stub(:new => @cookbook_uploader)
+ Chef::CookbookUploader.stub(:new => cookbook_uploader)
Chef::CookbookVersion.stub(:list_all_versions).and_return({})
end
it 'should print usage and exit when a cookbook name is not provided' do
- @knife.name_args = []
- @knife.should_receive(:show_usage)
- @knife.ui.should_receive(:fatal)
- lambda { @knife.run }.should raise_error(SystemExit)
+ knife.name_args = []
+ knife.should_receive(:show_usage)
+ knife.ui.should_receive(:fatal)
+ lambda { knife.run }.should raise_error(SystemExit)
end
describe 'when specifying a cookbook name' do
it 'should upload the cookbook' do
- @knife.should_receive(:upload).once
- @knife.run
+ knife.should_receive(:upload).once
+ knife.run
end
it 'should report on success' do
- @knife.should_receive(:upload).once
- @knife.ui.should_receive(:info).with(/Uploaded 1 cookbook/)
- @knife.run
+ knife.should_receive(:upload).once
+ knife.ui.should_receive(:info).with(/Uploaded 1 cookbook/)
+ knife.run
end
end
describe 'when specifying the same cookbook name twice' do
it 'should upload the cookbook only once' do
- @knife.name_args = ['test_cookbook', 'test_cookbook']
- @knife.should_receive(:upload).once
- @knife.run
+ knife.name_args = ['test_cookbook', 'test_cookbook']
+ knife.should_receive(:upload).once
+ knife.run
+ end
+ end
+
+ context "when uploading a cookbook that uses deprecated overlays" do
+
+ before do
+ cookbook_loader.stub(:merged_cookbooks).and_return(['test_cookbook'])
+ cookbook_loader.stub(:merged_cookbook_paths).
+ and_return({'test_cookbook' => %w{/path/one/test_cookbook /path/two/test_cookbook}})
+ end
+
+ it "emits a warning" do
+ knife.run
+ expected_message=<<-E
+WARNING: The cookbooks: test_cookbook exist in multiple places in your cookbook_path.
+A composite version of these cookbooks has been compiled for uploading.
+
+IMPORTANT: In a future version of Chef, this behavior will be removed and you will no longer
+be able to have the same version of a cookbook in multiple places in your cookbook_path.
+WARNING: The affected cookbooks are located:
+test_cookbook:
+ /path/one/test_cookbook
+ /path/two/test_cookbook
+E
+ output.string.should include(expected_message)
end
end
describe 'when specifying a cookbook name among many' do
- before(:each) do
- @knife.name_args = ['test_cookbook1']
- @cookbooks = {
+ let(:name_args) { ['test_cookbook1'] }
+
+ let(:cookbooks_by_name) do
+ {
'test_cookbook1' => Chef::CookbookVersion.new('test_cookbook1'),
'test_cookbook2' => Chef::CookbookVersion.new('test_cookbook2'),
'test_cookbook3' => Chef::CookbookVersion.new('test_cookbook3')
}
- @cookbook_loader = {}
- @cookbook_loader.stub(:merged_cookbooks).and_return([])
- @cookbook_loader.stub(:[]) { |ckbk| @cookbooks[ckbk] }
- Chef::CookbookLoader.stub(:new).and_return(@cookbook_loader)
end
it "should read only one cookbook" do
- @cookbook_loader.should_receive(:[]).once.with('test_cookbook1')
- @knife.run
+ cookbook_loader.should_receive(:[]).once.with('test_cookbook1').and_call_original
+ knife.run
end
it "should not read all cookbooks" do
- @cookbook_loader.should_not_receive(:load_cookbooks)
- @knife.run
+ cookbook_loader.should_not_receive(:load_cookbooks)
+ knife.run
end
it "should upload only one cookbook" do
- @knife.should_receive(:upload).exactly(1).times
- @knife.run
+ knife.should_receive(:upload).exactly(1).times
+ knife.run
end
end
# This is testing too much. We should break it up.
describe 'when specifying a cookbook name with dependencies' do
+ let(:name_args) { ["test_cookbook2"] }
+
+ let(:cookbooks_by_name) do
+ { "test_cookbook1" => test_cookbook1,
+ "test_cookbook2" => test_cookbook2,
+ "test_cookbook3" => test_cookbook3 }
+ end
+
+ let(:test_cookbook1) { Chef::CookbookVersion.new('test_cookbook1') }
+
+ let(:test_cookbook2) do
+ c = Chef::CookbookVersion.new('test_cookbook2')
+ c.metadata.depends("test_cookbook3")
+ c
+ end
+
+ let(:test_cookbook3) do
+ c = Chef::CookbookVersion.new('test_cookbook3')
+ c.metadata.depends("test_cookbook1")
+ c.metadata.depends("test_cookbook2")
+ c
+ end
+
it "should upload all dependencies once" do
- @knife.name_args = ["test_cookbook2"]
- @knife.config[:depends] = true
- @test_cookbook1 = Chef::CookbookVersion.new('test_cookbook1')
- @test_cookbook2 = Chef::CookbookVersion.new('test_cookbook2')
- @test_cookbook3 = Chef::CookbookVersion.new('test_cookbook3')
- @test_cookbook2.metadata.depends("test_cookbook3")
- @test_cookbook3.metadata.depends("test_cookbook1")
- @test_cookbook3.metadata.depends("test_cookbook2")
- @cookbook_loader.stub(:[]) do |ckbk|
- { "test_cookbook1" => @test_cookbook1,
- "test_cookbook2" => @test_cookbook2,
- "test_cookbook3" => @test_cookbook3 }[ckbk]
- end
- @knife.stub(:cookbook_names).and_return(["test_cookbook1", "test_cookbook2", "test_cookbook3"])
- @knife.should_receive(:upload).exactly(3).times
- Timeout::timeout(5) do
- @knife.run
+ knife.config[:depends] = true
+ knife.stub(:cookbook_names).and_return(["test_cookbook1", "test_cookbook2", "test_cookbook3"])
+ knife.should_receive(:upload).exactly(3).times
+ lambda do
+ Timeout::timeout(5) do
+ knife.run
+ end
end.should_not raise_error
end
end
+ describe 'when specifying a cookbook name with missing dependencies' do
+ let(:cookbook_dependency) { Chef::CookbookVersion.new('dependency') }
+
+ before(:each) do
+ cookbook.metadata.depends("dependency")
+ cookbook_loader.stub(:[]) do |ckbk|
+ { "test_cookbook" => cookbook,
+ "dependency" => cookbook_dependency}[ckbk]
+ end
+ knife.stub(:cookbook_names).and_return(["cookbook_dependency", "test_cookbook"])
+ @stdout, @stderr, @stdin = StringIO.new, StringIO.new, StringIO.new
+ knife.ui = Chef::Knife::UI.new(@stdout, @stderr, @stdin, {})
+ end
+
+ it 'should exit and not upload the cookbook' do
+ cookbook_loader.should_receive(:[]).once.with('test_cookbook')
+ cookbook_loader.should_not_receive(:load_cookbooks)
+ cookbook_uploader.should_not_receive(:upload_cookbooks)
+ expect {knife.run}.to raise_error(SystemExit)
+ end
+
+ it 'should output a message for a single missing dependency' do
+ expect {knife.run}.to raise_error(SystemExit)
+ @stderr.string.should include('Cookbook test_cookbook depends on cookbooks which are not currently')
+ @stderr.string.should include('being uploaded and cannot be found on the server.')
+ @stderr.string.should include("The missing cookbook(s) are: 'dependency' version '>= 0.0.0'")
+ end
+
+ it 'should output a message for a multiple missing dependencies which are concatenated' do
+ cookbook_dependency2 = Chef::CookbookVersion.new('dependency2')
+ cookbook.metadata.depends("dependency2")
+ cookbook_loader.stub(:[]) do |ckbk|
+ { "test_cookbook" => cookbook,
+ "dependency" => cookbook_dependency,
+ "dependency2" => cookbook_dependency2}[ckbk]
+ end
+ knife.stub(:cookbook_names).and_return(["dependency", "dependency2", "test_cookbook"])
+ expect {knife.run}.to raise_error(SystemExit)
+ @stderr.string.should include('Cookbook test_cookbook depends on cookbooks which are not currently')
+ @stderr.string.should include('being uploaded and cannot be found on the server.')
+ @stderr.string.should include("The missing cookbook(s) are:")
+ @stderr.string.should include("'dependency' version '>= 0.0.0'")
+ @stderr.string.should include("'dependency2' version '>= 0.0.0'")
+ end
+ end
+
it "should freeze the version of the cookbooks if --freeze is specified" do
- @knife.config[:freeze] = true
- @cookbook.should_receive(:freeze_version).once
- @knife.run
+ knife.config[:freeze] = true
+ cookbook.should_receive(:freeze_version).once
+ knife.run
end
describe 'with -a or --all' do
before(:each) do
- @knife.config[:all] = true
+ knife.config[:all] = true
@test_cookbook1 = Chef::CookbookVersion.new('test_cookbook1')
@test_cookbook2 = Chef::CookbookVersion.new('test_cookbook2')
- @cookbook_loader.stub(:each).and_yield("test_cookbook1", @test_cookbook1).and_yield("test_cookbook2", @test_cookbook2)
- @cookbook_loader.stub(:cookbook_names).and_return(["test_cookbook1", "test_cookbook2"])
+ cookbook_loader.stub(:each).and_yield("test_cookbook1", @test_cookbook1).and_yield("test_cookbook2", @test_cookbook2)
+ cookbook_loader.stub(:cookbook_names).and_return(["test_cookbook1", "test_cookbook2"])
end
it 'should upload all cookbooks' do
- @knife.should_receive(:upload).once
- @knife.run
+ knife.should_receive(:upload).once
+ knife.run
end
it 'should report on success' do
- @knife.should_receive(:upload).once
- @knife.ui.should_receive(:info).with(/Uploaded all cookbooks/)
- @knife.run
+ knife.should_receive(:upload).once
+ knife.ui.should_receive(:info).with(/Uploaded all cookbooks/)
+ knife.run
end
it 'should update the version constraints for an environment' do
- @knife.stub(:assert_environment_valid!).and_return(true)
- @knife.config[:environment] = "production"
- @knife.should_receive(:update_version_constraints).once
- @knife.run
+ knife.stub(:assert_environment_valid!).and_return(true)
+ knife.config[:environment] = "production"
+ knife.should_receive(:update_version_constraints).once
+ knife.run
end
end
describe 'when a frozen cookbook exists on the server' do
it 'should fail to replace it' do
exception = Chef::Exceptions::CookbookFrozen.new
- @cookbook_uploader.should_receive(:upload_cookbooks).
+ cookbook_uploader.should_receive(:upload_cookbooks).
and_raise(exception)
- @knife.ui.stub(:error)
- @knife.ui.should_receive(:error).with(exception)
- lambda { @knife.run }.should raise_error(SystemExit)
+ knife.ui.stub(:error)
+ knife.ui.should_receive(:error).with(exception)
+ lambda { knife.run }.should raise_error(SystemExit)
end
it 'should not update the version constraints for an environment' do
- @knife.stub(:assert_environment_valid!).and_return(true)
- @knife.config[:environment] = "production"
- @knife.stub(:upload).and_raise(Chef::Exceptions::CookbookFrozen)
- @knife.ui.should_receive(:error).with(/Failed to upload 1 cookbook/)
- @knife.ui.should_receive(:warn).with(/Not updating version constraints/)
- @knife.should_not_receive(:update_version_constraints)
- lambda { @knife.run }.should raise_error(SystemExit)
+ knife.stub(:assert_environment_valid!).and_return(true)
+ knife.config[:environment] = "production"
+ knife.stub(:upload).and_raise(Chef::Exceptions::CookbookFrozen)
+ knife.ui.should_receive(:error).with(/Failed to upload 1 cookbook/)
+ knife.ui.should_receive(:warn).with(/Not updating version constraints/)
+ knife.should_not_receive(:update_version_constraints)
+ lambda { knife.run }.should raise_error(SystemExit)
end
end
end # run
diff --git a/spec/unit/knife/core/bootstrap_context_spec.rb b/spec/unit/knife/core/bootstrap_context_spec.rb
index 47261e2068..17e265edad 100644
--- a/spec/unit/knife/core/bootstrap_context_spec.rb
+++ b/spec/unit/knife/core/bootstrap_context_spec.rb
@@ -41,13 +41,19 @@ describe Chef::Knife::Core::BootstrapContext do
bootstrap_context.start_chef.should eq "chef-client -j /etc/chef/first-boot.json -E _default"
end
+ describe "when in verbosity mode" do
+ let(:config) { {:verbosity => 2} }
+ it "adds '-l debug' when verbosity is >= 2" do
+ bootstrap_context.start_chef.should eq "chef-client -j /etc/chef/first-boot.json -l debug -E _default"
+ end
+ end
+
it "reads the validation key" do
bootstrap_context.validation_key.should eq IO.read(File.join(CHEF_SPEC_DATA, 'ssl', 'private_key.pem'))
end
it "generates the config file data" do
expected=<<-EXPECTED
-log_level :auto
log_location STDOUT
chef_server_url "http://chef.example.com:4444"
validation_client_name "chef-validator-testing"
@@ -56,6 +62,10 @@ EXPECTED
bootstrap_context.config_content.should eq expected
end
+ it "does not set a default log_level" do
+ expect(bootstrap_context.config_content).not_to match(/log_level/)
+ end
+
describe "alternate chef-client path" do
let(:chef_config){ {:chef_client_path => '/usr/local/bin/chef-client'} }
it "runs chef-client from another path when specified" do
diff --git a/spec/unit/knife/core/ui_spec.rb b/spec/unit/knife/core/ui_spec.rb
index c626747918..9044bc2f2f 100644
--- a/spec/unit/knife/core/ui_spec.rb
+++ b/spec/unit/knife/core/ui_spec.rb
@@ -406,61 +406,132 @@ EOM
end
describe "confirm" do
- before(:each) do
- @question = "monkeys rule"
- @stdout = StringIO.new
- @ui.stub(:stdout).and_return(@stdout)
- @ui.stdin.stub(:readline).and_return("y")
+ let(:stdout) {StringIO.new}
+ let(:output) {stdout.string}
+
+ let(:question) { "monkeys rule" }
+ let(:answer) { 'y' }
+
+ let(:default_choice) { nil }
+ let(:append_instructions) { true }
+
+ def run_confirm
+ @ui.stub(:stdout).and_return(stdout)
+ @ui.stdin.stub(:readline).and_return(answer)
+ @ui.confirm(question, append_instructions, default_choice)
end
- it "should return true if you answer Y" do
- @ui.stdin.stub(:readline).and_return("Y")
- @ui.confirm(@question).should == true
+ def run_confirm_without_exit
+ @ui.stub(:stdout).and_return(stdout)
+ @ui.stdin.stub(:readline).and_return(answer)
+ @ui.confirm_without_exit(question, append_instructions, default_choice)
end
- it "should return true if you answer y" do
- @ui.stdin.stub(:readline).and_return("y")
- @ui.confirm(@question).should == true
+ shared_examples_for "confirm with positive answer" do
+ it "confirm should return true" do
+ run_confirm.should be_true
+ end
+
+ it "confirm_without_exit should return true" do
+ run_confirm_without_exit.should be_true
+ end
end
- it "should exit 3 if you answer N" do
- @ui.stdin.stub(:readline).and_return("N")
- lambda {
- @ui.confirm(@question)
- }.should raise_error(SystemExit) { |e| e.status.should == 3 }
+ shared_examples_for "confirm with negative answer" do
+ it "confirm should exit 3" do
+ lambda {
+ run_confirm
+ }.should raise_error(SystemExit) { |e| e.status.should == 3 }
+ end
+
+ it "confirm_without_exit should return false" do
+ run_confirm_without_exit.should be_false
+ end
end
- it "should exit 3 if you answer n" do
- @ui.stdin.stub(:readline).and_return("n")
- lambda {
- @ui.confirm(@question)
- }.should raise_error(SystemExit) { |e| e.status.should == 3 }
+ describe "with default choice set to true" do
+ let(:default_choice) { true }
+
+ it "should show 'Y/n' in the instructions" do
+ run_confirm
+ output.should include("Y/n")
+ end
+
+ describe "with empty answer" do
+ let(:answer) { "" }
+
+ it_behaves_like "confirm with positive answer"
+ end
+
+ describe "with answer N " do
+ let(:answer) { "N" }
+
+ it_behaves_like "confirm with negative answer"
+ end
end
- describe "with --y or --yes passed" do
- it "should return true" do
- @ui.config[:yes] = true
- @ui.confirm(@question).should == true
+ describe "with default choice set to false" do
+ let(:default_choice) { false }
+
+ it "should show 'y/N' in the instructions" do
+ run_confirm
+ output.should include("y/N")
+ end
+
+ describe "with empty answer" do
+ let(:answer) { "" }
+
+ it_behaves_like "confirm with negative answer"
+ end
+
+ describe "with answer N " do
+ let(:answer) { "Y" }
+
+ it_behaves_like "confirm with positive answer"
end
end
- describe "when asking for free-form user input" do
- it "asks a question and returns the answer provided by the user" do
- out = StringIO.new
- @ui.stub(:stdout).and_return(out)
- @ui.stub(:stdin).and_return(StringIO.new("http://mychefserver.example.com\n"))
- @ui.ask_question("your chef server URL?").should == "http://mychefserver.example.com"
- out.string.should == "your chef server URL?"
+ ["Y", "y"].each do |answer|
+ describe "with answer #{answer}" do
+ let(:answer) { answer }
+
+ it_behaves_like "confirm with positive answer"
end
+ end
- it "suggests a default setting and returns the default when the user's response only contains whitespace" do
- out = StringIO.new
- @ui.stub(:stdout).and_return(out)
- @ui.stub(:stdin).and_return(StringIO.new(" \n"))
- @ui.ask_question("your chef server URL? ", :default => 'http://localhost:4000').should == "http://localhost:4000"
- out.string.should == "your chef server URL? [http://localhost:4000] "
+ ["N", "n"].each do |answer|
+ describe "with answer #{answer}" do
+ let(:answer) { answer }
+
+ it_behaves_like "confirm with negative answer"
end
end
+ describe "with --y or --yes passed" do
+ it "should return true" do
+ @ui.config[:yes] = true
+ run_confirm.should be_true
+ output.should eq("")
+ end
+ end
+ end
+
+ describe "when asking for free-form user input" do
+ it "asks a question and returns the answer provided by the user" do
+ out = StringIO.new
+ @ui.stub(:stdout).and_return(out)
+ @ui.stub(:stdin).and_return(StringIO.new("http://mychefserver.example.com\n"))
+ @ui.ask_question("your chef server URL?").should == "http://mychefserver.example.com"
+ out.string.should == "your chef server URL?"
+ end
+
+ it "suggests a default setting and returns the default when the user's response only contains whitespace" do
+ out = StringIO.new
+ @ui.stub(:stdout).and_return(out)
+ @ui.stub(:stdin).and_return(StringIO.new(" \n"))
+ @ui.ask_question("your chef server URL? ", :default => 'http://localhost:4000').should == "http://localhost:4000"
+ out.string.should == "your chef server URL? [http://localhost:4000] "
+ end
end
+
end
diff --git a/spec/unit/knife/node_run_list_add_spec.rb b/spec/unit/knife/node_run_list_add_spec.rb
index 61f2e6af4c..bd33a359a2 100644
--- a/spec/unit/knife/node_run_list_add_spec.rb
+++ b/spec/unit/knife/node_run_list_add_spec.rb
@@ -65,6 +65,29 @@ describe Chef::Knife::NodeRunListAdd do
end
end
+ describe "with -b or --before specified" do
+ it "should add to the run list before the specified entry" do
+ @node.run_list << "role[acorns]"
+ @node.run_list << "role[barn]"
+ @knife.config[:before] = "role[acorns]"
+ @knife.run
+ @node.run_list[0].should == "role[monkey]"
+ @node.run_list[1].should == "role[acorns]"
+ @node.run_list[2].should == "role[barn]"
+ end
+ end
+
+ describe "with both --after and --before specified" do
+ it "exits with an error" do
+ @node.run_list << "role[acorns]"
+ @node.run_list << "role[barn]"
+ @knife.config[:before] = "role[acorns]"
+ @knife.config[:after] = "role[acorns]"
+ @knife.ui.should_receive(:fatal)
+ lambda { @knife.run }.should raise_error(SystemExit)
+ end
+ end
+
describe "with more than one role or recipe" do
it "should add to the run list all the entries" do
@knife.name_args = [ "adam", "role[monkey],role[duck]" ]
@@ -98,7 +121,7 @@ describe Chef::Knife::NodeRunListAdd do
end
end
- describe "with more than one role or recipe as different arguments and list separated by comas" do
+ describe "with more than one role or recipe as different arguments and list separated by commas" do
it "should add to the run list all the entries" do
@knife.name_args = [ "adam", "role[monkey]", "role[duck],recipe[bird::fly]" ]
@node.run_list << "role[acorns]"
diff --git a/spec/unit/knife/ssh_spec.rb b/spec/unit/knife/ssh_spec.rb
index eff7c9ba5b..9247db3c90 100644
--- a/spec/unit/knife/ssh_spec.rb
+++ b/spec/unit/knife/ssh_spec.rb
@@ -54,7 +54,7 @@ describe Chef::Knife::Ssh do
@knife.config[:attribute] = "ipaddress"
@knife.config[:override_attribute] = "ipaddress"
configure_query([@node_foo, @node_bar])
- @knife.should_receive(:session_from_list).with(['10.0.0.1', '10.0.0.2'])
+ @knife.should_receive(:session_from_list).with([['10.0.0.1', nil], ['10.0.0.2', nil]])
@knife.configure_session
end
@@ -62,14 +62,17 @@ describe Chef::Knife::Ssh do
@knife.config[:attribute] = "config_file" # this value will be the config file
@knife.config[:override_attribute] = "ipaddress" # this is the value of the command line via #configure_attribute
configure_query([@node_foo, @node_bar])
- @knife.should_receive(:session_from_list).with(['10.0.0.1', '10.0.0.2'])
+ @knife.should_receive(:session_from_list).with([['10.0.0.1', nil], ['10.0.0.2', nil]])
@knife.configure_session
end
end
it "searchs for and returns an array of fqdns" do
configure_query([@node_foo, @node_bar])
- @knife.should_receive(:session_from_list).with(['foo.example.org', 'bar.example.org'])
+ @knife.should_receive(:session_from_list).with([
+ ['foo.example.org', nil],
+ ['bar.example.org', nil]
+ ])
@knife.configure_session
end
@@ -83,7 +86,10 @@ describe Chef::Knife::Ssh do
it "returns an array of cloud public hostnames" do
configure_query([@node_foo, @node_bar])
- @knife.should_receive(:session_from_list).with(['ec2-10-0-0-1.compute-1.amazonaws.com', 'ec2-10-0-0-2.compute-1.amazonaws.com'])
+ @knife.should_receive(:session_from_list).with([
+ ['ec2-10-0-0-1.compute-1.amazonaws.com', nil],
+ ['ec2-10-0-0-2.compute-1.amazonaws.com', nil]
+ ])
@knife.configure_session
end
@@ -179,12 +185,17 @@ describe Chef::Knife::Ssh do
end
it "uses the port from an ssh config file" do
- @knife.session_from_list(['the.b.org'])
+ @knife.session_from_list([['the.b.org', nil]])
@knife.session.servers[0].port.should == 23
end
+ it "uses the port from a cloud attr" do
+ @knife.session_from_list([['the.b.org', 123]])
+ @knife.session.servers[0].port.should == 123
+ end
+
it "uses the user from an ssh config file" do
- @knife.session_from_list(['the.b.org'])
+ @knife.session_from_list([['the.b.org', 123]])
@knife.session.servers[0].user.should == "locutus"
end
end
diff --git a/spec/unit/knife/ssl_check_spec.rb b/spec/unit/knife/ssl_check_spec.rb
new file mode 100644
index 0000000000..32405a5977
--- /dev/null
+++ b/spec/unit/knife/ssl_check_spec.rb
@@ -0,0 +1,187 @@
+#
+# Author:: Daniel DeLeo (<dan@getchef.com>)
+# 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"
+require 'stringio'
+
+describe Chef::Knife::SslCheck do
+
+ let(:name_args) { [] }
+ let(:stdout_io) { StringIO.new }
+ let(:stderr_io) { StringIO.new }
+
+ def stderr
+ stderr_io.string
+ end
+
+ def stdout
+ stdout_io.string
+ end
+
+ subject(:ssl_check) do
+ s = Chef::Knife::SslCheck.new
+ s.ui.stub(:stdout).and_return(stdout_io)
+ s.ui.stub(:stderr).and_return(stderr_io)
+ s.name_args = name_args
+ s
+ end
+
+ before do
+ Chef::Config.chef_server_url = "https://example.com:8443/chef-server"
+ end
+
+ context "when no arguments are given" do
+ it "uses the chef_server_url as the host to check" do
+ expect(ssl_check.host).to eq("example.com")
+ expect(ssl_check.port).to eq(8443)
+ end
+ end
+
+ context "when a specific URI is given" do
+ let(:name_args) { %w{https://example.test:10443/foo} }
+
+ it "checks the SSL configuration against the given host" do
+ expect(ssl_check.host).to eq("example.test")
+ expect(ssl_check.port).to eq(10443)
+ end
+ end
+
+ context "when an invalid URI is given" do
+
+ let(:name_args) { %w{foo.test} }
+
+ it "prints an error and exits" do
+ expect { ssl_check.run }.to raise_error(SystemExit)
+ expected_stdout=<<-E
+USAGE: knife ssl check [URL] (options)
+E
+ expected_stderr=<<-E
+ERROR: Given URI: `foo.test' is invalid
+E
+ expect(stdout_io.string).to eq(expected_stdout)
+ expect(stderr_io.string).to eq(expected_stderr)
+ end
+
+ context "and its malformed enough to make URI.parse barf" do
+
+ let(:name_args) { %w{ftp://lkj\\blah:example.com/blah} }
+
+ it "prints an error and exits" do
+ expect { ssl_check.run }.to raise_error(SystemExit)
+ expected_stdout=<<-E
+USAGE: knife ssl check [URL] (options)
+E
+ expected_stderr=<<-E
+ERROR: Given URI: `#{name_args[0]}' is invalid
+E
+ expect(stdout_io.string).to eq(expected_stdout)
+ expect(stderr_io.string).to eq(expected_stderr)
+ end
+ end
+ end
+
+ describe "verifying the remote certificate" do
+ let(:name_args) { %w{https://foo.example.com:8443} }
+
+ let(:tcp_socket) { double(TCPSocket) }
+ let(:ssl_socket) { double(OpenSSL::SSL::SSLSocket) }
+
+ before do
+ TCPSocket.should_receive(:new).with("foo.example.com", 8443).and_return(tcp_socket)
+ OpenSSL::SSL::SSLSocket.should_receive(:new).with(tcp_socket, ssl_check.verify_peer_ssl_context).and_return(ssl_socket)
+ end
+
+ def run
+ ssl_check.run
+ rescue Exception
+ #puts "OUT: #{stdout_io.string}"
+ #puts "ERR: #{stderr_io.string}"
+ raise
+ end
+
+ context "when the remote host's certificate is valid" do
+
+ before do
+ ssl_socket.should_receive(:connect) # no error
+ ssl_socket.should_receive(:post_connection_check).with("foo.example.com") # no error
+ end
+
+ it "prints a success message" do
+ ssl_check.run
+ expect(stdout_io.string).to include("Successfully verified certificates from `foo.example.com'")
+ end
+ end
+
+ describe "and the certificate is not valid" do
+
+ let(:tcp_socket_for_debug) { double(TCPSocket) }
+ let(:ssl_socket_for_debug) { double(OpenSSL::SSL::SSLSocket) }
+
+ let(:self_signed_crt_path) { File.join(CHEF_SPEC_DATA, "trusted_certs", "example.crt") }
+ let(:self_signed_crt) { OpenSSL::X509::Certificate.new(File.read(self_signed_crt_path)) }
+
+ before do
+ trap(:INT, "DEFAULT")
+
+ TCPSocket.should_receive(:new).
+ with("foo.example.com", 8443).
+ and_return(tcp_socket_for_debug)
+ OpenSSL::SSL::SSLSocket.should_receive(:new).
+ with(tcp_socket_for_debug, ssl_check.noverify_peer_ssl_context).
+ and_return(ssl_socket_for_debug)
+ end
+
+ context "when the certificate's CN does not match the hostname" do
+ before do
+ ssl_socket.should_receive(:connect) # no error
+ ssl_socket.should_receive(:post_connection_check).
+ with("foo.example.com").
+ and_raise(OpenSSL::SSL::SSLError)
+ ssl_socket_for_debug.should_receive(:connect)
+ ssl_socket_for_debug.should_receive(:peer_cert).and_return(self_signed_crt)
+ end
+
+ it "shows the CN used by the certificate and prints an error" do
+ expect { run }.to raise_error(SystemExit)
+ expect(stderr).to include("The SSL cert is signed by a trusted authority but is not valid for the given hostname")
+ expect(stderr).to include("You are attempting to connect to: 'foo.example.com'")
+ expect(stderr).to include("The server's certificate belongs to 'example.local'")
+ end
+
+ end
+
+ context "when the cert is not signed by any trusted authority" do
+ before do
+ ssl_socket.should_receive(:connect).
+ and_raise(OpenSSL::SSL::SSLError)
+ ssl_socket_for_debug.should_receive(:connect)
+ ssl_socket_for_debug.should_receive(:peer_cert).and_return(self_signed_crt)
+ end
+
+ it "shows the CN used by the certificate and prints an error" do
+ expect { run }.to raise_error(SystemExit)
+ expect(stderr).to include("The SSL certificate of foo.example.com could not be verified")
+ end
+
+ end
+ end
+
+ end
+
+end
+
diff --git a/spec/unit/knife/ssl_fetch_spec.rb b/spec/unit/knife/ssl_fetch_spec.rb
new file mode 100644
index 0000000000..0d3c8913f7
--- /dev/null
+++ b/spec/unit/knife/ssl_fetch_spec.rb
@@ -0,0 +1,151 @@
+#
+# Author:: Daniel DeLeo (<dan@getchef.com>)
+# 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'
+require 'chef/knife/ssl_fetch'
+
+describe Chef::Knife::SslFetch do
+
+ let(:name_args) { [] }
+ let(:stdout_io) { StringIO.new }
+ let(:stderr_io) { StringIO.new }
+
+ def stderr
+ stderr_io.string
+ end
+
+ def stdout
+ stdout_io.string
+ end
+
+ subject(:ssl_fetch) do
+ s = Chef::Knife::SslFetch.new
+ s.name_args = name_args
+ s.ui.stub(:stdout).and_return(stdout_io)
+ s.ui.stub(:stderr).and_return(stderr_io)
+ s
+ end
+
+ context "when no arguments are given" do
+
+ before do
+ Chef::Config.chef_server_url = "https://example.com:8443/chef-server"
+ end
+
+ it "uses the chef_server_url as the host to fetch" do
+ expect(ssl_fetch.host).to eq("example.com")
+ expect(ssl_fetch.port).to eq(8443)
+ end
+ end
+
+ context "when a specific URI is given" do
+ let(:name_args) { %w{https://example.test:10443/foo} }
+
+ it "fetchs the SSL configuration against the given host" do
+ expect(ssl_fetch.host).to eq("example.test")
+ expect(ssl_fetch.port).to eq(10443)
+ end
+ end
+
+ context "when an invalid URI is given" do
+
+ let(:name_args) { %w{foo.test} }
+
+ it "prints an error and exits" do
+ expect { ssl_fetch.run }.to raise_error(SystemExit)
+ expected_stdout=<<-E
+USAGE: knife ssl fetch [URL] (options)
+E
+ expected_stderr=<<-E
+ERROR: Given URI: `foo.test' is invalid
+E
+ expect(stdout_io.string).to eq(expected_stdout)
+ expect(stderr_io.string).to eq(expected_stderr)
+ end
+
+ context "and its malformed enough to make URI.parse barf" do
+
+ let(:name_args) { %w{ftp://lkj\\blah:example.com/blah} }
+
+ it "prints an error and exits" do
+ expect { ssl_fetch.run }.to raise_error(SystemExit)
+ expected_stdout=<<-E
+USAGE: knife ssl fetch [URL] (options)
+E
+ expected_stderr=<<-E
+ERROR: Given URI: `#{name_args[0]}' is invalid
+E
+ expect(stdout_io.string).to eq(expected_stdout)
+ expect(stderr_io.string).to eq(expected_stderr)
+ end
+ end
+ end
+
+ describe "normalizing CNs for use as paths" do
+
+ it "normalizes '*' to 'wildcard'" do
+ expect(ssl_fetch.normalize_cn("*.example.com")).to eq("wildcard_example_com")
+ end
+
+ it "normalizes non-alnum and hyphen characters to underscores" do
+ expect(ssl_fetch.normalize_cn("Billy-Bob's Super Awesome CA!")).to eq("Billy-Bob_s_Super_Awesome_CA_")
+ end
+
+ end
+
+ describe "fetching the remote cert chain" do
+
+ let(:name_args) { %w{https://foo.example.com:8443} }
+
+ let(:tcp_socket) { double(TCPSocket) }
+ let(:ssl_socket) { double(OpenSSL::SSL::SSLSocket) }
+
+ let(:self_signed_crt_path) { File.join(CHEF_SPEC_DATA, "trusted_certs", "example.crt") }
+ let(:self_signed_crt) { OpenSSL::X509::Certificate.new(File.read(self_signed_crt_path)) }
+
+ let(:trusted_certs_dir) { Dir.mktmpdir }
+
+ def run
+ ssl_fetch.run
+ rescue Exception
+ puts "OUT: #{stdout_io.string}"
+ puts "ERR: #{stderr_io.string}"
+ raise
+ end
+
+ before do
+ Chef::Config.trusted_certs_dir = trusted_certs_dir
+
+ TCPSocket.should_receive(:new).with("foo.example.com", 8443).and_return(tcp_socket)
+ OpenSSL::SSL::SSLSocket.should_receive(:new).with(tcp_socket, ssl_fetch.noverify_peer_ssl_context).and_return(ssl_socket)
+ ssl_socket.should_receive(:connect)
+ ssl_socket.should_receive(:peer_cert_chain).and_return([self_signed_crt])
+ end
+
+ after do
+ FileUtils.rm_rf(trusted_certs_dir)
+ end
+
+ it "fetches the cert chain and writes the certs to the trusted_certs_dir" do
+ run
+ stored_cert_path = File.join(trusted_certs_dir, "example_local.crt")
+ expect(File).to exist(stored_cert_path)
+ expect(File.read(stored_cert_path)).to eq(File.read(self_signed_crt_path))
+ end
+ end
+end
diff --git a/spec/unit/knife_spec.rb b/spec/unit/knife_spec.rb
index daace18106..8bf85bf604 100644
--- a/spec/unit/knife_spec.rb
+++ b/spec/unit/knife_spec.rb
@@ -22,6 +22,7 @@ module KnifeSpecs
end
require 'spec_helper'
+require 'uri'
describe Chef::Knife do
before(:each) do
@@ -141,6 +142,60 @@ describe Chef::Knife do
end
+ describe "the headers include X-Remote-Request-Id" do
+
+ let(:headers) {{"Accept"=>"application/json",
+ "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3",
+ 'X-Chef-Version' => Chef::VERSION,
+ "Host"=>"api.opscode.piab:443",
+ "X-REMOTE-REQUEST-ID"=>request_id}}
+
+ let(:request_id) {"1234"}
+
+ let(:request_mock) { {} }
+
+ let(:rest) do
+ Net::HTTP.stub(:new).and_return(http_client)
+ Chef::RequestID.instance.stub(:request_id).and_return(request_id)
+ Chef::Config.stub(:chef_server_url).and_return("https://api.opscode.piab")
+ command = Chef::Knife.run(%w{test yourself})
+ rest = command.noauth_rest
+ rest
+ end
+
+ let!(:http_client) do
+ http_client = Net::HTTP.new(url.host, url.port)
+ http_client.stub(:request).and_yield(http_response).and_return(http_response)
+ http_client
+ end
+
+ let(:url) { URI.parse("https://api.opscode.piab") }
+
+ let(:http_response) do
+ http_response = Net::HTTPSuccess.new("1.1", "200", "successful rest req")
+ http_response.stub(:read_body)
+ http_response.stub(:body).and_return(body)
+ http_response["Content-Length"] = body.bytesize.to_s
+ http_response
+ end
+
+ let(:body) { "ninja" }
+
+ before(:each) do
+ Chef::Config[:chef_server_url] = "https://api.opscode.piab"
+ if KnifeSpecs.const_defined?(:TestYourself)
+ KnifeSpecs.send :remove_const, :TestYourself
+ end
+ Kernel.load(File.join(CHEF_SPEC_DATA, 'knife_subcommand', 'test_yourself.rb'))
+ Chef::Knife.subcommands.each { |name, klass| Chef::Knife.subcommands.delete(name) unless klass.kind_of?(Class) }
+ end
+
+ it "confirms that the headers include X-Remote-Request-Id" do
+ Net::HTTP::Get.should_receive(:new).with("/monkey", headers).and_return(request_mock)
+ rest.get_rest("monkey")
+ end
+ end
+
describe "when running a command" do
before(:each) do
if KnifeSpecs.const_defined?(:TestYourself)
diff --git a/spec/unit/mixin/deep_merge_spec.rb b/spec/unit/mixin/deep_merge_spec.rb
index 0a7bbffa41..76f5c68a29 100644
--- a/spec/unit/mixin/deep_merge_spec.rb
+++ b/spec/unit/mixin/deep_merge_spec.rb
@@ -284,6 +284,10 @@ describe Chef::Mixin::DeepMerge do
ret.should == {"property" => ["1","2","3","4","5","6"]}
end
+ it "should not error merging un-dupable objects" do
+ @dm.deep_merge(nil, 4)
+ end
+
end
describe "role_merge" do
@@ -347,5 +351,18 @@ describe Chef::Mixin::DeepMerge do
merged_result["top_level_a"]["1_deep_b"].should == %w[B B B]
end
+ it "does not mutate deeply-nested original hashes by default" do
+ merge_ee_hash = {"top_level_a" => {"1_deep_a" => { "2_deep_a" => { "3_deep_a" => "foo" }}}}
+ merge_with_hash = {"top_level_a" => {"1_deep_a" => { "2_deep_a" => { "3_deep_b" => "bar" }}}}
+ @dm.hash_only_merge(merge_ee_hash, merge_with_hash)
+ merge_ee_hash.should == {"top_level_a" => {"1_deep_a" => { "2_deep_a" => { "3_deep_a" => "foo" }}}}
+ merge_with_hash.should == {"top_level_a" => {"1_deep_a" => { "2_deep_a" => { "3_deep_b" => "bar" }}}}
+ end
+
+ it "does not error merging un-dupable items" do
+ merge_ee_hash = {"top_level_a" => 1, "top_level_b" => false}
+ merge_with_hash = {"top_level_a" => 2, "top_level_b" => true }
+ @dm.hash_only_merge(merge_ee_hash, merge_with_hash)
+ end
end
end
diff --git a/spec/unit/node/attribute_spec.rb b/spec/unit/node/attribute_spec.rb
index ef3fc60cc6..bab2e33aa9 100644
--- a/spec/unit/node/attribute_spec.rb
+++ b/spec/unit/node/attribute_spec.rb
@@ -488,6 +488,13 @@ describe Chef::Node::Attribute do
end
end
+ describe "dup" do
+ it "array can be duped even if some elements can't" do
+ @attributes.default[:foo] = %w[foo bar baz] + Array(1..3) + [nil, true, false, [ "el", 0, nil ] ]
+ @attributes.default[:foo].dup
+ end
+ end
+
describe "has_key?" do
it "should return true if an attribute exists" do
@attributes.has_key?("music").should == true
diff --git a/spec/unit/node/immutable_collections_spec.rb b/spec/unit/node/immutable_collections_spec.rb
index 0c2b878cd2..d7abfa26e6 100644
--- a/spec/unit/node/immutable_collections_spec.rb
+++ b/spec/unit/node/immutable_collections_spec.rb
@@ -54,6 +54,32 @@ describe Chef::Node::ImmutableMash do
@immutable_mash[:top_level_4][:level2].should be_a(Chef::Node::ImmutableMash)
end
+ describe "to_hash" do
+ before do
+ @copy = @immutable_mash.to_hash
+ end
+
+ it "converts an immutable mash to a new mutable hash" do
+ @copy.should be_instance_of(Hash)
+ end
+
+ it "converts an immutable nested mash to a new mutable hash" do
+ @copy['top_level_4']['level2'].should be_instance_of(Hash)
+ end
+
+ it "converts an immutable nested array to a new mutable array" do
+ @copy['top_level_2'].should be_instance_of(Array)
+ end
+
+ it "should create a mash with the same content" do
+ @copy.should == @immutable_mash
+ end
+
+ it 'should allow mutation' do
+ lambda { @copy['m'] = 'm' }.should_not raise_error(Chef::Exceptions::ImmutableAttributeModification)
+ end
+
+ end
[
:[]=,
@@ -86,7 +112,9 @@ end
describe Chef::Node::ImmutableArray do
before do
- @immutable_array = Chef::Node::ImmutableArray.new(%w[foo bar baz])
+ @immutable_array = Chef::Node::ImmutableArray.new(%w[foo bar baz] + Array(1..3) + [nil, true, false, [ "el", 0, nil ] ])
+ immutable_mash = Chef::Node::ImmutableMash.new({:m => 'm'})
+ @immutable_nested_array = Chef::Node::ImmutableArray.new(["level1",@immutable_array, immutable_mash])
end
##
@@ -130,10 +158,41 @@ describe Chef::Node::ImmutableArray do
end
end
+ it "can be duped even if some elements can't" do
+ @immutable_array.dup
+ end
+
it "returns a mutable version of itself when duped" do
mutable = @immutable_array.dup
mutable[0] = :value
mutable[0].should == :value
end
+
+ describe "to_a" do
+ before do
+ @copy = @immutable_nested_array.to_a
+ end
+
+ it "converts an immutable array to a new mutable array" do
+ @copy.should be_instance_of(Array)
+ end
+
+ it "converts an immutable nested array to a new mutable array" do
+ @copy[1].should be_instance_of(Array)
+ end
+
+ it "converts an immutable nested mash to a new mutable hash" do
+ @copy[2].should be_instance_of(Hash)
+ end
+
+ it "should create an array with the same content" do
+ @copy.should == @immutable_nested_array
+ end
+
+ it 'should allow mutation' do
+ lambda { @copy << 'm' }.should_not raise_error(Chef::Exceptions::ImmutableAttributeModification)
+ end
+ end
+
end
diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb
index f2a78f87cd..832e10f645 100644
--- a/spec/unit/node_spec.rb
+++ b/spec/unit/node_spec.rb
@@ -724,6 +724,15 @@ describe Chef::Node do
json.should =~ /\"run_list\":\[\"role\[Cthulu\]\",\"role\[Hastur\]\"\]/
end
+ it "should serialize the correct run list", :json => true do
+ node.run_list << "role[marxist]"
+ node.run_list << "role[leninist]"
+ node.override_runlist << "role[stalinist]"
+ node.run_list.should be_include("role[stalinist]")
+ json = Chef::JSONCompat.to_json(node)
+ json.should =~ /\"run_list\":\[\"role\[marxist\]\",\"role\[leninist\]\"\]/
+ end
+
it "merges the override components into a combined override object" do
node.attributes.role_override["role override"] = "role override"
node.attributes.env_override["env override"] = "env override"
diff --git a/spec/unit/platform/query_helpers_spec.rb b/spec/unit/platform/query_helpers_spec.rb
new file mode 100644
index 0000000000..2414bdf552
--- /dev/null
+++ b/spec/unit/platform/query_helpers_spec.rb
@@ -0,0 +1,32 @@
+#
+# 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 'spec_helper'
+
+describe "Chef::Platform#windows_server_2003?" do
+ it "returns false early when not on windows" do
+ Chef::Platform.stub(:windows?).and_return(false)
+ expect(Chef::Platform).not_to receive(:require)
+ expect(Chef::Platform.windows_server_2003?).to be_false
+ end
+
+ # CHEF-4888: Need to call WIN32OLE.ole_initialize in new threads
+ it "does not raise an exception" do
+ expect { Thread.fork { Chef::Platform.windows_server_2003? }.join }.not_to raise_error
+ end
+end
diff --git a/spec/unit/platform_spec.rb b/spec/unit/platform_spec.rb
index e0386a1a61..3d7aef98a0 100644
--- a/spec/unit/platform_spec.rb
+++ b/spec/unit/platform_spec.rb
@@ -37,7 +37,8 @@ describe "Chef::Platform supports" do
:mswin,
:mingw32,
:windows,
- :gcel
+ :gcel,
+ :ibm_powerkvm
].each do |platform|
it "#{platform}" do
Chef::Platform.platforms.should have_key(platform)
@@ -47,209 +48,226 @@ end
describe Chef::Platform do
- before :all do
- @original_platform_map = Chef::Platform.platforms
- end
+ context "while testing with fake data" do
- after :all do ||
- Chef::Platform.platforms = @original_platform_map
- end
+ before :all do
+ @original_platform_map = Chef::Platform.platforms
+ end
- before(:each) do
- Chef::Platform.platforms = {
- :darwin => {
- ">= 10.11" => {
- :file => "new_darwinian"
+ after :all do ||
+ Chef::Platform.platforms = @original_platform_map
+ end
+
+ before(:each) do
+ Chef::Platform.platforms = {
+ :darwin => {
+ ">= 10.11" => {
+ :file => "new_darwinian"
+ },
+ "9.2.2" => {
+ :file => "darwinian",
+ :else => "thing"
+ },
+ :default => {
+ :file => "old school",
+ :snicker => "snack"
+ }
},
- "9.2.2" => {
- :file => "darwinian",
- :else => "thing"
+ :mars_volta => {
},
:default => {
- :file => "old school",
- :snicker => "snack"
+ :file => Chef::Provider::File,
+ :pax => "brittania",
+ :cat => "nice"
}
- },
- :mars_volta => {
- },
- :default => {
- :file => Chef::Provider::File,
- :pax => "brittania",
- :cat => "nice"
}
- }
- @events = Chef::EventDispatch::Dispatcher.new
- end
+ @events = Chef::EventDispatch::Dispatcher.new
+ end
- it "should allow you to look up a platform by name and version, returning the provider map for it" do
- pmap = Chef::Platform.find("Darwin", "9.2.2")
- pmap.should be_a_kind_of(Hash)
- pmap[:file].should eql("darwinian")
- end
+ it "should allow you to look up a platform by name and version, returning the provider map for it" do
+ pmap = Chef::Platform.find("Darwin", "9.2.2")
+ pmap.should be_a_kind_of(Hash)
+ pmap[:file].should eql("darwinian")
+ end
- it "should allow you to look up a platform by name and version using \"greater than\" style operators" do
- pmap = Chef::Platform.find("Darwin", "11.1.0")
- pmap.should be_a_kind_of(Hash)
- pmap[:file].should eql("new_darwinian")
- end
+ it "should allow you to look up a platform by name and version using \"greater than\" style operators" do
+ pmap = Chef::Platform.find("Darwin", "11.1.0")
+ pmap.should be_a_kind_of(Hash)
+ pmap[:file].should eql("new_darwinian")
+ end
- it "should use the default providers for an os if the specific version does not exist" do
- pmap = Chef::Platform.find("Darwin", "1")
- pmap.should be_a_kind_of(Hash)
- pmap[:file].should eql("old school")
- end
+ it "should use the default providers for an os if the specific version does not exist" do
+ pmap = Chef::Platform.find("Darwin", "1")
+ pmap.should be_a_kind_of(Hash)
+ pmap[:file].should eql("old school")
+ end
- it "should use the default providers if the os doesn't give me a default, but does exist" do
- pmap = Chef::Platform.find("mars_volta", "1")
- pmap.should be_a_kind_of(Hash)
- pmap[:file].should eql(Chef::Provider::File)
- end
+ it "should use the default providers if the os doesn't give me a default, but does exist" do
+ pmap = Chef::Platform.find("mars_volta", "1")
+ pmap.should be_a_kind_of(Hash)
+ pmap[:file].should eql(Chef::Provider::File)
+ end
- it "should use the default provider if the os does not exist" do
- pmap = Chef::Platform.find("AIX", "1")
- pmap.should be_a_kind_of(Hash)
- pmap[:file].should eql(Chef::Provider::File)
- end
+ it "should use the default provider if the os does not exist" do
+ pmap = Chef::Platform.find("AIX", "1")
+ pmap.should be_a_kind_of(Hash)
+ pmap[:file].should eql(Chef::Provider::File)
+ end
- it "should merge the defaults for an os with the specific version" do
- pmap = Chef::Platform.find("Darwin", "9.2.2")
- pmap[:file].should eql("darwinian")
- pmap[:snicker].should eql("snack")
- end
+ it "should merge the defaults for an os with the specific version" do
+ pmap = Chef::Platform.find("Darwin", "9.2.2")
+ pmap[:file].should eql("darwinian")
+ pmap[:snicker].should eql("snack")
+ end
- it "should merge the defaults for an os with the universal defaults" do
- pmap = Chef::Platform.find("Darwin", "9.2.2")
- pmap[:file].should eql("darwinian")
- pmap[:pax].should eql("brittania")
- end
+ it "should merge the defaults for an os with the universal defaults" do
+ pmap = Chef::Platform.find("Darwin", "9.2.2")
+ pmap[:file].should eql("darwinian")
+ pmap[:pax].should eql("brittania")
+ end
- it "should allow you to look up a provider for a platform directly by symbol" do
- Chef::Platform.find_provider("Darwin", "9.2.2", :file).should eql("darwinian")
- end
+ it "should allow you to look up a provider for a platform directly by symbol" do
+ Chef::Platform.find_provider("Darwin", "9.2.2", :file).should eql("darwinian")
+ end
- it "should raise an exception if a provider cannot be found for a resource type" do
- lambda { Chef::Platform.find_provider("Darwin", "9.2.2", :coffee) }.should raise_error(ArgumentError)
- end
+ it "should raise an exception if a provider cannot be found for a resource type" do
+ lambda { Chef::Platform.find_provider("Darwin", "9.2.2", :coffee) }.should raise_error(ArgumentError)
+ end
- it "should look up a provider for a resource with a Chef::Resource object" do
- kitty = Chef::Resource::Cat.new("loulou")
- Chef::Platform.find_provider("Darwin", "9.2.2", kitty).should eql("nice")
- end
+ it "should look up a provider for a resource with a Chef::Resource object" do
+ kitty = Chef::Resource::Cat.new("loulou")
+ Chef::Platform.find_provider("Darwin", "9.2.2", kitty).should eql("nice")
+ end
- it "should look up a provider with a node and a Chef::Resource object" do
- kitty = Chef::Resource::Cat.new("loulou")
- node = Chef::Node.new
- node.name("Intel")
- node.automatic_attrs[:platform] = "mac_os_x"
- node.automatic_attrs[:platform_version] = "9.2.2"
- Chef::Platform.find_provider_for_node(node, kitty).should eql("nice")
- end
+ it "should look up a provider with a node and a Chef::Resource object" do
+ kitty = Chef::Resource::Cat.new("loulou")
+ node = Chef::Node.new
+ node.name("Intel")
+ node.automatic_attrs[:platform] = "mac_os_x"
+ node.automatic_attrs[:platform_version] = "9.2.2"
+ Chef::Platform.find_provider_for_node(node, kitty).should eql("nice")
+ end
- it "should not throw an exception when the platform version has an unknown format" do
- Chef::Platform.find_provider(:darwin, "bad-version", :file).should eql("old school")
- end
+ it "should not throw an exception when the platform version has an unknown format" do
+ Chef::Platform.find_provider(:darwin, "bad-version", :file).should eql("old school")
+ end
- it "should prefer an explicit provider" do
- kitty = Chef::Resource::Cat.new("loulou")
- kitty.stub(:provider).and_return(Chef::Provider::File)
- node = Chef::Node.new
- node.name("Intel")
- node.automatic_attrs[:platform] = "mac_os_x"
- node.automatic_attrs[:platform_version] = "9.2.2"
- Chef::Platform.find_provider_for_node(node, kitty).should eql(Chef::Provider::File)
- end
+ it "should prefer an explicit provider" do
+ kitty = Chef::Resource::Cat.new("loulou")
+ kitty.stub(:provider).and_return(Chef::Provider::File)
+ node = Chef::Node.new
+ node.name("Intel")
+ node.automatic_attrs[:platform] = "mac_os_x"
+ node.automatic_attrs[:platform_version] = "9.2.2"
+ Chef::Platform.find_provider_for_node(node, kitty).should eql(Chef::Provider::File)
+ end
- it "should look up a provider based on the resource name if nothing else matches" do
- kitty = Chef::Resource::Cat.new("loulou")
- class Chef::Provider::Cat < Chef::Provider; end
- Chef::Platform.platforms[:default].delete(:cat)
- node = Chef::Node.new
- node.name("Intel")
- node.automatic_attrs[:platform] = "mac_os_x"
- node.automatic_attrs[:platform_version] = "8.5"
- Chef::Platform.find_provider_for_node(node, kitty).should eql(Chef::Provider::Cat)
- end
+ it "should look up a provider based on the resource name if nothing else matches" do
+ kitty = Chef::Resource::Cat.new("loulou")
+ class Chef::Provider::Cat < Chef::Provider; end
+ Chef::Platform.platforms[:default].delete(:cat)
+ node = Chef::Node.new
+ node.name("Intel")
+ node.automatic_attrs[:platform] = "mac_os_x"
+ node.automatic_attrs[:platform_version] = "8.5"
+ Chef::Platform.find_provider_for_node(node, kitty).should eql(Chef::Provider::Cat)
+ end
- def setup_file_resource
- node = Chef::Node.new
- node.automatic_attrs[:platform] = "mac_os_x"
- node.automatic_attrs[:platform_version] = "9.2.2"
- run_context = Chef::RunContext.new(node, {}, @events)
- [ Chef::Resource::File.new("whateva", run_context), run_context ]
- end
+ def setup_file_resource
+ node = Chef::Node.new
+ node.automatic_attrs[:platform] = "mac_os_x"
+ node.automatic_attrs[:platform_version] = "9.2.2"
+ run_context = Chef::RunContext.new(node, {}, @events)
+ [ Chef::Resource::File.new("whateva", run_context), run_context ]
+ end
- it "returns a provider object given a Chef::Resource object which has a valid run context and an action" do
- file, run_context = setup_file_resource
- provider = Chef::Platform.provider_for_resource(file, :foo)
- provider.should be_an_instance_of(Chef::Provider::File)
- provider.new_resource.should equal(file)
- provider.run_context.should equal(run_context)
- end
+ it "returns a provider object given a Chef::Resource object which has a valid run context and an action" do
+ file, run_context = setup_file_resource
+ provider = Chef::Platform.provider_for_resource(file, :foo)
+ provider.should be_an_instance_of(Chef::Provider::File)
+ provider.new_resource.should equal(file)
+ provider.run_context.should equal(run_context)
+ end
- it "returns a provider object given a Chef::Resource object which has a valid run context without an action" do
- file, run_context = setup_file_resource
- provider = Chef::Platform.provider_for_resource(file)
- provider.should be_an_instance_of(Chef::Provider::File)
- provider.new_resource.should equal(file)
- provider.run_context.should equal(run_context)
- end
+ it "returns a provider object given a Chef::Resource object which has a valid run context without an action" do
+ file, run_context = setup_file_resource
+ provider = Chef::Platform.provider_for_resource(file)
+ provider.should be_an_instance_of(Chef::Provider::File)
+ provider.new_resource.should equal(file)
+ provider.run_context.should equal(run_context)
+ end
- it "raises an error when trying to find the provider for a resource with no run context" do
- file = Chef::Resource::File.new("whateva")
- lambda {Chef::Platform.provider_for_resource(file)}.should raise_error(ArgumentError)
- end
+ it "raises an error when trying to find the provider for a resource with no run context" do
+ file = Chef::Resource::File.new("whateva")
+ lambda {Chef::Platform.provider_for_resource(file)}.should raise_error(ArgumentError)
+ end
- it "does not support finding a provider by resource and node -- a run context is required" do
- lambda {Chef::Platform.provider_for_node('node', 'resource')}.should raise_error(NotImplementedError)
- end
+ it "does not support finding a provider by resource and node -- a run context is required" do
+ lambda {Chef::Platform.provider_for_node('node', 'resource')}.should raise_error(NotImplementedError)
+ end
+
+ it "should update the provider map with map" do
+ Chef::Platform.set(
+ :platform => :darwin,
+ :version => "9.2.2",
+ :resource => :file,
+ :provider => "masterful"
+ )
+ Chef::Platform.platforms[:darwin]["9.2.2"][:file].should eql("masterful")
+ Chef::Platform.set(
+ :platform => :darwin,
+ :resource => :file,
+ :provider => "masterful"
+ )
+ Chef::Platform.platforms[:darwin][:default][:file].should eql("masterful")
+ Chef::Platform.set(
+ :resource => :file,
+ :provider => "masterful"
+ )
+ Chef::Platform.platforms[:default][:file].should eql("masterful")
+
+ Chef::Platform.set(
+ :platform => :hero,
+ :version => "9.2.2",
+ :resource => :file,
+ :provider => "masterful"
+ )
+ Chef::Platform.platforms[:hero]["9.2.2"][:file].should eql("masterful")
+
+ Chef::Platform.set(
+ :resource => :file,
+ :provider => "masterful"
+ )
+ Chef::Platform.platforms[:default][:file].should eql("masterful")
+
+ Chef::Platform.platforms = {}
+
+ Chef::Platform.set(
+ :resource => :file,
+ :provider => "masterful"
+ )
+ Chef::Platform.platforms[:default][:file].should eql("masterful")
+
+ Chef::Platform.platforms = { :neurosis => {} }
+ Chef::Platform.set(:platform => :neurosis, :resource => :package, :provider => "masterful")
+ Chef::Platform.platforms[:neurosis][:default][:package].should eql("masterful")
- it "should update the provider map with map" do
- Chef::Platform.set(
- :platform => :darwin,
- :version => "9.2.2",
- :resource => :file,
- :provider => "masterful"
- )
- Chef::Platform.platforms[:darwin]["9.2.2"][:file].should eql("masterful")
- Chef::Platform.set(
- :platform => :darwin,
- :resource => :file,
- :provider => "masterful"
- )
- Chef::Platform.platforms[:darwin][:default][:file].should eql("masterful")
- Chef::Platform.set(
- :resource => :file,
- :provider => "masterful"
- )
- Chef::Platform.platforms[:default][:file].should eql("masterful")
-
- Chef::Platform.set(
- :platform => :hero,
- :version => "9.2.2",
- :resource => :file,
- :provider => "masterful"
- )
- Chef::Platform.platforms[:hero]["9.2.2"][:file].should eql("masterful")
-
- Chef::Platform.set(
- :resource => :file,
- :provider => "masterful"
- )
- Chef::Platform.platforms[:default][:file].should eql("masterful")
-
- Chef::Platform.platforms = {}
-
- Chef::Platform.set(
- :resource => :file,
- :provider => "masterful"
- )
- Chef::Platform.platforms[:default][:file].should eql("masterful")
-
- Chef::Platform.platforms = { :neurosis => {} }
- Chef::Platform.set(:platform => :neurosis, :resource => :package, :provider => "masterful")
- Chef::Platform.platforms[:neurosis][:default][:package].should eql("masterful")
+ end
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")
+ pmap[:package].should eql(Chef::Provider::Package::Solaris)
+ end
+
+ it "should use the IPS package provider on Solaris 11" do
+ pmap = Chef::Platform.find("Solaris2", "5.11")
+ pmap[:package].should eql(Chef::Provider::Package::Ips)
+ end
+
+ end
end
diff --git a/spec/unit/policy_builder/expand_node_object_spec.rb b/spec/unit/policy_builder/expand_node_object_spec.rb
index 5c6f39d28c..a1e0b881d5 100644
--- a/spec/unit/policy_builder/expand_node_object_spec.rb
+++ b/spec/unit/policy_builder/expand_node_object_spec.rb
@@ -244,7 +244,7 @@ describe Chef::PolicyBuilder::ExpandNodeObject do
it "sets the override run_list on the node" do
expect(node.run_list).to eq([override_runlist])
- expect(policy_builder.original_runlist).to eq(primary_runlist)
+ expect(node.primary_runlist).to eq(primary_runlist)
end
it "reports that a temporary policy is being used" do
diff --git a/spec/unit/provider/cron_spec.rb b/spec/unit/provider/cron_spec.rb
index 3a7a96c549..b78266fb25 100644
--- a/spec/unit/provider/cron_spec.rb
+++ b/spec/unit/provider/cron_spec.rb
@@ -19,15 +19,137 @@
require 'spec_helper'
describe Chef::Provider::Cron do
+ describe "when with special time string" do
+ before do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::Cron.new("cronhole some stuff", @run_context)
+ @new_resource.user "root"
+ @new_resource.minute "30"
+ @new_resource.command "/bin/true"
+ @new_resource.time :reboot
+ @provider = Chef::Provider::Cron.new(@new_resource, @run_context)
+ end
+
+ context "with a matching entry in the user's crontab" do
+ before :each do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+@reboot /bin/true param1 param2
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+CRONTAB
+ end
+
+ it "should set cron_exists" do
+ @provider.load_current_resource
+ @provider.cron_exists.should == true
+ @provider.cron_empty.should == false
+ end
+
+ it "should pull the details out of the cron line" do
+ cron = @provider.load_current_resource
+ cron.time.should == :reboot
+ cron.command.should == '/bin/true param1 param2'
+ end
+
+ it "should pull env vars out" do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+MAILTO=foo@example.com
+SHELL=/bin/foosh
+PATH=/bin:/foo
+HOME=/home/foo
+@reboot /bin/true param1 param2
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+CRONTAB
+ cron = @provider.load_current_resource
+ cron.mailto.should == 'foo@example.com'
+ cron.shell.should == '/bin/foosh'
+ cron.path.should == '/bin:/foo'
+ cron.home.should == '/home/foo'
+ cron.time.should == :reboot
+ cron.command.should == '/bin/true param1 param2'
+ end
+
+ it "should parse and load generic and standard environment variables from cron entry" do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+# Chef Name: cronhole some stuff
+MAILTO=warn@example.com
+TEST=lol
+FLAG=1
+@reboot /bin/true
+CRONTAB
+ cron = @provider.load_current_resource
+
+ cron.mailto.should == "warn@example.com"
+ cron.environment.should == {"TEST" => "lol", "FLAG" => "1"}
+ end
+
+ it "should not break with variables that match the cron resource internals" do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+# Chef Name: cronhole some stuff
+MINUTE=40
+REBOOT=midnight
+TEST=lol
+ENVIRONMENT=production
+@reboot /bin/true
+CRONTAB
+ cron = @provider.load_current_resource
+
+ cron.time.should == :reboot
+ cron.environment.should == {"MINUTE" => "40", "REBOOT" => "midnight", "TEST" => "lol", "ENVIRONMENT" => "production"}
+ end
+
+ it "should report the match" do
+ Chef::Log.should_receive(:debug).with("Found cron '#{@new_resource.name}'")
+ @provider.load_current_resource
+ end
+
+ describe "action_create" do
+ before :each do
+ @provider.stub!(:write_crontab)
+ @provider.stub!(:read_crontab).and_return(nil)
+ end
+
+ context "when there is no existing crontab" do
+ before :each do
+ @provider.cron_exists = false
+ @provider.cron_empty = true
+ end
+
+ it "should create a crontab with the entry" do
+ @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+# Chef Name: cronhole some stuff
+@reboot /bin/true
+ ENDCRON
+ @provider.run_action(:create)
+ end
+ end
+ end
+ end
+ end
+
before do
@node = Chef::Node.new
@events = Chef::EventDispatch::Dispatcher.new
@run_context = Chef::RunContext.new(@node, {}, @events)
+
@new_resource = Chef::Resource::Cron.new("cronhole some stuff", @run_context)
@new_resource.user "root"
@new_resource.minute "30"
@new_resource.command "/bin/true"
-
@provider = Chef::Provider::Cron.new(@new_resource, @run_context)
end
@@ -110,6 +232,7 @@ CRONTAB
cron.day.should == '*'
cron.month.should == '1'
cron.weekday.should == '*'
+ cron.time.should == nil
cron.command.should == '/bin/true param1 param2'
end
@@ -138,6 +261,7 @@ CRONTAB
cron.day.should == '*'
cron.month.should == '1'
cron.weekday.should == '*'
+ cron.time.should == nil
cron.command.should == '/bin/true param1 param2'
end
@@ -227,6 +351,7 @@ CRONTAB
cron.day.should == '*'
cron.month.should == '*'
cron.weekday.should == '*'
+ cron.time.should == nil
cron.command.should == nil
end
@@ -244,6 +369,7 @@ CRONTAB
cron.day.should == '*'
cron.month.should == '*'
cron.weekday.should == '*'
+ cron.time.should == nil
cron.command.should == nil
end
@@ -265,6 +391,7 @@ CRONTAB
cron.day.should == '*'
cron.month.should == '*'
cron.weekday.should == '*'
+ cron.time.should == nil
cron.command.should == nil
end
end
@@ -286,6 +413,11 @@ CRONTAB
end
end
+ it "should return true if special time string doesn't match" do
+ @new_resource.send(:time, :reboot)
+ @provider.cron_different?.should eql(true)
+ end
+
it "should return true if environment doesn't match" do
@new_resource.environment "FOO" => "something_else"
@provider.cron_different?.should eql(true)
@@ -833,4 +965,46 @@ MAILTO=foo@example.com
end
end
+
+ describe "weekday_in_crontab" do
+ context "when weekday is symbol" do
+ it "should return weekday in crontab format" do
+ @new_resource.weekday :wednesday
+ @provider.send(:weekday_in_crontab).should eq("3")
+ end
+
+ it "should raise an error with an unknown weekday" do
+ expect { @new_resource.weekday :caturday }.to raise_error(RangeError)
+ end
+ end
+
+ context "when weekday is a number in a string" do
+ it "should return the string" do
+ @new_resource.weekday "3"
+ @provider.send(:weekday_in_crontab).should eq("3")
+ end
+
+ it "should raise an error with an out of range number" do
+ expect { @new_resource.weekday "-1" }.to raise_error(RangeError)
+ end
+ end
+
+ context "when weekday is string with the name of the week" do
+ it "should return the string" do
+ @new_resource.weekday "mon"
+ @provider.send(:weekday_in_crontab).should eq("mon")
+ end
+ end
+
+ context "when weekday is an integer" do
+ it "should return the integer" do
+ @new_resource.weekday 1
+ @provider.send(:weekday_in_crontab).should eq("1")
+ end
+
+ it "should raise an error with an out of range integer" do
+ expect { @new_resource.weekday 45 }.to raise_error(RangeError)
+ end
+ end
+ end
end
diff --git a/spec/unit/provider/group_spec.rb b/spec/unit/provider/group_spec.rb
index 9ff9f85c7c..b138f6b210 100644
--- a/spec/unit/provider/group_spec.rb
+++ b/spec/unit/provider/group_spec.rb
@@ -96,6 +96,11 @@ describe Chef::Provider::User do
@provider.compare_group.should be_false
end
+ it "should coerce an integer to a string for comparison" do
+ @current_resource.stub!(:gid).and_return("500")
+ @provider.compare_group.should be_false
+ end
+
it "should return false if append is true and the group member(s) already exists" do
@current_resource.members << "extra_user"
@new_resource.stub(:append).and_return(true)
diff --git a/spec/unit/provider/ifconfig/debian_spec.rb b/spec/unit/provider/ifconfig/debian_spec.rb
index c2e2d1bfd1..c6a37fdd5b 100644
--- a/spec/unit/provider/ifconfig/debian_spec.rb
+++ b/spec/unit/provider/ifconfig/debian_spec.rb
@@ -53,38 +53,264 @@ describe Chef::Provider::Ifconfig::Debian do
let(:config_filename_ifcfg) { "/etc/network/interfaces.d/ifcfg-#{new_resource.device}" }
- describe "generate_config for action_add" do
+ describe "generate_config" do
- let(:config_file_ifaces) { StringIO.new }
+ context "when writing a file" do
+ let(:config_file_ifcfg) { StringIO.new }
- let(:config_file_ifcfg) { StringIO.new }
+ let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") }
- before do
- expect(FileUtils).to receive(:cp)
- expect(File).to receive(:open).with(config_filename_ifaces).and_return(StringIO.new)
- expect(File).to receive(:open).with(config_filename_ifaces, "w").and_yield(config_file_ifaces)
- expect(File).to receive(:new).with(config_filename_ifcfg, "w").and_return(config_file_ifcfg)
- expect(File).to receive(:exist?).with(config_filename_ifaces).and_return(true)
- end
+ let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") }
+
+ let(:config_filename_ifcfg) { "#{tempdir_path}/ifcfg-#{new_resource.device}" }
+
+ before do
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path)
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path)
+ expect(File).to receive(:new).with(config_filename_ifcfg, "w").and_return(config_file_ifcfg)
+ end
+
+ it "should write a network-script" do
+ provider.run_action(:add)
+ expect(config_file_ifcfg.string).to match(/^iface eth0 inet static\s*$/)
+ expect(config_file_ifcfg.string).to match(/^\s+address 10\.0\.0\.1\s*$/)
+ expect(config_file_ifcfg.string).to match(/^\s+netmask 255\.255\.254\.0\s*$/)
+ end
+
+ context "when the interface_dot_d directory does not exist" do
+ before do
+ FileUtils.rmdir tempdir_path
+ expect(File.exists?(tempdir_path)).to be_false
+ end
+
+ it "should create the /etc/network/interfaces.d directory" do
+ provider.run_action(:add)
+ expect(File.exists?(tempdir_path)).to be_true
+ expect(File.directory?(tempdir_path)).to be_true
+ end
- it "should create network-scripts directory" do
- expect(File).to receive(:directory?).with(File.dirname(config_filename_ifcfg)).and_return(false)
- expect(Dir).to receive(:mkdir).with(File.dirname(config_filename_ifcfg))
- provider.run_action(:add)
+ it "should mark the resource as updated" do
+ provider.run_action(:add)
+ expect(new_resource.updated_by_last_action?).to be_true
+ end
+ end
+
+ context "when the interface_dot_d directory exists" do
+ before do
+ expect(File.exists?(tempdir_path)).to be_true
+ end
+
+ it "should still mark the resource as updated (we still write a file to it)" do
+ provider.run_action(:add)
+ expect(new_resource.updated_by_last_action?).to be_true
+ end
+ end
end
- it "should write configure network-scripts directory" do
- expect(File).to receive(:directory?).with(File.dirname(config_filename_ifcfg)).and_return(true)
- provider.run_action(:add)
- expect(config_file_ifaces.string).to match(/^\s*source\s+\/etc\/network\/interfaces[.]d\/[*]\s*$/)
+ context "when the file is up-to-date" do
+ let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") }
+
+ let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") }
+
+ let(:config_filename_ifcfg) { "#{tempdir_path}/ifcfg-#{new_resource.device}" }
+
+ before do
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path)
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path)
+ config_file_ifcfg = StringIO.new(<<-EOF
+iface eth0 inet static
+ address 10.0.0.1
+ netmask 255.255.254.0
+EOF
+ )
+ expect(File).to receive(:new).with(config_filename_ifcfg, "w").and_return(config_file_ifcfg)
+ expect(File.exists?(tempdir_path)).to be_true # since the file exists, the enclosing dir must also exist
+ end
+
+ context "when the /etc/network/interfaces file has the source line" do
+ let(:expected_string) do
+ <<-EOF
+a line
+source #{tempdir_path}/*
+another line
+EOF
+ end
+
+ before do
+ tempfile.write(expected_string)
+ tempfile.close
+ end
+
+ it "should preserve all the contents" do
+ provider.run_action(:add)
+ 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_false
+ end
+ end
+
+ context "when the /etc/network/interfaces file does not have the source line" do
+ let(:expected_string) do
+ <<-EOF
+a line
+another line
+source #{tempdir_path}/*
+EOF
+ end
+
+ before do
+ tempfile.write("a line\nanother line\n")
+ tempfile.close
+ end
+
+ it "should preserve the original contents and add the source line" do
+ provider.run_action(:add)
+ expect(IO.read(tempfile.path)).to eq(expected_string)
+ end
+
+ it "should mark the resource as updated" do
+ provider.run_action(:add)
+ expect(new_resource.updated_by_last_action?).to be_true
+ end
+ end
end
- it "should write a network-script" do
- expect(File).to receive(:directory?).with(File.dirname(config_filename_ifcfg)).and_return(true)
- provider.run_action(:add)
- expect(config_file_ifcfg.string).to match(/^iface eth0 inet static\s*$/)
- expect(config_file_ifcfg.string).to match(/^\s+address 10\.0\.0\.1\s*$/)
- expect(config_file_ifcfg.string).to match(/^\s+netmask 255\.255\.254\.0\s*$/)
+ describe "when running under why run" do
+
+ before do
+ Chef::Config[:why_run] = true
+ end
+
+ after do
+ Chef::Config[:why_run] = false
+ end
+
+ context "when writing a file" do
+ let(:config_file_ifcfg) { StringIO.new }
+
+ let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") }
+
+ let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") }
+
+ let(:config_filename_ifcfg) { "#{tempdir_path}/ifcfg-#{new_resource.device}" }
+
+ before do
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path)
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path)
+ expect(File).not_to receive(:new).with(config_filename_ifcfg, "w")
+ end
+
+ it "should write a network-script" do
+ provider.run_action(:add)
+ expect(config_file_ifcfg.string).not_to match(/^iface eth0 inet static\s*$/)
+ expect(config_file_ifcfg.string).not_to match(/^\s+address 10\.0\.0\.1\s*$/)
+ expect(config_file_ifcfg.string).not_to match(/^\s+netmask 255\.255\.254\.0\s*$/)
+ end
+
+ context "when the interface_dot_d directory does not exist" do
+ before do
+ FileUtils.rmdir tempdir_path
+ expect(File.exists?(tempdir_path)).to be_false
+ end
+
+ it "should not create the /etc/network/interfaces.d directory" do
+ provider.run_action(:add)
+ expect(File.exists?(tempdir_path)).not_to be_true
+ end
+
+ it "should mark the resource as updated" do
+ provider.run_action(:add)
+ expect(new_resource.updated_by_last_action?).to be_true
+ end
+ end
+
+ context "when the interface_dot_d directory exists" do
+ before do
+ expect(File.exists?(tempdir_path)).to be_true
+ end
+
+ it "should still mark the resource as updated (we still write a file to it)" do
+ provider.run_action(:add)
+ expect(new_resource.updated_by_last_action?).to be_true
+ end
+ end
+ end
+
+ context "when the file is up-to-date" do
+ let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") }
+
+ let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") }
+
+ let(:config_filename_ifcfg) { "#{tempdir_path}/ifcfg-#{new_resource.device}" }
+
+ before do
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path)
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path)
+ config_file_ifcfg = StringIO.new(<<-EOF
+iface eth0 inet static
+ address 10.0.0.1
+ netmask 255.255.254.0
+ EOF
+ )
+ expect(File).not_to receive(:new).with(config_filename_ifcfg, "w")
+ expect(File.exists?(tempdir_path)).to be_true # since the file exists, the enclosing dir must also exist
+ end
+
+ context "when the /etc/network/interfaces file has the source line" do
+ let(:expected_string) do
+ <<-EOF
+a line
+source #{tempdir_path}/*
+another line
+ EOF
+ end
+
+ before do
+ tempfile.write(expected_string)
+ tempfile.close
+ end
+
+ it "should preserve all the contents" do
+ provider.run_action(:add)
+ 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_false
+ end
+ end
+
+ context "when the /etc/network/interfaces file does not have the source line" do
+ let(:expected_string) do
+ <<-EOF
+a line
+another line
+source #{tempdir_path}/*
+ EOF
+ end
+
+ before do
+ tempfile.write("a line\nanother line\n")
+ tempfile.close
+ end
+
+ it "should preserve the original contents and not add the source line" do
+ provider.run_action(:add)
+ expect(IO.read(tempfile.path)).to eq("a line\nanother line\n")
+ end
+
+ it "should mark the resource as updated" do
+ provider.run_action(:add)
+ expect(new_resource.updated_by_last_action?).to be_true
+ end
+ end
+ end
end
end
@@ -98,4 +324,5 @@ describe Chef::Provider::Ifconfig::Debian do
provider.run_action(:delete)
end
end
+
end
diff --git a/spec/unit/provider/mount/mount_spec.rb b/spec/unit/provider/mount/mount_spec.rb
index 99e78590f1..e27cf71e01 100644
--- a/spec/unit/provider/mount/mount_spec.rb
+++ b/spec/unit/provider/mount/mount_spec.rb
@@ -141,6 +141,17 @@ describe Chef::Provider::Mount::Mount do
@provider.current_resource.mounted.should be_true
end
+ it "should set mounted true if the symlink target of the device is relative and is found in the mounts list - CHEF-4957" do
+ target = "xsdz1"
+
+ ::File.stub(:symlink?).with("#{@new_resource.device}").and_return(true)
+ ::File.stub(:readlink).with("#{@new_resource.device}").and_return(target)
+
+ @provider.stub(:shell_out!).and_return(OpenStruct.new(:stdout => "/dev/xsdz1 on /tmp/foo type ext3 (rw)\n"))
+ @provider.load_current_resource()
+ @provider.current_resource.mounted.should be_true
+ end
+
it "should set mounted true if the mount point is found last in the mounts list" do
mount = "/dev/sdy1 on #{@new_resource.mount_point} type ext3 (rw)\n"
mount << "#{@new_resource.device} on #{@new_resource.mount_point} type ext3 (rw)\n"
@@ -199,6 +210,20 @@ describe Chef::Provider::Mount::Mount do
@provider.current_resource.enabled.should be_true
end
+ it "should set enabled to true if the symlink target is relative and is in fstab - CHEF-4957" do
+ target = "xsdz1"
+
+ ::File.stub(:symlink?).with("#{@new_resource.device}").and_return(true)
+ ::File.stub(:readlink).with("#{@new_resource.device}").and_return(target)
+
+ fstab = "/dev/sdz1 /tmp/foo ext3 defaults 1 2\n"
+
+ ::File.stub(:foreach).with("/etc/fstab").and_yield fstab
+
+ @provider.load_current_resource
+ @provider.current_resource.enabled.should be_true
+ end
+
it "should set enabled to false if the mount point is not in fstab" do
fstab = "/dev/sdy1 #{@new_resource.mount_point} ext3 defaults 1 2\n"
::File.stub(:foreach).with("/etc/fstab").and_yield fstab
diff --git a/spec/unit/provider/ohai_spec.rb b/spec/unit/provider/ohai_spec.rb
index 8b8a6b5939..2085f44309 100644
--- a/spec/unit/provider/ohai_spec.rb
+++ b/spec/unit/provider/ohai_spec.rb
@@ -41,9 +41,8 @@ describe Chef::Provider::Ohai do
:newdata => "somevalue"
}
}
- mock_ohai.stub(:all_plugins).and_return(true)
- mock_ohai.stub(:require_plugin).and_return(true)
- mock_ohai.stub(:data).and_return(mock_ohai[:data],
+ mock_ohai.stub!(:all_plugins).and_return(true)
+ mock_ohai.stub!(:data).and_return(mock_ohai[:data],
mock_ohai[:data2])
Ohai::System.stub(:new).and_return(mock_ohai)
Chef::Platform.stub(:find_platform_and_version).and_return({ "platform" => @platform,
diff --git a/spec/unit/provider/package/dpkg_spec.rb b/spec/unit/provider/package/dpkg_spec.rb
index 6ba7695a1e..22edeb7b9b 100644
--- a/spec/unit/provider/package/dpkg_spec.rb
+++ b/spec/unit/provider/package/dpkg_spec.rb
@@ -72,6 +72,10 @@ describe Chef::Provider::Package::Dpkg do
it 'if distro-specific version provided' do
check_version('1.11.4-1ubuntu1~lucid')
end
+
+ it 'returns the version if an epoch is used' do
+ check_version('1:1.8.3-2')
+ end
end
it "gets the source package name from dpkg-deb correctly when the package name has `-', `+' or `.' characters" do
diff --git a/spec/unit/provider/package/windows/msi_spec.rb b/spec/unit/provider/package/windows/msi_spec.rb
new file mode 100644
index 0000000000..c8a63ad066
--- /dev/null
+++ b/spec/unit/provider/package/windows/msi_spec.rb
@@ -0,0 +1,60 @@
+#
+# 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 'spec_helper'
+
+describe Chef::Provider::Package::Windows::MSI, :windows_only do
+ 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(:provider) { Chef::Provider::Package::Windows::MSI.new(new_resource) }
+
+ describe "expand_options" do
+ it "returns an empty string if passed no options" do
+ expect(provider.expand_options(nil)).to eql ""
+ end
+
+ it "returns a string with a leading space if passed options" do
+ expect(provider.expand_options("--train nope --town no_way")).to eql(" --train nope --town no_way")
+ end
+ end
+
+ describe "installed_version" do
+ it "returns the installed version" do
+ provider.stub(:get_product_property).and_return("{23170F69-40C1-2702-0920-000001000000}")
+ provider.stub(:get_installed_version).with("{23170F69-40C1-2702-0920-000001000000}").and_return("3.14159.1337.42")
+ expect(provider.installed_version).to eql("3.14159.1337.42")
+ end
+ end
+
+ describe "package_version" do
+ it "returns the version of a package" do
+ provider.stub(:get_product_property).with(/calculator.msi$/, "ProductVersion").and_return(42)
+ expect(provider.package_version).to eql(42)
+ end
+ end
+
+ describe "install_package" do
+ # calls shell_out!
+ end
+
+ describe "remove_package" do
+ # calls shell_out!
+ end
+end
diff --git a/spec/unit/provider/package/windows_spec.rb b/spec/unit/provider/package/windows_spec.rb
new file mode 100644
index 0000000000..e94404eea5
--- /dev/null
+++ b/spec/unit/provider/package/windows_spec.rb
@@ -0,0 +1,80 @@
+#
+# 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 'spec_helper'
+
+describe Chef::Provider::Package::Windows, :windows_only do
+ 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(:provider) { Chef::Provider::Package::Windows.new(new_resource, run_context) }
+
+ describe "load_current_resource" do
+ before(:each) do
+ provider.stub(:package_provider).and_return(double('package_provider',
+ :installed_version => "1.0", :package_version => "2.0"))
+ 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 "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 version to be installed" do
+ provider.load_current_resource
+ expect(provider.new_resource.version).to eql("2.0")
+ end
+ end
+
+ describe "package_provider" do
+ it "sets the package provider to MSI if the the installer type is :msi" do
+ provider.stub(: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
+ provider.stub(:installer_type).and_return(:apt_for_windows)
+ expect { provider.package_provider }.to raise_error
+ end
+ end
+
+ describe "installer_type" do
+ it "it returns @installer_type if it is set" do
+ provider.new_resource.installer_type("downeaster")
+ expect(provider.installer_type).to eql("downeaster")
+ end
+
+ it "sets installer_type to msi if the source ends in .msi" do
+ provider.new_resource.source("microsoft_installer.msi")
+ expect(provider.installer_type).to eql(:msi)
+ end
+
+ it "raises an error if it cannot determine the installer type" do
+ provider.new_resource.installer_type(nil)
+ provider.new_resource.source("tomfoolery.now")
+ expect { provider.installer_type }.to raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/unit/provider/service/macosx_spec.rb b/spec/unit/provider/service/macosx_spec.rb
index 65639f2084..1e9656aeac 100644
--- a/spec/unit/provider/service/macosx_spec.rb
+++ b/spec/unit/provider/service/macosx_spec.rb
@@ -46,14 +46,32 @@ describe Chef::Provider::Service::Macosx do
let(:events) {Chef::EventDispatch::Dispatcher.new}
let(:run_context) { Chef::RunContext.new(node, {}, events) }
let(:provider) { described_class.new(new_resource, run_context) }
- let(:stdout) { StringIO.new }
+ let(:launchctl_stdout) { StringIO.new }
+ let(:plutil_stdout) { String.new <<-XML }
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>Label</key>
+ <string>io.redis.redis-server</string>
+</dict>
+</plist>
+XML
["redis-server", "io.redis.redis-server"].each do |service_name|
before do
Dir.stub(:glob).and_return(["/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist"], [])
provider.stub(:shell_out!).
with("launchctl list", {:group => 1001, :user => 101}).
- and_return(double("ouput", :stdout => stdout))
+ and_return(double("Status", :stdout => launchctl_stdout))
+ provider.stub(:shell_out).
+ with(/launchctl list /,
+ {:group => nil, :user => nil}).
+ and_return(double("Status",
+ :stdout => launchctl_stdout, :exitstatus => 0))
+ provider.stub(:shell_out!).
+ with(/plutil -convert xml1 -o/).
+ and_return(double("Status", :stdout => plutil_stdout))
File.stub(:stat).and_return(double("stat", :gid => 1001, :uid => 101))
end
@@ -64,7 +82,7 @@ describe Chef::Provider::Service::Macosx do
describe "#load_current_resource" do
context "when launchctl returns pid in service list" do
- let(:stdout) { StringIO.new <<-SVC_LIST }
+ let(:launchctl_stdout) { StringIO.new <<-SVC_LIST }
12761 - 0x100114220.old.machinit.thing
7777 - io.redis.redis-server
- - com.lol.stopped-thing
@@ -84,21 +102,21 @@ describe Chef::Provider::Service::Macosx do
end
describe "running unsupported actions" do
+ let(:launchctl_stdout) { StringIO.new <<-SVC_LIST }
+12761 - 0x100114220.old.machinit.thing
+7777 - io.redis.redis-server
+- - com.lol.stopped-thing
+SVC_LIST
+
before do
Dir.stub(:glob).and_return(["/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist"], [])
end
- it "should throw an exception when enable action is attempted" do
- lambda {provider.run_action(:enable)}.should raise_error(Chef::Exceptions::UnsupportedAction)
- end
it "should throw an exception when reload action is attempted" do
lambda {provider.run_action(:reload)}.should raise_error(Chef::Exceptions::UnsupportedAction)
end
- it "should throw an exception when disable action is attempted" do
- lambda {provider.run_action(:disable)}.should raise_error(Chef::Exceptions::UnsupportedAction)
- end
end
context "when launchctl returns empty service pid" do
- let(:stdout) { StringIO.new <<-SVC_LIST }
+ let(:launchctl_stdout) { StringIO.new <<-SVC_LIST }
12761 - 0x100114220.old.machinit.thing
- - io.redis.redis-server
- - com.lol.stopped-thing
@@ -118,7 +136,7 @@ describe Chef::Provider::Service::Macosx do
end
context "when launchctl doesn't return service entry at all" do
- let(:stdout) { StringIO.new <<-SVC_LIST }
+ let(:launchctl_stdout) { StringIO.new <<-SVC_LIST }
12761 - 0x100114220.old.machinit.thing
- - com.lol.stopped-thing
SVC_LIST
diff --git a/spec/unit/provider/service/solaris_smf_service_spec.rb b/spec/unit/provider/service/solaris_smf_service_spec.rb
index 887c1f6b5f..af1351a4ff 100644
--- a/spec/unit/provider/service/solaris_smf_service_spec.rb
+++ b/spec/unit/provider/service/solaris_smf_service_spec.rb
@@ -54,34 +54,47 @@ describe Chef::Provider::Service::Solaris do
describe "when discovering the current service state" do
it "should create a current resource with the name of the new resource" do
- @provider.stub(:popen4).with("/bin/svcs -l chef").and_return(@status)
+ @provider.stub!(:shell_out!).with("/bin/svcs -l chef").and_return(@status)
Chef::Resource::Service.should_receive(:new).and_return(@current_resource)
@provider.load_current_resource
end
it "should return the current resource" do
- @provider.stub(:popen4).with("/bin/svcs -l chef").and_return(@status)
+ @provider.stub!(:shell_out!).with("/bin/svcs -l chef").and_return(@status)
@provider.load_current_resource.should eql(@current_resource)
end
- it "should popen4 '/bin/svcs -l service_name'" do
- @provider.should_receive(:popen4).with("/bin/svcs -l chef").and_return(@status)
+ it "should call '/bin/svcs -l service_name'" do
+ @provider.should_receive(:shell_out!).with("/bin/svcs -l chef").and_return(@status)
@provider.load_current_resource
end
it "should mark service as not running" do
- @provider.stub(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.stub!(:shell_out!).and_return(@status)
@current_resource.should_receive(:running).with(false)
@provider.load_current_resource
end
it "should mark service as running" do
- @stdout.stub(:each).and_yield("state online")
- @provider.stub(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @status = mock("Status", :exitstatus => 0, :stdout => 'state online')
+ @provider.stub!(:shell_out!).and_return(@status)
@current_resource.should_receive(:running).with(true)
@provider.load_current_resource
end
+
+ it "should not mark service as maintenance" do
+ @provider.stub!(:shell_out!).and_return(@status)
+ @provider.load_current_resource
+ @provider.maintenance.should be_false
+ end
+
+ it "should mark service as maintenance" do
+ @status = mock("Status", :exitstatus => 0, :stdout => 'state maintenance')
+ @provider.stub!(:shell_out!).and_return(@status)
+ @provider.load_current_resource
+ @provider.maintenance.should be_true
+ end
end
describe "when enabling the service" do
@@ -91,19 +104,31 @@ describe Chef::Provider::Service::Solaris do
end
it "should call svcadm enable -s chef" do
- @new_resource.stub(:enable_command).and_return("#{@new_resource.enable_command}")
+ @new_resource.stub!(:enable_command).and_return("#{@new_resource.enable_command}")
+ @provider.should_not_receive(:shell_out!).with("/usr/sbin/svcadm clear #{@current_resource.service_name}")
@provider.should_receive(:shell_out!).with("/usr/sbin/svcadm enable -s #{@current_resource.service_name}").and_return(@status)
- @provider.enable_service.should be_true
+ @provider.enable_service.should be_true
@current_resource.enabled.should be_true
end
it "should call svcadm enable -s chef for start_service" do
- @new_resource.stub(:start_command).and_return("#{@new_resource.start_command}")
+ @new_resource.stub!(:start_command).and_return("#{@new_resource.start_command}")
+ @provider.should_not_receive(:shell_out!).with("/usr/sbin/svcadm clear #{@current_resource.service_name}")
@provider.should_receive(:shell_out!).with("/usr/sbin/svcadm enable -s #{@current_resource.service_name}").and_return(@status)
@provider.start_service.should be_true
@current_resource.enabled.should be_true
end
+ it "should call svcadm clear chef for start_service when state maintenance" do
+ @status = mock("Status", :exitstatus => 0, :stdout => 'state maintenance')
+ @provider.stub!(:shell_out!).and_return(@status)
+ @provider.load_current_resource
+ @new_resource.stub!(:enable_command).and_return("#{@new_resource.enable_command}")
+ @provider.should_receive(:shell_out!).with("/usr/sbin/svcadm clear #{@current_resource.service_name}").and_return(@status)
+ @provider.should_receive(:shell_out!).with("/usr/sbin/svcadm enable -s #{@current_resource.service_name}").and_return(@status)
+ @provider.enable_service.should be_true
+ @current_resource.enabled.should be_true
+ end
end
diff --git a/spec/unit/recipe_spec.rb b/spec/unit/recipe_spec.rb
index b0cd04b245..2bdf470143 100644
--- a/spec/unit/recipe_spec.rb
+++ b/spec/unit/recipe_spec.rb
@@ -339,6 +339,7 @@ describe Chef::Recipe do
describe "include_recipe" do
it "should evaluate another recipe with include_recipe" do
node.should_receive(:loaded_recipe).with(:openldap, "gigantor")
+ run_context.stub(:unreachable_cookbook?).with(:openldap).and_return(false)
run_context.include_recipe "openldap::gigantor"
res = run_context.resource_collection.resources(:cat => "blanket")
res.name.should eql("blanket")
@@ -347,6 +348,7 @@ describe Chef::Recipe do
it "should load the default recipe for a cookbook if include_recipe is called without a ::" do
node.should_receive(:loaded_recipe).with(:openldap, "default")
+ run_context.stub(:unreachable_cookbook?).with(:openldap).and_return(false)
run_context.include_recipe "openldap"
res = run_context.resource_collection.resources(:cat => "blanket")
res.name.should eql("blanket")
@@ -355,12 +357,14 @@ describe Chef::Recipe do
it "should store that it has seen a recipe in the run_context" do
node.should_receive(:loaded_recipe).with(:openldap, "default")
+ run_context.stub(:unreachable_cookbook?).with(:openldap).and_return(false)
run_context.include_recipe "openldap"
run_context.loaded_recipe?("openldap").should be_true
end
it "should not include the same recipe twice" do
node.should_receive(:loaded_recipe).with(:openldap, "default").exactly(:once)
+ run_context.stub(:unreachable_cookbook?).with(:openldap).and_return(false)
cookbook_collection[:openldap].should_receive(:load_recipe).with("default", run_context)
recipe.include_recipe "openldap"
cookbook_collection[:openldap].should_not_receive(:load_recipe).with("default", run_context)
diff --git a/spec/unit/resource/conditional_spec.rb b/spec/unit/resource/conditional_spec.rb
index 1be7bcea71..4df185bcd6 100644
--- a/spec/unit/resource/conditional_spec.rb
+++ b/spec/unit/resource/conditional_spec.rb
@@ -24,12 +24,13 @@ describe Chef::Resource::Conditional do
Mixlib::ShellOut.any_instance.stub(:run_command).and_return(nil)
@status = OpenStruct.new(:success? => true)
Mixlib::ShellOut.any_instance.stub(:status).and_return(@status)
+ @parent_resource = Chef::Resource.new(nil, Chef::Node.new)
end
describe "when created as an `only_if`" do
describe "after running a successful command" do
before do
- @conditional = Chef::Resource::Conditional.only_if("true")
+ @conditional = Chef::Resource::Conditional.only_if(@parent_resource, "true")
end
it "indicates that resource convergence should continue" do
@@ -40,7 +41,7 @@ describe Chef::Resource::Conditional do
describe "after running a negative/false command" do
before do
@status.send("success?=", false)
- @conditional = Chef::Resource::Conditional.only_if("false")
+ @conditional = Chef::Resource::Conditional.only_if(@parent_resource, "false")
end
it "indicates that resource convergence should not continue" do
@@ -50,8 +51,8 @@ describe Chef::Resource::Conditional do
describe 'after running a command which timed out' do
before do
- @conditional = Chef::Resource::Conditional.only_if("false")
- @conditional.stub(:shell_out).and_raise(Chef::Exceptions::CommandTimeout)
+ @conditional = Chef::Resource::Conditional.only_if(@parent_resource, "false")
+ Chef::GuardInterpreter::DefaultGuardInterpreter.any_instance.stub(:shell_out).and_raise(Chef::Exceptions::CommandTimeout)
end
it 'indicates that resource convergence should not continue' do
@@ -66,7 +67,7 @@ describe Chef::Resource::Conditional do
describe "after running a block that returns a truthy value" do
before do
- @conditional = Chef::Resource::Conditional.only_if { Object.new }
+ @conditional = Chef::Resource::Conditional.only_if(@parent_resource) { Object.new }
end
it "indicates that resource convergence should continue" do
@@ -76,7 +77,7 @@ describe Chef::Resource::Conditional do
describe "after running a block that returns a falsey value" do
before do
- @conditional = Chef::Resource::Conditional.only_if { nil }
+ @conditional = Chef::Resource::Conditional.only_if(@parent_resource) { nil }
end
it "indicates that resource convergence should not continue" do
@@ -88,7 +89,7 @@ describe Chef::Resource::Conditional do
describe "when created as a `not_if`" do
describe "after running a successful/true command" do
before do
- @conditional = Chef::Resource::Conditional.not_if("true")
+ @conditional = Chef::Resource::Conditional.not_if(@parent_resource, "true")
end
it "indicates that resource convergence should not continue" do
@@ -99,7 +100,7 @@ describe Chef::Resource::Conditional do
describe "after running a failed/false command" do
before do
@status.send("success?=", false)
- @conditional = Chef::Resource::Conditional.not_if("false")
+ @conditional = Chef::Resource::Conditional.not_if(@parent_resource, "false")
end
it "indicates that resource convergence should continue" do
@@ -109,8 +110,8 @@ describe Chef::Resource::Conditional do
describe 'after running a command which timed out' do
before do
- @conditional = Chef::Resource::Conditional.not_if("false")
- @conditional.stub(:shell_out).and_raise(Chef::Exceptions::CommandTimeout)
+ @conditional = Chef::Resource::Conditional.not_if(@parent_resource, "false")
+ Chef::GuardInterpreter::DefaultGuardInterpreter.any_instance.stub(:shell_out).and_raise(Chef::Exceptions::CommandTimeout)
end
it 'indicates that resource convergence should continue' do
@@ -125,7 +126,7 @@ describe Chef::Resource::Conditional do
describe "after running a block that returns a truthy value" do
before do
- @conditional = Chef::Resource::Conditional.not_if { Object.new }
+ @conditional = Chef::Resource::Conditional.not_if(@parent_resource) { Object.new }
end
it "indicates that resource convergence should not continue" do
@@ -135,7 +136,7 @@ describe Chef::Resource::Conditional do
describe "after running a block that returns a falsey value" do
before do
- @conditional = Chef::Resource::Conditional.not_if { nil }
+ @conditional = Chef::Resource::Conditional.not_if(@parent_resource) { nil }
end
it "indicates that resource convergence should continue" do
diff --git a/spec/unit/resource/cron_spec.rb b/spec/unit/resource/cron_spec.rb
index 355a7f09ba..cf821e3d32 100644
--- a/spec/unit/resource/cron_spec.rb
+++ b/spec/unit/resource/cron_spec.rb
@@ -143,8 +143,13 @@ describe Chef::Resource::Cron do
lambda { @resource.month "13" }.should raise_error(RangeError)
end
- it "should reject any weekday over 7" do
- lambda { @resource.weekday "8" }.should raise_error(RangeError)
+ describe "weekday" do
+ it "should reject any weekday over 7" do
+ lambda { @resource.weekday "8" }.should raise_error(RangeError)
+ end
+ it "should reject any symbols which don't represent day of week" do
+ lambda { @resource.weekday :foo }.should raise_error(RangeError)
+ end
end
it "should convert integer schedule values to a string" do
diff --git a/spec/unit/resource/powershell_spec.rb b/spec/unit/resource/powershell_spec.rb
index a35e37c696..da20c4f0bf 100644
--- a/spec/unit/resource/powershell_spec.rb
+++ b/spec/unit/resource/powershell_spec.rb
@@ -36,7 +36,91 @@ describe Chef::Resource::PowershellScript do
@resource.should be_a_kind_of(Chef::Resource::PowershellScript)
end
- context "windowsscript" do
+ it "should set convert_boolean_return to false by default" do
+ @resource.convert_boolean_return.should == false
+ end
+
+ it "should return the value for convert_boolean_return that was set" do
+ @resource.convert_boolean_return true
+ @resource.convert_boolean_return.should == true
+ @resource.convert_boolean_return false
+ @resource.convert_boolean_return.should == false
+ end
+
+ context "when using guards" do
+ let(:resource) { @resource }
+ before(:each) do
+ resource.stub(:run_action)
+ resource.stub(:updated).and_return(true)
+ end
+
+ it "inherits exactly the :cwd, :environment, :group, :path, :user, :umask, and :architecture attributes from a parent resource class" do
+ inherited_difference = Chef::Resource::PowershellScript.guard_inherited_attributes -
+ [:cwd, :environment, :group, :path, :user, :umask, :architecture ]
+
+ inherited_difference.should == []
+ end
+
+ it "should allow guard interpreter to be set to Chef::Resource::Script" do
+ resource.guard_interpreter(:script)
+ allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false)
+ resource.only_if("echo hi")
+ end
+
+ it "should allow guard interpreter to be set to Chef::Resource::Bash derived from Chef::Resource::Script" do
+ resource.guard_interpreter(:bash)
+ allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false)
+ resource.only_if("echo hi")
+ end
+
+ it "should allow guard interpreter to be set to Chef::Resource::PowershellScript derived indirectly from Chef::Resource::Script" do
+ resource.guard_interpreter(:powershell_script)
+ allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false)
+ resource.only_if("echo hi")
+ end
+
+ it "should enable convert_boolean_return by default for guards in the context of powershell_script when no guard params are specified" do
+ allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(true)
+ allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with(
+ {:convert_boolean_return => true, :code => "$true"}).and_return(Proc.new {})
+ resource.only_if("$true")
+ end
+
+ it "should enable convert_boolean_return by default for guards in non-Chef::Resource::Script derived resources when no guard params are specified" do
+ node = Chef::Node.new
+ run_context = Chef::RunContext.new(node, nil, nil)
+ file_resource = Chef::Resource::File.new('idontexist', run_context)
+ file_resource.guard_interpreter :powershell_script
+
+ allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with(
+ {:convert_boolean_return => true, :code => "$true"}).and_return(Proc.new {})
+ resource.only_if("$true")
+ end
+
+ it "should enable convert_boolean_return by default for guards in the context of powershell_script when guard params are specified" do
+ guard_parameters = {:cwd => '/etc/chef', :architecture => :x86_64}
+ allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with(
+ {:convert_boolean_return => true, :code => "$true"}.merge(guard_parameters)).and_return(Proc.new {})
+ resource.only_if("$true", guard_parameters)
+ end
+
+ it "should pass convert_boolean_return as true if it was specified as true in a guard parameter" do
+ guard_parameters = {:cwd => '/etc/chef', :convert_boolean_return => true, :architecture => :x86_64}
+ allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with(
+ {:convert_boolean_return => true, :code => "$true"}.merge(guard_parameters)).and_return(Proc.new {})
+ resource.only_if("$true", guard_parameters)
+ end
+
+ it "should pass convert_boolean_return as false if it was specified as true in a guard parameter" do
+ other_guard_parameters = {:cwd => '/etc/chef', :architecture => :x86_64}
+ parameters_with_boolean_disabled = other_guard_parameters.merge({:convert_boolean_return => false, :code => "$true"})
+ allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with(
+ parameters_with_boolean_disabled).and_return(Proc.new {})
+ resource.only_if("$true", parameters_with_boolean_disabled)
+ end
+ end
+
+ context "as a script running in Windows-based scripting language" do
let(:resource_instance) { @resource }
let(:resource_instance_name ) { @resource.command }
let(:resource_name) { :powershell_script }
@@ -44,5 +128,4 @@ describe Chef::Resource::PowershellScript do
it_should_behave_like "a Windows script resource"
end
-
end
diff --git a/spec/unit/resource/subversion_spec.rb b/spec/unit/resource/subversion_spec.rb
index 67593c5a7c..ae06ce665a 100644
--- a/spec/unit/resource/subversion_spec.rb
+++ b/spec/unit/resource/subversion_spec.rb
@@ -55,4 +55,9 @@ describe Chef::Resource::Subversion do
@svn.svn_arguments.should be_nil
end
+ it "hides password from custom exception message" do
+ @svn.svn_password "l33th4x0rpa$$w0rd"
+ e = @svn.customize_exception(Chef::Exceptions::Exec.new "Exception with password #{@svn.svn_password}")
+ e.message.include?(@svn.svn_password).should be_false
+ end
end
diff --git a/spec/unit/resource/windows_package_spec.rb b/spec/unit/resource/windows_package_spec.rb
new file mode 100644
index 0000000000..c9ef8d910c
--- /dev/null
+++ b/spec/unit/resource/windows_package_spec.rb
@@ -0,0 +1,74 @@
+#
+# 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 'spec_helper'
+
+describe Chef::Resource::WindowsPackage, "initialize", :windows_only do
+
+ let(:resource) { Chef::Resource::WindowsPackage.new("solitaire.msi") }
+
+ it "returns a Chef::Resource::WindowsPackage" do
+ expect(resource).to be_a_kind_of(Chef::Resource::WindowsPackage)
+ end
+
+ it "sets the resource_name to :windows_package" do
+ expect(resource.resource_name).to eql(:windows_package)
+ end
+
+ it "sets the provider to Chef::Provider::Package::Windows" do
+ expect(resource.provider).to eql(Chef::Provider::Package::Windows)
+ end
+
+ it "supports setting installer_type" do
+ resource.installer_type("msi")
+ expect(resource.installer_type).to eql("msi")
+ end
+
+ # String, Integer
+ [ "600", 600 ].each do |val|
+ it "supports setting a timeout as a #{val.class}" do
+ resource.timeout(val)
+ expect(resource.timeout).to eql(val)
+ end
+ end
+
+ # String, Integer, Array
+ [ "42", 42, [47, 48, 49] ].each do |val|
+ it "supports setting an alternate return value as a #{val.class}" do
+ resource.returns(val)
+ expect(resource.returns).to eql(val)
+ end
+ end
+
+ it "coverts a source to an absolute path" do
+ ::File.stub(:absolute_path).and_return("c:\\Files\\frost.msi")
+ resource.source("frost.msi")
+ expect(resource.source).to eql "c:\\Files\\frost.msi"
+ end
+
+ it "converts slashes to backslashes in the source path" do
+ ::File.stub(:absolute_path).and_return("c:\\frost.msi")
+ resource.source("c:/frost.msi")
+ expect(resource.source).to eql "c:\\frost.msi"
+ end
+
+ it "defaults source to the resource name" do
+ # it's a little late to stub out File.absolute_path
+ expect(resource.source).to include("solitaire.msi")
+ end
+end
diff --git a/spec/unit/resource_reporter_spec.rb b/spec/unit/resource_reporter_spec.rb
index 52fd44e692..d412234596 100644
--- a/spec/unit/resource_reporter_spec.rb
+++ b/spec/unit/resource_reporter_spec.rb
@@ -38,7 +38,6 @@ describe Chef::ResourceReporter do
@rest_client = double("Chef::REST (mock)")
@rest_client.stub(:post_rest).and_return(true)
@resource_reporter = Chef::ResourceReporter.new(@rest_client)
- @run_id = @resource_reporter.run_id
@new_resource = Chef::Resource::File.new("/tmp/a-file.txt")
@new_resource.cookbook_name = "monkey"
@cookbook_version = double("Cookbook::Version", :version => "1.2.3")
@@ -49,6 +48,7 @@ describe Chef::ResourceReporter do
@events = Chef::EventDispatch::Dispatcher.new
@run_context = Chef::RunContext.new(@node, {}, @events)
@run_status = Chef::RunStatus.new(@node, @events)
+ @run_id = @run_status.run_id
Time.stub(:now).and_return(@start_time, @end_time)
end
diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb
index e9a60c9861..60f3bdb8ea 100644
--- a/spec/unit/resource_spec.rb
+++ b/spec/unit/resource_spec.rb
@@ -344,7 +344,7 @@ describe Chef::Resource do
expected_keys = [ :allowed_actions, :params, :provider, :updated,
:updated_by_last_action, :before, :supports,
:noop, :ignore_failure, :name, :source_line,
- :action, :retries, :retry_delay, :elapsed_time]
+ :action, :retries, :retry_delay, :elapsed_time, :guard_interpreter]
(hash.keys - expected_keys).should == []
(expected_keys - hash.keys).should == []
hash[:name].should eql("funk")
@@ -526,6 +526,28 @@ describe Chef::Resource do
snitch_var2.should be_false
end
+ describe "guard_interpreter attribute" do
+ let(:resource) { @resource }
+
+ it "should be set to :default by default" do
+ resource.guard_interpreter.should == :default
+ end
+
+ it "if set to :default should return :default when read" do
+ resource.guard_interpreter(:default)
+ resource.guard_interpreter.should == :default
+ end
+
+ it "should raise Chef::Exceptions::ValidationFailed on an attempt to set the guard_interpreter attribute to something other than a Symbol" do
+ expect { resource.guard_interpreter('command_dot_com') }.to raise_error(Chef::Exceptions::ValidationFailed)
+ end
+
+ it "should not raise an exception when setting the guard interpreter attribute to a Symbol" do
+ Chef::GuardInterpreter::ResourceGuardInterpreter.stub(:new).and_return(nil)
+ expect { resource.guard_interpreter(:command_dot_com) }.not_to raise_error
+ end
+ end
+
end
describe "should_skip?" do
diff --git a/spec/unit/rest_spec.rb b/spec/unit/rest_spec.rb
index a53b4c9507..a8eb1ac7db 100644
--- a/spec/unit/rest_spec.rb
+++ b/spec/unit/rest_spec.rb
@@ -59,17 +59,38 @@ describe Chef::REST do
let(:log_stringio) { StringIO.new }
+ let(:request_id) {"1234"}
+
let(:rest) do
Chef::REST::CookieJar.stub(:instance).and_return({})
+ Chef::RequestID.instance.stub(:request_id).and_return(request_id)
rest = Chef::REST.new(base_url, nil, nil)
Chef::REST::CookieJar.instance.clear
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}}
+
before(:each) do
Chef::Log.init(log_stringio)
end
+ it "should have content length validation middleware after compressor middleware" do
+ middlewares = rest.instance_variable_get(:@middlewares)
+ content_length = middlewares.find_index { |e| e.is_a? Chef::HTTP::ValidateContentLength }
+ decompressor = middlewares.find_index { |e| e.is_a? Chef::HTTP::Decompressor }
+
+ content_length.should_not be_nil
+ decompressor.should_not be_nil
+ (decompressor < content_length).should be_true
+ end
+
+ it "should allow the options hash to be frozen" do
+ options = {}.freeze
+ # should not raise any exception
+ Chef::REST.new(base_url, nil, nil, options)
+ end
describe "calling an HTTP verb on a path or absolute URL" do
it "adds a relative URL to the base url it was initialized with" do
@@ -82,7 +103,7 @@ describe Chef::REST do
it "makes a :GET request with the composed url object" do
rest.should_receive(:send_http_request).
- with(:GET, monkey_uri, STANDARD_READ_HEADERS, false).
+ with(:GET, monkey_uri, standard_read_headers, false).
and_return([1,2,3])
rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3])
rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
@@ -94,12 +115,9 @@ describe Chef::REST do
rest.get_rest("monkey", true)
end
- STANDARD_READ_HEADERS = {"Accept"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3"}
- 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"}
-
it "makes a :DELETE request with the composed url object" do
rest.should_receive(:send_http_request).
- with(:DELETE, monkey_uri, STANDARD_READ_HEADERS, false).
+ with(:DELETE, monkey_uri, standard_read_headers, false).
and_return([1,2,3])
rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3])
rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
@@ -108,7 +126,7 @@ describe Chef::REST do
it "makes a :POST request with the composed url object and data" do
rest.should_receive(:send_http_request).
- with(:POST, monkey_uri, STANDARD_WRITE_HEADERS, "\"data\"").
+ with(:POST, monkey_uri, standard_write_headers, "\"data\"").
and_return([1,2,3])
rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3])
rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
@@ -117,7 +135,7 @@ describe Chef::REST do
it "makes a :PUT request with the composed url object and data" do
rest.should_receive(:send_http_request).
- with(:PUT, monkey_uri, STANDARD_WRITE_HEADERS, "\"data\"").
+ with(:PUT, monkey_uri, standard_write_headers, "\"data\"").
and_return([1,2,3])
rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3])
rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
@@ -142,27 +160,27 @@ describe Chef::REST do
it 'calls the authn middleware' do
data = "\"secure data\""
- auth_headers = STANDARD_WRITE_HEADERS.merge({"auth_done"=>"yep"})
+ auth_headers = standard_write_headers.merge({"auth_done"=>"yep"})
rest.authenticator.should_receive(:handle_request).
- with(:POST, monkey_uri, STANDARD_WRITE_HEADERS, data).
+ with(:POST, monkey_uri, standard_write_headers, data).
and_return([:POST, monkey_uri, auth_headers, data])
rest.should_receive(:send_http_request).
with(:POST, monkey_uri, auth_headers, data).
and_return([1,2,3])
rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
- rest.raw_http_request(:POST, monkey_uri, STANDARD_WRITE_HEADERS, data)
+ rest.raw_http_request(:POST, monkey_uri, standard_write_headers, data)
end
it 'sets correct authn headers' do
data = "\"secure data\""
- method, uri, auth_headers, d = rest.authenticator.handle_request(:POST, monkey_uri, STANDARD_WRITE_HEADERS, data)
+ method, uri, auth_headers, d = rest.authenticator.handle_request(:POST, monkey_uri, standard_write_headers, data)
rest.should_receive(:send_http_request).
with(:POST, monkey_uri, auth_headers, data).
and_return([1,2,3])
rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
- rest.raw_http_request(:POST, monkey_uri, STANDARD_WRITE_HEADERS, data)
+ rest.raw_http_request(:POST, monkey_uri, standard_write_headers, data)
end
end
@@ -244,6 +262,7 @@ describe Chef::REST do
let(:rest) do
Net::HTTP.stub(:new).and_return(http_client)
Chef::REST::CookieJar.stub(:instance).and_return({})
+ Chef::RequestID.instance.stub(:request_id).and_return(request_id)
rest = Chef::REST.new(base_url, nil, nil)
Chef::REST::CookieJar.instance.clear
rest
@@ -254,6 +273,7 @@ describe Chef::REST do
'Accept' => 'application/json',
'X-Chef-Version' => Chef::VERSION,
'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
+ 'X-REMOTE-REQUEST-ID' => request_id
}
end
@@ -275,6 +295,7 @@ 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
}
end
@@ -287,6 +308,11 @@ describe Chef::REST do
rest.request(:GET, url, {})
end
+ it "should always include the X-Remote-Request-Id header" do
+ Net::HTTP::Get.should_receive(:new).with("/?foo=bar", base_headers).and_return(request_mock)
+ rest.request(:GET, url, {})
+ end
+
it "sets the user agent to chef-client" do
# XXX: must reset to default b/c knife changes the UA
Chef::REST::RESTRequest.user_agent = Chef::REST::RESTRequest::DEFAULT_UA
@@ -342,6 +368,7 @@ describe Chef::REST do
let(:rest) do
Net::HTTP.stub(:new).and_return(http_client)
Chef::REST::CookieJar.instance["#{url.host}:#{url.port}"] = "cookie monster"
+ Chef::RequestID.instance.stub(:request_id).and_return(request_id)
rest = Chef::REST.new(base_url, nil, nil)
rest
end
@@ -542,7 +569,20 @@ describe Chef::REST do
expected_headers = {'Accept' => "*/*",
'X-Chef-Version' => Chef::VERSION,
'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
- 'Host' => host_header}
+ 'Host' => host_header,
+ 'X-REMOTE-REQUEST-ID'=> request_id
+ }
+ Net::HTTP::Get.should_receive(:new).with("/?foo=bar", expected_headers).and_return(request_mock)
+ rest.streaming_request(url, {})
+ end
+
+ it "build a new HTTP GET request with the X-Remote-Request-Id header" do
+ expected_headers = {'Accept' => "*/*",
+ 'X-Chef-Version' => Chef::VERSION,
+ 'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
+ 'Host' => host_header,
+ 'X-REMOTE-REQUEST-ID'=> request_id
+ }
Net::HTTP::Get.should_receive(:new).with("/?foo=bar", expected_headers).and_return(request_mock)
rest.streaming_request(url, {})
end
diff --git a/spec/unit/run_context/cookbook_compiler_spec.rb b/spec/unit/run_context/cookbook_compiler_spec.rb
index 52f4772206..5c50c3dd4b 100644
--- a/spec/unit/run_context/cookbook_compiler_spec.rb
+++ b/spec/unit/run_context/cookbook_compiler_spec.rb
@@ -170,5 +170,17 @@ describe Chef::RunContext::CookbookCompiler do
:"circular-dep1",
:"test-with-circular-deps"]
end
+
+ it "determines if a cookbook is in the list of cookbooks reachable by dependency" do
+ node.run_list("test-with-deps::default", "test-with-deps::server")
+ compiler.cookbook_order.should == [:dependency1, :dependency2, :"test-with-deps"]
+ compiler.unreachable_cookbook?(:dependency1).should be_false
+ compiler.unreachable_cookbook?(:dependency2).should be_false
+ compiler.unreachable_cookbook?(:'test-with-deps').should be_false
+ compiler.unreachable_cookbook?(:'circular-dep1').should be_true
+ compiler.unreachable_cookbook?(:'circular-dep2').should be_true
+ end
+
+
end
end
diff --git a/spec/unit/run_context_spec.rb b/spec/unit/run_context_spec.rb
index 39b8a8a50d..813102527b 100644
--- a/spec/unit/run_context_spec.rb
+++ b/spec/unit/run_context_spec.rb
@@ -79,6 +79,13 @@ describe Chef::RunContext do
@node.include_attribute("test::george")
end
+ it "raises an error when attempting to include_recipe from a cookbook not reachable by run list or dependencies" do
+ @node.should_receive(:loaded_recipe).with(:ancient, "aliens")
+ lambda do
+ @run_context.include_recipe("ancient::aliens")
+ # In CHEF-5120, this becomes a Chef::Exceptions::MissingCookbookDependency error:
+ end.should raise_error(Chef::Exceptions::CookbookNotFound)
+ end
end
diff --git a/spec/unit/util/editor_spec.rb b/spec/unit/util/editor_spec.rb
new file mode 100644
index 0000000000..06370f7de0
--- /dev/null
+++ b/spec/unit/util/editor_spec.rb
@@ -0,0 +1,152 @@
+require 'spec_helper'
+require 'chef/util/editor'
+
+describe Chef::Util::Editor do
+ describe '#initialize' do
+ it 'takes an Enumerable of lines' do
+ editor = described_class.new(File.open(__FILE__))
+ expect(editor.lines).to be == IO.readlines(__FILE__)
+ end
+
+ it 'makes a copy of an Array' do
+ array = Array.new
+ editor = described_class.new(array)
+ expect(editor.lines).to_not be(array)
+ end
+ end
+
+ subject(:editor) { described_class.new(input_lines) }
+ let(:input_lines) { ['one', 'two', 'two', 'three'] }
+
+ describe '#append_line_after' do
+ context 'when there is no match' do
+ subject(:execute) { editor.append_line_after('missing', 'new') }
+
+ it('returns the number of added lines') { should be == 0 }
+ it 'does not add any lines' do
+ expect { execute }.to_not change { editor.lines }
+ end
+ end
+
+ context 'when there is a match' do
+ subject(:execute) { editor.append_line_after('two', 'new') }
+
+ it('returns the number of added lines') { should be == 2 }
+ it 'adds a line after each match' do
+ execute
+ expect(editor.lines).to be == ['one', 'two', 'new', 'two', 'new', 'three']
+ end
+ end
+
+ it 'matches a Regexp' do
+ expect(editor.append_line_after(/^ee/, 'new')).to be == 0
+ expect(editor.append_line_after(/ee$/, 'new')).to be == 1
+ end
+ end
+
+ describe '#append_line_if_missing' do
+ context 'when there is no match' do
+ subject(:execute) { editor.append_line_if_missing('missing', 'new') }
+
+ it('returns the number of added lines') { should be == 1 }
+ it 'adds a line to the end' do
+ execute
+ expect(editor.lines).to be == ['one', 'two', 'two', 'three', 'new']
+ end
+ end
+
+ context 'when there is a match' do
+ subject(:execute) { editor.append_line_if_missing('one', 'new') }
+
+ it('returns the number of added lines') { should be == 0 }
+ it 'does not add any lines' do
+ expect { execute }.to_not change { editor.lines }
+ end
+ end
+
+ it 'matches a Regexp' do
+ expect(editor.append_line_if_missing(/ee$/, 'new')).to be == 0
+ expect(editor.append_line_if_missing(/^ee/, 'new')).to be == 1
+ end
+ end
+
+ describe '#remove_lines' do
+ context 'when there is no match' do
+ subject(:execute) { editor.remove_lines('missing') }
+
+ it('returns the number of removed lines') { should be == 0 }
+ it 'does not remove any lines' do
+ expect { execute }.to_not change { editor.lines }
+ end
+ end
+
+ context 'when there is a match' do
+ subject(:execute) { editor.remove_lines('two') }
+
+ it('returns the number of removed lines') { should be == 2 }
+ it 'removes the matching lines' do
+ execute
+ expect(editor.lines).to be == ['one', 'three']
+ end
+ end
+
+ it 'matches a Regexp' do
+ expect(editor.remove_lines(/^ee/)).to be == 0
+ expect(editor.remove_lines(/ee$/)).to be == 1
+ end
+ end
+
+ describe '#replace' do
+ context 'when there is no match' do
+ subject(:execute) { editor.replace('missing', 'new') }
+
+ it('returns the number of changed lines') { should be == 0 }
+ it 'does not change any lines' do
+ expect { execute }.to_not change { editor.lines }
+ end
+ end
+
+ context 'when there is a match' do
+ subject(:execute) { editor.replace('two', 'new') }
+
+ it('returns the number of changed lines') { should be == 2 }
+ it 'replaces the matching portions' do
+ execute
+ expect(editor.lines).to be == ['one', 'new', 'new', 'three']
+ end
+ end
+
+ it 'matches a Regexp' do
+ expect(editor.replace(/^ee/, 'new')).to be == 0
+ expect(editor.replace(/ee$/, 'new')).to be == 1
+ expect(editor.lines).to be == ['one', 'two', 'two', 'thrnew']
+ end
+ end
+
+ describe '#replace_lines' do
+ context 'when there is no match' do
+ subject(:execute) { editor.replace_lines('missing', 'new') }
+
+ it('returns the number of changed lines') { should be == 0 }
+ it 'does not change any lines' do
+ expect { execute }.to_not change { editor.lines }
+ end
+ end
+
+ context 'when there is a match' do
+ subject(:execute) { editor.replace_lines('two', 'new') }
+
+ it('returns the number of replaced lines') { should be == 2 }
+ it 'replaces the matching line' do
+ execute
+ expect(editor.lines).to be == ['one', 'new', 'new', 'three']
+ end
+ end
+
+ it 'matches a Regexp' do
+ expect(editor.replace_lines(/^ee/, 'new')).to be == 0
+ expect(editor.replace_lines(/ee$/, 'new')).to be == 1
+ expect(editor.lines).to be == ['one', 'two', 'two', 'new']
+ end
+ end
+end
diff --git a/spec/unit/util/file_edit_spec.rb b/spec/unit/util/file_edit_spec.rb
index d1d87a6bda..139b29d9ce 100644
--- a/spec/unit/util/file_edit_spec.rb
+++ b/spec/unit/util/file_edit_spec.rb
@@ -17,6 +17,7 @@
#
require 'spec_helper'
+require 'tempfile'
describe Chef::Util::FileEdit do
@@ -80,6 +81,17 @@ new line inserted
EOF
end
+ let(:append_twice) do
+ <<-EOF
+127.0.0.1 localhost
+255.255.255.255 broadcasthost
+::1 localhost
+fe80::1%lo0 localhost
+once
+twice
+ EOF
+ end
+
let(:target_file) do
f = Tempfile.open('file_edit_spec')
f.write(starting_content)
@@ -104,7 +116,7 @@ new line inserted
# CHEF-5018: people have monkey patched this and it has accidentally been broken
it "should read the contents into memory as an array" do
- expect(fedit.send(:contents)).to be_instance_of(Array)
+ expect(fedit.send(:editor).lines).to be_instance_of(Array)
end
end
@@ -123,18 +135,21 @@ new line inserted
describe "search_file_replace" do
it "should accept regex passed in as a string (not Regexp object) and replace the match if there is one" do
fedit.search_file_replace("localhost", "replacement")
+ fedit.unwritten_changes?.should be_true
fedit.write_file
expect(edited_file_contents).to eq(localhost_replaced)
end
it "should accept regex passed in as a Regexp object and replace the match if there is one" do
fedit.search_file_replace(/localhost/, "replacement")
+ fedit.unwritten_changes?.should be_true
fedit.write_file
expect(edited_file_contents).to eq(localhost_replaced)
end
it "should do nothing if there isn't a match" do
fedit.search_file_replace(/pattern/, "replacement")
+ fedit.unwritten_changes?.should be_false
fedit.write_file
expect(edited_file_contents).to eq(starting_content)
end
@@ -143,6 +158,7 @@ new line inserted
describe "search_file_replace_line" do
it "should search for match and replace the whole line" do
fedit.search_file_replace_line(/localhost/, "replacement line")
+ fedit.unwritten_changes?.should be_true
fedit.write_file
expect(edited_file_contents).to eq(localhost_line_replaced)
end
@@ -151,6 +167,7 @@ new line inserted
describe "search_file_delete" do
it "should search for match and delete the match" do
fedit.search_file_delete(/localhost/)
+ fedit.unwritten_changes?.should be_true
fedit.write_file
expect(edited_file_contents).to eq(localhost_deleted)
end
@@ -159,6 +176,7 @@ new line inserted
describe "search_file_delete_line" do
it "should search for match and delete the matching line" do
fedit.search_file_delete_line(/localhost/)
+ fedit.unwritten_changes?.should be_true
fedit.write_file
expect(edited_file_contents).to eq(localhost_line_deleted)
end
@@ -167,6 +185,7 @@ new line inserted
describe "insert_line_after_match" do
it "should search for match and insert the given line after the matching line" do
fedit.insert_line_after_match(/localhost/, "new line inserted")
+ fedit.unwritten_changes?.should be_true
fedit.write_file
expect(edited_file_contents).to eq(append_after_all_localhost)
end
@@ -175,14 +194,31 @@ new line inserted
describe "insert_line_if_no_match" do
it "should search for match and insert the given line if no line match" do
fedit.insert_line_if_no_match(/pattern/, "new line inserted")
+ fedit.unwritten_changes?.should be_true
fedit.write_file
expect(edited_file_contents).to eq(append_after_content)
end
it "should do nothing if there is a match" do
fedit.insert_line_if_no_match(/localhost/, "replacement")
+ fedit.unwritten_changes?.should be_false
fedit.write_file
expect(edited_file_contents).to eq(starting_content)
end
+
+ it "should work more than once" do
+ fedit.insert_line_if_no_match(/missing/, "once")
+ fedit.insert_line_if_no_match(/missing/, "twice")
+ fedit.write_file
+ expect(edited_file_contents).to eq(append_twice)
+ end
+ end
+
+ describe "file_edited" do
+ it "should return true if a file got edited" do
+ fedit.insert_line_if_no_match(/pattern/, "new line inserted")
+ fedit.write_file
+ expect(fedit.file_edited?).to be_true
+ end
end
end