pre_rules_tests.py

Code Hygiene Score: 100

Keine Issues gefunden.

Dependencies 3

Funktionen 4

Code

#!/usr/bin/env python3
"""
Pre-Hook Test Isolation Regeln (BLOCK) - Test Isolation.

P14.x Regeln: Erzwingt Test-Isolation durch Blockieren von Produktions-DB-Zugriff.

Prinzip: "Tests beeinflussen sich nicht gegenseitig. Globaler Zustand ist kontrolliert."

WICHTIG: Diese Regeln gelten NUR für Test-Dateien (/tests/, /Test/).
"""

import re
from typing import Optional
from .rule_base import block


# =============================================================================
# KONFIGURATION
# =============================================================================

# Pfade die als Test-Dateien gelten
TEST_PATHS = ["/tests/", "/Test/"]

# Produktions-Datenbanken (ohne _test Suffix)
PRODUCTION_DATABASES = {
    "P14.1": {
        "pattern": r"['\"]ki_content['\"]",
        "exclude_pattern": r"['\"]ki_content_test['\"]",
        "message": "Production database 'ki_content' used in tests. Use 'ki_content_test' instead.",
    },
    "P14.2": {
        "pattern": r"['\"]ki_dev['\"]",
        "exclude_pattern": r"['\"]ki_dev_test['\"]",
        "message": "Production database 'ki_dev' used in tests. Use 'ki_dev_test' instead.",
    },
    "P14.3": {
        "pattern": r"['\"]ki_protokoll['\"]",
        "exclude_pattern": r"['\"]ki_protokoll_test['\"]",
        "message": "Production database 'ki_protokoll' used in tests. Use 'ki_protokoll_test' instead.",
    },
}

# Gefährliche Operationen in Tests
DANGEROUS_TEST_PATTERNS = {
    "P14.4": {
        "pattern": r"TRUNCATE\s+TABLE",
        "message": "TRUNCATE TABLE in tests. Use test fixtures or transactions instead.",
    },
    "P14.5": {
        "pattern": r"DROP\s+(?:TABLE|DATABASE)",
        "message": "DROP TABLE/DATABASE in tests. Never drop in tests.",
    },
}


# =============================================================================
# HELPER
# =============================================================================

def is_test_file(file_path: str) -> bool:
    """Prüft ob die Datei eine Test-Datei ist."""
    return any(test_path in file_path for test_path in TEST_PATHS)


# =============================================================================
# REGELN
# =============================================================================

def p14_production_database(file_path: str, content: str) -> Optional[dict]:
    """P14.1-P14.3: Produktions-Datenbank in Tests blockieren."""
    # Nur für Test-Dateien prüfen
    if not is_test_file(file_path):
        return None

    for rule_id, config in PRODUCTION_DATABASES.items():
        # Suche nach Produktions-DB
        if re.search(config["pattern"], content):
            # Prüfe ob es die Test-Variante ist
            if not re.search(config["exclude_pattern"], content):
                return block(rule_id, config["message"])

    return None


def p14_dangerous_operations(file_path: str, content: str) -> Optional[dict]:
    """P14.4-P14.5: Gefährliche Operationen in Tests blockieren."""
    # Nur für Test-Dateien prüfen
    if not is_test_file(file_path):
        return None

    for rule_id, config in DANGEROUS_TEST_PATTERNS.items():
        if re.search(config["pattern"], content, re.IGNORECASE):
            return block(rule_id, config["message"])

    return None


def p14_shared_state(file_path: str, content: str) -> Optional[dict]:
    """P14.6: Statische Properties ohne Reset in Tests."""
    # Nur für Test-Dateien prüfen
    if not is_test_file(file_path):
        return None

    # Prüfe auf static Properties die in Tests gesetzt werden
    # ohne entsprechenden Reset in tearDown
    static_assignments = re.findall(
        r"self::\$\w+\s*=|static::\$\w+\s*=",
        content
    )

    if static_assignments:
        # Prüfe ob tearDown vorhanden ist
        has_teardown = bool(re.search(
            r"(?:protected|public)\s+function\s+tearDown\s*\(",
            content
        ))

        if not has_teardown:
            return block(
                "P14.6",
                f"Static property assignment in tests ({len(static_assignments)}x) "
                "without tearDown() method. Add tearDown() to reset static state."
            )

    return None


# =============================================================================
# RULE COLLECTION
# =============================================================================

RULES = [
    p14_production_database,
    p14_dangerous_operations,
    p14_shared_state,
]
← Übersicht