#!/usr/bin/env python3
"""
Unit tests for rm_block_hook.py

Tests cover:
    - Direct rm command blocking
    - rm with absolute paths (/bin/rm, /usr/bin/rm)
    - rm chained with shell operators (&&, ||, ;, |, &)
    - rm hidden in subshells ($() and backticks)
    - Safe commands that should pass through
"""
import unittest
import sys
import os

# Add the hooks directory to path for imports
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from rm_block_hook import check_rm_command, _is_rm_command


class TestIsRmCommand(unittest.TestCase):
    """Tests for _is_rm_command() single command detection."""

    def test_bare_rm(self):
        """Bare 'rm' command is detected."""
        self.assertTrue(_is_rm_command("rm"))

    def test_rm_with_args(self):
        """rm with arguments is detected."""
        self.assertTrue(_is_rm_command("rm foo.txt"))
        self.assertTrue(_is_rm_command("rm -rf /tmp/test"))
        self.assertTrue(_is_rm_command("rm -f file1 file2"))

    def test_rm_with_path(self):
        """rm with absolute path is detected."""
        self.assertTrue(_is_rm_command("/bin/rm foo"))
        self.assertTrue(_is_rm_command("/usr/bin/rm -rf /"))

    def test_not_rm_commands(self):
        """Commands that are not rm should not be detected."""
        self.assertFalse(_is_rm_command("ls"))
        self.assertFalse(_is_rm_command("echo rm"))
        self.assertFalse(_is_rm_command("grep rm file"))
        self.assertFalse(_is_rm_command("firmware"))  # Contains 'rm' but not rm command
        self.assertFalse(_is_rm_command(""))

    def test_whitespace_handling(self):
        """Whitespace is normalized."""
        self.assertTrue(_is_rm_command("  rm   foo  "))
        self.assertTrue(_is_rm_command("\trm\t-rf\t/"))


class TestCheckRmCommand(unittest.TestCase):
    """Tests for check_rm_command() comprehensive detection."""

    def test_direct_rm_blocked(self):
        """Direct rm commands are blocked."""
        blocked, reason = check_rm_command("rm foo.txt")
        self.assertTrue(blocked)
        self.assertIsNotNone(reason)

    def test_rm_with_path_blocked(self):
        """rm with absolute path is blocked."""
        blocked, _ = check_rm_command("/bin/rm foo")
        self.assertTrue(blocked)

        blocked, _ = check_rm_command("/usr/bin/rm -rf /")
        self.assertTrue(blocked)

    def test_safe_commands_pass(self):
        """Safe commands are not blocked."""
        blocked, reason = check_rm_command("ls -la")
        self.assertFalse(blocked)
        self.assertIsNone(reason)

        blocked, _ = check_rm_command("git status")
        self.assertFalse(blocked)

        blocked, _ = check_rm_command("echo hello")
        self.assertFalse(blocked)

    # Security bypass tests - these are the key regression tests

    def test_pipe_bypass_blocked(self):
        """rm after pipe operator is blocked - security regression test."""
        blocked, _ = check_rm_command("echo ok | rm foo")
        self.assertTrue(blocked, "rm after pipe should be blocked")

        blocked, _ = check_rm_command("cat file | rm -rf /tmp")
        self.assertTrue(blocked, "rm in pipe chain should be blocked")

    def test_background_bypass_blocked(self):
        """rm after background operator is blocked - security regression test."""
        blocked, _ = check_rm_command("sleep 1 & rm foo")
        self.assertTrue(blocked, "rm after background operator should be blocked")

        blocked, _ = check_rm_command("cmd & /bin/rm bar")
        self.assertTrue(blocked, "rm with path after & should be blocked")

    def test_and_operator_blocked(self):
        """rm after && operator is blocked."""
        blocked, _ = check_rm_command("cd /tmp && rm foo")
        self.assertTrue(blocked)

    def test_or_operator_blocked(self):
        """rm after || operator is blocked."""
        blocked, _ = check_rm_command("test -f x || rm y")
        self.assertTrue(blocked)

    def test_semicolon_operator_blocked(self):
        """rm after ; operator is blocked."""
        blocked, _ = check_rm_command("echo done; rm foo")
        self.assertTrue(blocked)

    def test_subshell_dollar_paren_blocked(self):
        """rm inside $() subshell is blocked - security regression test."""
        blocked, _ = check_rm_command("echo $(rm foo)")
        self.assertTrue(blocked, "rm in $() subshell should be blocked")

        blocked, _ = check_rm_command("$(rm -rf /)")
        self.assertTrue(blocked, "bare $() with rm should be blocked")

    def test_subshell_backtick_blocked(self):
        """rm inside backtick subshell is blocked - security regression test."""
        blocked, _ = check_rm_command("echo `rm foo`")
        self.assertTrue(blocked, "rm in backticks should be blocked")

        blocked, _ = check_rm_command("cat `rm bar`")
        self.assertTrue(blocked, "rm in backticks should be blocked")

    def test_complex_bypass_attempts(self):
        """Complex commands attempting to hide rm are blocked."""
        # Multiple levels of indirection
        blocked, _ = check_rm_command("echo safe | cat | rm evil")
        self.assertTrue(blocked, "rm at end of pipe chain should be blocked")

        # Subshell inside chained command
        blocked, _ = check_rm_command("echo $(rm foo) && ls")
        self.assertTrue(blocked, "rm in subshell with && should be blocked")

        # Background with subshell
        blocked, _ = check_rm_command("$(rm x) & echo done")
        self.assertTrue(blocked, "rm in subshell with & should be blocked")

    def test_nested_subshell_bypass_blocked(self):
        """rm hidden in nested $() subshells is blocked - P1 security fix."""
        # This was a bypass: the regex stopped at first ), missing inner rm
        blocked, _ = check_rm_command("echo $(echo $(rm foo))")
        self.assertTrue(blocked, "rm in nested subshell should be blocked")

        # Deeper nesting
        blocked, _ = check_rm_command("$(cat $(ls $(rm secret)))")
        self.assertTrue(blocked, "rm in deeply nested subshell should be blocked")

    def test_reason_message_content(self):
        """Blocked commands include helpful guidance in reason."""
        blocked, reason = check_rm_command("rm foo")
        self.assertTrue(blocked)
        self.assertIn("TRASH", reason)
        self.assertIn("mv", reason)
        self.assertIn("TRASH-FILES.md", reason)


class TestEdgeCases(unittest.TestCase):
    """Edge case tests for robustness."""

    def test_empty_command(self):
        """Empty command is not blocked."""
        blocked, _ = check_rm_command("")
        self.assertFalse(blocked)

    def test_whitespace_only(self):
        """Whitespace-only command is not blocked."""
        blocked, _ = check_rm_command("   ")
        self.assertFalse(blocked)

    def test_rm_in_string_not_blocked(self):
        """String containing 'rm' but not as command passes."""
        # 'rm' as part of echo string
        blocked, _ = check_rm_command("echo 'do not rm this'")
        self.assertFalse(blocked, "rm in quoted string should not be blocked")

    def test_command_starting_with_rm_prefix(self):
        """Commands starting with 'rm' prefix but not rm command."""
        blocked, _ = check_rm_command("rmdir empty_dir")
        self.assertFalse(blocked, "rmdir should not be blocked")


if __name__ == "__main__":
    unittest.main()
