{
"event": "PreToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/pre_rules_tests.py",
"content": "#!\/usr\/bin\/env python3\n\"\"\"\nPre-Hook Test Isolation Regeln (BLOCK) - Test Isolation.\n\nP14.x Regeln: Erzwingt Test-Isolation durch Blockieren von Produktions-DB-Zugriff.\n\nPrinzip: \"Tests beeinflussen sich nicht gegenseitig. Globaler Zustand ist kontrolliert.\"\n\nWICHTIG: Diese Regeln gelten NUR für Test-Dateien (\/tests\/, \/Test\/).\n\"\"\"\n\nimport re\nfrom typing import Optional\nfrom .rule_base import block\n\n\n# =============================================================================\n# KONFIGURATION\n# =============================================================================\n\n# Pfade die als Test-Dateien gelten\nTEST_PATHS = [\"\/tests\/\", \"\/Test\/\"]\n\n# Produktions-Datenbanken (ohne _test Suffix)\nPRODUCTION_DATABASES = {\n \"P14.1\": {\n \"pattern\": r\"['\\\"]ki_content['\\\"]\",\n \"exclude_pattern\": r\"['\\\"]ki_content_test['\\\"]\",\n \"message\": \"Production database 'ki_content' used in tests. Use 'ki_content_test' instead.\",\n },\n \"P14.2\": {\n \"pattern\": r\"['\\\"]ki_dev['\\\"]\",\n \"exclude_pattern\": r\"['\\\"]ki_dev_test['\\\"]\",\n \"message\": \"Production database 'ki_dev' used in tests. Use 'ki_dev_test' instead.\",\n },\n \"P14.3\": {\n \"pattern\": r\"['\\\"]ki_protokoll['\\\"]\",\n \"exclude_pattern\": r\"['\\\"]ki_protokoll_test['\\\"]\",\n \"message\": \"Production database 'ki_protokoll' used in tests. Use 'ki_protokoll_test' instead.\",\n },\n}\n\n# Gefährliche Operationen in Tests\nDANGEROUS_TEST_PATTERNS = {\n \"P14.4\": {\n \"pattern\": r\"TRUNCATE\\s+TABLE\",\n \"message\": \"TRUNCATE TABLE in tests. Use test fixtures or transactions instead.\",\n },\n \"P14.5\": {\n \"pattern\": r\"DROP\\s+(?:TABLE|DATABASE)\",\n \"message\": \"DROP TABLE\/DATABASE in tests. Never drop in tests.\",\n },\n}\n\n\n# =============================================================================\n# HELPER\n# =============================================================================\n\ndef is_test_file(file_path: str) -> bool:\n \"\"\"Prüft ob die Datei eine Test-Datei ist.\"\"\"\n return any(test_path in file_path for test_path in TEST_PATHS)\n\n\n# =============================================================================\n# REGELN\n# =============================================================================\n\ndef p14_production_database(file_path: str, content: str) -> Optional[dict]:\n \"\"\"P14.1-P14.3: Produktions-Datenbank in Tests blockieren.\"\"\"\n # Nur für Test-Dateien prüfen\n if not is_test_file(file_path):\n return None\n\n for rule_id, config in PRODUCTION_DATABASES.items():\n # Suche nach Produktions-DB\n if re.search(config[\"pattern\"], content):\n # Prüfe ob es die Test-Variante ist\n if not re.search(config[\"exclude_pattern\"], content):\n return block(rule_id, config[\"message\"])\n\n return None\n\n\ndef p14_dangerous_operations(file_path: str, content: str) -> Optional[dict]:\n \"\"\"P14.4-P14.5: Gefährliche Operationen in Tests blockieren.\"\"\"\n # Nur für Test-Dateien prüfen\n if not is_test_file(file_path):\n return None\n\n for rule_id, config in DANGEROUS_TEST_PATTERNS.items():\n if re.search(config[\"pattern\"], content, re.IGNORECASE):\n return block(rule_id, config[\"message\"])\n\n return None\n\n\ndef p14_shared_state(file_path: str, content: str) -> Optional[dict]:\n \"\"\"P14.6: Statische Properties ohne Reset in Tests.\"\"\"\n # Nur für Test-Dateien prüfen\n if not is_test_file(file_path):\n return None\n\n # Prüfe auf static Properties die in Tests gesetzt werden\n # ohne entsprechenden Reset in tearDown\n static_assignments = re.findall(\n r\"self::\\$\\w+\\s*=|static::\\$\\w+\\s*=\",\n content\n )\n\n if static_assignments:\n # Prüfe ob tearDown vorhanden ist\n has_teardown = bool(re.search(\n r\"(?:protected|public)\\s+function\\s+tearDown\\s*\\(\",\n content\n ))\n\n if not has_teardown:\n return block(\n \"P14.6\",\n f\"Static property assignment in tests ({len(static_assignments)}x) \"\n \"without tearDown() method. Add tearDown() to reset static state.\"\n )\n\n return None\n\n\n# =============================================================================\n# RULE COLLECTION\n# =============================================================================\n\nRULES = [\n p14_production_database,\n p14_dangerous_operations,\n p14_shared_state,\n]\n"
}
}
{
"tool_response": {
"type": "create",
"filePath": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/pre_rules_tests.py",
"content": "#!\/usr\/bin\/env python3\n\"\"\"\nPre-Hook Test Isolation Regeln (BLOCK) - Test Isolation.\n\nP14.x Regeln: Erzwingt Test-Isolation durch Blockieren von Produktions-DB-Zugriff.\n\nPrinzip: \"Tests beeinflussen sich nicht gegenseitig. Globaler Zustand ist kontrolliert.\"\n\nWICHTIG: Diese Regeln gelten NUR für Test-Dateien (\/tests\/, \/Test\/).\n\"\"\"\n\nimport re\nfrom typing import Optional\nfrom .rule_base import block\n\n\n# =============================================================================\n# KONFIGURATION\n# =============================================================================\n\n# Pfade die als Test-Dateien gelten\nTEST_PATHS = [\"\/tests\/\", \"\/Test\/\"]\n\n# Produktions-Datenbanken (ohne _test Suffix)\nPRODUCTION_DATABASES = {\n \"P14.1\": {\n \"pattern\": r\"['\\\"]ki_content['\\\"]\",\n \"exclude_pattern\": r\"['\\\"]ki_content_test['\\\"]\",\n \"message\": \"Production database 'ki_content' used in tests. Use 'ki_content_test' instead.\",\n },\n \"P14.2\": {\n \"pattern\": r\"['\\\"]ki_dev['\\\"]\",\n \"exclude_pattern\": r\"['\\\"]ki_dev_test['\\\"]\",\n \"message\": \"Production database 'ki_dev' used in tests. Use 'ki_dev_test' instead.\",\n },\n \"P14.3\": {\n \"pattern\": r\"['\\\"]ki_protokoll['\\\"]\",\n \"exclude_pattern\": r\"['\\\"]ki_protokoll_test['\\\"]\",\n \"message\": \"Production database 'ki_protokoll' used in tests. Use 'ki_protokoll_test' instead.\",\n },\n}\n\n# Gefährliche Operationen in Tests\nDANGEROUS_TEST_PATTERNS = {\n \"P14.4\": {\n \"pattern\": r\"TRUNCATE\\s+TABLE\",\n \"message\": \"TRUNCATE TABLE in tests. Use test fixtures or transactions instead.\",\n },\n \"P14.5\": {\n \"pattern\": r\"DROP\\s+(?:TABLE|DATABASE)\",\n \"message\": \"DROP TABLE\/DATABASE in tests. Never drop in tests.\",\n },\n}\n\n\n# =============================================================================\n# HELPER\n# =============================================================================\n\ndef is_test_file(file_path: str) -> bool:\n \"\"\"Prüft ob die Datei eine Test-Datei ist.\"\"\"\n return any(test_path in file_path for test_path in TEST_PATHS)\n\n\n# =============================================================================\n# REGELN\n# =============================================================================\n\ndef p14_production_database(file_path: str, content: str) -> Optional[dict]:\n \"\"\"P14.1-P14.3: Produktions-Datenbank in Tests blockieren.\"\"\"\n # Nur für Test-Dateien prüfen\n if not is_test_file(file_path):\n return None\n\n for rule_id, config in PRODUCTION_DATABASES.items():\n # Suche nach Produktions-DB\n if re.search(config[\"pattern\"], content):\n # Prüfe ob es die Test-Variante ist\n if not re.search(config[\"exclude_pattern\"], content):\n return block(rule_id, config[\"message\"])\n\n return None\n\n\ndef p14_dangerous_operations(file_path: str, content: str) -> Optional[dict]:\n \"\"\"P14.4-P14.5: Gefährliche Operationen in Tests blockieren.\"\"\"\n # Nur für Test-Dateien prüfen\n if not is_test_file(file_path):\n return None\n\n for rule_id, config in DANGEROUS_TEST_PATTERNS.items():\n if re.search(config[\"pattern\"], content, re.IGNORECASE):\n return block(rule_id, config[\"message\"])\n\n return None\n\n\ndef p14_shared_state(file_path: str, content: str) -> Optional[dict]:\n \"\"\"P14.6: Statische Properties ohne Reset in Tests.\"\"\"\n # Nur für Test-Dateien prüfen\n if not is_test_file(file_path):\n return None\n\n # Prüfe auf static Properties die in Tests gesetzt werden\n # ohne entsprechenden Reset in tearDown\n static_assignments = re.findall(\n r\"self::\\$\\w+\\s*=|static::\\$\\w+\\s*=\",\n content\n )\n\n if static_assignments:\n # Prüfe ob tearDown vorhanden ist\n has_teardown = bool(re.search(\n r\"(?:protected|public)\\s+function\\s+tearDown\\s*\\(\",\n content\n ))\n\n if not has_teardown:\n return block(\n \"P14.6\",\n f\"Static property assignment in tests ({len(static_assignments)}x) \"\n \"without tearDown() method. Add tearDown() to reset static state.\"\n )\n\n return None\n\n\n# =============================================================================\n# RULE COLLECTION\n# =============================================================================\n\nRULES = [\n p14_production_database,\n p14_dangerous_operations,\n p14_shared_state,\n]\n",
"structuredPatch": [],
"originalFile": null
}
}