require 'helper'
require 'sshkit'

module SSHKit
  class TestCommand < UnitTest

    def test_maps_a_command
      c = Command.new('example')
      assert_equal '/usr/bin/env example', c.to_command
    end

    def test_not_mapping_a_builtin
      %w{if test time}.each do |builtin|
        c = Command.new(builtin)
        assert_equal builtin, c.to_command
      end
    end

    def test_multiple_lines_are_stripped_of_extra_space_and_joined_by_semicolons
      c = Command.new <<-EOHEREDOC
        if test ! -d /var/log; then
          echo "Example"
        fi
      EOHEREDOC
      assert_equal "if test ! -d /var/log; then; echo \"Example\"; fi", c.to_command
    end

    def test_leading_and_trailing_space_is_stripped
      c = Command.new(" echo hi ")
      assert_equal "echo hi", c.to_command
    end

    def test_including_the_env
      SSHKit.config = nil
      c = Command.new(:rails, 'server', env: {rails_env: :production})
      assert_equal %{( export RAILS_ENV="production" ; /usr/bin/env rails server )}, c.to_command
    end

    def test_including_the_env_with_multiple_keys
      SSHKit.config = nil
      c = Command.new(:rails, 'server', env: {rails_env: :production, foo: 'bar'})
      assert_equal %{( export RAILS_ENV="production" FOO="bar" ; /usr/bin/env rails server )}, c.to_command
    end

    def test_including_the_env_with_string_keys
      SSHKit.config = nil
      c = Command.new(:rails, 'server', env: {'FACTER_env' => :production, foo: 'bar'})
      assert_equal %{( export FACTER_env="production" FOO="bar" ; /usr/bin/env rails server )}, c.to_command
    end

    def test_double_quotes_are_escaped_in_env
      SSHKit.config = nil
      c = Command.new(:rails, 'server', env: {foo: 'asdf"hjkl'})
      assert_equal %{( export FOO="asdf\\"hjkl" ; /usr/bin/env rails server )}, c.to_command
    end

    def test_percentage_symbol_handled_in_env
      SSHKit.config = nil
      c = Command.new(:rails, 'server', env: {foo: 'asdf%hjkl'}, user: "anotheruser")
      assert_equal %{( export FOO="asdf%hjkl" ; sudo -u anotheruser FOO=\"asdf%hjkl\" -- sh -c /usr/bin/env\\ rails\\ server )}, c.to_command
    end

    def test_including_the_env_doesnt_addressively_escape
      SSHKit.config = nil
      c = Command.new(:rails, 'server', env: {path: '/example:$PATH'})
      assert_equal %{( export PATH="/example:$PATH" ; /usr/bin/env rails server )}, c.to_command
    end

    def test_global_env
      SSHKit.config = nil
      SSHKit.config.default_env = { default: 'env' }
      c = Command.new(:rails, 'server', env: {})
      assert_equal %{( export DEFAULT="env" ; /usr/bin/env rails server )}, c.to_command
    end

    def test_default_env_is_overwritten_with_locally_defined
      SSHKit.config.default_env = { foo: 'bar', over: 'under' }
      c = Command.new(:rails, 'server', env: { over: 'write'})
      assert_equal %{( export FOO="bar" OVER="write" ; /usr/bin/env rails server )}, c.to_command
    end

    def test_working_in_a_given_directory
      c = Command.new(:ls, '-l', in: "/opt/sites")
      assert_equal "cd /opt/sites && /usr/bin/env ls -l", c.to_command
    end

    def test_working_in_home_directory
      c = Command.new(:ls, '-l', in: "~/sites")
      assert_equal "cd ~/sites && /usr/bin/env ls -l", c.to_command
    end

    def test_working_in_a_given_weird_directory
      c = Command.new(:ls, '-l', in: "/opt/sites and stuff")
      assert_equal "cd /opt/sites\\ and\\ stuff && /usr/bin/env ls -l", c.to_command
    end

    def test_working_in_a_given_directory_with_env
      c = Command.new(:ls, '-l', in: "/opt/sites", env: {a: :b})
      assert_equal %{cd /opt/sites && ( export A="b" ; /usr/bin/env ls -l )}, c.to_command
    end

    def test_having_a_host_passed
      refute Command.new(:date).host
      assert Command.new(:date, host: :foo)
      assert_equal :foo, Command.new(host: :foo).host
    end

    def test_working_as_a_given_user
      c = Command.new(:whoami, user: :anotheruser)
      assert_equal "sudo -u anotheruser -- sh -c /usr/bin/env\\ whoami", c.to_command
    end

    def test_working_as_a_given_weird_user
      c = Command.new(:whoami, user: "mr space |")
      assert_equal "sudo -u mr\\ space\\ \\| -- sh -c /usr/bin/env\\ whoami", c.to_command
    end

    def test_working_as_a_given_group
      c = Command.new(:whoami, group: :devvers)
      assert_equal 'sg devvers -c /usr/bin/env\\ whoami', c.to_command
    end

    def test_working_as_a_given_weird_group
      c = Command.new(:whoami, group: "space | group")
      assert_equal "sg space\\ \\|\\ group -c /usr/bin/env\\ whoami", c.to_command
    end

    def test_working_as_a_given_user_and_group
      c = Command.new(:whoami, user: :anotheruser, group: :devvers)
      assert_equal %Q(sudo -u anotheruser -- sh -c sg\\ devvers\\ -c\\ /usr/bin/env\\\\\\ whoami), c.to_command
    end

    def test_umask
      SSHKit.config.umask = '007'
      c = Command.new(:touch, 'somefile')
      assert_equal "umask 007 && /usr/bin/env touch somefile", c.to_command
    end

    def test_umask_with_working_directory
      SSHKit.config.umask = '007'
      c = Command.new(:touch, 'somefile', in: '/opt')
      assert_equal "cd /opt && umask 007 && /usr/bin/env touch somefile", c.to_command
    end

    def test_umask_with_working_directory_and_user
      SSHKit.config.umask = '007'
      c = Command.new(:touch, 'somefile', in: '/var', user: 'alice')
      assert_equal "cd /var && umask 007 && sudo -u alice -- sh -c /usr/bin/env\\ touch\\ somefile", c.to_command
    end

    def test_umask_with_env_and_working_directory_and_user
      SSHKit.config.umask = '007'
      c = Command.new(:touch, 'somefile', user: 'bob', env: {a: 'b'}, in: '/var')
      assert_equal %{cd /var && umask 007 && ( export A="b" ; sudo -u bob A="b" -- sh -c /usr/bin/env\\ touch\\ somefile )}, c.to_command
    end

    def test_verbosity_defaults_to_logger_info
      assert_equal Logger::INFO, Command.new(:ls).verbosity
    end

    def test_overriding_verbosity_level_with_a_constant
      assert_equal Logger::DEBUG, Command.new(:ls, verbosity: Logger::DEBUG).verbosity
    end

    def test_overriding_verbosity_level_with_a_symbol
      assert_equal Logger::DEBUG, Command.new(:ls, verbosity: :debug).verbosity
    end

    def test_complete?
      c = Command.new(:whoami, raise_on_non_zero_exit: false)
      refute c.complete?
      c.exit_status = 1
      assert c.complete?
      c.exit_status = 0
      assert c.complete?
    end

    def test_successful?
      c = Command.new(:whoami)
      refute c.successful?
      refute c.success?
      c.exit_status = 0
      assert c.successful?
      assert c.success?
    end

    def test_failure?
      c = Command.new(:whoami, raise_on_non_zero_exit: false)
      refute c.failure?
      refute c.failed?
      c.exit_status = 1
      assert c.failure?
      assert c.failed?
      c.exit_status = 127
      assert c.failure?
      assert c.failed?
    end

    def test_on_stdout
      c = Command.new(:whoami)
      c.on_stdout(nil, "test\n")
      c.on_stdout(nil, 'test2')
      c.on_stdout(nil, 'test3')
      assert_equal "test\ntest2test3", c.full_stdout
    end

    def test_on_stderr
      c = Command.new(:whoami)
      c.on_stderr(nil, 'test')
      assert_equal 'test', c.full_stderr
    end

    def test_deprecated_stdtream_accessors
      deprecation_out = ''
      SSHKit.config.deprecation_output = deprecation_out

      c = Command.new(:whoami)
      c.stdout='a test'
      assert_equal('a test', c.stdout)
      c.stderr='another test'
      assert_equal('another test', c.stderr)
      deprecation_lines = deprecation_out.lines.to_a

      assert_equal 8, deprecation_lines.size
      assert_equal(
        '[Deprecated] The stdout= method on Command is deprecated. ' +
        "The @stdout attribute will be removed in a future release.\n",
        deprecation_lines[0])
      assert_equal(
        '[Deprecated] The stdout method on Command is deprecated. ' +
        "The @stdout attribute will be removed in a future release. Use full_stdout() instead.\n",
        deprecation_lines[2])

      assert_equal(
        '[Deprecated] The stderr= method on Command is deprecated. ' +
        "The @stderr attribute will be removed in a future release.\n",
        deprecation_lines[4])
      assert_equal(
        '[Deprecated] The stderr method on Command is deprecated. ' +
        "The @stderr attribute will be removed in a future release. Use full_stderr() instead.\n",
        deprecation_lines[6])
    end

    def test_setting_exit_status
      c = Command.new(:whoami, raise_on_non_zero_exit: false)
      assert_nil c.exit_status
      assert c.exit_status = 1
      assert_equal 1, c.exit_status
    end

    def test_command_has_a_guid
      assert Command.new(:whosmi).uuid
    end

    def test_wont_take_no_args
      assert_raises ArgumentError do
        Command.new
      end
    end

    def test_command_raises_command_failed_error_when_non_zero_exit
      error = assert_raises SSHKit::Command::Failed do
        Command.new(:whoami).exit_status = 1
      end
      assert_equal "whoami exit status: 1\nwhoami stdout: Nothing written\nwhoami stderr: Nothing written\n", error.message
    end

    def test_shares_same_uuid_before_and_after_redaction
      command = Command.new(:whoami)
      command_with_redaction = command.with_redaction
      assert_equal command.uuid, command_with_redaction.uuid, "UUID should be stable before and after redaction"
    end
  end
end
