"""
Tests for bash_security module.

Tests are organized by validator function, matching the npm test structure.
"""

import pytest

from src.bash_security import (
    SecurityBehavior,
    SecurityResult,
    ValidationContext,
    bash_command_is_safe,
    check_shell_security,
    extract_quoted_content,
    get_destructive_command_warning,
    has_unescaped_char,
    interpret_command_result,
    is_command_read_only,
    split_command,
    strip_safe_redirections,
    validate_backslash_escaped_operators,
    validate_backslash_escaped_whitespace,
    validate_brace_expansion,
    validate_carriage_return,
    validate_comment_quote_desync,
    validate_control_characters,
    validate_dangerous_patterns,
    validate_dangerous_variables,
    validate_empty,
    validate_git_commit,
    validate_ifs_injection,
    validate_incomplete_commands,
    validate_jq_command,
    validate_mid_word_hash,
    validate_newlines,
    validate_obfuscated_flags,
    validate_proc_environ_access,
    validate_quoted_newline,
    validate_redirections,
    validate_shell_metacharacters,
    validate_unicode_whitespace,
    validate_zsh_dangerous_commands,
)


# ---- Helper to build a context ----

def _ctx(cmd: str) -> ValidationContext:
    """Build a ValidationContext for the given command."""
    base = cmd.strip().split()[0] if cmd.strip() else ''
    with_dq, fully_unq, keep_qc = extract_quoted_content(cmd)
    return ValidationContext(
        original_command=cmd,
        base_command=base,
        unquoted_content=with_dq,
        fully_unquoted_content=strip_safe_redirections(fully_unq),
        fully_unquoted_pre_strip=fully_unq,
        unquoted_keep_quote_chars=keep_qc,
    )


# ===========================================================================
# extract_quoted_content
# ===========================================================================

class TestExtractQuotedContent:
    def test_no_quotes(self):
        dq, full, kqc = extract_quoted_content('echo hello')
        assert dq == 'echo hello'
        assert full == 'echo hello'

    def test_single_quotes_stripped(self):
        dq, full, kqc = extract_quoted_content("echo 'hello world'")
        assert 'hello world' not in full
        assert 'echo' in full

    def test_double_quotes_in_dq_output(self):
        dq, full, kqc = extract_quoted_content('echo "hello world"')
        assert 'hello world' in dq  # double-quoted content preserved in dq
        assert 'hello world' not in full  # but stripped in fully_unquoted

    def test_escape_handling(self):
        dq, full, kqc = extract_quoted_content('echo \\$HOME')
        assert '$HOME' in full

    def test_keep_quote_chars(self):
        _, _, kqc = extract_quoted_content("echo 'x'#")
        assert "'" in kqc  # quote chars preserved


# ===========================================================================
# strip_safe_redirections
# ===========================================================================

class TestStripSafeRedirections:
    def test_dev_null_output(self):
        assert '>/dev/null' not in strip_safe_redirections('cmd > /dev/null')

    def test_stderr_redirect(self):
        assert '2>&1' not in strip_safe_redirections('cmd 2>&1')

    def test_dev_null_input(self):
        assert '</dev/null' not in strip_safe_redirections('cmd < /dev/null')

    def test_preserves_other_redirections(self):
        result = strip_safe_redirections('cmd > output.txt')
        assert '> output.txt' in result


# ===========================================================================
# has_unescaped_char
# ===========================================================================

class TestHasUnescapedChar:
    def test_unescaped_backtick(self):
        assert has_unescaped_char('echo `date`', '`') is True

    def test_escaped_backtick(self):
        assert has_unescaped_char('echo \\`safe\\`', '`') is False

    def test_double_backslash_then_backtick(self):
        # \\\` → \\ (literal backslash) + ` (unescaped)
        assert has_unescaped_char('test\\\\`date`', '`') is True

    def test_no_match(self):
        assert has_unescaped_char('echo hello', '`') is False


# ===========================================================================
# split_command
# ===========================================================================

class TestSplitCommand:
    def test_simple(self):
        assert split_command('echo hello') == ['echo hello']

    def test_semicolon(self):
        assert split_command('echo a; echo b') == ['echo a', 'echo b']

    def test_and_and(self):
        assert split_command('cmd1 && cmd2') == ['cmd1', 'cmd2']

    def test_pipe(self):
        assert split_command('cat file | grep pattern') == ['cat file', 'grep pattern']

    def test_or_or(self):
        assert split_command('cmd1 || cmd2') == ['cmd1', 'cmd2']

    def test_quotes_preserved(self):
        result = split_command("echo 'a; b'")
        assert len(result) == 1  # semicolon inside quotes not split

    def test_complex(self):
        result = split_command('cd /tmp && echo hi; ls | head')
        assert len(result) == 4


