require 'spec_helper' class LoginCommandRun attr_accessor :stdout, :stderr, :exitstatus def initialize(args, stdin=nil) out = StringIO.new err = StringIO.new if stdin $stdin = StringIO.new $stdin << stdin $stdin.rewind end $stdout = out $stderr = err begin Vcloud::Core::LoginCli.new(args).run @exitstatus = 0 rescue SystemExit => e # Capture exit(n) value. @exitstatus = e.status end @stdout = out.string.strip @stderr = err.string.strip if stdin $stdin = STDIN end $stdout = STDOUT $stderr = STDERR end end describe Vcloud::Core::LoginCli do let(:stdin) { nil } subject { LoginCommandRun.new(args, stdin) } describe "normal usage" do context "when given no arguments and a password on stdin" do let(:args) { %w{} } let(:pass) { 'supersekret' } let(:stdin) { pass } context "interactive tty" do before(:each) do expect(STDIN).to receive(:tty?).and_return(true) end it "should prompt on stderr so that stdout can be scripted and mask password input" do expect(Vcloud::Core::Fog::Login).to receive(:token_export).with(pass) expect(subject.stderr).to eq("vCloud password: " + "*" * pass.size) end end context "non-interactive tty" do before(:each) do expect(STDIN).to receive(:tty?).and_return(false) end it "should write stderr message to say that it's reading from pipe and not echo any input" do expect(Vcloud::Core::Fog::Login).to receive(:token_export).with(pass) expect(subject.stderr).to eq("Reading password from pipe..") end end end context "when asked to display version" do let(:args) { %w{--version} } it "should not call Login" do expect(Vcloud::Core::Fog::Login).not_to receive(:token_export) end it "should print version and exit normally" do expect(subject.stdout).to eq(Vcloud::Core::VERSION) expect(subject.exitstatus).to eq(0) end end context "when asked to display help" do let(:args) { %w{--help} } it "should not call Login" do expect(Vcloud::Core::Fog::Login).not_to receive(:token_export) end it "should print usage and exit normally" do expect(subject.stderr).to match(/\AUsage: \S+ \[options\]\n/) expect(subject.exitstatus).to eq(0) end end end describe "incorrect usage" do shared_examples "print usage and exit abnormally" do |error| it "should not call Login" do expect(Vcloud::Core::Fog::Login).not_to receive(:token_export) end it "should print error message and usage" do expect(subject.stderr).to match(/\A\S+: #{error}\nUsage: \S+/) end it "should exit abnormally for incorrect usage" do expect(subject.exitstatus).to eq(2) end end context "when given more than one argument" do let(:args) { %w{an_extra_arg} } it_behaves_like "print usage and exit abnormally", "too many arguments" end context "when given an unrecognised argument" do let(:args) { %w{--this-is-garbage} } it_behaves_like "print usage and exit abnormally", "invalid option: --this-is-garbage" end end describe "error handling" do context "when underlying code raises an exception" do let(:args) { %w{} } # We have to pass a string here to prevent highline blowing up. This # appears to be related to swapping out $stdin for StringIO and can't # be reproduced by normal CLI use. let(:pass) { 'some string' } let(:stdin) { pass } let(:exception_string) { 'something went horribly wrong' } it "should print error without backtrace and exit abnormally" do expect(Vcloud::Core::Fog::Login).to receive(:token_export). and_raise(exception_string) if STDIN.tty? expect(subject.stderr).to eq("vCloud password: #{'*' * pass.size}\n#{exception_string}") else expect(subject.stderr).to eq("Reading password from pipe..\n#{exception_string}") end expect(subject.exitstatus).to eq(1) end end end end