# # Author:: Adam Jacob () # Author:: Tim Hinderliter () # Author:: Daniel DeLeo () # Copyright:: Copyright (c) 2008, 2011 Opscode, 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::Knife::UI do before do @out, @err, @in = StringIO.new, StringIO.new, StringIO.new @config = {} @ui = Chef::Knife::UI.new(@out, @err, @in, @config) end describe "edit" do ruby_for_json = { 'foo' => 'bar' } json_from_ruby = "{\n \"foo\": \"bar\"\n}" json_from_editor = "{\n \"bar\": \"foo\"\n}" ruby_from_editor = { 'bar' => 'foo' } my_editor = "veeeye" temp_path = "/tmp/bar/baz" let(:subject) { @ui.edit_data(ruby_for_json, parse_output) } let(:parse_output) { false } context "when editing is disabled" do before do @ui.config[:disable_editing] = true stub_const("Tempfile", double) # Tempfiles should never be invoked end context "when parse_output is false" do it "returns pretty json string" do expect(subject).to eql(json_from_ruby) end end context "when parse_output is true" do let(:parse_output) { true } it "returns a ruby object" do expect(subject).to eql(ruby_for_json) end end end context "when editing is enabled" do before do @ui.config[:disable_editing] = false @ui.config[:editor] = my_editor @mock = mock('Tempfile') @mock.should_receive(:sync=).with(true) @mock.should_receive(:puts).with(json_from_ruby) @mock.should_receive(:close) @mock.should_receive(:path).at_least(:once).and_return(temp_path) Tempfile.should_receive(:open).with([ 'knife-edit-', '.json' ]).and_yield(@mock) end context "and the editor works" do before do @ui.should_receive(:system).with("#{my_editor} #{temp_path}").and_return(true) IO.should_receive(:read).with(temp_path).and_return(json_from_editor) end context "when parse_output is false" do it "returns an edited pretty json string" do expect(subject).to eql(json_from_editor) end end context "when parse_output is true" do let(:parse_output) { true } it "returns an edited ruby object" do expect(subject).to eql(ruby_from_editor) end end end context "when running the editor fails with nil" do before do @ui.should_receive(:system).with("#{my_editor} #{temp_path}").and_return(nil) IO.should_not_receive(:read) end it "throws an exception" do expect{ subject }.to raise_error(RuntimeError) end end context "when running the editor fails with false" do before do @ui.should_receive(:system).with("#{my_editor} #{temp_path}").and_return(false) IO.should_not_receive(:read) end it "throws an exception" do expect{ subject }.to raise_error(RuntimeError) end end end context "when editing and not stubbing Tempfile (semi-functional test)" do before do @ui.config[:disable_editing] = false @ui.config[:editor] = my_editor @tempfile = Tempfile.new([ 'knife-edit-', '.json' ]) Tempfile.should_receive(:open).with([ 'knife-edit-', '.json' ]).and_yield(@tempfile) end context "and the editor works" do before do @ui.should_receive(:system).with("#{my_editor} #{@tempfile.path}").and_return(true) IO.should_receive(:read).with(@tempfile.path).and_return(json_from_editor) end context "when parse_output is false" do it "returns an edited pretty json string" do expect(subject).to eql(json_from_editor) end it "the tempfile should have mode 0600", :unix_only do # XXX: this looks odd because we're really testing Tempfile.new here expect(File.stat(@tempfile.path).mode & 0777).to eql(0600) expect(subject).to eql(json_from_editor) end end context "when parse_output is true" do let(:parse_output) { true } it "returns an edited ruby object" do expect(subject).to eql(ruby_from_editor) end it "the tempfile should have mode 0600", :unix_only do # XXX: this looks odd because we're really testing Tempfile.new here expect(File.stat(@tempfile.path).mode & 0777).to eql(0600) expect(subject).to eql(ruby_from_editor) end end end end end describe "format_list_for_display" do it "should print the full hash if --with-uri is true" do @ui.config[:with_uri] = true @ui.format_list_for_display({ :marcy => :playground }).should == { :marcy => :playground } end it "should print only the keys if --with-uri is false" do @ui.config[:with_uri] = false @ui.format_list_for_display({ :marcy => :playground }).should == [ :marcy ] end end describe "format_for_display" do it "should return the raw data" do input = { :gi => :go } @ui.format_for_display(input).should == input end describe "with --attribute passed" do it "should return the deeply nested attribute" do input = { "gi" => { "go" => "ge" } } @ui.config[:attribute] = "gi.go" @ui.format_for_display(input).should == { "gi.go" => "ge" } end end describe "with --run-list passed" do it "should return the run list" do input = Chef::Node.new input.run_list("role[monkey]", "role[churchmouse]") @ui.config[:run_list] = true response = @ui.format_for_display(input) response["run_list"][0].should == "role[monkey]" response["run_list"][1].should == "role[churchmouse]" end end end describe "format_cookbook_list_for_display" do before(:each) do @item = { "cookbook_name" => { "url" => "http://url/cookbooks/cookbook", "versions" => [ { "version" => "3.0.0", "url" => "http://url/cookbooks/3.0.0" }, { "version" => "2.0.0", "url" => "http://url/cookbooks/2.0.0" }, { "version" => "1.0.0", "url" => "http://url/cookbooks/1.0.0" } ] } } end it "should return an array of the cookbooks with versions" do expected_response = [ "cookbook_name 3.0.0 2.0.0 1.0.0" ] response = @ui.format_cookbook_list_for_display(@item) response.should == expected_response end describe "with --with-uri" do it "should return the URIs" do response = { "cookbook_name"=>{ "1.0.0" => "http://url/cookbooks/1.0.0", "2.0.0" => "http://url/cookbooks/2.0.0", "3.0.0" => "http://url/cookbooks/3.0.0"} } @ui.config[:with_uri] = true @ui.format_cookbook_list_for_display(@item).should == response end end 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") end it "should return true if you answer Y" do @ui.stdin.stub!(:readline).and_return("Y") @ui.confirm(@question).should == true end it "should return true if you answer y" do @ui.stdin.stub!(:readline).and_return("y") @ui.confirm(@question).should == true 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 } 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 } end describe "with --y or --yes passed" do it "should return true" do @ui.config[:yes] = true @ui.confirm(@question).should == true 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 end