# ===========================================================================
# validate_empty
# ===========================================================================

class TestValidateEmpty:
    def test_empty(self):
        assert validate_empty(_ctx('')).behavior == SecurityBehavior.ALLOW

    def test_whitespace_only(self):
        assert validate_empty(_ctx('   ')).behavior == SecurityBehavior.ALLOW

    def test_non_empty(self):
        assert validate_empty(_ctx('ls')).behavior == SecurityBehavior.PASSTHROUGH


# ===========================================================================
# validate_control_characters
# ===========================================================================

class TestValidateControlCharacters:
    def test_null_byte(self):
        result = validate_control_characters(_ctx('echo\x00hello'))
        assert result.behavior == SecurityBehavior.ASK

    def test_bell(self):
        result = validate_control_characters(_ctx('echo\x07hello'))
        assert result.behavior == SecurityBehavior.ASK

    def test_clean_command(self):
        result = validate_control_characters(_ctx('echo hello'))
        assert result.behavior == SecurityBehavior.PASSTHROUGH

    def test_tab_allowed(self):
        result = validate_control_characters(_ctx('echo\thello'))
        assert result.behavior == SecurityBehavior.PASSTHROUGH

    def test_newline_allowed(self):
        result = validate_control_characters(_ctx('echo\nhello'))
        assert result.behavior == SecurityBehavior.PASSTHROUGH


# ===========================================================================
# validate_incomplete_commands
# ===========================================================================

class TestValidateIncompleteCommands:
    def test_starts_with_tab(self):
        result = validate_incomplete_commands(_ctx('\techo hello'))
        assert result.behavior == SecurityBehavior.ASK

    def test_starts_with_dash(self):
        result = validate_incomplete_commands(_ctx('-rf /'))
        assert result.behavior == SecurityBehavior.ASK

    def test_starts_with_operator(self):
        result = validate_incomplete_commands(_ctx('&& echo hello'))
        assert result.behavior == SecurityBehavior.ASK
        result = validate_incomplete_commands(_ctx('; echo hello'))
        assert result.behavior == SecurityBehavior.ASK

    def test_normal_command(self):
        result = validate_incomplete_commands(_ctx('ls -la'))
        assert result.behavior == SecurityBehavior.PASSTHROUGH


# ===========================================================================
# validate_git_commit
# ===========================================================================

class TestValidateGitCommit:
    def test_not_git(self):
        result = validate_git_commit(_ctx('echo hello'))
        assert result.behavior == SecurityBehavior.PASSTHROUGH

    def test_simple_commit(self):
        result = validate_git_commit(_ctx("git commit -m 'initial commit'"))
        assert result.behavior == SecurityBehavior.ALLOW

    def test_double_quoted_commit(self):
        result = validate_git_commit(_ctx('git commit -m "fix bug"'))
        assert result.behavior == SecurityBehavior.ALLOW

    def test_commit_with_substitution(self):
        result = validate_git_commit(_ctx('git commit -m "$(date)"'))
        assert result.behavior == SecurityBehavior.ASK

    def test_commit_with_backtick(self):
        result = validate_git_commit(_ctx('git commit -m "`date`"'))
        assert result.behavior == SecurityBehavior.ASK

    def test_commit_with_chained_commands(self):
        result = validate_git_commit(_ctx("git commit -m 'msg'; rm -rf /"))
        # Should passthrough (not early-allow) due to ; in remainder
        assert result.behavior != SecurityBehavior.ALLOW

    def test_commit_with_backslash(self):
        result = validate_git_commit(_ctx('git commit -m "test\\"msg"'))
        assert result.behavior == SecurityBehavior.PASSTHROUGH


# ===========================================================================
# validate_jq_command
# ===========================================================================

