require 'spec_helper'
require 'rest_spec_helper'
require 'rhc/commands/snapshot'
require 'rhc/config'
require 'rhc/tar_gz'

describe RHC::Commands::Snapshot do

  APP_NAME = 'mockapp'

  let!(:rest_client) { MockRestClient.new }
  before do
    user_config
    @app = rest_client.add_domain("mockdomain").add_application APP_NAME, 'mock-1.0'
    @ssh_uri = URI.parse @app.ssh_url
    @targz_filename = APP_NAME + '.tar.gz'
    FileUtils.cp(File.expand_path('../../assets/targz_sample.tar.gz', __FILE__), @targz_filename)
    File.chmod 0644, @targz_filename unless File.executable? @targz_filename
  end

  after do
    File.delete @targz_filename if File.exist? @targz_filename
  end

  describe 'snapshot without an action' do
    let(:arguments) {['snapshot', '--trace', '--noprompt']}
    it('should raise') { expect{ run }.to raise_error(ArgumentError, /Please specify an action to take/) }
  end

  describe 'snapshot save' do
    let(:arguments) {['snapshot', 'save', '--app', 'mockapp', '-d']}

    context 'when saving a snapshot' do
      before do
        RHC::Helpers.should_receive(:agree).with(/Do you want to overwrite this file?/).and_return(true)
        subject.class.any_instance.should_receive(:run_with_system_ssh).with("ssh #{@ssh_uri.user}@#{@ssh_uri.host} 'snapshot' > #{@app.name}.tar.gz").and_return([0, 'some save output'])
      end
      it { expect { run }.to exit_with_code(0) }
      it { run_output.should_not match 'some save output' }
    end

    context 'when saving a snapshot into existing file' do
      let(:arguments) {['snapshot', 'save', '--app', 'mockapp', '-f', 'mockapp.tar.gz']}
      before do
        File.stub(:exists?).and_return(true)
        RHC::Helpers.should_receive(:agree).with(/Do you want to overwrite this file?/).and_return(true)
        subject.class.any_instance.should_receive(:exec).with(/ssh #{@ssh_uri.user}@#{@ssh_uri.host} 'snapshot' > #{@app.name}.tar.gz/).and_return([0, 'some save output'])
      end
      it { run_output.should match(/done/) }
    end

    context 'when failing to save a snapshot' do
      before do
        RHC::Helpers.should_receive(:agree).with(/Do you want to overwrite this file?/).and_return(true)
        subject.class.any_instance.stub(:run_with_system_ssh).and_raise(RHC::SSHCommandFailed.new(1, "some save failures"))
        subject.class.any_instance.should_receive(:run_with_system_ssh).with("ssh #{@ssh_uri.user}@#{@ssh_uri.host} 'snapshot' > #{@app.name}.tar.gz")
      end
      it { expect { run }.to exit_with_code(130) }
      it { run_output.should match 'some save failures' }
    end

    context 'when saving a snapshot on windows' do
      before do
        RHC::Helpers.stub(:windows?) do ; true; end
        RHC::Helpers.stub(:jruby?) do ; false ; end
        RHC::Helpers.stub(:linux?) do ; false ; end
        ssh = double(Net::SSH)
        Net::SSH.should_receive(:start).with(@ssh_uri.host, @ssh_uri.user).and_yield(ssh)
        subject.class.any_instance.stub(:discover_ssh_executable){ 'ssh' }
        subject.class.any_instance.stub(:discover_git_executable){ 'git' }
        ssh.should_receive(:exec!).with("snapshot").and_yield(nil, :stdout, 'foo').and_yield(nil, :stderr, 'foo')
        RHC::Helpers.should_receive(:agree).with(/Do you want to overwrite this file?/).and_return(true)
      end
      it { expect { run }.to exit_with_code(0) }
      it { run_output.should match("done") }
    end

    context 'when ssh times out on windows' do
      before do
        RHC::Helpers.stub(:windows?) do ; true; end
        RHC::Helpers.stub(:jruby?) do ; false ; end
        RHC::Helpers.stub(:linux?) do ; false ; end
        ssh = double(Net::SSH)
        Net::SSH.should_receive(:start).with(@ssh_uri.host, @ssh_uri.user).and_raise(Timeout::Error)
        subject.class.any_instance.stub(:discover_ssh_executable){ 'ssh' }
        subject.class.any_instance.stub(:discover_git_executable){ 'git' }
        RHC::Helpers.should_receive(:agree).with(/Do you want to overwrite this file?/).and_return(true)
      end
      it { expect { run }.to exit_with_code(130) }
    end

    context 'when the specified timeout is hit' do
      before do
        timeout = 1
        base_config { |c, d| d.add 'timeout', timeout }
        RHC::Helpers.stub(:windows?) do ; false; end
        RHC::Helpers.stub(:jruby?) do ; false ; end
        RHC::Helpers.stub(:linux?) do ; true ; end
        subject.class.any_instance.stub(:exec) { sleep 10 }
        subject.class.any_instance.stub(:discover_ssh_executable){ 'ssh' }
        subject.class.any_instance.stub(:discover_git_executable){ 'git' }
        RHC::Helpers.should_receive(:agree).with(/Do you want to overwrite this file?/).and_return(true)
      end
      it { expect { run }.to exit_with_code(130) }
      it { run_output.should match(/Save operation took longer than/) }
    end

    context 'when the application is unreachable' do
      before do
        RHC::Helpers.stub(:windows?) do ; true; end
        RHC::Helpers.stub(:jruby?) do ; false ; end
        RHC::Helpers.stub(:linux?) do ; false ; end
        subject.class.any_instance.stub(:discover_ssh_executable){ 'ssh' }
        subject.class.any_instance.stub(:discover_git_executable){ 'git' }
        RHC::Helpers.should_receive(:agree).with(/Do you want to overwrite this file?/).and_return(true)
        Net::SSH.should_receive(:start).with(@ssh_uri.host, @ssh_uri.user).and_raise(Errno::EHOSTUNREACH)
      end
      it { expect { run }.to exit_with_code(130) }
      it { run_output.should match(/Error in trying to save snapshot/) }
    end

    describe 'snapshot save deployment' do
      let(:arguments) {['snapshot', 'save', '--app', 'mockapp', '--deployment', '-d']}

      context 'when saving a deployment snapshot' do
        before do
          RHC::Helpers.should_receive(:agree).with(/Do you want to overwrite this file?/).and_return(true)
          subject.class.any_instance.should_receive(:run_with_system_ssh).with("ssh #{@ssh_uri.user}@#{@ssh_uri.host} 'gear archive-deployment' > #{@app.name}.tar.gz").and_return([0, 'some save output'])
        end
        it { expect { run }.to exit_with_code(0) }
        it { run_output.should_not match 'some save output' }
      end
    end

  end

  describe 'snapshot save with invalid ssh executable' do
    let(:arguments) {['snapshot', 'save', '--trace', '--noprompt', '-l', 'test@test.foo', '-p', 'password', '--app', 'mockapp', '--ssh', 'path_to_ssh']}
    before do
      RHC::Helpers.should_receive(:agree).with(/Do you want to overwrite this file?/).and_return(true)
    end
    it('should raise') { expect{ run }.to raise_error(RHC::InvalidSSHExecutableException, /SSH executable 'path_to_ssh' does not exist./) }
  end

  describe 'snapshot save when ssh is not executable' do
    let(:arguments) {['snapshot', 'save', '--trace', '--noprompt', '-l', 'test@test.foo', '-p', 'password', '--app', 'mockapp', '--ssh', @targz_filename]}
    before do
      RHC::Helpers.should_receive(:agree).with(/Do you want to overwrite this file?/).and_return(true)
    end
    it('should raise') { expect{ run }.to raise_error(RHC::InvalidSSHExecutableException, /SSH executable '#{@targz_filename}' is not executable./) }
  end

  describe 'snapshot restore' do
    let(:arguments) {['snapshot', 'restore', '--app', 'mockapp', '-d']}

    context 'when restoring a snapshot' do
      before do
        File.stub(:exists?).and_return(true)
        RHC::TarGz.stub(:contains).and_return(true)
        subject.class.any_instance.should_receive(:run_with_system_ssh).with("cat '#{@app.name}.tar.gz' | ssh #{@ssh_uri.user}@#{@ssh_uri.host} 'restore INCLUDE_GIT'").and_return([0, 'some restore output'])
      end
      it('should succeed') { expect { run }.to exit_with_code(0) }
      it { run_output.should_not match 'some restore output' }
    end

    context 'when restoring a snapshot and failing to ssh' do
      before do
        File.stub(:exists?).and_return(true)
        RHC::TarGz.stub(:contains).and_return(true)
        subject.class.any_instance.stub(:run_with_system_ssh).and_raise(RHC::SSHCommandFailed.new(1, "some restore failures"))
        subject.class.any_instance.should_receive(:run_with_system_ssh).with("cat '#{@app.name}.tar.gz' | ssh #{@ssh_uri.user}@#{@ssh_uri.host} 'restore INCLUDE_GIT'")
      end
      it { expect { run }.to exit_with_code(130) }
      it { run_output.should match 'some restore failures' }
    end

    context 'when restoring a snapshot on windows' do
      before do
        RHC::Helpers.stub(:windows?) do ; true; end
        RHC::Helpers.stub(:jruby?) do ; false ; end
        RHC::Helpers.stub(:linux?) do ; false ; end
        ssh = double(Net::SSH)
        session = double(Net::SSH::Connection::Session)
        channel = double(Net::SSH::Connection::Channel)
        Net::SSH.should_receive(:start).with(@ssh_uri.host, @ssh_uri.user).and_return(session)
        session.should_receive(:open_channel).and_yield(channel)
        channel.should_receive(:exec).with("restore INCLUDE_GIT").and_yield(nil, nil)
        channel.should_receive(:on_data).and_yield(nil, 'foo')
        channel.should_receive(:on_extended_data).and_yield(nil, nil, 'foo')
        channel.should_receive(:on_close).and_yield(nil)
        lines = ''
        subject.class.any_instance.stub(:discover_ssh_executable){ 'ssh' }
        subject.class.any_instance.stub(:discover_git_executable){ 'git' }
        File.open(File.expand_path('../../assets/targz_sample.tar.gz', __FILE__), 'rb') do |file|
          file.chunk(1024) do |chunk|
            lines << chunk
          end
        end
        channel.should_receive(:send_data).with(lines)
        channel.should_receive(:eof!)
        session.should_receive(:loop)
      end
      it { expect { run }.to exit_with_code(0) }
    end

    context 'when timing out on windows' do
      before do
        RHC::Helpers.stub(:windows?) do ; true; end
        RHC::Helpers.stub(:jruby?) do ; false ; end
        RHC::Helpers.stub(:linux?) do ; false ; end
        ssh = double(Net::SSH)
        Net::SSH.should_receive(:start).with(@ssh_uri.host, @ssh_uri.user).and_raise(Timeout::Error)
        subject.class.any_instance.stub(:discover_ssh_executable){ 'ssh' }
        subject.class.any_instance.stub(:discover_git_executable){ 'git' }
      end
      it { expect { run }.to exit_with_code(130) }
    end

  end

  describe 'snapshot restore file not found' do
    let(:arguments) {['snapshot', 'restore', 'mockapp', '-f', 'foo.tar.gz']}
    context 'when restoring a snapshot' do
      it { expect { run }.to exit_with_code(130) }
    end
  end

  describe 'snapshot restore with invalid ssh executable' do
    let(:arguments) {['snapshot', 'restore', '--trace', '--noprompt', '-l', 'test@test.foo', '-p', 'password', '--app', 'mockapp', '--ssh', 'path_to_ssh']}
    it('should raise') { expect{ run }.to raise_error(RHC::InvalidSSHExecutableException, /SSH executable 'path_to_ssh' does not exist./) }
  end

  describe 'snapshot save when ssh is not executable' do
    let(:arguments) {['snapshot', 'restore', '--trace', '--noprompt', '-l', 'test@test.foo', '-p', 'password', '--app', 'mockapp', '--ssh', @targz_filename]}
    it('should raise') { expect{ run }.to raise_error(RHC::InvalidSSHExecutableException, /SSH executable '#{@targz_filename}' is not executable./) }
  end
end
