Protokoll #28139

ID28139
Zeitstempel2025-12-28 12:54:53.269815
Clientroot
IP145.224.72.140
Modellclaude-sonnet-4-20250514
Statuscompleted
Tokens4,389 (Input: 2,192, Output: 2,197)
Dauer88 ms
Request-Zeit2025-12-28 12:54:53.269815
Response-Zeit2025-12-28 12:54:53.358141

Request

{
    "event": "PreToolUse",
    "tool_name": "Write",
    "tool_input": {
        "file_path": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/rules_testisolation.py",
        "content": "#!\/usr\/bin\/env python3\n\"\"\"\nPost-Hook Test Isolation Regeln (WARN) - Test Isolation.\n\nW14.x Regeln: Warnt bei potenziellen Test-Isolation-Problemen.\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 List\nfrom .rule_base import Rule\n\n\n# =============================================================================\n# KONFIGURATION\n# =============================================================================\n\n# Pfade die als Test-Dateien gelten\nTEST_PATHS = [\"\/tests\/\", \"\/Test\/\"]\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# W14: TEST ISOLATION\n# =============================================================================\n\nclass W14_1_MissingTearDown(Rule):\n    \"\"\"W14.1: Test-Klasse ohne tearDown-Methode bei Setup.\"\"\"\n\n    def __init__(self):\n        # Leere Allowlist - Tests werden explizit geprüft\n        super().__init__(allowlist=[])\n\n    def should_skip(self, file_path: str) -> bool:\n        # Nur Test-Dateien prüfen\n        return not is_test_file(file_path)\n\n    def check(self, file_path: str, content: str) -> List[str]:\n        warnings = []\n\n        # Hat die Klasse setUp?\n        has_setup = bool(re.search(\n            r\"(?:protected|public)\\s+function\\s+setUp\\s*\\(\",\n            content\n        ))\n\n        # Hat die Klasse tearDown?\n        has_teardown = bool(re.search(\n            r\"(?:protected|public)\\s+function\\s+tearDown\\s*\\(\",\n            content\n        ))\n\n        if has_setup and not has_teardown:\n            warnings.append(\n                \"W14.1: Test has setUp() but no tearDown(). \"\n                \"Consider adding tearDown() to clean up test state.\"\n            )\n\n        return warnings\n\n\nclass W14_2_FileSystemAccess(Rule):\n    \"\"\"W14.2: Direkter Dateisystem-Zugriff in Tests ohne Cleanup.\"\"\"\n\n    def __init__(self):\n        super().__init__(allowlist=[])\n\n    def should_skip(self, file_path: str) -> bool:\n        return not is_test_file(file_path)\n\n    def check(self, file_path: str, content: str) -> List[str]:\n        warnings = []\n\n        # Dateisystem-Schreiboperationen\n        fs_write_patterns = [\n            r\"file_put_contents\\s*\\(\",\n            r\"fwrite\\s*\\(\",\n            r\"mkdir\\s*\\(\",\n            r\"touch\\s*\\(\",\n            r\"copy\\s*\\(\",\n            r\"rename\\s*\\(\",\n        ]\n\n        has_fs_write = any(\n            re.search(pattern, content)\n            for pattern in fs_write_patterns\n        )\n\n        if has_fs_write:\n            # Prüfe ob Cleanup vorhanden\n            has_cleanup = bool(re.search(\n                r\"unlink\\s*\\(|rmdir\\s*\\(|tearDown\",\n                content\n            ))\n\n            if not has_cleanup:\n                warnings.append(\n                    \"W14.2: Test writes to filesystem without visible cleanup. \"\n                    \"Ensure files are removed in tearDown() or after test.\"\n                )\n\n        return warnings\n\n\nclass W14_3_GlobalState(Rule):\n    \"\"\"W14.3: Modifikation von globalem Zustand in Tests.\"\"\"\n\n    def __init__(self):\n        super().__init__(allowlist=[])\n\n    def should_skip(self, file_path: str) -> bool:\n        return not is_test_file(file_path)\n\n    def check(self, file_path: str, content: str) -> List[str]:\n        warnings = []\n\n        global_state_patterns = [\n            (r\"ini_set\\s*\\(\", \"ini_set() modifies PHP configuration\"),\n            (r\"putenv\\s*\\(\", \"putenv() modifies environment variables\"),\n            (r\"define\\s*\\(\", \"define() creates global constants\"),\n            (r\"date_default_timezone_set\\s*\\(\", \"Timezone setting is global\"),\n        ]\n\n        for pattern, description in global_state_patterns:\n            if re.search(pattern, content):\n                warnings.append(\n                    f\"W14.3: {description}. \"\n                    \"Ensure original value is restored in tearDown().\"\n                )\n\n        return warnings\n\n\nclass W14_4_HardcodedPaths(Rule):\n    \"\"\"W14.4: Hardcoded absolute Pfade in Tests.\"\"\"\n\n    def __init__(self):\n        super().__init__(allowlist=[])\n\n    def should_skip(self, file_path: str) -> bool:\n        return not is_test_file(file_path)\n\n    def check(self, file_path: str, content: str) -> List[str]:\n        warnings = []\n\n        # Absolute Pfade die problematisch sein könnten\n        hardcoded_paths = re.findall(\n            r\"['\\\"]\/(var|tmp|home|Users)\/[^'\\\"]+['\\\"]\",\n            content\n        )\n\n        # Erlaube \/tmp für Temp-Dateien\n        problematic = [p for p in hardcoded_paths if \"\/tmp\/\" not in p]\n\n        if problematic:\n            warnings.append(\n                f\"W14.4: Hardcoded absolute paths in test ({len(problematic)}x). \"\n                \"Use sys_get_temp_dir() or __DIR__ for portable paths.\"\n            )\n\n        return warnings\n\n\nclass W14_5_SleepInTests(Rule):\n    \"\"\"W14.5: sleep() in Tests - verlangsamt Test-Suite.\"\"\"\n\n    def __init__(self):\n        super().__init__(allowlist=[])\n\n    def should_skip(self, file_path: str) -> bool:\n        return not is_test_file(file_path)\n\n    def check(self, file_path: str, content: str) -> List[str]:\n        warnings = []\n\n        sleep_calls = re.findall(r\"\\bsleep\\s*\\(\\s*(\\d+)\\s*\\)\", content)\n        usleep_calls = re.findall(r\"\\busleep\\s*\\(\", content)\n\n        total_sleep = sum(int(s) for s in sleep_calls)\n\n        if sleep_calls or usleep_calls:\n            warnings.append(\n                f\"W14.5: sleep()\/usleep() in test ({len(sleep_calls) + len(usleep_calls)}x, \"\n                f\"~{total_sleep}s total). Consider mocking time-dependent behavior.\"\n            )\n\n        return warnings\n\n\nclass W14_6_TestDependencies(Rule):\n    \"\"\"W14.6: Tests mit @depends ohne klare Isolation.\"\"\"\n\n    def __init__(self):\n        super().__init__(allowlist=[])\n\n    def should_skip(self, file_path: str) -> bool:\n        return not is_test_file(file_path)\n\n    def check(self, file_path: str, content: str) -> List[str]:\n        warnings = []\n\n        depends_annotations = re.findall(r\"@depends\\s+(\\w+)\", content)\n\n        if len(depends_annotations) > 2:\n            warnings.append(\n                f\"W14.6: Test has {len(depends_annotations)} @depends annotations. \"\n                \"Complex test dependencies can cause cascading failures. Consider isolation.\"\n            )\n\n        return warnings\n\n\nclass W14_7_AssertionCount(Rule):\n    \"\"\"W14.7: Test-Methode ohne Assertions.\"\"\"\n\n    def __init__(self):\n        super().__init__(allowlist=[])\n\n    def should_skip(self, file_path: str) -> bool:\n        return not is_test_file(file_path)\n\n    def check(self, file_path: str, content: str) -> List[str]:\n        warnings = []\n\n        # Finde Test-Methoden\n        test_methods = re.findall(\n            r\"public\\s+function\\s+(test\\w+)\\s*\\([^)]*\\)\\s*(?::\\s*void)?\\s*\\{([^}]+)\\}\",\n            content,\n            re.DOTALL\n        )\n\n        for method_name, method_body in test_methods:\n            # Zähle Assertions\n            assertions = len(re.findall(\n                r\"\\$this->assert|\\$this->expect|self::assert|self::expect\",\n                method_body\n            ))\n\n            if assertions == 0:\n                warnings.append(\n                    f\"W14.7: Test method '{method_name}' has no assertions. \"\n                    \"Tests should verify expected behavior.\"\n                )\n\n        return warnings\n\n\n# =============================================================================\n# RULE COLLECTION\n# =============================================================================\n\nRULES = [\n    W14_1_MissingTearDown(),\n    W14_2_FileSystemAccess(),\n    W14_3_GlobalState(),\n    W14_4_HardcodedPaths(),\n    W14_5_SleepInTests(),\n    W14_6_TestDependencies(),\n    W14_7_AssertionCount(),\n]\n"
    }
}