class TestValidateJqCommand:
    def test_not_jq(self):
        assert validate_jq_command(_ctx('echo hi')).behavior == SecurityBehavior.PASSTHROUGH

    def test_jq_system(self):
        result = validate_jq_command(_ctx('jq "system(\"rm -rf /\")"'))
        assert result.behavior == SecurityBehavior.ASK

    def test_jq_from_file(self):
        result = validate_jq_command(_ctx('jq -f evil.jq'))
        assert result.behavior == SecurityBehavior.ASK

    def test_jq_slurpfile(self):
        result = validate_jq_command(_ctx('jq --slurpfile x data.json'))
        assert result.behavior == SecurityBehavior.ASK

    def test_safe_jq(self):
        result = validate_jq_command(_ctx('jq ".name" data.json'))
        assert result.behavior == SecurityBehavior.PASSTHROUGH


# ===========================================================================
# validate_obfuscated_flags
# ===========================================================================

class TestValidateObfuscatedFlags:
    def test_ansi_c_quoting(self):
        result = validate_obfuscated_flags(_ctx("find . $'-exec' evil"))
        assert result.behavior == SecurityBehavior.ASK

    def test_locale_quoting(self):
        result = validate_obfuscated_flags(_ctx('find . $"-exec" evil'))
        assert result.behavior == SecurityBehavior.ASK

    def test_echo_safe(self):
        result = validate_obfuscated_flags(_ctx("echo $'hello'"))
        assert result.behavior == SecurityBehavior.PASSTHROUGH

    def test_empty_quotes_before_dash(self):
        result = validate_obfuscated_flags(_ctx("find . '' -exec evil"))
        assert result.behavior == SecurityBehavior.ASK

    def test_quoted_flag(self):
        result = validate_obfuscated_flags(_ctx('find . "-exec" rm {} ;'))
        assert result.behavior == SecurityBehavior.ASK

    def test_normal_command(self):
        result = validate_obfuscated_flags(_ctx('ls -la'))
        assert result.behavior == SecurityBehavior.PASSTHROUGH

    def test_three_consecutive_quotes(self):
        result = validate_obfuscated_flags(_ctx("find . '''exec"))
        assert result.behavior == SecurityBehavior.ASK


# ===========================================================================
# validate_shell_metacharacters
# ===========================================================================

class TestValidateShellMetacharacters:
    def test_semicolon_in_quotes(self):
        result = validate_shell_metacharacters(_ctx('echo "a;b"'))
        # unquoted_content (with_double_quotes) has the ; inside
        assert result.behavior == SecurityBehavior.PASSTHROUGH or result.behavior == SecurityBehavior.ASK

    def test_find_name_with_pipe(self):
        # Single-quoted pipe is stripped entirely from unquoted content → safe
        ctx = _ctx("find . -name '|evil'")
        result = validate_shell_metacharacters(ctx)
        assert result.behavior == SecurityBehavior.PASSTHROUGH

    def test_double_quoted_metachar(self):
        # Check that we catch metacharacters in unquoted positions
        # The npm version checks the double-quote-retained string for
        # quoted metacharacters, but we strip quote chars. So we test
        # the actual dangerous case: unquoted semicolon
        ctx = _ctx('find . -name evil; rm -rf /')
        # This won't be caught by this specific validator (it looks for
        # metacharacters INSIDE quoted args, not command separators)
        result = validate_shell_metacharacters(ctx)
        assert result.behavior == SecurityBehavior.PASSTHROUGH

    def test_clean_command(self):
        assert validate_shell_metacharacters(_ctx('ls -la')).behavior == SecurityBehavior.PASSTHROUGH


# ===========================================================================
# validate_dangerous_variables
# ===========================================================================

class TestValidateDangerousVariables:
    def test_variable_in_pipe(self):
        result = validate_dangerous_variables(_ctx('$CMD | grep x'))
        assert result.behavior == SecurityBehavior.ASK

    def test_variable_in_redirect(self):
        result = validate_dangerous_variables(_ctx('echo x > $FILE'))
        assert result.behavior == SecurityBehavior.ASK

    def test_safe_variable(self):
        result = validate_dangerous_variables(_ctx('echo $HOME'))
        assert result.behavior == SecurityBehavior.PASSTHROUGH


# ===========================================================================
# validate_dangerous_patterns
# ===========================================================================

