require 'spec_helper' require 'escape' require 'result' require 'engineyard-serverside/callbacks/executor/executable' module EY module Serverside module Callbacks module Executor describe Executable do let(:config) {Object.new} let(:shell) {Object.new} let(:paths) {Object.new} let(:hook) {Object.new} let(:short_name) {'hook'} let(:active_release_path) {'/path/to/the/active/release'} let(:hook_path) {'/path/to/the/dang/hook'} let(:account_name) {'george'} let(:app) {'tacoma'} let(:env_name) {'gracie'} let(:framework_env) {'sausages'} let(:current_roles) {['hamlet', 'ophelia']} let(:current_name) {nil} let(:config_json) {'a big ol hash'} let(:verbose) {false} let(:execution_success) {true} let(:hook_string) {'hooky'} let(:executor) {described_class.new(config, shell, hook)} before(:each) do allow(hook).to receive(:path).and_return(hook_path) allow(hook).to receive(:short_name).and_return(short_name) allow(hook).to receive(:to_s).and_return(hook_string) allow(hook). to receive(:respond_to?). with(:service_name). and_return(false) allow(shell).to receive(:info) allow(shell).to receive(:fatal) allow(shell).to receive(:warning) allow(config).to receive(:paths).and_return(paths) allow(config).to receive(:current_roles).and_return(current_roles) allow(config).to receive(:account_name).and_return(account_name) allow(config).to receive(:app).and_return(app) allow(config).to receive(:environment_name).and_return(env_name) allow(config).to receive(:verbose).and_return(verbose) allow(config).to receive(:current_name).and_return(current_name) allow(config).to receive(:framework_env).and_return(framework_env) allow(config).to receive(:to_json).and_return(config_json) allow(config). to receive(:framework_envs). and_return("RACK_ENV=#{framework_env} RAILS_ENV=#{framework_env}") allow(paths).to receive(:active_release).and_return(active_release_path) allow(hook_path).to receive(:executable?).and_return(true) # This is a wee bit dirty, but the easiest way to test our actual code # here is to stub out the included Runner interface. allow(executor).to receive(:run) do |cmd| EY::Serverside::Spawner::Result.new(cmd, execution_success, cmd, nil) end # We also need to stub out the #abort method, as we don't need the # test suite to just up and stop running when we test executor failure # modes. allow(executor).to receive(:abort) end it 'is a Railway' do expect(executor).to be_a(Railway) end it 'knows how to spawn CLI processes' do expect(executor).to be_a(Runner) end it 'has the exact steps for executing a Ruby hook' do steps = described_class.steps.map {|s| s[:name]} expect(steps).to eql( [ :validate_hook, :populate_environment, :calculate_wrapper, :run_hook ] ) end describe '#validate_hook' do let(:input) { {} } let(:result) {executor.validate_hook} context 'when the hook has the executable bit' do before(:each) do allow(hook_path).to receive(:executable?).and_return(true) end it 'is a Success' do expect(result).to be_a(Result::Success) end it 'does not alter the input' do expect(result.value).to eql(input) end end context 'when the hook lacks the executable bit' do before(:each) do allow(hook_path).to receive(:executable?).and_return(false) end it 'is a Failure' do expect(result).to be_a(Result::Failure) end it 'contains a reason for the failure' do expect(result.error[:reason]).to eql(:not_executable) end end end describe '#populate_environment' do let(:input) {{}} let(:result) {executor.populate_environment(input)} let(:result_env) {result.value[:environment]} it 'is a Success' do expect(result).to be_a(Result::Success) end it 'adds the environment to the input' do expect(result_env).to be_a(String) end it 'includes the account name in the environment' do escaped = Escape.shell_command([account_name]) expect(result_env). to match(%r{EY_DEPLOY_ACCOUNT_NAME=#{escaped}}) end it 'includes the application in the environment' do escaped = Escape.shell_command([app]) expect(result_env). to match(%{EY_DEPLOY_APP=#{escaped}}) end it 'includes the JSON representation of the config in the environment' do escaped = Escape.shell_command([config_json]) expect(result_env). to match(%r{EY_DEPLOY_CONFIG=#{escaped}}) end it 'includes all of the current roles in the environment' do expect(result_env). to match(%r{EY_DEPLOY_CURRENT_ROLES='#{current_roles.join(' ')}'}) end context 'when the server is named' do let(:current_name) {'frankie'} let(:escaped) {Escape.shell_command([current_name])} it 'includes the current server name' do expect(result_env). to match(%r{EY_DEPLOY_CURRENT_NAME=#{escaped}}) end end context 'when the server is unnamed' do let(:current_name) {nil} it 'omits the current server name' do expect(result_env). not_to match(%r{EY_DEPLOY_CURRENT_NAME}) end end it 'includes the environment name in the environment' do escaped = Escape.shell_command([env_name]) expect(result_env). to match(%r{EY_DEPLOY_ENVIRONMENT_NAME=#{escaped}}) end it 'includes the framwork env in the environment' do escaped = Escape.shell_command([framework_env]) expect(result_env). to match(%r{EY_DEPLOY_FRAMEWORK_ENV=#{escaped}}) end it 'includes the release path in the environment' do escaped = Escape.shell_command([active_release_path]) expect(result_env). to match(%r{EY_DEPLOY_RELEASE_PATH=#{escaped}}) end context 'with verbosity configured' do let(:verbose) {true} it 'enables verbosity in the environment' do expect(result.value[:environment]). to match(%r{EY_DEPLOY_VERBOSE=1}) end end context 'without verbosity configured' do let(:verbose) {false} it 'disables verbosity in the environment' do expect(result.value[:environment]). to match(%r{EY_DEPLOY_VERBOSE=0}) end end context 'when any of the values involved are nil' do let(:account_name) {nil} let(:app) {nil} let(:config_json) {nil} let(:env_name) {nil} it 'omits those env vars' do affected = ['ACCOUNT_NAME', 'APP', 'CONFIG', 'ENVIRONMENT_NAME'] affected.each do |var| expect(result_env).not_to match(%r{EY_DEPLOY_#{var}}) end end end end describe '#calculate_wrapper' do let(:input) {{}} let(:result) {executor.calculate_wrapper(input)} it 'is a Success' do expect(result).to be_a(Result::Success) end it 'adds a wrapper to the input' do expect(result.value[:wrapper]).not_to be_nil end context 'when dealing with a service hook' do before(:each) do allow(hook). to receive(:respond_to?). with(:service_name). and_return(true) end it 'wraps the service hook executor' do expect(result.value[:wrapper]).to eql(About.service_hook_executor) end end context 'when dealing with an app hook' do before(:each) do allow(hook). to receive(:respond_to?). with(:service_name). and_return(false) end it 'wraps the app hook executor' do expect(result.value[:wrapper]).to eql(About.hook_executor) end end end describe '#run_hook' do let(:dummy_env) {'i am an environment'} let(:wrapper) {'candy-bar'} let(:input) { { :wrapper => wrapper, :environment => dummy_env } } let(:result) {executor.run_hook(input)} it 'runs the proper command' do expect(executor). to receive(:run). with( "#{dummy_env} #{config.framework_envs} #{wrapper} #{short_name}" ) result end context 'when the execution succeeds' do let(:execution_success) {true} it 'is a Success' do expect(result).to be_a(Result::Success) end it 'does not alter the input' do expect(result.value).to eql(input) end end context 'when the execution fails' do let(:execution_success) {false} it 'is a Failure' do expect(result).to be_a(Result::Failure) end it 'has exeuction failure as its reason' do expect(result.error[:reason]).to eql(:execution_failed) end end end describe '#handle_failure' do let(:reason) {nil} let(:error) { { :reason => reason } } let(:result) {executor.handle_failure(error)} context 'when the hook is not actually executable' do let(:reason) {:not_executable} it 'does nothing and returns true' do expect(result).to eql(true) end end context 'when execution of the hook fails' do let(:reason) {:execution_failed} it 'aborts with a message about the execution failure' do expect(executor). to receive(:abort). with( "*** [Error] Hook failed to exit cleanly: #{hook_path} ***\n" ) result end end context 'when an otherwise unexpected error occurs' do let(:reason) {nil} it 'aborts with a message about the unknown error' do expect(executor). to receive(:abort). with("*** [Error] An unknown error occurred for hook: #{hook_path} ***\n") result end end end end end end end end