diff options
author | tylercloke <tylercloke@gmail.com> | 2015-05-27 14:32:24 -0700 |
---|---|---|
committer | tylercloke <tylercloke@gmail.com> | 2015-06-05 10:38:48 -0700 |
commit | 9b9d376f2e86cd2738c2c2928f77bf48f1dd20ee (patch) | |
tree | 226227e7e32f421a0dde0f1ad2046059eff449c3 /spec/unit | |
parent | 20f7e1c78c55d2a16d5033bc2bbe5904d225adb0 (diff) | |
download | chef-9b9d376f2e86cd2738c2c2928f77bf48f1dd20ee.tar.gz |
Added versioned Chef Object API support code and repaired Chef::User.create.
Also added Chef::User.create V1 API support. Chef::User.create will attempt to use V1 of the server API, and if it fails, it will fall back to V0.
Diffstat (limited to 'spec/unit')
-rw-r--r-- | spec/unit/formatters/error_inspectors/api_error_formatting_spec.rb | 22 | ||||
-rw-r--r-- | spec/unit/http/authenticator_spec.rb | 13 | ||||
-rw-r--r-- | spec/unit/knife/user_create_spec.rb | 243 | ||||
-rw-r--r-- | spec/unit/knife_spec.rb | 14 | ||||
-rw-r--r-- | spec/unit/rest_spec.rb | 10 | ||||
-rw-r--r-- | spec/unit/user_spec.rb | 411 |
6 files changed, 567 insertions, 146 deletions
diff --git a/spec/unit/formatters/error_inspectors/api_error_formatting_spec.rb b/spec/unit/formatters/error_inspectors/api_error_formatting_spec.rb index 4b3b8bff44..b8c2de2b8b 100644 --- a/spec/unit/formatters/error_inspectors/api_error_formatting_spec.rb +++ b/spec/unit/formatters/error_inspectors/api_error_formatting_spec.rb @@ -29,19 +29,21 @@ describe Chef::Formatters::APIErrorFormatting do context "when describe_406_error is called" do - context "when response.body['error'] == 'invalid-x-ops-server-api-version'" do + context "when response['x-ops-server-api-version'] exists" do let(:min_version) { "2" } let(:max_version) { "5" } + let(:request_version) { "30" } let(:return_hash) { { - "error" => "invalid-x-ops-server-api-version", "min_version" => min_version, - "max_version" => max_version + "max_version" => max_version, + "request_version" => request_version } } before do - allow(Chef::JSONCompat).to receive(:from_json).and_return(return_hash) + # mock out the header + allow(response).to receive(:[]).with('x-ops-server-api-version').and_return(Chef::JSONCompat.to_json(return_hash)) end it "prints an error about client and server API version incompatibility with a min API version" do @@ -53,17 +55,17 @@ describe Chef::Formatters::APIErrorFormatting do expect(error_description).to receive(:section).with("Incompatible server API version:",/a max API version of #{max_version}/) class_instance.describe_406_error(error_description, response) end + + it "prints an error describing the request API version" do + expect(error_description).to receive(:section).with("Incompatible server API version:",/a request with an API version of #{request_version}/) + class_instance.describe_406_error(error_description, response) + end end context "when response.body['error'] != 'invalid-x-ops-server-api-version'" do - let(:return_hash) { - { - "error" => "some-other-error" - } - } before do - allow(Chef::JSONCompat).to receive(:from_json).and_return(return_hash) + allow(response).to receive(:[]).with('x-ops-server-api-version').and_return(nil) end it "forwards the error_description to describe_http_error" do diff --git a/spec/unit/http/authenticator_spec.rb b/spec/unit/http/authenticator_spec.rb index 82d38d6c2b..48bbdcf76c 100644 --- a/spec/unit/http/authenticator_spec.rb +++ b/spec/unit/http/authenticator_spec.rb @@ -32,10 +32,19 @@ describe Chef::HTTP::Authenticator do context "when handle_request is called" do shared_examples_for "merging the server API version into the headers" do - it "merges X-Ops-Server-API-Version into the headers" do + it "merges the default version of X-Ops-Server-API-Version into the headers" do # headers returned expect(class_instance.handle_request(method, url, headers, data)[2]). - to include({'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::SERVER_API_VERSION}) + to include({'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION}) + end + + context "when api_version is set to something other than the default" do + let(:class_instance) { Chef::HTTP::Authenticator.new({:api_version => '-10'}) } + + it "merges the requested version of X-Ops-Server-API-Version into the headers" do + expect(class_instance.handle_request(method, url, headers, data)[2]). + to include({'X-Ops-Server-API-Version' => '-10'}) + end end end diff --git a/spec/unit/knife/user_create_spec.rb b/spec/unit/knife/user_create_spec.rb index ad8821cd0e..dc2293396c 100644 --- a/spec/unit/knife/user_create_spec.rb +++ b/spec/unit/knife/user_create_spec.rb @@ -1,6 +1,7 @@ # -# Author:: Steven Danna (<steve@opscode.com>) -# Copyright:: Copyright (c) 2012 Opscode, Inc. +# Author:: Steven Danna (<steve@chef.io>) +# Author:: Tyler Cloke (<tyler@chef.io>) +# Copyright:: Copyright (c) 2015 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,68 +22,214 @@ require 'spec_helper' Chef::Knife::UserCreate.load_deps describe Chef::Knife::UserCreate do - before(:each) do - @knife = Chef::Knife::UserCreate.new + let(:knife) { Chef::Knife::UserCreate.new } + before(:each) do @stdout = StringIO.new @stderr = StringIO.new - allow(@knife.ui).to receive(:stdout).and_return(@stdout) - allow(@knife.ui).to receive(:stderr).and_return(@stderr) - - @knife.name_args = [ 'a_user' ] - @knife.config[:user_password] = "foobar" - @user = Chef::User.new - @user.name "a_user" - @user_with_private_key = Chef::User.new - @user_with_private_key.name "a_user" - @user_with_private_key.private_key 'private_key' - allow(@user).to receive(:create).and_return(@user_with_private_key) - allow(Chef::User).to receive(:new).and_return(@user) - allow(Chef::User).to receive(:from_hash).and_return(@user) - allow(@knife).to receive(:edit_data).and_return(@user.to_hash) + allow(knife.ui).to receive(:stdout).and_return(@stdout) + allow(knife.ui).to receive(:stderr).and_return(@stderr) end - it "creates a new user" do - expect(Chef::User).to receive(:new).and_return(@user) - expect(@user).to receive(:create) - @knife.run - expect(@stderr.string).to match /created user.+a_user/i + shared_examples_for "mandatory field missing" do + context "when field is nil" do + before do + knife.name_args = name_args + end + + it "exits 1" do + expect { knife.run }.to raise_error(SystemExit) + end + + it "prints the usage" do + expect(knife).to receive(:show_usage) + expect { knife.run }.to raise_error(SystemExit) + end + + it "prints a relevant error message" do + expect { knife.run }.to raise_error(SystemExit) + expect(@stderr.string).to match /You must specify a #{fieldname}/ + end + end end - it "sets the password" do - @knife.config[:user_password] = "a_password" - expect(@user).to receive(:password).with("a_password") - @knife.run + context "when USERNAME isn't specified" do + it_should_behave_like "mandatory field missing" do + let(:name_args) { [] } + let(:fieldname) { 'username' } + end end - it "exits with an error if password is blank" do - @knife.config[:user_password] = '' - expect { @knife.run }.to raise_error SystemExit - expect(@stderr.string).to match /You must specify a non-blank password/ + context "when DISPLAY_NAME isn't specified" do + it_should_behave_like "mandatory field missing" do + let(:name_args) { ['some_user'] } + let(:fieldname) { 'display name' } + end end - it "sets the user name" do - expect(@user).to receive(:name).with("a_user") - @knife.run + context "when FIRST_NAME isn't specified" do + it_should_behave_like "mandatory field missing" do + let(:name_args) { ['some_user', 'some_display_name'] } + let(:fieldname) { 'first name' } + end end - it "sets the public key if given" do - @knife.config[:user_key] = "/a/filename" - allow(File).to receive(:read).with(File.expand_path("/a/filename")).and_return("a_key") - expect(@user).to receive(:public_key).with("a_key") - @knife.run + context "when LAST_NAME isn't specified" do + it_should_behave_like "mandatory field missing" do + let(:name_args) { ['some_user', 'some_display_name', 'some_first_name'] } + let(:fieldname) { 'last name' } + end end - it "allows you to edit the data" do - expect(@knife).to receive(:edit_data).with(@user) - @knife.run + context "when EMAIL isn't specified" do + it_should_behave_like "mandatory field missing" do + let(:name_args) { ['some_user', 'some_display_name', 'some_first_name', 'some_last_name'] } + let(:fieldname) { 'email' } + end end - it "writes the private key to a file when --file is specified" do - @knife.config[:file] = "/tmp/a_file" - filehandle = double("filehandle") - expect(filehandle).to receive(:print).with('private_key') - expect(File).to receive(:open).with("/tmp/a_file", "w").and_yield(filehandle) - @knife.run + context "when PASSWORD isn't specified" do + it_should_behave_like "mandatory field missing" do + let(:name_args) { ['some_user', 'some_display_name', 'some_first_name', 'some_last_name', 'some_email'] } + let(:fieldname) { 'password' } + end end + + context "when all mandatory fields are validly specified" do + before do + knife.name_args = ['some_user', 'some_display_name', 'some_first_name', 'some_last_name', 'some_email', 'some_password'] + allow(knife).to receive(:edit_data).and_return(knife.user.to_hash) + allow(knife).to receive(:create_user_from_hash).and_return(knife.user) + end + + before(:each) do + # reset the user field every run + knife.user_field = nil + end + + it "sets all the mandatory fields" do + knife.run + expect(knife.user.username).to eq('some_user') + expect(knife.user.display_name).to eq('some_display_name') + expect(knife.user.first_name).to eq('some_first_name') + expect(knife.user.last_name).to eq('some_last_name') + expect(knife.user.email).to eq('some_email') + expect(knife.user.password).to eq('some_password') + end + + context "when user_key and no_key are passed" do + before do + knife.config[:user_key] = "some_key" + knife.config[:no_key] = true + end + it "prints the usage" do + expect(knife).to receive(:show_usage) + expect { knife.run }.to raise_error(SystemExit) + end + + it "prints a relevant error message" do + expect { knife.run }.to raise_error(SystemExit) + expect(@stderr.string).to match /You cannot pass --user-key and --no-key/ + end + end + + context "when user_key and no_key are passed" do + before do + knife.config[:user_key] = "some_key" + knife.config[:no_key] = true + end + it "prints the usage" do + expect(knife).to receive(:show_usage) + expect { knife.run }.to raise_error(SystemExit) + end + + it "prints a relevant error message" do + expect { knife.run }.to raise_error(SystemExit) + expect(@stderr.string).to match /You cannot pass --user-key and --no-key/ + end + end + + context "when --admin is passed" do + before do + knife.config[:admin] = true + end + + it "sets the admin field on the user to true" do + knife.run + expect(knife.user.admin).to be_truthy + end + end + + context "when --admin is not passed" do + it "does not set the admin field to true" do + knife.run + expect(knife.user.admin).to be_falsey + end + end + + context "when --no-key is passed" do + before do + knife.config[:no_key] = true + end + + it "does not set user.create_key" do + knife.run + expect(knife.user.create_key).to be_falsey + end + end + + context "when --no-key is not passed" do + it "sets user.create_key to true" do + knife.run + expect(knife.user.create_key).to be_truthy + end + end + + context "when --user-key is passed" do + before do + knife.config[:user_key] = 'some_key' + allow(File).to receive(:read).and_return('some_key') + allow(File).to receive(:expand_path) + end + + it "sets user.public_key" do + knife.run + expect(knife.user.public_key).to eq('some_key') + end + end + + context "when --user-key is not passed" do + it "does not set user.public_key" do + knife.run + expect(knife.user.public_key).to be_nil + end + end + + context "when a private_key is returned" do + before do + allow(knife).to receive(:create_user_from_hash).and_return(Chef::User.from_hash(knife.user.to_hash.merge({"private_key" => "some_private_key"}))) + end + + context "when --file is passed" do + before do + knife.config[:file] = '/some/path' + end + + it "creates a new file of the path passed" do + filehandle = double('filehandle') + expect(filehandle).to receive(:print).with('some_private_key') + expect(File).to receive(:open).with('/some/path', 'w').and_yield(filehandle) + knife.run + end + end + + context "when --file is not passed" do + it "prints the private key to stdout" do + expect(knife.ui).to receive(:msg).with('some_private_key') + knife.run + end + end + end + + end # when all mandatory fields are validly specified end diff --git a/spec/unit/knife_spec.rb b/spec/unit/knife_spec.rb index 3e8a43eaf5..022256f370 100644 --- a/spec/unit/knife_spec.rb +++ b/spec/unit/knife_spec.rb @@ -140,7 +140,7 @@ describe Chef::Knife do 'X-Chef-Version' => Chef::VERSION, "Host"=>"api.opscode.piab", "X-REMOTE-REQUEST-ID"=>request_id, - 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::SERVER_API_VERSION}} + 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION}} let(:request_id) {"1234"} @@ -399,11 +399,17 @@ describe Chef::Knife do it "formats 406s (non-supported API version error) nicely" do response = Net::HTTPNotAcceptable.new("1.1", "406", "Not Acceptable") response.instance_variable_set(:@read, true) # I hate you, net/http. - allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(:error => "sad trombone", :min_version => "0", :max_version => "1")) + + # set the header + response["x-ops-server-api-version"] = Chef::JSONCompat.to_json(:min_version => "0", :max_version => "1", :request_version => "10000000") + + allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(:error => "sad trombone")) allow(knife).to receive(:run).and_raise(Net::HTTPServerException.new("406 Not Acceptable", response)) + knife.run_with_pretty_exceptions - expect(stderr.string).to include('The version of Chef that Knife is using is not supported by the Chef server you sent this request to') - expect(stderr.string).to include("This version of Chef requires a server API version of #{Chef::HTTP::Authenticator::SERVER_API_VERSION}") + expect(stderr.string).to include('The request that Knife sent was using API version 10000000') + expect(stderr.string).to include('The Chef server you sent the request to supports a min API verson of 0 and a max API version of 1') + expect(stderr.string).to include('Please either update your Chef client or server to be a compatible set') end it "formats 500s nicely" do diff --git a/spec/unit/rest_spec.rb b/spec/unit/rest_spec.rb index b4f8f336a9..3b04981610 100644 --- a/spec/unit/rest_spec.rb +++ b/spec/unit/rest_spec.rb @@ -69,8 +69,8 @@ describe Chef::REST do 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, 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::SERVER_API_VERSION}} - 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, 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::SERVER_API_VERSION}} + 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, 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION}} + 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, 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION}} before(:each) do Chef::Log.init(log_stringio) @@ -292,7 +292,7 @@ describe Chef::REST do 'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE, 'Host' => host_header, 'X-REMOTE-REQUEST-ID' => request_id, - 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::SERVER_API_VERSION + 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION } end @@ -575,7 +575,7 @@ describe Chef::REST do 'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE, 'Host' => host_header, 'X-REMOTE-REQUEST-ID'=> request_id, - 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::SERVER_API_VERSION + 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION } expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", expected_headers).and_return(request_mock) rest.streaming_request(url, {}) @@ -587,7 +587,7 @@ describe Chef::REST do 'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE, 'Host' => host_header, 'X-REMOTE-REQUEST-ID'=> request_id, - 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::SERVER_API_VERSION + 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION } expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", expected_headers).and_return(request_mock) rest.streaming_request(url, {}) diff --git a/spec/unit/user_spec.rb b/spec/unit/user_spec.rb index d451531b16..7380484409 100644 --- a/spec/unit/user_spec.rb +++ b/spec/unit/user_spec.rb @@ -26,98 +26,146 @@ describe Chef::User do @user = Chef::User.new end + shared_examples_for "string fields with no contraints" do + it "should let you set the public key" do + expect(@user.send(method, "some_string")).to eq("some_string") + end + + it "should return the current public key" do + @user.send(method, "some_string") + expect(@user.send(method)).to eq("some_string") + end + + it "should throw an ArgumentError if you feed it something lame" do + expect { @user.send(method, Hash.new) }.to raise_error(ArgumentError) + end + end + + shared_examples_for "boolean fields with no constraints" do + it "should let you set the admin bit" do + expect(@user.send(method, true)).to eq(true) + end + + it "should return the current admin value" do + @user.send(method, true) + expect(@user.send(method)).to eq(true) + end + + it "should throw an ArgumentError if you feed it anything but true or false" do + expect { @user.send(method, Hash.new) }.to raise_error(ArgumentError) + end + end + describe "initialize" do it "should be a Chef::User" do expect(@user).to be_a_kind_of(Chef::User) end end - describe "name" do - it "should let you set the name to a string" do - expect(@user.name("ops_master")).to eq("ops_master") + describe "username" do + it "should let you set the username to a string" do + expect(@user.username("ops_master")).to eq("ops_master") end - it "should return the current name" do - @user.name "ops_master" - expect(@user.name).to eq("ops_master") + it "should return the current username" do + @user.username "ops_master" + expect(@user.username).to eq("ops_master") end # It is not feasible to check all invalid characters. Here are a few # that we probably care about. it "should not accept invalid characters" do # capital letters - expect { @user.name "Bar" }.to raise_error(ArgumentError) + expect { @user.username "Bar" }.to raise_error(ArgumentError) # slashes - expect { @user.name "foo/bar" }.to raise_error(ArgumentError) + expect { @user.username "foo/bar" }.to raise_error(ArgumentError) # ? - expect { @user.name "foo?" }.to raise_error(ArgumentError) + expect { @user.username "foo?" }.to raise_error(ArgumentError) # & - expect { @user.name "foo&" }.to raise_error(ArgumentError) + expect { @user.username "foo&" }.to raise_error(ArgumentError) end it "should not accept spaces" do - expect { @user.name "ops master" }.to raise_error(ArgumentError) + expect { @user.username "ops master" }.to raise_error(ArgumentError) end it "should throw an ArgumentError if you feed it anything but a string" do - expect { @user.name Hash.new }.to raise_error(ArgumentError) + expect { @user.username Hash.new }.to raise_error(ArgumentError) end end - describe "admin" do - it "should let you set the admin bit" do - expect(@user.admin(true)).to eq(true) + describe "boolean fields" do + describe "admin" do + it_should_behave_like "boolean fields with no constraints" do + let(:method) { :admin } + end + + it "should default to false" do + expect(@user.admin).to eq(false) + end end - it "should return the current admin value" do - @user.admin true - expect(@user.admin).to eq(true) + describe "create_key" do + it_should_behave_like "boolean fields with no constraints" do + let(:method) { :create_key } + end end + end - it "should default to false" do - expect(@user.admin).to eq(false) + describe "string fields" do + describe "public_key" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :public_key } + end end - it "should throw an ArgumentError if you feed it anything but true or false" do - expect { @user.name Hash.new }.to raise_error(ArgumentError) + describe "private_key" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :private_key } + end end - end - describe "public_key" do - it "should let you set the public key" do - expect(@user.public_key("super public")).to eq("super public") + describe "display_name" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :display_name } + end end - it "should return the current public key" do - @user.public_key("super public") - expect(@user.public_key).to eq("super public") + describe "first_name" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :first_name } + end end - it "should throw an ArgumentError if you feed it something lame" do - expect { @user.public_key Hash.new }.to raise_error(ArgumentError) + describe "middle_name" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :middle_name } + end end - end - describe "private_key" do - it "should let you set the private key" do - expect(@user.private_key("super private")).to eq("super private") + describe "last_name" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :last_name } + end end - it "should return the private key" do - @user.private_key("super private") - expect(@user.private_key).to eq("super private") + describe "email" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :email } + end end - it "should throw an ArgumentError if you feed it something lame" do - expect { @user.private_key Hash.new }.to raise_error(ArgumentError) + describe "password" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :password } + end end end describe "when serializing to JSON" do before(:each) do - @user.name("black") - @user.public_key("crowes") + @user.username("black") @json = @user.to_json end @@ -125,18 +173,68 @@ describe Chef::User do expect(@json).to match(/^\{.+\}$/) end - it "includes the name value" do - expect(@json).to include(%q{"name":"black"}) - end - - it "includes the public key value" do - expect(@json).to include(%{"public_key":"crowes"}) + it "includes the username value" do + expect(@json).to include(%q{"username":"black"}) end it "includes the 'admin' flag" do expect(@json).to include(%q{"admin":false}) end + it "includes the display name when present" do + @user.display_name("get_displayed") + expect(@user.to_json).to include(%{"display_name":"get_displayed"}) + end + + it "does not include the display name if not present" do + expect(@json).not_to include("display_name") + end + + it "includes the first name when present" do + @user.first_name("char") + expect(@user.to_json).to include(%{"first_name":"char"}) + end + + it "does not include the first name if not present" do + expect(@json).not_to include("first_name") + end + + it "includes the middle name when present" do + @user.middle_name("man") + expect(@user.to_json).to include(%{"middle_name":"man"}) + end + + it "does not include the middle name if not present" do + expect(@json).not_to include("middle_name") + end + + it "includes the last name when present" do + @user.last_name("der") + expect(@user.to_json).to include(%{"last_name":"der"}) + end + + it "does not include the last name if not present" do + expect(@json).not_to include("last_name") + end + + it "includes the email when present" do + @user.email("charmander@pokemon.poke") + expect(@user.to_json).to include(%{"email":"charmander@pokemon.poke"}) + end + + it "does not include the email if not present" do + expect(@json).not_to include("email") + end + + it "includes the public key when present" do + @user.public_key("crowes") + expect(@user.to_json).to include(%{"public_key":"crowes"}) + end + + it "does not include the public key if not present" do + expect(@json).not_to include("public_key") + end + it "includes the private key when present" do @user.private_key("monkeypants") expect(@user.to_json).to include(%q{"private_key":"monkeypants"}) @@ -162,11 +260,19 @@ describe Chef::User do describe "when deserializing from JSON" do before(:each) do - user = { "name" => "mr_spinks", + user = { + "username" => "mr_spinks", + "admin" => true, + "display_name" => "displayed", + "first_name" => "char", + "middle_name" => "man", + "last_name" => "der", + "email" => "charmander@pokemon.poke", + "password" => "password", "public_key" => "turtles", "private_key" => "pandas", - "password" => "password", - "admin" => true } + "create_key" => true + } @user = Chef::User.from_json(Chef::JSONCompat.to_json(user)) end @@ -174,32 +280,192 @@ describe Chef::User do expect(@user).to be_a_kind_of(Chef::User) end - it "preserves the name" do - expect(@user.name).to eq("mr_spinks") - end - - it "preserves the public key" do - expect(@user.public_key).to eq("turtles") + it "preserves the username" do + expect(@user.username).to eq("mr_spinks") end it "preserves the admin status" do expect(@user.admin).to be_truthy end - it "includes the private key if present" do - expect(@user.private_key).to eq("pandas") + it "preserves the display name if present" do + expect(@user.display_name).to eq("displayed") + end + + it "preserves the first name if present" do + expect(@user.first_name).to eq("char") + end + + it "preserves the middle name if present" do + expect(@user.middle_name).to eq("man") + end + + it "preserves the last name if present" do + expect(@user.last_name).to eq("der") + end + + it "preserves the email if present" do + expect(@user.email).to eq("charmander@pokemon.poke") end it "includes the password if present" do expect(@user.password).to eq("password") end + it "preserves the public key if present" do + expect(@user.public_key).to eq("turtles") + end + + it "includes the private key if present" do + expect(@user.private_key).to eq("pandas") + end + + it "includes the create key status if present" do + expect(@user.create_key).to be_truthy + end end + describe "Versioned API Interactions" do + before (:each) do + @user = Chef::User.new + allow(@user).to receive(:chef_root_rest_v0).and_return(double('chef rest root v0 object')) + allow(@user).to receive(:chef_root_rest_v1).and_return(double('chef rest root v1 object')) + end + + describe "create" do + let(:payload) { + { + :username => "some_username", + :display_name => "some_display_name", + :first_name => "some_first_name", + :last_name => "some_last_name", + :email => "some_email", + :password => "some_password", + :admin => true + } + } + before do + @user.username "some_username" + @user.display_name "some_display_name" + @user.first_name "some_first_name" + @user.last_name "some_last_name" + @user.email "some_email" + @user.password "some_password" + @user.admin true + end + + shared_examples_for "create valid user" do + it "creates a new user via the API" do + expect(chef_rest_object).to receive(:post).with("users", payload).and_return({}) + @user.create + end + + it "creates a new user via the API with a public_key when it exists" do + @user.public_key "some_public_key" + expect(chef_rest_object).to receive(:post).with("users", payload.merge({:public_key => "some_public_key"})).and_return({}) + @user.create + end + + it "creates a new user via the API with a middle_name when it exists" do + @user.middle_name "some_middle_name" + expect(chef_rest_object).to receive(:post).with("users", payload.merge({:middle_name => "some_middle_name"})).and_return({}) + @user.create + end + end + + context "when server API V1 is valid on the Chef Server receiving the request" do + context "when create_key and public_key are both set" do + before do + @user.public_key "key" + @user.create_key true + end + it "rasies a Chef::Exceptions::InvalidUserAttribute" do + expect { @user.create }.to raise_error(Chef::Exceptions::InvalidUserAttribute) + end + end + + it_should_behave_like "create valid user" do + let(:chef_rest_object) { @user.chef_root_rest_v1 } + end + + it "creates a new user via the API with create_key == true when it exists" do + @user.create_key true + expect(@user.chef_root_rest_v1).to receive(:post).with("users", payload.merge({:create_key => true})).and_return({}) + @user.create + end + + context "when chef_key is returned by the server" do + let(:chef_key) { + { + "chef_key" => { + "public_key" => "some_public_key" + } + } + } + + it "puts the public key into the user returned by create" do + expect(@user.chef_root_rest_v1).to receive(:post).with("users", payload).and_return(payload.merge(chef_key)) + new_user = @user.create + expect(new_user.public_key).to eq("some_public_key") + end + + context "when private_key is returned in chef_key" do + let(:chef_key) { + { + "chef_key" => { + "public_key" => "some_public_key", + "private_key" => "some_private_key" + } + } + } + + it "puts the private key into the user returned by create" do + expect(@user.chef_root_rest_v1).to receive(:post).with("users", payload).and_return(payload.merge(chef_key)) + new_user = @user.create + expect(new_user.private_key).to eq("some_private_key") + end + end + + end # when chef_key is returned by the server + end # when server API V1 is valid on the Chef Server receiving the request + + context "when server API V1 is not valid on the Chef Server receiving the request" do + let(:response_406) { OpenStruct.new(:code => '406') } + let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) } + + before do + allow(@user.chef_root_rest_v1).to receive(:post).and_raise(exception_406) + end + + context "when the server does not support the min or max server API version that Chef::User supports" do + before do + allow(@user).to receive(:handle_version_http_exception).and_return(false) + end + + it "raises the original exception" do + expect{ @user.create }.to raise_error(exception_406) + end + end # when the server does not support the min or max server API version that Chef::User supports + + context "when the server supports API V0" do + before do + allow(@user).to receive(:handle_version_http_exception).and_return(true) + allow(@user.chef_root_rest_v1).to receive(:post).and_raise(exception_406) + end + + it_should_behave_like "create valid user" do + let(:chef_rest_object) { @user.chef_root_rest_v0 } + end + + end # when the server supports API V0 + end # when server API V1 is not valid on the Chef Server receiving the request + end # create + end # Versioned API Interactions + describe "API Interactions" do before (:each) do @user = Chef::User.new - @user.name "foobar" + @user.username "foobar" @http_client = double("Chef::REST mock") allow(Chef::REST).to receive(:new).and_return(@http_client) end @@ -214,41 +480,32 @@ describe Chef::User do end it "lists all clients on an OSC server" do - allow(@http_client).to receive(:get_rest).with("users").and_return(@osc_response) + allow(@http_client).to receive(:get).with("users").and_return(@osc_response) expect(Chef::User.list).to eq(@osc_response) end it "inflate all clients on an OSC server" do - allow(@http_client).to receive(:get_rest).with("users").and_return(@osc_response) + allow(@http_client).to receive(:get).with("users").and_return(@osc_response) expect(Chef::User.list(true)).to eq(@osc_inflated_response) end it "lists all clients on an OHC/OPC server" do - allow(@http_client).to receive(:get_rest).with("users").and_return(@ohc_response) + allow(@http_client).to receive(:get).with("users").and_return(@ohc_response) # We expect that Chef::User.list will give a consistent response # so OHC API responses should be transformed to OSC-style output. expect(Chef::User.list).to eq(@osc_response) end it "inflate all clients on an OHC/OPC server" do - allow(@http_client).to receive(:get_rest).with("users").and_return(@ohc_response) + allow(@http_client).to receive(:get).with("users").and_return(@ohc_response) expect(Chef::User.list(true)).to eq(@osc_inflated_response) end end - - describe "create" do - it "creates a new user via the API" do - @user.password "password" - expect(@http_client).to receive(:post_rest).with("users", {:name => "foobar", :admin => false, :password => "password"}).and_return({}) - @user.create - end - end - describe "read" do it "loads a named user from the API" do - expect(@http_client).to receive(:get_rest).with("users/foobar").and_return({"name" => "foobar", "admin" => true, "public_key" => "pubkey"}) + expect(@http_client).to receive(:get).with("users/foobar").and_return({"username" => "foobar", "admin" => true, "public_key" => "pubkey"}) user = Chef::User.load("foobar") - expect(user.name).to eq("foobar") + expect(user.username).to eq("foobar") expect(user.admin).to eq(true) expect(user.public_key).to eq("pubkey") end @@ -256,14 +513,14 @@ describe Chef::User do describe "update" do it "updates an existing user on via the API" do - expect(@http_client).to receive(:put_rest).with("users/foobar", {:name => "foobar", :admin => false}).and_return({}) + expect(@http_client).to receive(:put).with("users/foobar", {:username => "foobar", :admin => false}).and_return({}) @user.update end end describe "destroy" do it "deletes the specified user via the API" do - expect(@http_client).to receive(:delete_rest).with("users/foobar") + expect(@http_client).to receive(:delete).with("users/foobar") @user.destroy end end |