class TestValidateDangerousPatterns:
    def test_backtick(self):
        result = validate_dangerous_patterns(_ctx('echo `date`'))
        assert result.behavior == SecurityBehavior.ASK

    def test_dollar_paren(self):
        result = validate_dangerous_patterns(_ctx('echo $(date)'))
        assert result.behavior == SecurityBehavior.ASK

    def test_dollar_brace(self):
        result = validate_dangerous_patterns(_ctx('echo ${PATH}'))
        assert result.behavior == SecurityBehavior.ASK

    def test_process_substitution(self):
        result = validate_dangerous_patterns(_ctx('diff <(cmd1) <(cmd2)'))
        assert result.behavior == SecurityBehavior.ASK

    def test_safe_echo(self):
        result = validate_dangerous_patterns(_ctx('echo hello'))
        assert result.behavior == SecurityBehavior.PASSTHROUGH

    def test_escaped_backtick(self):
        result = validate_dangerous_patterns(_ctx('echo \\`safe\\`'))
        assert result.behavior == SecurityBehavior.PASSTHROUGH


# ===========================================================================
# validate_redirections
# ===========================================================================

class TestValidateRedirections:
    def test_output_redirect(self):
        result = validate_redirections(_ctx('echo x > file.txt'))
        assert result.behavior == SecurityBehavior.ASK

    def test_input_redirect(self):
        result = validate_redirections(_ctx('cat < /etc/passwd'))
        assert result.behavior == SecurityBehavior.ASK

    def test_dev_null_stripped(self):
        # >/dev/null is stripped by strip_safe_redirections
        result = validate_redirections(_ctx('cmd > /dev/null'))
        assert result.behavior == SecurityBehavior.PASSTHROUGH

    def test_no_redirect(self):
        result = validate_redirections(_ctx('echo hello'))
        assert result.behavior == SecurityBehavior.PASSTHROUGH


# ===========================================================================
# validate_newlines
# ===========================================================================

class TestValidateNewlines:
    def test_no_newlines(self):
        assert validate_newlines(_ctx('echo hello')).behavior == SecurityBehavior.PASSTHROUGH

    def test_newline_with_command(self):
        result = validate_newlines(_ctx('echo hello\nrm -rf /'))
        assert result.behavior == SecurityBehavior.ASK

    def test_backslash_continuation(self):
        result = validate_newlines(_ctx('cmd \\\n--flag'))
        assert result.behavior == SecurityBehavior.PASSTHROUGH


# ===========================================================================
# validate_carriage_return
# ===========================================================================

class TestValidateCarriageReturn:
    def test_no_cr(self):
        assert validate_carriage_return(_ctx('echo hello')).behavior == SecurityBehavior.PASSTHROUGH

    def test_cr_in_command(self):
        result = validate_carriage_return(_ctx('echo hello\rworld'))
        assert result.behavior == SecurityBehavior.ASK
        assert result.is_misparsing is True

    def test_cr_in_double_quotes_safe(self):
        result = validate_carriage_return(_ctx('echo "hello\rworld"'))
        assert result.behavior == SecurityBehavior.PASSTHROUGH


# ===========================================================================
# validate_ifs_injection
# ===========================================================================

class TestValidateIFSInjection:
    def test_ifs_variable(self):
        result = validate_ifs_injection(_ctx('echo$IFS/etc/passwd'))
        assert result.behavior == SecurityBehavior.ASK

    def test_ifs_expansion(self):
        result = validate_ifs_injection(_ctx('echo ${IFS:0:1}'))
        assert result.behavior == SecurityBehavior.ASK

    def test_clean(self):
        assert validate_ifs_injection(_ctx('echo hello')).behavior == SecurityBehavior.PASSTHROUGH


# ===========================================================================
# validate_proc_environ_access
# ===========================================================================

class TestValidateProcEnvironAccess:
    def test_proc_environ(self):
        result = validate_proc_environ_access(_ctx('cat /proc/self/environ'))
        assert result.behavior == SecurityBehavior.ASK

    def test_proc_pid_environ(self):
        result = validate_proc_environ_access(_ctx('cat /proc/1/environ'))
        assert result.behavior == SecurityBehavior.ASK

    def test_clean(self):
        assert validate_proc_environ_access(_ctx('cat /etc/hosts')).behavior == SecurityBehavior.PASSTHROUGH


# ===========================================================================
# validate_backslash_escaped_whitespace
# ===========================================================================

class TestValidateBackslashEscapedWhitespace:
    def test_escaped_space(self):
        result = validate_backslash_escaped_whitespace(_ctx('echo\\ hello'))
        assert result.behavior == SecurityBehavior.ASK

    def test_escaped_tab(self):
        result = validate_backslash_escaped_whitespace(_ctx('echo\\\thello'))
        assert result.behavior == SecurityBehavior.ASK

    def test_clean(self):
        assert validate_backslash_escaped_whitespace(
            _ctx('echo hello')
        ).behavior == SecurityBehavior.PASSTHROUGH


