require sprintf('%s/../../path_helper', File.dirname(File.expand_path(__FILE__)))

require 'rouster'
require 'test/unit'

# TODO rename this to 'test_instantiate' and flesh out tests for all instantiaion options

class TestNew < Test::Unit::TestCase

  # when run under Rake, if named 'setup', @@user_sshkey is not initialized in the
  # TestNew namespace (does not matter if running directly). since setup() really
  # only needed to be run once anyway, this isn't a huge problem
  def test_0_setup
    @app = Rouster.new(:name => 'app', :sshtunnel => false)
    @app.destroy() if @app.status().eql?('running') # TODO do we really need to do this?
    @app = nil

    @@user_sshkey = sprintf('%s/.ssh/id_rsa', ENV['HOME'])

    unless File.file?(@@user_sshkey)
      #`ssh-keygen -t rsa -q -f #{@@user_sshkey} -N ''` # this will create the file, and we can track with another class variable.. but how do we safely install/uninstall this to localhost ~/.ssh/authorized_keys ?
      File.write(@@user_sshkey, '') # either this or `touch`
      File.chmod(0600, @@user_sshkey)
    end

  end

  def test_able_to_instantiate

    assert_nothing_raised do
      @app = Rouster.new(:name => 'app')
    end

  end

  def test_defaults

    assert_nothing_raised do
      @app = Rouster.new(:name => 'app')
    end

    assert_equal('app', @app.name)
    assert_equal(false, @app.cache_timeout)
    assert_equal(false, @app.instance_variable_get(:@logfile))
    assert_equal(false, @app.is_passthrough?())
    assert_equal(0, @app.retries)
    assert_equal(true, @app.instance_variable_get(:@sshtunnel))
    assert_equal(false, @app.instance_variable_get(:@unittest))
    assert_equal(false, @app.instance_variable_get(:@vagrant_concurrency))
    assert_equal(3, @app.instance_variable_get(:@verbosity_console))
    assert_equal(2, @app.instance_variable_get(:@verbosity_logfile))

  end

  def test_good_openssh_tunnel
    @app = Rouster.new(:name => 'app', :sshtunnel => true)

    # TODO how do we properly test this? we really need the rspec should_call mechanism...
    assert_equal(true, @app.is_available_via_ssh?)
  end

  def test_good_advanced_instantiation

    assert_nothing_raised do
      @app = Rouster.new(
        :name          => 'app',
        :passthrough   => false,
        :sudo          => false,
        :verbosity     => [4,0],
        #:vagrantfile  => traverse_up(Dir.pwd, 'Vagrantfile'), # this is what happens anyway..
        :sshkey        =>  ENV['VAGRANT_HOME'].nil? ? sprintf('%s/.vagrant.d/insecure_private_key', ENV['HOME']) : sprintf('%s/insecure_private_key', ENV['VAGRANT_HOME']),
        :cache_timeout => 10,
        :logfile       => true,
      )

    end

    assert_equal('app', @app.name)
    assert_equal(false, @app.is_passthrough?())
    assert_equal(false, @app.uses_sudo?())
    assert_equal(4, @app.instance_variable_get(:@verbosity_console))
    assert_equal(0, @app.instance_variable_get(:@verbosity_logfile))
    assert_equal(true, File.file?(@app.vagrantfile))
    assert_equal(true, File.file?(@app.sshkey))
    assert_equal(10, @app.cache_timeout)

    ## logfile validation -- do we need to do more here?
    logfile = @app.instance_variable_get(:@logfile)

    assert_not_equal(true, logfile)
    assert(File.file?(logfile))

    contents = File.read(logfile)
    assert_not_nil(contents)
  end

  def test_bad_name_instantiation

    # TODO this is probably wrong, should really be an ArgumentError
    assert_raise Rouster::InternalError do
      @app = Rouster.new(:name => 'foo')
    end

    assert_raise Rouster::ArgumentError do
      @app = Rouster.new(:not_a_name => 'test')
    end

  end

  def test_bad_vagrantfile_instantiation

    assert_raise Rouster::InternalError do
      @app = Rouster.new(:name => 'FIZZY') # auto find Vagrantfile
    end

    assert_raise Rouster::ArgumentError do
      @app = Rouster.new(:name => 'testing', :vagrantfile => '/this/file/dne')
    end

  end

  def test_bad_sshkey_instantiation

    assert_raise Rouster::InternalError do
      @app = Rouster.new(:name => 'app', :sshkey => '/this/file/dne')
    end

  end

  def test_good_local_passthrough

    assert_nothing_raised do
      @app = Rouster.new(:name => 'local', :passthrough => { :type => :local }, :verbosity => 4)
    end

    assert_equal('local', @app.name)
    assert_equal(true, @app.is_passthrough?())
    assert_equal(false, @app.uses_sudo?())
    assert_equal(false, @app.is_available_via_ssh?())

  end

  def test_good_remote_passthrough

    omit('not running test_good_remote_passthrough, autogenerated a fake ssh key') if File.file?(@@user_sshkey) and File.read(@@user_sshkey).eql?("")

    host = '127.0.0.1'
    `ssh -i #{@@user_sshkey} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null #{host} exit` # if this succeeds, we want to exit immediately so we don't get hung
    omit("found an ssh key, but it doesn't appear to be valid for this host") unless $?.success?

    assert_nothing_raised do
      @app = Rouster.new(
        :name => 'remote',
        :sudo => false,
        :passthrough => {
          :type => :remote,
          :host => host,
          :user => ENV['USER'],
          :key  => @@user_sshkey,
          :paranoid => false,
        },
        :verbosity => 4,
      )
    end

    assert_equal('remote', @app.name)
    assert_equal(true, @app.is_passthrough?())
    assert_equal(false, @app.uses_sudo?())
    assert_equal(true, @app.is_available_via_ssh?())

  end

  def test_paranoia_remote_passthrough

    omit('not running test_good_remote_passthrough, autogenerated a fake ssh key') if File.file?(@@user_sshkey) and File.read(@@user_sshkey).eql?("")

    host = '127.0.0.1'
    `ssh -i #{@@user_sshkey} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null #{host} exit` # if this succeeds, we want to exit immediately so we don't get hung
    omit("found an ssh key, but it doesn't appear to be valid for this host") unless $?.success?

    assert_nothing_raised do
      @app = Rouster.new(
          :name => 'remote',
          :sudo => false,
          :passthrough => {
              :type => :remote,
              :host => host,
              :user => ENV['USER'],
              :key  => @@user_sshkey,
              :paranoid => :very,
          },
          :verbosity => 4,
      )
    end

    assert_equal('remote', @app.name)
    assert_equal(true, @app.is_passthrough?())
    assert_equal(false, @app.uses_sudo?())
    assert_equal(true, @app.is_available_via_ssh?())

  end


  def test_invalid_passthrough

    # invalid type
    # missing required parameters

    assert_raise Rouster::ArgumentError do
      @app = Rouster.new(:name => 'fizzy', :passthrough => {}, :verbosity => 4)
    end

    assert_raise Rouster::ArgumentError do
      @app = Rouster.new(:name => 'fizzy', :passthrough => { :type => 'invalid' }, :verbosity => 4)
    end

    assert_raise Rouster::ArgumentError do
      @app = Rouster.new(:name => 'fizzy', :passthrough => { :type => :remote }, :verbosity => 4)
    end

    assert_raise Rouster::ArgumentError do
      @app = Rouster.new(:name => 'fizzy', :passthrough => { :type => :remote, :user => 'foo' }, :verbosity => 4)
    end

  end

  def test_bad_passthrough

    # invalid key
    assert_raise Rouster::InternalError do
      @app = Rouster.new(
        :name => 'fizzy',
        :passthrough => {
          :type => :remote,
          :key  => '/etc/hosts',
          :user => 'foo',
          :host => 'bar',
        },
        :verbosity => 4,
      )
    end

    # key that DNE
    assert_raise Rouster::ArgumentError do
      @app = Rouster.new(
        :name => 'fizzy',
        :passthrough => {
            :type => :remote,
            :key  => '/etc/this-file-dne',
            :user => 'foo',
            :host => 'bar',
        },
        :verbosity => 4,
      )
    end

    # host that DNE
    assert_raise SocketError do
      @app = Rouster.new(
        :name => 'fizzy',
        :passthrough => {
          :type => :remote,
          :key  => @@user_sshkey,
          :user => 'foo',
          :host => 'this.host.does.not.exist',

          # don't retry.. too much
          :ssh_sleep_ceiling => 1,
          :ssh_sleep_time    => 1,
        },

        :sshtunnel => true,
        :verbosity => 4,
      )
    end

    # IP that doesn't resolve
    assert_raise SocketError do
      @app = Rouster.new(
        :name => 'fizzy',
        :passthrough => {
          :type => :remote,
          :key  => @@user_sshkey,
          :user => 'foo',
          :host => '255.256.257.258',

          :ssh_sleep_ceiling => 1,
          :ssh_sleep_time    => 1,
        },

        :sshtunnel => true,
        :verbosity => 4,
      )
    end

  end

  def teardown
    # if the file is empty, we know we created it (or it doesn't matter)..
    File.delete(@@user_sshkey) if File.file?(@@user_sshkey) and File.read(@@user_sshkey).eql?('')
  end

end
