""" Tests for terminal detection functionality in enhance_skill_local.py This module tests the detect_terminal_app() function and terminal launching logic to ensure correct terminal selection across different environments. """ import os import sys import unittest from pathlib import Path from unittest.mock import MagicMock, patch # Add parent directory to path for imports sys.path.insert(0, str(Path(__file__).parent.parent)) from skill_seekers.cli.enhance_skill_local import LocalSkillEnhancer, detect_terminal_app class TestDetectTerminalApp(unittest.TestCase): """Test the detect_terminal_app() function.""" original_skill_seeker: str | None = None original_term_program: str | None = None def setUp(self): """Save original environment variables.""" self.original_skill_seeker = os.environ.get("SKILL_SEEKER_TERMINAL") self.original_term_program = os.environ.get("TERM_PROGRAM") def tearDown(self): """Restore original environment variables.""" # Remove test env vars if "SKILL_SEEKER_TERMINAL" in os.environ: del os.environ["SKILL_SEEKER_TERMINAL"] if "TERM_PROGRAM" in os.environ: del os.environ["TERM_PROGRAM"] # Restore originals if they existed if self.original_skill_seeker is not None: os.environ["SKILL_SEEKER_TERMINAL"] = self.original_skill_seeker if self.original_term_program is not None: os.environ["TERM_PROGRAM"] = self.original_term_program # HIGH PRIORITY TESTS def test_detect_terminal_with_skill_seeker_env(self): """Test that SKILL_SEEKER_TERMINAL env var takes highest priority.""" os.environ["SKILL_SEEKER_TERMINAL"] = "Ghostty" terminal_app, detection_method = detect_terminal_app() self.assertEqual(terminal_app, "Ghostty") self.assertEqual(detection_method, "SKILL_SEEKER_TERMINAL") def test_detect_terminal_with_term_program_known(self): """Test detection from TERM_PROGRAM with known terminal (iTerm).""" # Ensure SKILL_SEEKER_TERMINAL is not set if "SKILL_SEEKER_TERMINAL" in os.environ: del os.environ["SKILL_SEEKER_TERMINAL"] os.environ["TERM_PROGRAM"] = "iTerm.app" terminal_app, detection_method = detect_terminal_app() self.assertEqual(terminal_app, "iTerm") self.assertEqual(detection_method, "TERM_PROGRAM") def test_detect_terminal_with_term_program_ghostty(self): """Test detection from TERM_PROGRAM with Ghostty terminal.""" if "SKILL_SEEKER_TERMINAL" in os.environ: del os.environ["SKILL_SEEKER_TERMINAL"] os.environ["TERM_PROGRAM"] = "ghostty" terminal_app, detection_method = detect_terminal_app() self.assertEqual(terminal_app, "Ghostty") self.assertEqual(detection_method, "TERM_PROGRAM") def test_detect_terminal_with_term_program_apple_terminal(self): """Test detection from TERM_PROGRAM with Apple Terminal.""" if "SKILL_SEEKER_TERMINAL" in os.environ: del os.environ["SKILL_SEEKER_TERMINAL"] os.environ["TERM_PROGRAM"] = "Apple_Terminal" terminal_app, detection_method = detect_terminal_app() self.assertEqual(terminal_app, "Terminal") self.assertEqual(detection_method, "TERM_PROGRAM") def test_detect_terminal_with_term_program_wezterm(self): """Test detection from TERM_PROGRAM with WezTerm.""" if "SKILL_SEEKER_TERMINAL" in os.environ: del os.environ["SKILL_SEEKER_TERMINAL"] os.environ["TERM_PROGRAM"] = "WezTerm" terminal_app, detection_method = detect_terminal_app() self.assertEqual(terminal_app, "WezTerm") self.assertEqual(detection_method, "TERM_PROGRAM") def test_detect_terminal_with_term_program_unknown(self): """Test fallback behavior when TERM_PROGRAM is unknown (e.g., IDE terminals).""" if "SKILL_SEEKER_TERMINAL" in os.environ: del os.environ["SKILL_SEEKER_TERMINAL"] os.environ["TERM_PROGRAM"] = "zed" terminal_app, detection_method = detect_terminal_app() self.assertEqual(terminal_app, "Terminal") self.assertEqual(detection_method, "unknown TERM_PROGRAM (zed)") def test_detect_terminal_default_fallback(self): """Test default fallback when no environment variables are set.""" # Remove both env vars if "SKILL_SEEKER_TERMINAL" in os.environ: del os.environ["SKILL_SEEKER_TERMINAL"] if "TERM_PROGRAM" in os.environ: del os.environ["TERM_PROGRAM"] terminal_app, detection_method = detect_terminal_app() self.assertEqual(terminal_app, "Terminal") self.assertEqual(detection_method, "default") def test_detect_terminal_priority_order(self): """Test that SKILL_SEEKER_TERMINAL takes priority over TERM_PROGRAM.""" os.environ["SKILL_SEEKER_TERMINAL"] = "Ghostty" os.environ["TERM_PROGRAM"] = "iTerm.app" terminal_app, detection_method = detect_terminal_app() # SKILL_SEEKER_TERMINAL should win self.assertEqual(terminal_app, "Ghostty") self.assertEqual(detection_method, "SKILL_SEEKER_TERMINAL") @patch("subprocess.Popen") def test_subprocess_popen_called_with_correct_args(self, mock_popen): """Test that subprocess.Popen is called with correct arguments on macOS.""" # Only test on macOS if sys.platform != "darwin": self.skipTest("This test only runs on macOS") # Setup os.environ["SKILL_SEEKER_TERMINAL"] = "Ghostty" # Create a test skill directory with minimal setup import tempfile with tempfile.TemporaryDirectory() as tmpdir: skill_dir = Path(tmpdir) / "test_skill" skill_dir.mkdir() # Create references directory (required by LocalSkillEnhancer) (skill_dir / "references").mkdir() (skill_dir / "references" / "test.md").write_text("# Test") # Create SKILL.md (required) (skill_dir / "SKILL.md").write_text("---\nname: test\n---\n# Test") # Mock Popen to prevent actual terminal launch mock_popen.return_value = MagicMock() # Run enhancer in interactive mode (not headless) enhancer = LocalSkillEnhancer(skill_dir) _result = enhancer.run(headless=False) # Verify Popen was called self.assertTrue(mock_popen.called) # Verify call arguments call_args = mock_popen.call_args[0][0] self.assertEqual(call_args[0], "open") self.assertEqual(call_args[1], "-a") self.assertEqual(call_args[2], "Ghostty") # call_args[3] should be the script file path self.assertTrue(call_args[3].endswith(".sh")) # MEDIUM PRIORITY TESTS def test_detect_terminal_whitespace_handling(self): """Test that whitespace is stripped from environment variables.""" os.environ["SKILL_SEEKER_TERMINAL"] = " Ghostty " terminal_app, detection_method = detect_terminal_app() self.assertEqual(terminal_app, "Ghostty") self.assertEqual(detection_method, "SKILL_SEEKER_TERMINAL") def test_detect_terminal_empty_string_env_vars(self): """Test that empty string env vars fall through to next priority.""" os.environ["SKILL_SEEKER_TERMINAL"] = "" os.environ["TERM_PROGRAM"] = "iTerm.app" terminal_app, detection_method = detect_terminal_app() # Should skip empty SKILL_SEEKER_TERMINAL and use TERM_PROGRAM self.assertEqual(terminal_app, "iTerm") self.assertEqual(detection_method, "TERM_PROGRAM") def test_detect_terminal_empty_string_both_vars(self): """Test that empty strings on both vars falls back to default.""" os.environ["SKILL_SEEKER_TERMINAL"] = "" os.environ["TERM_PROGRAM"] = "" terminal_app, detection_method = detect_terminal_app() # Should fall back to default self.assertEqual(terminal_app, "Terminal") # Empty TERM_PROGRAM should be treated as not set self.assertEqual(detection_method, "default") @patch("subprocess.Popen") def test_terminal_launch_error_handling(self, mock_popen): """Test error handling when terminal launch fails.""" # Only test on macOS if sys.platform != "darwin": self.skipTest("This test only runs on macOS") # Setup Popen to raise exception mock_popen.side_effect = Exception("Terminal not found") import tempfile with tempfile.TemporaryDirectory() as tmpdir: skill_dir = Path(tmpdir) / "test_skill" skill_dir.mkdir() (skill_dir / "references").mkdir() (skill_dir / "references" / "test.md").write_text("# Test") (skill_dir / "SKILL.md").write_text("---\nname: test\n---\n# Test") enhancer = LocalSkillEnhancer(skill_dir) # Capture stdout to check error message from io import StringIO captured_output = StringIO() old_stdout = sys.stdout sys.stdout = captured_output # Run in interactive mode (not headless) to test terminal launch result = enhancer.run(headless=False) # Restore stdout sys.stdout = old_stdout # Should return False on error self.assertFalse(result) # Should print error message output = captured_output.getvalue() self.assertIn("Error launching", output) def test_output_message_unknown_terminal(self): """Test that unknown terminal prints warning message.""" if sys.platform != "darwin": self.skipTest("This test only runs on macOS") os.environ["TERM_PROGRAM"] = "vscode" if "SKILL_SEEKER_TERMINAL" in os.environ: del os.environ["SKILL_SEEKER_TERMINAL"] import tempfile with tempfile.TemporaryDirectory() as tmpdir: skill_dir = Path(tmpdir) / "test_skill" skill_dir.mkdir() (skill_dir / "references").mkdir() (skill_dir / "references" / "test.md").write_text("# Test") (skill_dir / "SKILL.md").write_text("---\nname: test\n---\n# Test") enhancer = LocalSkillEnhancer(skill_dir) # Capture stdout from io import StringIO captured_output = StringIO() old_stdout = sys.stdout sys.stdout = captured_output # Mock Popen to prevent actual launch with patch("subprocess.Popen") as mock_popen: mock_popen.return_value = MagicMock() # Run in interactive mode (not headless) to test terminal detection enhancer.run(headless=False) # Restore stdout sys.stdout = old_stdout output = captured_output.getvalue() # Should contain warning about unknown terminal self.assertIn("⚠️", output) self.assertIn("unknown TERM_PROGRAM", output) self.assertIn("vscode", output) self.assertIn("Using Terminal.app as fallback", output) class TestTerminalMapCompleteness(unittest.TestCase): """Test that TERMINAL_MAP covers all documented terminals.""" def test_terminal_map_has_all_documented_terminals(self): """Verify TERMINAL_MAP contains all terminals mentioned in documentation.""" from skill_seekers.cli.enhance_skill_local import detect_terminal_app # Get the TERMINAL_MAP from the function's scope # We need to test this indirectly by checking each known terminal known_terminals = [ ("Apple_Terminal", "Terminal"), ("iTerm.app", "iTerm"), ("ghostty", "Ghostty"), ("WezTerm", "WezTerm"), ] for term_program_value, expected_app_name in known_terminals: # Set TERM_PROGRAM and verify detection os.environ["TERM_PROGRAM"] = term_program_value if "SKILL_SEEKER_TERMINAL" in os.environ: del os.environ["SKILL_SEEKER_TERMINAL"] terminal_app, detection_method = detect_terminal_app() self.assertEqual( terminal_app, expected_app_name, f"TERM_PROGRAM='{term_program_value}' should map to '{expected_app_name}'", ) self.assertEqual(detection_method, "TERM_PROGRAM") if __name__ == "__main__": unittest.main()