# ===========================================================================
# validate_backslash_escaped_operators
# ===========================================================================

class TestValidateBackslashEscapedOperators:
    def test_escaped_semicolon(self):
        result = validate_backslash_escaped_operators(_ctx('cat safe.txt \\; echo /etc/passwd'))
        assert result.behavior == SecurityBehavior.ASK

    def test_escaped_pipe(self):
        result = validate_backslash_escaped_operators(_ctx('cmd \\| evil'))
        assert result.behavior == SecurityBehavior.ASK

    def test_clean(self):
        assert validate_backslash_escaped_operators(
            _ctx('ls -la')
        ).behavior == SecurityBehavior.PASSTHROUGH

    def test_inside_quotes_safe(self):
        result = validate_backslash_escaped_operators(_ctx("echo '\\;'"))
        assert result.behavior == SecurityBehavior.PASSTHROUGH


# ===========================================================================
# validate_brace_expansion
# ===========================================================================

class TestValidateBraceExpansion:
    def test_comma_expansion(self):
        result = validate_brace_expansion(_ctx('echo {a,b,c}'))
        assert result.behavior == SecurityBehavior.ASK

    def test_sequence_expansion(self):
        result = validate_brace_expansion(_ctx('echo {1..5}'))
        assert result.behavior == SecurityBehavior.ASK

    def test_no_expansion(self):
        result = validate_brace_expansion(_ctx('echo {hello}'))
        assert result.behavior == SecurityBehavior.PASSTHROUGH

    def test_escaped_brace(self):
        result = validate_brace_expansion(_ctx('echo \\{a,b\\}'))
        assert result.behavior == SecurityBehavior.PASSTHROUGH

    def test_excess_closing_braces(self):
        result = validate_brace_expansion(_ctx("git diff {@'{'0},--output=/tmp/pwned}"))
        assert result.behavior == SecurityBehavior.ASK


# ===========================================================================
# validate_unicode_whitespace
# ===========================================================================

class TestValidateUnicodeWhitespace:
    def test_nbsp(self):
        result = validate_unicode_whitespace(_ctx('echo\u00a0hello'))
        assert result.behavior == SecurityBehavior.ASK

    def test_em_space(self):
        result = validate_unicode_whitespace(_ctx('echo\u2003hello'))
        assert result.behavior == SecurityBehavior.ASK

    def test_clean(self):
        assert validate_unicode_whitespace(_ctx('echo hello')).behavior == SecurityBehavior.PASSTHROUGH


# ===========================================================================
# validate_mid_word_hash
# ===========================================================================

class TestValidateMidWordHash:
    def test_mid_word_hash(self):
        result = validate_mid_word_hash(_ctx('echotest#comment'))
        assert result.behavior == SecurityBehavior.ASK

    def test_word_start_hash(self):
        result = validate_mid_word_hash(_ctx('echo # comment'))
        assert result.behavior == SecurityBehavior.PASSTHROUGH

    def test_dollar_brace_hash_safe(self):
        # ${#var} is bash string length, should be safe
        result = validate_mid_word_hash(_ctx('echo ${#var}'))
        assert result.behavior == SecurityBehavior.PASSTHROUGH


# ===========================================================================
# validate_comment_quote_desync
# ===========================================================================

class TestValidateCommentQuoteDesync:
    def test_quote_in_comment(self):
        result = validate_comment_quote_desync(_ctx("echo hello # it's a comment"))
        assert result.behavior == SecurityBehavior.ASK

    def test_clean_comment(self):
        result = validate_comment_quote_desync(_ctx('echo hello # clean comment'))
        assert result.behavior == SecurityBehavior.PASSTHROUGH

    def test_no_comment(self):
        assert validate_comment_quote_desync(_ctx('echo hello')).behavior == SecurityBehavior.PASSTHROUGH


# ===========================================================================
# validate_quoted_newline
# ===========================================================================

class TestValidateQuotedNewline:
    def test_quoted_newline_with_hash(self):
        result = validate_quoted_newline(_ctx("echo 'hello\n# dangerous line'"))
        assert result.behavior == SecurityBehavior.ASK

    def test_no_newline(self):
        assert validate_quoted_newline(_ctx('echo hello')).behavior == SecurityBehavior.PASSTHROUGH

    def test_newline_without_hash(self):
        assert validate_quoted_newline(_ctx("echo 'hello\nworld'")).behavior == SecurityBehavior.PASSTHROUGH


