require_relative './common'
require 'net/ssh/config'
require 'net/ssh'
require 'pathname'
require 'tempfile'

class TestConfig < NetSSHTest
  def test_home_should_be_absolute_path
    assert Pathname.new(ENV['HOME']).absolute?
  end

  def test_load_for_non_existant_file_should_return_empty_hash
    bogus_file = File.expand_path("/bogus/file")
    File.expects(:readable?).with(bogus_file).returns(false)
    assert_equal({}, Net::SSH::Config.load(bogus_file, "host.name"))
  end

  def test_load_should_expand_path
    expected = File.expand_path("~/.ssh/config")
    File.expects(:readable?).with(expected).returns(false)
    Net::SSH::Config.load("~/.ssh/config", "host.name")
  end

  def test_load_with_exact_host_match_should_load_that_section
    config = Net::SSH::Config.load(config(:exact_match), "test.host")
    assert config['compression']
    assert config['forwardagent']
    assert_equal 1234, config['port']
  end

  def test_load_with_wild_card_matches_should_load_all_matches_with_first_match_taking_precedence
    config = Net::SSH::Config.load(config(:wild_cards), "test.host")
    assert_equal 1234, config['port']
    assert !config['compression']
    assert config['forwardagent']
    assert_equal %w(~/.ssh/id_dsa), config['identityfile']
    assert !config.key?('rekeylimit')
  end

  def test_load_with_wild_card_and_negative_pattern_does_not_match
    config = Net::SSH::Config.load(config(:negative_match), "test.host")
    assert_equal 9876, config['port']
    assert !config.key?('compression')
  end

  def test_load_with_pattern_does_match
    data = '
      Host test.*
        Port 1234
        Compression no
    '
    with_config_from_data data do |f|
      config = Net::SSH::Config.load(f, "test.host")
      assert_equal 1234, config['port']
    end
  end

  def test_check_host_ip
    data = '
      Host *
        CheckHostIP no
    '
    with_config_from_data data do |f|
      config = Net::SSH::Config.load(f, 'foo')
      assert_equal false, config['checkhostip']

      config = Net::SSH::Config.for("foo", [f])
      assert_equal false, config[:check_host_ip]
    end
  end

  def test_load_with_regex_chars
    data = '
      Host |
        Port 1234
        Compression no
    '
    with_config_from_data data do |f|
      config = Net::SSH::Config.load(f, "test.host")
      assert_nil config['port']
      config = Net::SSH::Config.load(f, "|")
      assert_equal 1234, config['port']
    end
  end

  def test_for_should_load_all_files_and_translate_to_net_ssh_options
    config = Net::SSH::Config.for("test.host", [config(:exact_match), config(:wild_cards)])
    assert_equal 1234, config[:port]
    assert config[:compression]
    assert config[:forward_agent]
    assert_equal %w(~/.ssh/id_dsa), config[:keys]
    assert_equal %w(~/.ssh/id_rsa-my-cert.pub ~/.ssh/cert.pub), config[:keycerts]
    assert !config.key?(:rekey_limit)
  end

  def test_load_with_no_host
    config = Net::SSH::Config.load(config(:nohost), "test.host")
    assert_equal %w(~/.ssh/id_dsa ~/.ssh/id_rsa), config['identityfile']
    assert_equal 1985, config['port']
  end

  def test_load_with_multiple_hosts
    config = Net::SSH::Config.load(config(:multihost), "test.host")
    assert config['compression']
    assert_equal '2G', config['rekeylimit']
    assert_equal 1980, config['port']
  end

  def test_load_with_multiple_hosts_and_config_should_match_for_both
    aconfig = Net::SSH::Config.load(config(:multihost), "test.host")
    bconfig = Net::SSH::Config.load(config(:multihost), "other.host")
    assert_equal aconfig['port'], bconfig['port']
    assert_equal aconfig['compression'], bconfig['compression']
    assert_equal aconfig['rekeylimit'], bconfig['rekeylimit']
  end

  def test_load_should_parse_equal_sign_delimiters
    config = Net::SSH::Config.load(config(:eqsign), "test.test")
    assert config['compression']
    assert_equal 1234, config['port']
  end

  def test_translate_should_correctly_translate_from_openssh_to_net_ssh_names
    open_ssh = {
      'bindaddress' => "127.0.0.1",
      'ciphers' => "a,b,c",
      'compression' => true,
      'compressionlevel' => 6,
      'connecttimeout' => 100,
      'forwardagent' => true,
      'hostbasedauthentication' => true,
      'hostkeyalgorithms' => "d,e,f",
      'identityfile' => %w(g h i),
      'macs' => "j,k,l",
      'certificatefile' => %w(m n o),
      'passwordauthentication' => true,
      'port' => 1234,
      'pubkeyauthentication' => true,
      'rekeylimit' => 1024,
      'sendenv' => "LC_*",
      'setenv' => 'foo="bar" baz=whale cat="black hole"',
      'numberofpasswordprompts' => '123',
      'serveraliveinterval' => '2',
      'serveralivecountmax' => '4',
      'fingerprinthash' => 'MD5',
      'userknownhostsfile' => '/dev/null',
      'stricthostkeychecking' => false
    }

    net_ssh = Net::SSH::Config.translate(open_ssh)

    assert_equal %w(a b c), net_ssh[:encryption]
    assert_equal true,      net_ssh[:compression]
    assert_equal 6,         net_ssh[:compression_level]
    assert_equal 100,       net_ssh[:timeout]
    assert_equal true,      net_ssh[:forward_agent]
    assert_equal %w(hostbased keyboard-interactive none password publickey), net_ssh[:auth_methods].sort
    assert_equal %w(d e f), net_ssh[:host_key]
    assert_equal %w(g h i), net_ssh[:keys]
    assert_equal %w(j k l), net_ssh[:hmac]
    assert_equal %w(m n o), net_ssh[:keycerts]
    assert_equal 1234,      net_ssh[:port]
    assert_equal 1024,      net_ssh[:rekey_limit]
    assert_equal "127.0.0.1", net_ssh[:bind_address]
    assert_equal [/^LC_.*$/], net_ssh[:send_env]
    assert_equal Hash['foo' => 'bar', 'baz' => 'whale', 'cat' => 'black hole'], net_ssh[:set_env]
    assert_equal 123,       net_ssh[:number_of_password_prompts]
    assert_equal 4,         net_ssh[:keepalive_maxcount]
    assert_equal 2,         net_ssh[:keepalive_interval]
    assert_equal 'MD5',     net_ssh[:fingerprint_hash]
    assert_equal true,      net_ssh[:keepalive]
    assert_equal '/dev/null', net_ssh[:user_known_hosts_file]
    assert_equal :never, net_ssh[:verify_host_key]
  end

  def test_translate_should_turn_on_host_key_verification
    open_ssh = { 'stricthostkeychecking' => true }

    net_ssh = Net::SSH::Config.translate(open_ssh)

    assert_equal :always, net_ssh[:verify_host_key]
  end

  def test_translate_should_accept_new_host_key
    open_ssh = { 'stricthostkeychecking' => 'accept-new' }

    net_ssh = Net::SSH::Config.translate(open_ssh)

    assert_equal :accept_new, net_ssh[:verify_host_key]
  end

  def test_translate_should_turn_off_authentication_methods
    open_ssh = {
      'hostbasedauthentication' => false,
      'passwordauthentication' => false,
      'pubkeyauthentication' => false,
      'challengeresponseauthentication' => false,
      'kbdinteractiveauthentication' => false
    }

    net_ssh = Net::SSH::Config.translate(open_ssh)

    assert_equal %w(none), net_ssh[:auth_methods].sort
  end

  def test_translate_should_turn_on_authentication_methods
    open_ssh = {
      'hostbasedauthentication' => true,
      'passwordauthentication' => true,
      'pubkeyauthentication' => true,
      'challengeresponseauthentication' => true,
      'kbdinteractiveauthentication' => true
    }

    net_ssh = Net::SSH::Config.translate(open_ssh)

    assert_equal %w(hostbased keyboard-interactive none password publickey), net_ssh[:auth_methods].sort
  end

  def test_translate_should_not_disable_keyboard_interactive_when_challange_or_keyboardinterective_is_on
    open_ssh = { 'kbdinteractiveauthentication' => false }
    net_ssh = Net::SSH::Config.translate(open_ssh)
    assert_equal %w(keyboard-interactive none password publickey), net_ssh[:auth_methods].sort

    open_ssh = {
      'challengeresponseauthentication' => false
    }
    net_ssh = Net::SSH::Config.translate(open_ssh)
    assert_equal %w(keyboard-interactive none password publickey), net_ssh[:auth_methods].sort
  end

  def test_should_ddisable_keyboard_interactive_when_challeng_and_keyboardinteractive_is_off
    open_ssh = {
      'challengeresponseauthentication' => false,
      'kbdinteractiveauthentication' => false
    }

    net_ssh = Net::SSH::Config.translate(open_ssh)
    assert_equal %w(none password publickey), net_ssh[:auth_methods].sort
  end

  def test_for_should_turn_off_authentication_methods
    config = Net::SSH::Config.for("test.host", [config(:empty), config(:auth_off), config(:auth_on)])
    assert_equal %w(none), config[:auth_methods].sort
  end

  def test_for_should_turn_on_authentication_methods
    config = Net::SSH::Config.for("test.host", [config(:empty), config(:auth_on), config(:auth_off)])
    assert_equal %w(hostbased keyboard-interactive none password publickey), config[:auth_methods].sort
  end

  def test_configuration_for_when_HOME_is_null_should_not_raise
    with_home_env(nil) do
      with_restored_default_files do
        Net::SSH.configuration_for("test.host", true)
      end
    end
  end

  def test_config_for_when_HOME_is_null_should_not_raise
    with_home_env(nil) do
      with_restored_default_files do
        Net::SSH::Config.for("test.host")
      end
    end
  end

  def test_load_with_plus_sign_hosts
    config = Net::SSH::Config.load(config(:host_plus), "test.host")
    assert config['compression']
  end

  def test_load_with_numeric_host
    config = Net::SSH::Config.load(config(:numeric_host), "1234")
    assert config['compression']
    assert_equal '2G', config['rekeylimit']
    assert_equal 1980, config['port']
  end

  def test_load_wildcar_with_substitutes
    config = Net::SSH::Config.load(config(:substitutes), "toto")
    net_ssh = Net::SSH::Config.translate(config)
    assert_equal 'toto', net_ssh[:host_name]
  end

  def test_load_sufix_with_substitutes
    config = Net::SSH::Config.load(config(:substitutes), "test")
    net_ssh = Net::SSH::Config.translate(config)
    assert_equal 'test.sufix', net_ssh[:host_name]
  end

  def test_load_prefix_and_sufix_with_substitutes
    config = Net::SSH::Config.load(config(:substitutes), "1234")
    net_ssh = Net::SSH::Config.translate(config)
    assert_equal 'prefix.1234.sufix', net_ssh[:host_name]
  end

  def test_load_with_send_env
    config = Net::SSH::Config.load(config(:send_env), "1234")
    net_ssh = Net::SSH::Config.translate(config)
    assert_equal [/^GIT_.*$/, /^LANG$/, /^LC_.*$/], net_ssh[:send_env]
  end

  def test_load_with_set_env
    config = Net::SSH::Config.load(config(:set_env), '1234')
    net_ssh = Net::SSH::Config.translate(config)
    assert_equal Hash['foo' => 'bar', 'baz' => 'whale', 'cat' => 'black hole'], net_ssh[:set_env]
  end

  def test_load_with_remote_user
    config = Net::SSH::Config.load(config(:proxy_remote_user), "behind-proxy")
    net_ssh = Net::SSH::Config.translate(config)
    assert net_ssh[:proxy]
  end

  def test_load_with_proxy_jump
    config = Net::SSH::Config.load(config(:proxy_jump), "behind-proxy")
    net_ssh = Net::SSH::Config.translate(config)
    assert net_ssh[:proxy]
  end

  def test_load_with_identity_agent
    config = Net::SSH::Config.load(config(:identity_agent), "use-agent")
    net_ssh = Net::SSH::Config.translate(config)
    assert_equal "/path/to/auth.sock", net_ssh[:identity_agent]
  end

  def test_load_with_include_keyword
    config = Net::SSH::Config.load(config(:include), "xyz")
    net_ssh = Net::SSH::Config.translate(config)
    assert_equal 'example.com', net_ssh[:host_name]
    assert_equal 'foo', net_ssh[:user]
    assert_equal 2345, net_ssh[:port]
    assert_equal true, net_ssh[:compression]
    assert net_ssh[:keys_only]
    assert_equal %w(~/.ssh/id.pem ~/.ssh/id2.pem ~/.ssh/id3.pem), net_ssh[:keys]
  end

  def test_default_files_not_mutable
    original_default_files = Net::SSH::Config.default_files.clone

    default_files = Net::SSH::Config.default_files
    default_files.push('garbage')

    assert_equal(original_default_files, Net::SSH::Config.default_files)
  end

  def test_default_auth_methods_not_mutable
    original_default_auth_methods = Net::SSH::Config.default_auth_methods.clone

    default_auth_methods = Net::SSH::Config.default_auth_methods
    default_auth_methods.push('garbage')

    assert_equal(original_default_auth_methods, Net::SSH::Config.default_auth_methods)
  end

  def test_load_with_match_block
    config = Net::SSH::Config.load(config(:match), "test.host")
    net_ssh = Net::SSH::Config.translate(config)
    assert_equal true, net_ssh[:forward_agent]
    assert_equal true, net_ssh[:compression]
    assert_equal 22, net_ssh[:port]
  end

  def test_load_with_match_block_with_host
    data = '
      Match Host foo
        Port 1234
        Compression no
    '
    with_config_from_data data do |f|
      config = Net::SSH::Config.load(f, "bar")
      assert_nil config['port']
      config = Net::SSH::Config.load(f, "foo")
      assert_equal 1234, config['port']
    end
  end

  def test_load_with_match_block_with_hosts
    data = '
      Match Host foo,bar
        Port 1234
        Compression no
    '
    with_config_from_data data do |f|
      config = Net::SSH::Config.load(f, "bar2")
      assert_nil config['port']
      config = Net::SSH::Config.load(f, "bar")
      assert_equal 1234, config['port']
      config = Net::SSH::Config.load(f, "foo")
      assert_equal 1234, config['port']
    end
  end

  def test_load_with_match_block_with_hosts_wildcard
    data = '
      Match Host foo,*.baz.com
        Port 1234
        Compression no
    '
    with_config_from_data data do |f|
      config = Net::SSH::Config.load(f, "bar2")
      assert_nil config['port']
      config = Net::SSH::Config.load(f, "bbaz.com")
      assert_nil config['port']
      config = Net::SSH::Config.load(f, "bar.baz.com")
      assert_equal 1234, config['port']
      config = Net::SSH::Config.load(f, "foo")
      assert_equal 1234, config['port']
    end
  end

  def test_load_with_match_block_with_multi_space_separated_hosts_condition
    # Extra tabs are thrown in between, for good measure
    data = '
      Match host 		 foo,*.baz.com
        Port 1234
        Compression no
    '
    with_config_from_data data do |f|
      config = Net::SSH::Config.load(f, "bar2")
      assert_nil config['port']
      config = Net::SSH::Config.load(f, "bbaz.com")
      assert_nil config['port']
      config = Net::SSH::Config.load(f, "bar.baz.com")
      assert_equal 1234, config['port']
      config = Net::SSH::Config.load(f, "foo")
      assert_equal 1234, config['port']
    end
  end

  def test_load_with_match_block_with_quoted_hosts_condition
    data = '
      Match host "foo,*.baz.com"
        Port 1234
        Compression no
    '
    with_config_from_data data do |f|
      config = Net::SSH::Config.load(f, "bar2")
      assert_nil config['port']
      config = Net::SSH::Config.load(f, "bbaz.com")
      assert_nil config['port']
      config = Net::SSH::Config.load(f, "bar.baz.com")
      assert_equal 1234, config['port']
      config = Net::SSH::Config.load(f, "foo")
      assert_equal 1234, config['port']
    end
  end

  def test_load_with_match_block_with_equal_signed_hosts_condition
    data = '
      Match host=foo,*.baz.com
        Port 1234
        Compression no
    '
    with_config_from_data data do |f|
      config = Net::SSH::Config.load(f, "bar2")
      assert_nil config['port']
      config = Net::SSH::Config.load(f, "bbaz.com")
      assert_nil config['port']
      config = Net::SSH::Config.load(f, "bar.baz.com")
      assert_equal 1234, config['port']
      config = Net::SSH::Config.load(f, "foo")
      assert_equal 1234, config['port']
    end
  end

  def test_load_with_match_block_with_quoted_equal_signed_hosts_condition
    data = '
      Match host="foo,*.baz.com"
        Port 1234
        Compression no
    '
    with_config_from_data data do |f|
      config = Net::SSH::Config.load(f, "bar2")
      assert_nil config['port']
      config = Net::SSH::Config.load(f, "bbaz.com")
      assert_nil config['port']
      config = Net::SSH::Config.load(f, "bar.baz.com")
      assert_equal 1234, config['port']
      config = Net::SSH::Config.load(f, "foo")
      assert_equal 1234, config['port']
    end
  end

  def test_load_with_match_block_with_whitespace_separated_equal_signed_hosts_condition
    data = '
      Match host = foo,*.baz.com
        Port 1234
        Compression no
    '
    with_config_from_data data do |f|
      config = Net::SSH::Config.load(f, "bar2")
      assert_nil config['port']
      config = Net::SSH::Config.load(f, "bbaz.com")
      assert_nil config['port']
      config = Net::SSH::Config.load(f, "bar.baz.com")
      assert_equal 1234, config['port']
      config = Net::SSH::Config.load(f, "foo")
      assert_equal 1234, config['port']
    end
  end

  def test_load_with_match_block_with_multi_equal_signed_hosts_condition
    data = '
      Match host==foo,*.baz.com
        Port 1234
        Compression no
    '
    with_config_from_data data do |f|
      config = Net::SSH::Config.load(f, "bar2")
      assert_nil config['port']
      config = Net::SSH::Config.load(f, "bbaz.com")
      assert_nil config['port']
      config = Net::SSH::Config.load(f, "bar.baz.com")
      assert_nil config['port']
      config = Net::SSH::Config.load(f, "foo")
      assert_nil config['port']
    end
  end

  def test_load_with_multiple_hosts_criteria
    data = '
      Match host *.baz.com host !bar.baz.com
        Port 1234
    '
    with_config_from_data data do |f|
      config = Net::SSH::Config.load(f, "bar2")
      assert_nil config['port']
      config = Net::SSH::Config.load(f, "bbaz.com")
      assert_nil config['port']
      config = Net::SSH::Config.load(f, "bar.baz.com")
      assert_nil config['port']
      config = Net::SSH::Config.load(f, "meh.baz.com")
      assert_equal 1234, config['port']
    end
  end

  def test_mix_of_proxy_command_and_proxy_jump
    %w(test.mix1 test.mix2).each do |host|
      config = Net::SSH::Config.for(host, [config(:proxy_command_proxy_jump_mix)])

      proxy = config[:proxy]
      proxy.build_proxy_command_equivalent if proxy.is_a? Net::SSH::Proxy::Jump

      assert_equal 'ssh -W %h:%p jump2', config[:proxy].command_line_template
    end
  end

  private

  def with_home_env(value, &block)
    env_home_before = ENV['HOME']
    begin
      ENV['HOME'] = value
      yield
    ensure
      ENV['HOME'] = env_home_before
    end
  end

  def config(name)
    "test/configs/#{name}"
  end

  def with_config_from_data(data, &block)
    Tempfile.open('config') do |f|
      f.write(data)
      f.close
      yield(f.path)
    end
  end
end