Response

{
    "tool_response": {
        "type": "create",
        "filePath": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/rules_testisolation.py",
        "content": "#!\/usr\/bin\/env python3\n\"\"\"\nPost-Hook Test Isolation Regeln (WARN) - Test Isolation.\n\nW14.x Regeln: Warnt bei potenziellen Test-Isolation-Problemen.\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 List\nfrom .rule_base import Rule\n\n\n# =============================================================================\n# KONFIGURATION\n# =============================================================================\n\n# Pfade die als Test-Dateien gelten\nTEST_PATHS = [\"\/tests\/\", \"\/Test\/\"]\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# W14: TEST ISOLATION\n# =============================================================================\n\nclass W14_1_MissingTearDown(Rule):\n    \"\"\"W14.1: Test-Klasse ohne tearDown-Methode bei Setup.\"\"\"\n\n    def __init__(self):\n        # Leere Allowlist - Tests werden explizit geprüft\n        super().__init__(allowlist=[])\n\n    def should_skip(self, file_path: str) -> bool:\n        # Nur Test-Dateien prüfen\n        return not is_test_file(file_path)\n\n    def check(self, file_path: str, content: str) -> List[str]:\n        warnings = []\n\n        # Hat die Klasse setUp?\n        has_setup = bool(re.search(\n            r\"(?:protected|public)\\s+function\\s+setUp\\s*\\(\",\n            content\n        ))\n\n        # Hat die Klasse tearDown?\n        has_teardown = bool(re.search(\n            r\"(?:protected|public)\\s+function\\s+tearDown\\s*\\(\",\n            content\n        ))\n\n        if has_setup and not has_teardown:\n            warnings.append(\n                \"W14.1: Test has setUp() but no tearDown(). \"\n                \"Consider adding tearDown() to clean up test state.\"\n            )\n\n        return warnings\n\n\nclass W14_2_FileSystemAccess(Rule):\n    \"\"\"W14.2: Direkter Dateisystem-Zugriff in Tests ohne Cleanup.\"\"\"\n\n    def __init__(self):\n        super().__init__(allowlist=[])\n\n    def should_skip(self, file_path: str) -> bool:\n        return not is_test_file(file_path)\n\n    def check(self, file_path: str, content: str) -> List[str]:\n        warnings = []\n\n        # Dateisystem-Schreiboperationen\n        fs_write_patterns = [\n            r\"file_put_contents\\s*\\(\",\n            r\"fwrite\\s*\\(\",\n            r\"mkdir\\s*\\(\",\n            r\"touch\\s*\\(\",\n            r\"copy\\s*\\(\",\n            r\"rename\\s*\\(\",\n        ]\n\n        has_fs_write = any(\n            re.search(pattern, content)\n            for pattern in fs_write_patterns\n        )\n\n        if has_fs_write:\n            # Prüfe ob Cleanup vorhanden\n            has_cleanup = bool(re.search(\n                r\"unlink\\s*\\(|rmdir\\s*\\(|tearDown\",\n                content\n            ))\n\n            if not has_cleanup:\n                warnings.append(\n                    \"W14.2: Test writes to filesystem without visible cleanup. \"\n                    \"Ensure files are removed in tearDown() or after test.\"\n                )\n\n        return warnings\n\n\nclass W14_3_GlobalState(Rule):\n    \"\"\"W14.3: Modifikation von globalem Zustand in Tests.\"\"\"\n\n    def __init__(self):\n        super().__init__(allowlist=[])\n\n    def should_skip(self, file_path: str) -> bool:\n        return not is_test_file(file_path)\n\n    def check(self, file_path: str, content: str) -> List[str]:\n        warnings = []\n\n        global_state_patterns = [\n            (r\"ini_set\\s*\\(\", \"ini_set() modifies PHP configuration\"),\n            (r\"putenv\\s*\\(\", \"putenv() modifies environment variables\"),\n            (r\"define\\s*\\(\", \"define() creates global constants\"),\n            (r\"date_default_timezone_set\\s*\\(\", \"Timezone setting is global\"),\n        ]\n\n        for pattern, description in global_state_patterns:\n            if re.search(pattern, content):\n                warnings.append(\n                    f\"W14.3: {description}. \"\n                    \"Ensure original value is restored in tearDown().\"\n                )\n\n        return warnings\n\n\nclass W14_4_HardcodedPaths(Rule):\n    \"\"\"W14.4: Hardcoded absolute Pfade in Tests.\"\"\"\n\n    def __init__(self):\n        super().__init__(allowlist=[])\n\n    def should_skip(self, file_path: str) -> bool:\n        return not is_test_file(file_path)\n\n    def check(self, file_path: str, content: str) -> List[str]:\n        warnings = []\n\n        # Absolute Pfade die problematisch sein könnten\n        hardcoded_paths = re.findall(\n            r\"['\\\"]\/(var|tmp|home|Users)\/[^'\\\"]+['\\\"]\",\n            content\n        )\n\n        # Erlaube \/tmp für Temp-Dateien\n        problematic = [p for p in hardcoded_paths if \"\/tmp\/\" not in p]\n\n        if problematic:\n            warnings.append(\n                f\"W14.4: Hardcoded absolute paths in test ({len(problematic)}x). \"\n                \"Use sys_get_temp_dir() or __DIR__ for portable paths.\"\n            )\n\n        return warnings\n\n\nclass W14_5_SleepInTests(Rule):\n    \"\"\"W14.5: sleep() in Tests - verlangsamt Test-Suite.\"\"\"\n\n    def __init__(self):\n        super().__init__(allowlist=[])\n\n    def should_skip(self, file_path: str) -> bool:\n        return not is_test_file(file_path)\n\n    def check(self, file_path: str, content: str) -> List[str]:\n        warnings = []\n\n        sleep_calls = re.findall(r\"\\bsleep\\s*\\(\\s*(\\d+)\\s*\\)\", content)\n        usleep_calls = re.findall(r\"\\busleep\\s*\\(\", content)\n\n        total_sleep = sum(int(s) for s in sleep_calls)\n\n        if sleep_calls or usleep_calls:\n            warnings.append(\n                f\"W14.5: sleep()\/usleep() in test ({len(sleep_calls) + len(usleep_calls)}x, \"\n                f\"~{total_sleep}s total). Consider mocking time-dependent behavior.\"\n            )\n\n        return warnings\n\n\nclass W14_6_TestDependencies(Rule):\n    \"\"\"W14.6: Tests mit @depends ohne klare Isolation.\"\"\"\n\n    def __init__(self):\n        super().__init__(allowlist=[])\n\n    def should_skip(self, file_path: str) -> bool:\n        return not is_test_file(file_path)\n\n    def check(self, file_path: str, content: str) -> List[str]:\n        warnings = []\n\n        depends_annotations = re.findall(r\"@depends\\s+(\\w+)\", content)\n\n        if len(depends_annotations) > 2:\n            warnings.append(\n                f\"W14.6: Test has {len(depends_annotations)} @depends annotations. \"\n                \"Complex test dependencies can cause cascading failures. Consider isolation.\"\n            )\n\n        return warnings\n\n\nclass W14_7_AssertionCount(Rule):\n    \"\"\"W14.7: Test-Methode ohne Assertions.\"\"\"\n\n    def __init__(self):\n        super().__init__(allowlist=[])\n\n    def should_skip(self, file_path: str) -> bool:\n        return not is_test_file(file_path)\n\n    def check(self, file_path: str, content: str) -> List[str]:\n        warnings = []\n\n        # Finde Test-Methoden\n        test_methods = re.findall(\n            r\"public\\s+function\\s+(test\\w+)\\s*\\([^)]*\\)\\s*(?::\\s*void)?\\s*\\{([^}]+)\\}\",\n            content,\n            re.DOTALL\n        )\n\n        for method_name, method_body in test_methods:\n            # Zähle Assertions\n            assertions = len(re.findall(\n                r\"\\$this->assert|\\$this->expect|self::assert|self::expect\",\n                method_body\n            ))\n\n            if assertions == 0:\n                warnings.append(\n                    f\"W14.7: Test method '{method_name}' has no assertions. \"\n                    \"Tests should verify expected behavior.\"\n                )\n\n        return warnings\n\n\n# =============================================================================\n# RULE COLLECTION\n# =============================================================================\n\nRULES = [\n    W14_1_MissingTearDown(),\n    W14_2_FileSystemAccess(),\n    W14_3_GlobalState(),\n    W14_4_HardcodedPaths(),\n    W14_5_SleepInTests(),\n    W14_6_TestDependencies(),\n    W14_7_AssertionCount(),\n]\n",
        "structuredPatch": [],
        "originalFile": null
    }
}
← Vorheriger Zur Liste Nächster →