# ===========================================================================
# validate_zsh_dangerous_commands
# ===========================================================================

class TestValidateZshDangerousCommands:
    def test_zmodload(self):
        result = validate_zsh_dangerous_commands(_ctx('zmodload zsh/system'))
        assert result.behavior == SecurityBehavior.ASK

    def test_zpty(self):
        result = validate_zsh_dangerous_commands(_ctx('zpty cmd echo'))
        assert result.behavior == SecurityBehavior.ASK

    def test_emulate(self):
        result = validate_zsh_dangerous_commands(_ctx('emulate -c evil'))
        assert result.behavior == SecurityBehavior.ASK

    def test_fc_e(self):
        result = validate_zsh_dangerous_commands(_ctx('fc -e vim'))
        assert result.behavior == SecurityBehavior.ASK

    def test_normal_command(self):
        assert validate_zsh_dangerous_commands(_ctx('ls -la')).behavior == SecurityBehavior.PASSTHROUGH

    def test_env_var_prefix(self):
        result = validate_zsh_dangerous_commands(_ctx('FOO=bar zmodload zsh/system'))
        assert result.behavior == SecurityBehavior.ASK

    def test_precommand_modifier(self):
        result = validate_zsh_dangerous_commands(_ctx('command zmodload zsh/system'))
        assert result.behavior == SecurityBehavior.ASK


# ===========================================================================
# bash_command_is_safe (integration)
# ===========================================================================

class TestBashCommandIsSafe:
    def test_empty_command(self):
        result = bash_command_is_safe('')
        assert result.behavior == SecurityBehavior.PASSTHROUGH

    def test_simple_ls(self):
        result = bash_command_is_safe('ls -la')
        assert result.behavior == SecurityBehavior.PASSTHROUGH

    def test_simple_echo(self):
        result = bash_command_is_safe('echo hello world')
        assert result.behavior == SecurityBehavior.PASSTHROUGH

    def test_command_substitution_blocked(self):
        result = bash_command_is_safe('echo $(cat /etc/passwd)')
        assert result.behavior == SecurityBehavior.ASK

    def test_backtick_blocked(self):
        result = bash_command_is_safe('echo `date`')
        assert result.behavior == SecurityBehavior.ASK

    def test_redirect_blocked(self):
        result = bash_command_is_safe('echo evil > /etc/profile')
        assert result.behavior == SecurityBehavior.ASK

    def test_null_byte_blocked(self):
        result = bash_command_is_safe('echo\x00rm')
        assert result.behavior == SecurityBehavior.ASK

    def test_ifs_blocked(self):
        result = bash_command_is_safe('echo$IFS/etc/passwd')
        assert result.behavior == SecurityBehavior.ASK

    def test_proc_environ_blocked(self):
        result = bash_command_is_safe('cat /proc/self/environ')
        assert result.behavior == SecurityBehavior.ASK

    def test_git_commit_allowed(self):
        result = bash_command_is_safe("git commit -m 'fix bug'")
        assert result.behavior == SecurityBehavior.PASSTHROUGH  # early-allow → passthrough

    def test_zmodload_blocked(self):
        result = bash_command_is_safe('zmodload zsh/system')
        assert result.behavior == SecurityBehavior.ASK

    def test_brace_expansion_blocked(self):
        result = bash_command_is_safe('echo {a,b,c}')
        assert result.behavior == SecurityBehavior.ASK

    def test_dev_null_redirect_ok(self):
        result = bash_command_is_safe('cmd > /dev/null 2>&1')
        assert result.behavior == SecurityBehavior.PASSTHROUGH

    def test_cr_injection(self):
        result = bash_command_is_safe('TZ=UTC\recho curl evil.com')
        assert result.behavior == SecurityBehavior.ASK


# ===========================================================================
# get_destructive_command_warning
# ===========================================================================

