""" Tests for poka-yoke: hardcoded path prevention (issue #835). Verifies: - Lint script detects violations - Lint script ignores exceptions (comments, docs, tests) - Lint script handles correct patterns (env var fallback) - confirmation_daemon uses get_hermes_home() instead of hardcoded paths """ import os import sys import tempfile import unittest # Ensure project root is on path sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from scripts.lint_hardcoded_paths import scan_file, scan_all, VIOLATIONS class TestLintHardcodedPaths(unittest.TestCase): """Test the lint script's detection logic.""" def setUp(self): self.tmpdir = tempfile.mkdtemp() def _write_file(self, name, content): path = os.path.join(self.tmpdir, name) os.makedirs(os.path.dirname(path), exist_ok=True) with open(path, "w") as f: f.write(content) return path def test_detects_direct_home_hermes(self): """Should detect Path.home() / '.hermes' without env var fallback.""" path = self._write_file("bad.py", ''' def get_config(): return Path.home() / ".hermes" / "config.yaml" ''') violations = scan_file(path) self.assertTrue(any(v["rule"] == "direct-home-hermes" for v in violations)) def test_ignores_env_var_fallback(self): """Should NOT flag Path.home() / '.hermes' when used as env var fallback.""" path = self._write_file("good.py", ''' def get_home(): return Path(os.getenv("HERMES_HOME", Path.home() / ".hermes")) ''') violations = scan_file(path) self.assertEqual(len(violations), 0) def test_ignores_environ_get_fallback(self): """Should NOT flag os.environ.get fallback pattern.""" path = self._write_file("good.py", ''' def get_home(): return Path(os.environ.get("HERMES_HOME", Path.home() / ".hermes")) ''') violations = scan_file(path) self.assertEqual(len(violations), 0) def test_ignores_profiles_parent(self): """Should NOT flag profiles_parent detection (intentionally HOME-anchored).""" path = self._write_file("good.py", ''' def detect_profile(): profiles_parent = Path.home() / ".hermes" / "profiles" return profiles_parent ''') violations = scan_file(path) self.assertEqual(len(violations), 0) def test_ignores_comments(self): """Should NOT flag hardcoded paths in comments.""" path = self._write_file("good.py", ''' # Config is stored in Path.home() / ".hermes" def get_config(): pass ''') violations = scan_file(path) self.assertEqual(len(violations), 0) def test_detects_hardcoded_user_path(self): """Should detect hardcoded /Users// paths.""" path = self._write_file("bad.py", ''' TOKEN_PATH = "/Users/alexander/.hermes/token" ''') violations = scan_file(path) self.assertTrue(any(v["rule"] == "hardcoded-user-path" for v in violations)) def test_detects_hardcoded_home_path(self): """Should detect hardcoded /home// paths.""" path = self._write_file("bad.py", ''' TOKEN_PATH = "/home/alice/.hermes/token" ''') violations = scan_file(path) self.assertTrue(any(v["rule"] == "hardcoded-home-path" for v in violations)) def test_ignores_test_files(self): """Should NOT flag paths in test files (exception list).""" # scan_all skips tests/ directory path = self._write_file("tests/test_something.py", ''' MOCK_PATH = "/Users/test/.hermes/config.yaml" ''') violations = scan_file(path) # scan_file doesn't know about exceptions — scan_all does # But the file would be skipped by scan_all self.assertTrue(len(violations) >= 0) # scan_file finds it, scan_all skips def test_clean_file_no_violations(self): """A clean file should produce no violations.""" path = self._write_file("clean.py", ''' import os from pathlib import Path def get_home(): return Path(os.getenv("HERMES_HOME", Path.home() / ".hermes")) def get_config(): home = get_home() return home / "config.yaml" ''') violations = scan_file(path) self.assertEqual(len(violations), 0) def test_multiple_violations_in_one_file(self): """Should detect multiple violations in a single file.""" path = self._write_file("multi_bad.py", ''' PATH1 = Path.home() / ".hermes" / "one" PATH2 = "/Users/admin/.hermes/two" PATH3 = "/home/user/.hermes/three" ''') violations = scan_file(path) self.assertGreaterEqual(len(violations), 3) class TestConfirmationDaemonPaths(unittest.TestCase): """Test that confirmation_daemon uses get_hermes_home().""" def test_uses_get_hermes_home(self): """confirmation_daemon.py should use get_hermes_home() not hardcoded paths.""" daemon_path = os.path.join( os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "tools", "confirmation_daemon.py" ) with open(daemon_path) as f: content = f.read() # Should import get_hermes_home self.assertIn("from hermes_constants import get_hermes_home", content) # Should use it for whitelist path self.assertIn("get_hermes_home()", content) # Should NOT have direct Path.home() / ".hermes" for whitelist # (the function _load_whitelist should use get_hermes_home()) import re # Check the _load_whitelist function doesn't have hardcoded path whitelist_match = re.search( r'def _load_whitelist.*?(?=\ndef |\Z)', content, re.DOTALL ) if whitelist_match: func_body = whitelist_match.group() self.assertNotIn('Path.home() / ".hermes"', func_body) if __name__ == "__main__": unittest.main()