require 'utils'
require 'capistrano/transfer'

class TransferTest < Test::Unit::TestCase
  def test_class_process_should_delegate_to_instance_process
    Capistrano::Transfer.expects(:new).with(:up, "from", "to", %w(a b c), {}).returns(mock('transfer', :process! => nil)).yields
    yielded = false
    Capistrano::Transfer.process(:up, "from", "to", %w(a b c), {}) { yielded = true }
    assert yielded
  end

  def test_default_transport_is_sftp
    transfer = Capistrano::Transfer.new(:up, "from", "to", [])
    assert_equal :sftp, transfer.transport
  end

  def test_active_is_true_when_any_sftp_transfers_are_active
    returns = [false, false, true]
    sessions = [session('app1', :sftp), session('app2', :sftp), session('app3', :sftp)].each { |s| s.xsftp.expects(:upload).returns(stub('operation', :active? => returns.shift)) }
    transfer = Capistrano::Transfer.new(:up, "from", "to", sessions, :via => :sftp)
    assert_equal true, transfer.active?
  end

  def test_active_is_false_when_all_sftp_transfers_are_not_active
    sessions = [session('app1', :sftp), session('app2', :sftp)].each { |s| s.xsftp.expects(:upload).returns(stub('operation', :active? => false)) }
    transfer = Capistrano::Transfer.new(:up, "from", "to", sessions, :via => :sftp)
    assert_equal false, transfer.active?
  end

  def test_active_is_true_when_any_scp_transfers_are_active
    returns = [false, false, true]
    sessions = [session('app1', :scp), session('app2', :scp), session('app3', :scp)].each do |s|
      channel = stub('channel', :[]= => nil, :active? => returns.shift)
      s.scp.expects(:upload).returns(channel)
    end
    transfer = Capistrano::Transfer.new(:up, "from", "to", sessions, :via => :scp)
    assert_equal true, transfer.active?
  end

  def test_active_is_false_when_all_scp_transfers_are_not_active
    sessions = [session('app1', :scp), session('app2', :scp), session('app3', :scp)].each do |s|
      channel = stub('channel', :[]= => nil, :active? => false)
      s.scp.expects(:upload).returns(channel)
    end
    transfer = Capistrano::Transfer.new(:up, "from", "to", sessions, :via => :scp)
    assert_equal false, transfer.active?
  end

  [:up, :down].each do |direction|
    define_method("test_sftp_#{direction}load_from_file_to_file_should_normalize_from_and_to") do
      sessions = [session('app1', :sftp), session('app2', :sftp)]

      sessions.each do |session|
        session.xsftp.expects("#{direction}load".to_sym).with("from-#{session.xserver.host}", "to-#{session.xserver.host}",
          :properties => { :server => session.xserver, :host => session.xserver.host })
      end

      transfer = Capistrano::Transfer.new(direction, "from-$CAPISTRANO:HOST$", "to-$CAPISTRANO:HOST$", sessions)
    end

    define_method("test_scp_#{direction}load_from_file_to_file_should_normalize_from_and_to") do
      sessions = [session('app1', :scp), session('app2', :scp)]

      sessions.each do |session|
        session.scp.expects("#{direction}load".to_sym).returns({}).with("from-#{session.xserver.host}", "to-#{session.xserver.host}", :via => :scp)
      end

      transfer = Capistrano::Transfer.new(direction, "from-$CAPISTRANO:HOST$", "to-$CAPISTRANO:HOST$", sessions, :via => :scp)
    end
  end

  def test_sftp_upload_from_IO_to_file_should_clone_the_IO_for_each_connection
    sessions = [session('app1', :sftp), session('app2', :sftp)]
    io = StringIO.new("from here")

    sessions.each do |session|
      session.xsftp.expects(:upload).with do |from, to, opts|
        from != io && from.is_a?(StringIO) && from.string == io.string &&
        to == "/to/here-#{session.xserver.host}" &&
        opts[:properties][:server] == session.xserver &&
        opts[:properties][:host] == session.xserver.host
      end
    end

    transfer = Capistrano::Transfer.new(:up, StringIO.new("from here"), "/to/here-$CAPISTRANO:HOST$", sessions)
  end

  def test_scp_upload_from_IO_to_file_should_clone_the_IO_for_each_connection
    sessions = [session('app1', :scp), session('app2', :scp)]
    io = StringIO.new("from here")

    sessions.each do |session|
      channel = mock('channel')
      channel.expects(:[]=).with(:server, session.xserver)
      channel.expects(:[]=).with(:host, session.xserver.host)
      session.scp.expects(:upload).returns(channel).with do |from, to, opts|
        from != io && from.is_a?(StringIO) && from.string == io.string &&
        to == "/to/here-#{session.xserver.host}"
      end
    end

    transfer = Capistrano::Transfer.new(:up, StringIO.new("from here"), "/to/here-$CAPISTRANO:HOST$", sessions, :via => :scp)
  end

  def test_process_should_block_until_transfer_is_no_longer_active
    transfer = Capistrano::Transfer.new(:up, "from", "to", [])
    transfer.expects(:process_iteration).times(4).yields.returns(true,true,true,false)
    transfer.expects(:active?).times(4)
    transfer.process!
  end

  def test_errors_raised_for_a_sftp_session_should_abort_session_and_continue_with_remaining_sessions
    s = session('app1')
    error = ExceptionWithSession.new(s)
    transfer = Capistrano::Transfer.new(:up, "from", "to", [])
    transfer.expects(:process_iteration).raises(error).times(3).returns(true, false)
    txfr = mock('transfer', :abort! => true)
    txfr.expects(:[]=).with(:failed, true)
    txfr.expects(:[]=).with(:error, error)
    transfer.expects(:session_map).returns(s => txfr)
    transfer.process!
  end

  def test_errors_raised_for_a_scp_session_should_abort_session_and_continue_with_remaining_sessions
    s = session('app1')
    error = ExceptionWithSession.new(s)
    transfer = Capistrano::Transfer.new(:up, "from", "to", [], :via => :scp)
    transfer.expects(:process_iteration).raises(error).times(3).returns(true, false)
    txfr = mock('channel', :close => true)
    txfr.expects(:[]=).with(:failed, true)
    txfr.expects(:[]=).with(:error, error)
    transfer.expects(:session_map).returns(s => txfr)
    transfer.process!
  end

  def test_uploading_a_non_existing_file_should_raise_an_understandable_error
    s = session('app1')
    error = Capistrano::Processable::SessionAssociation.on(ArgumentError.new('expected a file to upload'), s)
    transfer = Capistrano::Transfer.new(:up, "from", "to", [], :via => :scp)
    transfer.expects(:process_iteration).raises(error)
    assert_raise(ArgumentError, 'expected a file to upload') { transfer.process! }
  end

  private

    class ExceptionWithSession < ::Exception
      attr_reader :session

      def initialize(session)
        @session = session
        super()
      end
    end

    def session(host, mode=nil)
      session = stub('session', :xserver => stub('server', :host => host))
      case mode
      when :sftp
        sftp = stub('sftp')
        session.expects(:sftp).with(false).returns(sftp)
        sftp.expects(:connect).yields(sftp).returns(sftp)
        session.stubs(:xsftp).returns(sftp)
      when :scp
        session.stubs(:scp).returns(stub('scp'))
      end
      session
    end
end