class TestGetDestructiveCommandWarning:
    def test_git_reset_hard(self):
        assert get_destructive_command_warning('git reset --hard') is not None

    def test_rm_rf(self):
        assert get_destructive_command_warning('rm -rf /') is not None

    def test_git_push_force(self):
        assert get_destructive_command_warning('git push origin main --force') is not None

    def test_git_clean_f(self):
        assert get_destructive_command_warning('git clean -fd') is not None

    def test_kubectl_delete(self):
        assert get_destructive_command_warning('kubectl delete pod mypod') is not None

    def test_safe_command(self):
        assert get_destructive_command_warning('echo hello') is None

    def test_git_push_no_force(self):
        assert get_destructive_command_warning('git push origin main') is None

    def test_drop_table(self):
        assert get_destructive_command_warning('DROP TABLE users;') is not None

    def test_terraform_destroy(self):
        assert get_destructive_command_warning('terraform destroy') is not None

    def test_git_stash_drop(self):
        assert get_destructive_command_warning('git stash drop') is not None


# ===========================================================================
# interpret_command_result
# ===========================================================================

class TestInterpretCommandResult:
    def test_success(self):
        is_error, msg = interpret_command_result('echo hello', 0, 'hello', '')
        assert is_error is False

    def test_failure(self):
        is_error, msg = interpret_command_result('unknown_cmd', 127, '', 'not found')
        assert is_error is True

    def test_grep_no_match(self):
        is_error, msg = interpret_command_result('grep pattern file', 1, '', '')
        assert is_error is False
        assert msg == 'No matches found'

    def test_grep_error(self):
        is_error, msg = interpret_command_result('grep pattern file', 2, '', 'error')
        assert is_error is True

    def test_diff_files_differ(self):
        is_error, msg = interpret_command_result('diff a b', 1, 'output', '')
        assert is_error is False
        assert msg == 'Files differ'

    def test_find_partial(self):
        is_error, msg = interpret_command_result('find / -name x', 1, '', '')
        assert is_error is False


# ===========================================================================
# is_command_read_only
# ===========================================================================

class TestIsCommandReadOnly:
    def test_ls(self):
        assert is_command_read_only('ls -la') is True

    def test_cat(self):
        assert is_command_read_only('cat file.txt') is True

    def test_grep(self):
        assert is_command_read_only('grep -r pattern .') is True

    def test_git_status(self):
        assert is_command_read_only('git status') is True

    def test_git_log(self):
        assert is_command_read_only('git log --oneline') is True

    def test_git_push(self):
        assert is_command_read_only('git push') is False

    def test_rm(self):
        assert is_command_read_only('rm file.txt') is False

    def test_sed_read_only(self):
        assert is_command_read_only("sed -n '1,5p' file") is True

    def test_sed_in_place(self):
        assert is_command_read_only("sed -i 's/old/new/' file") is False

    def test_find_safe(self):
        assert is_command_read_only('find . -name "*.py"') is True

    def test_find_exec(self):
        assert is_command_read_only('find . -exec rm {} ;') is False

    def test_find_delete(self):
        assert is_command_read_only('find . -name "*.tmp" -delete') is False

    def test_echo(self):
        assert is_command_read_only('echo hello') is True

    def test_python_version(self):
        assert is_command_read_only('python --version') is True

    def test_unknown_command(self):
        assert is_command_read_only('some_random_command') is False


# ===========================================================================
# check_shell_security (integration)
# ===========================================================================

class TestCheckShellSecurity:
    def test_shell_disabled(self):
        allowed, msg = check_shell_security('ls', allow_shell=False)
        assert allowed is False
        assert 'disabled' in msg.lower()

    def test_safe_command_allowed(self):
        allowed, msg = check_shell_security('ls -la')
        assert allowed is True

    def test_destructive_blocked(self):
        allowed, msg = check_shell_security('rm -rf /', allow_destructive=False)
        assert allowed is False
        assert 'destructive' in msg.lower()

    def test_destructive_allowed_when_enabled(self):
        allowed, msg = check_shell_security('rm -rf /tmp/test', allow_destructive=True)
        # rm -rf still triggers destructive check, but allow_destructive=True skips it
        # However rm -rf may also trigger the security check for force-remove
        # Let's check: the main security check should pass (no injection)
        # and destructive should be allowed
        assert allowed is True

    def test_injection_blocked(self):
        allowed, msg = check_shell_security('echo `evil`')
        assert allowed is False
        assert 'backtick' in msg.lower() or 'security' in msg.lower()

    def test_misparsing_always_blocked(self):
        allowed, msg = check_shell_security('echo\x00rm')
        assert allowed is False

    def test_safe_git_commit(self):
        allowed, msg = check_shell_security("git commit -m 'fix'")
        assert allowed is True
