require 'spec_helper' require 'puppet/application/face_base' require 'tmpdir' class Puppet::Application::FaceBase::Basetest < Puppet::Application::FaceBase end describe Puppet::Application::FaceBase do let :app do app = Puppet::Application::FaceBase::Basetest.new allow(app.command_line).to receive(:subcommand_name).and_return('subcommand') allow(Puppet::Util::Log).to receive(:newdestination) app end after :each do app.class.clear_everything_for_tests end describe "#find_global_settings_argument" do it "should not match --ca to --ca-location" do option = double('ca option', :optparse_args => ["--ca"]) expect(Puppet.settings).to receive(:each).and_yield(:ca, option) expect(app.find_global_settings_argument("--ca-location")).to be_nil end end describe "#parse_options" do before :each do allow(app.command_line).to receive(:args).and_return(%w{}) end describe "with just an action" do before(:each) do # We have to stub Signal.trap to avoid a crazy mess where we take # over signal handling and make it impossible to cancel the test # suite run. # # It would be nice to fix this elsewhere, but it is actually hard to # capture this in rspec 2.5 and all. :( --daniel 2011-04-08 allow(Signal).to receive(:trap) allow(app.command_line).to receive(:args).and_return(%w{foo}) app.preinit app.parse_options end it "should set the face based on the type" do expect(app.face.name).to eq(:basetest) end it "should find the action" do expect(app.action).to be expect(app.action.name).to eq(:foo) end end it "should stop if the first thing found is not an action" do allow(app.command_line).to receive(:args).and_return(%w{banana count_args}) expect { app.run }.to exit_with(1) expect(@logs.map(&:message)).to eq(["'basetest' has no 'banana' action. See `puppet help basetest`."]) end it "should use the default action if not given any arguments" do allow(app.command_line).to receive(:args).and_return([]) action = double(:options => [], :render_as => nil) expect(Puppet::Face[:basetest, '0.0.1']).to receive(:get_default_action).and_return(action) allow(app).to receive(:main) app.run expect(app.action).to eq(action) expect(app.arguments).to eq([ { } ]) end it "should use the default action if not given a valid one" do allow(app.command_line).to receive(:args).and_return(%w{bar}) action = double(:options => [], :render_as => nil) expect(Puppet::Face[:basetest, '0.0.1']).to receive(:get_default_action).and_return(action) allow(app).to receive(:main) app.run expect(app.action).to eq(action) expect(app.arguments).to eq([ 'bar', { } ]) end it "should have no action if not given a valid one and there is no default action" do allow(app.command_line).to receive(:args).and_return(%w{bar}) expect(Puppet::Face[:basetest, '0.0.1']).to receive(:get_default_action).and_return(nil) allow(app).to receive(:main) expect { app.run }.to exit_with(1) expect(@logs.first.message).to match(/has no 'bar' action./) end [%w{something_I_cannot_do}, %w{something_I_cannot_do argument}].each do |input| it "should report unknown actions nicely" do allow(app.command_line).to receive(:args).and_return(input) expect(Puppet::Face[:basetest, '0.0.1']).to receive(:get_default_action).and_return(nil) allow(app).to receive(:main) expect { app.run }.to exit_with(1) expect(@logs.first.message).to match(/has no 'something_I_cannot_do' action/) end end [%w{something_I_cannot_do --unknown-option}, %w{something_I_cannot_do argument --unknown-option}].each do |input| it "should report unknown actions even if there are unknown options" do allow(app.command_line).to receive(:args).and_return(input) expect(Puppet::Face[:basetest, '0.0.1']).to receive(:get_default_action).and_return(nil) allow(app).to receive(:main) expect { app.run }.to exit_with(1) expect(@logs.first.message).to match(/has no 'something_I_cannot_do' action/) end end it "should report a sensible error when options with = fail" do allow(app.command_line).to receive(:args).and_return(%w{--action=bar foo}) expect { app.preinit; app.parse_options }. to raise_error(OptionParser::InvalidOption, /invalid option: --action/) end it "should fail if an action option is before the action" do allow(app.command_line).to receive(:args).and_return(%w{--action foo}) expect { app.preinit; app.parse_options }. to raise_error(OptionParser::InvalidOption, /invalid option: --action/) end it "should fail if an unknown option is before the action" do allow(app.command_line).to receive(:args).and_return(%w{--bar foo}) expect { app.preinit; app.parse_options }. to raise_error(OptionParser::InvalidOption, /invalid option: --bar/) end it "should fail if an unknown option is after the action" do allow(app.command_line).to receive(:args).and_return(%w{foo --bar}) expect { app.preinit; app.parse_options }. to raise_error(OptionParser::InvalidOption, /invalid option: --bar/) end it "should accept --bar as an argument to a mandatory option after action" do allow(app.command_line).to receive(:args).and_return(%w{foo --mandatory --bar}) app.preinit app.parse_options expect(app.action.name).to eq(:foo) expect(app.options).to eq({ :mandatory => "--bar" }) end it "should accept --bar as an argument to a mandatory option before action" do allow(app.command_line).to receive(:args).and_return(%w{--mandatory --bar foo}) app.preinit app.parse_options expect(app.action.name).to eq(:foo) expect(app.options).to eq({ :mandatory => "--bar" }) end it "should not skip when --foo=bar is given" do allow(app.command_line).to receive(:args).and_return(%w{--mandatory=bar --bar foo}) expect { app.preinit; app.parse_options }. to raise_error(OptionParser::InvalidOption, /invalid option: --bar/) end it "does not skip when a puppet global setting is given as one item" do allow(app.command_line).to receive(:args).and_return(%w{--confdir=/tmp/puppet foo}) app.preinit app.parse_options expect(app.action.name).to eq(:foo) expect(app.options).to eq({}) end it "does not skip when a puppet global setting is given as two items" do allow(app.command_line).to receive(:args).and_return(%w{--confdir /tmp/puppet foo}) app.preinit app.parse_options expect(app.action.name).to eq(:foo) expect(app.options).to eq({}) end it "should not add :debug to the application-level options" do allow(app.command_line).to receive(:args).and_return(%w{--confdir /tmp/puppet foo --debug}) app.preinit app.parse_options expect(app.action.name).to eq(:foo) expect(app.options).to eq({}) end it "should not add :verbose to the application-level options" do allow(app.command_line).to receive(:args).and_return(%w{--confdir /tmp/puppet foo --verbose}) app.preinit app.parse_options expect(app.action.name).to eq(:foo) expect(app.options).to eq({}) end { "boolean options before" => %w{--trace foo}, "boolean options after" => %w{foo --trace} }.each do |name, args| it "should accept global boolean settings #{name} the action" do allow(app.command_line).to receive(:args).and_return(args) Puppet.settings.initialize_global_settings(args) app.preinit app.parse_options expect(Puppet[:trace]).to be_truthy end end { "before" => %w{--syslogfacility user1 foo}, " after" => %w{foo --syslogfacility user1} }.each do |name, args| it "should accept global settings with arguments #{name} the action" do allow(app.command_line).to receive(:args).and_return(args) Puppet.settings.initialize_global_settings(args) app.preinit app.parse_options expect(Puppet[:syslogfacility]).to eq("user1") end end it "should handle application-level options" do allow(app.command_line).to receive(:args).and_return(%w{--verbose return_true}) app.preinit app.parse_options expect(app.face.name).to eq(:basetest) end end describe "#setup" do it "should remove the action name from the arguments" do allow(app.command_line).to receive(:args).and_return(%w{--mandatory --bar foo}) app.preinit app.parse_options app.setup expect(app.arguments).to eq([{ :mandatory => "--bar" }]) end it "should pass positional arguments" do myargs = %w{--mandatory --bar foo bar baz quux} allow(app.command_line).to receive(:args).and_return(myargs) app.preinit app.parse_options app.setup expect(app.arguments).to eq(['bar', 'baz', 'quux', { :mandatory => "--bar" }]) end end describe "#main" do before :each do allow(app).to receive(:puts) # don't dump text to screen. app.face = Puppet::Face[:basetest, '0.0.1'] app.action = app.face.get_action(:foo) app.arguments = ["myname", "myarg"] end it "should send the specified verb and name to the face" do expect(app.face).to receive(:foo).with(*app.arguments) expect { app.main }.to exit_with(0) end it "should lookup help when it cannot do anything else" do app.action = nil expect(Puppet::Face[:help, :current]).to receive(:help).with(:basetest) expect { app.main }.to exit_with(1) end it "should use its render method to render any result" do expect(app).to receive(:render).with(app.arguments.length + 1, ["myname", "myarg"]) expect { app.main }.to exit_with(0) end it "should issue a deprecation warning if the face is deprecated" do # since app is shared across examples, stub to avoid affecting shared context allow(app.face).to receive(:deprecated?).and_return(true) expect(app.face).to receive(:foo).with(*app.arguments) expect(Puppet).to receive(:deprecation_warning).with(/'puppet basetest' is deprecated/) expect { app.main }.to exit_with(0) end it "should not issue a deprecation warning if the face is not deprecated" do expect(Puppet).not_to receive(:deprecation_warning) # since app is shared across examples, stub to avoid affecting shared context allow(app.face).to receive(:deprecated?).and_return(false) expect(app.face).to receive(:foo).with(*app.arguments) expect { app.main }.to exit_with(0) end end describe "error reporting" do before :each do allow(app).to receive(:puts) # don't dump text to screen. app.render_as = :json app.face = Puppet::Face[:basetest, '0.0.1'] app.arguments = [{}] # we always have options in there... end it "should exit 0 when the action returns true" do app.action = app.face.get_action :return_true expect { app.main }.to exit_with(0) end it "should exit 0 when the action returns false" do app.action = app.face.get_action :return_false expect { app.main }.to exit_with(0) end it "should exit 0 when the action returns nil" do app.action = app.face.get_action :return_nil expect { app.main }.to exit_with(0) end it "should exit non-0 when the action raises" do app.action = app.face.get_action :return_raise expect { app.main }.not_to exit_with(0) end it "should use the exit code set by the action" do app.action = app.face.get_action :with_specific_exit_code expect { app.main }.to exit_with(5) end end describe "#render" do before :each do app.face = Puppet::Interface.new('basetest', '0.0.1') app.action = Puppet::Interface::Action.new(app.face, :foo) end context "default rendering" do before :each do app.setup end ["hello", 1, 1.0].each do |input| it "should just return a #{input.class.name}" do expect(app.render(input, {})).to eq(input) end end [[1, 2], ["one"], [{ 1 => 1 }]].each do |input| it "should render Array as one item per line" do expect(app.render(input, {})).to eq(input.collect { |item| item.to_s + "\n" }.join('')) end end it "should render a non-trivially-keyed Hash with using pretty printed JSON" do hash = { [1,2] => 3, [2,3] => 5, [3,4] => 7 } expect(app.render(hash, {})).to eq(Puppet::Util::Json.dump(hash, :pretty => true).chomp) end it "should render a {String,Numeric}-keyed Hash into a table" do object = Object.new hash = { "one" => 1, "two" => [], "three" => {}, "four" => object, 5 => 5, 6.0 => 6 } # Gotta love ASCII-betical sort order. Hope your objects are better # structured for display than my test one is. --daniel 2011-04-18 expect(app.render(hash, {})).to eq < { "1" => '1' * 40, "2" => '2' * 40, '3' => '3' * 40 }, "text" => { "a" => 'a' * 40, 'b' => 'b' * 40, 'c' => 'c' * 40 } } expect(app.render(hash, {})).to eq <"1111111111111111111111111111111111111111", "2"=>"2222222222222222222222222222222222222222", "3"=>"3333333333333333333333333333333333333333"} text {"a"=>"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "b"=>"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "c"=>"cccccccccccccccccccccccccccccccccccccccc"} EOT end describe "when setting the rendering method" do after do # need to reset the when_rendering block so that other tests can set it later app.action.instance_variable_set("@when_rendering", {}) end it "should invoke the action rendering hook while rendering" do app.action.set_rendering_method_for(:console, proc { |value| "bi-winning!" }) expect(app.render("bi-polar?", {})).to eq("bi-winning!") end it "should invoke the action rendering hook with args and options while rendering" do app.action.instance_variable_set("@when_rendering", {}) app.action.when_invoked = proc { |name, options| 'just need to match arity for rendering' } app.action.set_rendering_method_for( :console, proc { |value, name, options| "I'm #{name}, no wait, I'm #{options[:altername]}" } ) expect(app.render("bi-polar?", ['bob', {:altername => 'sue'}])).to eq("I'm bob, no wait, I'm sue") end end it "should render JSON when asked for json" do app.render_as = :json json = app.render({ :one => 1, :two => 2 }, {}) expect(json).to match(/"one":\s*1\b/) expect(json).to match(/"two":\s*2\b/) expect(JSON.parse(json)).to eq({ "one" => 1, "two" => 2 }) end end it "should fail early if asked to render an invalid format" do allow(app.command_line).to receive(:args).and_return(%w{--render-as interpretive-dance return_true}) # We shouldn't get here, thanks to the exception, and our expectation on # it, but this helps us fail if that slips up and all. --daniel 2011-04-27 expect(Puppet::Face[:help, :current]).not_to receive(:help) expect(Puppet).to receive(:err).with("Could not parse application options: I don't know how to render 'interpretive-dance'") expect { app.run }.to exit_with(1) end it "should work if asked to render json" do allow(app.command_line).to receive(:args).and_return(%w{count_args a b c --render-as json}) expect { expect { app.run }.to exit_with(0) }.to have_printed(/3/) end it "should invoke when_rendering hook 's' when asked to render-as 's'" do allow(app.command_line).to receive(:args).and_return(%w{with_s_rendering_hook --render-as s}) app.action = app.face.get_action(:with_s_rendering_hook) expect { expect { app.run }.to exit_with(0) }.to have_printed(/you invoked the 's' rendering hook/) end end describe "#help" do it "should generate help for --help" do allow(app.command_line).to receive(:args).and_return(%w{--help}) expect(Puppet::Face[:help, :current]).to receive(:help) expect { app.run }.to exit_with(0) end it "should generate help for -h" do allow(app.command_line).to receive(:args).and_return(%w{-h}) expect(Puppet::Face[:help, :current]).to receive(:help) expect { app.run }.to exit_with(0) end end end