# 🏗️ Web Sentinel - Spécifications d'Architecture Technique

*Patterns de développement, bonnes pratiques et standards pour maintenir la qualité du code*

## 🎯 Vue d'ensemble architecturale

Web Sentinel suit une **architecture modulaire robuste** avec séparation claire des responsabilités, permettant une maintenance aisée et une extensibilité maximale pour les agents IA futurs.

---

## 🏆 Points forts de l'architecture actuelle

### **🔧 Modularité excellente**

#### **Pattern de modules de sécurité**
```python
# Structure standardisée pour tous les modules
# web_sentinel/checks/[module_name].py

def evaluate_[module_name](request: ScanRequest) -> Iterable[Finding]:
    """
    Pattern standard pour tous les modules de vérification
    
    Args:
        request: ScanRequest contenant l'URL et les paramètres de scan
        
    Returns:
        Iterable[Finding]: Liste des vulnérabilités détectées
        
    Raises:
        Aucune - doit gérer toutes les exceptions en interne
    """
    findings: List[Finding] = []
    
    try:
        # 1. Validation des prérequis
        if not request.url:
            return findings
            
        # 2. Tests de sécurité spécifiques au module
        # ...logique du module...
        
        # 3. Création des findings standardisés
        findings.append(Finding(
            check="module-name-check",
            title="Titre descriptif",
            severity="critical|high|medium|low|info",
            description="Description détaillée du problème",
            remediation="Instructions de correction précises",
            impact="Impact business optionnel",
            evidence="Preuves techniques optionnelles"
        ))
        
    except (requests.RequestException, socket.error, ssl.SSLError) as network_error:
        # Gestion gracieuse des erreurs réseau
        findings.append(Finding(
            check="module-name-connection",
            title="Connection failed",
            severity="medium",
            description=f"Network error during {module_name} check: {network_error}",
            remediation="Verify connectivity and firewall configuration",
        ))
    except Exception as unexpected_error:
        # Log des erreurs inattendues sans casser le scan
        logger.warning(f"Unexpected error in {module_name}: {unexpected_error}")
    
    return findings

# Enregistrement dans l'orchestrateur
def build_default_modules() -> List[CheckModule]:
    return [
        CheckModule(
            name="module-name",
            description="Brief description for CLI help",
            runner=evaluate_module_name,
            tags=("passive", "category", "subcategory")  # Tags pour filtrage
        ),
    ]
```

#### **Avantages de cette approche** :
- ✅ **Isolation complète** : Échec d'un module n'affecte pas les autres
- ✅ **Extensibilité** : Ajout facile de nouveaux modules
- ✅ **Testabilité** : Tests unitaires simples par module
- ✅ **Parallélisation** : Exécution concurrente via ThreadPoolExecutor

### **🖥️ Interfaces multiples découplées**

#### **Pattern d'interface abstraite**
```python
# web_sentinel/interfaces/base_interface.py
from abc import ABC, abstractmethod
from typing import Dict, Any, Optional

class ScanInterface(ABC):
    """Interface abstraite pour tous les types d'interfaces utilisateur"""
    
    def __init__(self, orchestrator: ScanOrchestrator):
        self.orchestrator = orchestrator
        self.config: Dict[str, Any] = {}
    
    @abstractmethod
    def initialize(self) -> bool:
        """Initialise l'interface (GUI, CLI args, etc.)"""
        pass
    
    @abstractmethod
    def get_scan_request(self) -> ScanRequest:
        """Récupère les paramètres de scan depuis l'interface"""
        pass
    
    @abstractmethod
    def display_progress(self, progress: float, current_module: str):
        """Affiche le progrès du scan"""
        pass
    
    @abstractmethod
    def display_results(self, results: ScanResult):
        """Affiche les résultats du scan"""
        pass
    
    @abstractmethod
    def handle_error(self, error: Exception):
        """Gère les erreurs de scan"""
        pass
    
    def run_scan(self, request: ScanRequest) -> ScanResult:
        """Méthode commune d'exécution de scan"""
        return self.orchestrator.run_scan(request)

# Implémentations concrètes
class CLIInterface(ScanInterface):
    """Interface ligne de commande"""
    
    def initialize(self) -> bool:
        self.args = self._parse_cli_args()
        return True
    
    def display_progress(self, progress: float, current_module: str):
        print(f"[{progress:6.1f}%] {current_module}")

class GUIInterface(ScanInterface):
    """Interface graphique tkinter"""
    
    def initialize(self) -> bool:
        self.window = tk.Tk()
        self._setup_gui()
        return True
    
    def display_progress(self, progress: float, current_module: str):
        self.progress_bar['value'] = progress
        self.status_label.config(text=current_module)

class HeadlessInterface(ScanInterface):
    """Interface sans UI pour CI/CD"""
    
    def display_progress(self, progress: float, current_module: str):
        pass  # Silencieux en mode headless
```

#### **Avantages du découplage** :
- ✅ **Maintenance séparée** : Chaque interface évolue indépendamment
- ✅ **Tests ciblés** : Tests spécifiques par type d'interface
- ✅ **Réutilisation** : Logique métier partagée entre interfaces
- ✅ **Flexibilité** : Ajout facile de nouvelles interfaces (mobile, web, etc.)

### **🛡️ Dégradation gracieuse**

#### **Pattern de gestion d'erreurs robuste**
```python
# web_sentinel/core/error_handling.py
from typing import Optional, Callable, Any
import functools
import logging

logger = logging.getLogger(__name__)

def graceful_degradation(fallback_value=None, log_error=True):
    """
    Décorateur pour dégradation gracieuse
    Permet au scan de continuer même si un module échoue
    """
    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper(*args, **kwargs) -> Any:
            try:
                return func(*args, **kwargs)
            except ImportError as e:
                if log_error:
                    logger.warning(f"Optional dependency missing in {func.__name__}: {e}")
                return fallback_value
            except Exception as e:
                if log_error:
                    logger.error(f"Error in {func.__name__}: {e}")
                return fallback_value
        return wrapper
    return decorator

# Usage dans les modules
@graceful_degradation(fallback_value=[])
def evaluate_static_analysis(request: ScanRequest) -> Iterable[Finding]:
    """Module d'analyse statique avec dépendance optionnelle BeautifulSoup4"""
    from bs4 import BeautifulSoup  # ImportError possible
    
    # ...logique d'analyse...
    return findings

# Gestion des dépendances optionnelles
class OptionalDependencyManager:
    """Gestionnaire des dépendances optionnelles"""
    
    _available_features = {}
    
    @classmethod
    def check_feature(cls, feature_name: str, import_statement: str) -> bool:
        """Vérifie la disponibilité d'une fonctionnalité optionnelle"""
        if feature_name not in cls._available_features:
            try:
                exec(import_statement)
                cls._available_features[feature_name] = True
            except ImportError:
                cls._available_features[feature_name] = False
                logger.info(f"Optional feature '{feature_name}' not available")
        
        return cls._available_features[feature_name]
    
    @classmethod
    def require_feature(cls, feature_name: str, error_message: str = None):
        """Décorateur exigeant une fonctionnalité optionnelle"""
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                if not cls._available_features.get(feature_name, False):
                    msg = error_message or f"Feature '{feature_name}' not available"
                    raise RuntimeError(msg)
                return func(*args, **kwargs)
            return wrapper
        return decorator

# Configuration des fonctionnalités optionnelles
OPTIONAL_FEATURES = {
    'static_analysis': 'from bs4 import BeautifulSoup',
    'mobile_interface': 'import kivy',
    'advanced_crypto': 'import cryptography',
    'pdf_generation': 'from reportlab.pdfgen import canvas'
}

# Initialisation au démarrage
for feature, import_stmt in OPTIONAL_FEATURES.items():
    OptionalDependencyManager.check_feature(feature, import_stmt)
```

### **🧪 Tests robustes avec pytest**

#### **Structure de tests standardisée**
```python
# tests/test_module_template.py
import pytest
from unittest.mock import Mock, patch
from web_sentinel.model import ScanRequest, Finding
from web_sentinel.checks.module_name import evaluate_module_name

class TestModuleName:
    """Template de tests pour modules de sécurité"""
    
    def setup_method(self):
        """Setup exécuté avant chaque test"""
        self.base_request = ScanRequest(
            url="https://example.com",
            timeout=10,
            allow_invasive=False
        )
    
    def test_module_with_valid_url(self):
        """Test cas nominal avec URL valide"""
        findings = list(evaluate_module_name(self.base_request))
        
        # Assertions communes
        assert isinstance(findings, list)
        for finding in findings:
            assert isinstance(finding, Finding)
            assert finding.check.startswith("module-name")
            assert finding.severity in ["critical", "high", "medium", "low", "info"]
            assert len(finding.description) > 10  # Description substantielle
            assert len(finding.remediation) > 10  # Remédiation utile
    
    def test_module_with_invalid_url(self):
        """Test avec URL invalide"""
        invalid_request = ScanRequest(url="invalid-url", timeout=5, allow_invasive=False)
        findings = list(evaluate_module_name(invalid_request))
        
        # Doit gérer gracieusement les URLs invalides
        assert isinstance(findings, list)  # Pas d'exception levée
    
    @patch('requests.get')
    def test_module_with_network_error(self, mock_get):
        """Test avec erreur réseau"""
        mock_get.side_effect = requests.ConnectionError("Network error")
        
        findings = list(evaluate_module_name(self.base_request))
        
        # Doit créer un finding d'erreur réseau
        assert any("connection" in f.check.lower() for f in findings)
    
    def test_module_with_timeout(self):
        """Test avec timeout court"""
        timeout_request = ScanRequest(
            url="https://httpbin.org/delay/10",  # URL avec délai
            timeout=1,
            allow_invasive=False
        )
        
        # Ne doit pas bloquer plus que le timeout
        import time
        start_time = time.time()
        findings = list(evaluate_module_name(timeout_request))
        duration = time.time() - start_time
        
        assert duration < 5  # Marge pour traitement
    
    def test_module_performance(self):
        """Test de performance - module doit être rapide"""
        import time
        
        start_time = time.time()
        findings = list(evaluate_module_name(self.base_request))
        duration = time.time() - start_time
        
        # Module individuel ne doit pas prendre plus de 30 secondes
        assert duration < 30
    
    def test_findings_quality(self):
        """Test de qualité des findings"""
        findings = list(evaluate_module_name(self.base_request))
        
        for finding in findings:
            # Pas de doublons dans les checks
            assert findings.count(finding) == 1
            
            # Titre informatif
            assert len(finding.title) > 5
            assert not finding.title.isupper()  # Pas tout en majuscules
            
            # Description utile
            assert "TODO" not in finding.description
            assert len(finding.description.split()) > 3  # Au moins 3 mots
            
            # Remédiation actionable
            assert len(finding.remediation.split()) > 2  # Au moins 2 mots

# Fixtures partagées pour les tests
@pytest.fixture
def mock_successful_request():
    """Mock d'une requête HTTP réussie"""
    mock_response = Mock()
    mock_response.status_code = 200
    mock_response.headers = {
        'Server': 'nginx/1.18.0',
        'Content-Type': 'text/html'
    }
    mock_response.text = "<html><head><title>Test</title></head><body>Content</body></html>"
    return mock_response

@pytest.fixture
def mock_ssl_info():
    """Mock d'informations SSL"""
    return {
        'subject': [('CN', 'example.com')],
        'issuer': [('CN', 'Let\'s Encrypt Authority X3')],
        'version': 3,
        'notBefore': 'Aug  1 00:00:00 2023 GMT',
        'notAfter': 'Nov  1 23:59:59 2024 GMT'
    }

# Tests d'intégration
class TestModuleIntegration:
    """Tests d'intégration entre modules"""
    
    def test_orchestrator_runs_all_modules(self):
        """Test que l'orchestrateur exécute tous les modules"""
        from web_sentinel.orchestrator import ScanOrchestrator
        
        orchestrator = ScanOrchestrator()
        request = ScanRequest(url="https://example.com", timeout=30, allow_invasive=False)
        
        result = orchestrator.run_scan(request)
        
        # Vérifier que tous les modules ont été exécutés
        expected_modules = {"ssl", "headers", "static-analysis", "injection", "third-party"}
        found_modules = {f.check.split('-')[0] for f in result.findings}
        
        # Au moins 3 modules doivent avoir produit des résultats
        assert len(found_modules.intersection(expected_modules)) >= 3
```

#### **Configuration pytest avancée**
```python
# tests/conftest.py
import pytest
import os
import tempfile
from unittest.mock import Mock

# Configuration pour tests sans émojis
os.environ['TESTING'] = 'true'

@pytest.fixture(scope="session")
def test_config():
    """Configuration de test partagée"""
    return {
        'timeout': 5,
        'test_url': 'https://httpbin.org/',
        'invalid_url': 'https://definitely-not-a-real-domain-12345.com'
    }

@pytest.fixture
def temp_db():
    """Base de données temporaire pour tests"""
    with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as tmp_file:
        db_path = tmp_file.name
    
    # Créer schéma de test
    yield db_path
    
    # Cleanup
    if os.path.exists(db_path):
        os.unlink(db_path)

@pytest.fixture(autouse=True)
def mock_network_in_tests(monkeypatch):
    """Mock automatique du réseau dans les tests marqués 'no_network'"""
    def mock_get(*args, **kwargs):
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.headers = {}
        mock_response.text = "<html></html>"
        return mock_response
    
    # Appliquer seulement aux tests marqués
    test_item = getattr(pytest, 'current_item', None)
    if test_item and test_item.get_closest_marker('no_network'):
        monkeypatch.setattr('requests.get', mock_get)

# Marqueurs personnalisés
pytest_plugins = []

def pytest_configure(config):
    """Configuration des marqueurs pytest"""
    config.addinivalue_line(
        "markers", "no_network: mark test to mock network calls"
    )
    config.addinivalue_line(
        "markers", "slow: mark test as slow running"
    )
    config.addinivalue_line(
        "markers", "integration: mark test as integration test"
    )
```

### **📝 Documentation complète**

#### **Standards de documentation du code**
```python
# web_sentinel/checks/example_module.py
"""
Module d'exemple pour Web Sentinel

Ce module implémente des vérifications de sécurité spécifiques à [DOMAINE].
Il suit les patterns standards de Web Sentinel pour l'intégration dans
l'orchestrateur de scans.

Exemple:
    from web_sentinel.checks.example_module import evaluate_example
    from web_sentinel.model import ScanRequest
    
    request = ScanRequest(url="https://example.com", timeout=30, allow_invasive=False)
    findings = list(evaluate_example(request))

Author: Web Sentinel Team
Version: 1.0.0
"""

from typing import Iterable, List, Optional
import requests
import logging
from web_sentinel.model import ScanRequest, Finding

logger = logging.getLogger(__name__)

def evaluate_example(request: ScanRequest) -> Iterable[Finding]:
    """
    Évalue les vulnérabilités [TYPE] d'un site web.
    
    Cette fonction effectue une analyse [PASSIVE/ACTIVE] du site cible pour
    détecter les problèmes de sécurité liés à [DOMAINE SPÉCIFIQUE].
    
    Args:
        request (ScanRequest): Requête de scan contenant:
            - url (str): URL complète du site à analyser
            - timeout (int): Timeout pour les requêtes HTTP en secondes
            - allow_invasive (bool): Autorisation des tests invasifs
            
    Returns:
        Iterable[Finding]: Générateur de vulnérabilités détectées.
            Chaque Finding contient:
            - check (str): Identifiant unique du test
            - title (str): Titre descriptif de la vulnérabilité
            - severity (str): Niveau de criticité (critical/high/medium/low/info)
            - description (str): Description détaillée du problème
            - remediation (str): Instructions de correction
            - impact (str, optional): Impact business de la vulnérabilité
            - evidence (str, optional): Preuves techniques
    
    Raises:
        Aucune exception n'est levée. Les erreurs sont gérées en interne
        et transformées en findings d'erreur si nécessaire.
    
    Note:
        - Mode passif par défaut, respecte allow_invasive pour tests actifs
        - Timeout automatique selon request.timeout
        - Gestion gracieuse des erreurs réseau
        
    Example:
        >>> request = ScanRequest(url="https://vulnerable-site.com", timeout=10)
        >>> findings = list(evaluate_example(request))
        >>> print(f"Found {len(findings)} issues")
        Found 3 issues
        
        >>> for finding in findings:
        ...     print(f"{finding.severity}: {finding.title}")
        high: Missing Security Headers
        medium: Outdated Server Version
        low: Information Disclosure
    """
    findings: List[Finding] = []
    
    # Validation des paramètres d'entrée
    if not request or not request.url:
        logger.warning("evaluate_example called with invalid request")
        return findings
    
    logger.info(f"Starting example security checks for {request.url}")
    
    try:
        # [LOGIQUE DU MODULE ICI]
        pass
        
    except requests.RequestException as network_error:
        # Gestion spécifique des erreurs réseau
        logger.warning(f"Network error in example module: {network_error}")
        findings.append(Finding(
            check="example-connection-error",
            title="Connection Error During Example Check",
            severity="medium",
            description=f"Unable to connect to target during example analysis: {network_error}",
            remediation="Verify target accessibility and network connectivity",
            impact="Security analysis incomplete"
        ))
    except Exception as unexpected_error:
        # Gestion des erreurs inattendues
        logger.error(f"Unexpected error in example module: {unexpected_error}")
        # Ne pas lever l'exception - permettre au scan de continuer
    
    logger.info(f"Example module completed with {len(findings)} findings")
    return findings

class ExampleSecurityChecker:
    """
    Classe utilitaire pour les vérifications de sécurité [TYPE].
    
    Cette classe encapsule la logique spécialisée pour analyser
    [ASPECT SPÉCIFIQUE] et détecter les vulnérabilités associées.
    
    Attributes:
        timeout (int): Timeout par défaut pour les requêtes
        user_agent (str): User-Agent utilisé pour les requêtes HTTP
        
    Example:
        >>> checker = ExampleSecurityChecker(timeout=30)
        >>> vulnerabilities = checker.check_specific_issue("https://site.com")
        >>> print(f"Found {len(vulnerabilities)} vulnerabilities")
    """
    
    def __init__(self, timeout: int = 30, user_agent: str = None):
        """
        Initialise le vérificateur de sécurité.
        
        Args:
            timeout (int): Timeout pour les requêtes HTTP
            user_agent (str): User-Agent personnalisé (optionnel)
        """
        self.timeout = timeout
        self.user_agent = user_agent or "Web Sentinel Security Scanner"
        
    def check_specific_issue(self, url: str) -> List[Finding]:
        """
        Vérifie un problème de sécurité spécifique.
        
        Args:
            url (str): URL à analyser
            
        Returns:
            List[Finding]: Liste des vulnérabilités détectées
            
        Raises:
            requests.RequestException: En cas d'erreur de connexion
        """
        # [IMPLÉMENTATION SPÉCIFIQUE]
        pass

# Configuration du module
MODULE_CONFIG = {
    'name': 'example',
    'description': 'Example security checks for demonstration',
    'version': '1.0.0',
    'author': 'Web Sentinel Team',
    'tags': ['passive', 'example', 'demonstration'],
    'dependencies': ['requests'],
    'optional_dependencies': ['beautifulsoup4']
}

# Export des fonctions publiques
__all__ = ['evaluate_example', 'ExampleSecurityChecker', 'MODULE_CONFIG']
```

---

## 🎯 Patterns de développement recommandés

### **1. Pattern Factory pour les modules**
```python
# web_sentinel/core/module_factory.py
from typing import Dict, Type, Callable
from web_sentinel.model import ScanRequest, Finding

class ModuleFactory:
    """Factory pour créer et enregistrer les modules de sécurité"""
    
    _modules: Dict[str, Callable] = {}
    _module_metadata: Dict[str, Dict] = {}
    
    @classmethod
    def register_module(cls, name: str, runner: Callable, 
                       description: str = "", tags: tuple = (), 
                       dependencies: list = None):
        """Enregistre un nouveau module de sécurité"""
        cls._modules[name] = runner
        cls._module_metadata[name] = {
            'description': description,
            'tags': tags,
            'dependencies': dependencies or [],
            'registered_at': datetime.now()
        }
    
    @classmethod
    def get_module(cls, name: str) -> Optional[Callable]:
        """Récupère un module par son nom"""
        return cls._modules.get(name)
    
    @classmethod
    def list_modules(cls, tag_filter: str = None) -> List[str]:
        """Liste les modules disponibles, optionnellement filtrés par tag"""
        if not tag_filter:
            return list(cls._modules.keys())
        
        return [name for name, metadata in cls._module_metadata.items()
                if tag_filter in metadata.get('tags', [])]
    
    @classmethod
    def validate_dependencies(cls, name: str) -> bool:
        """Vérifie que les dépendances d'un module sont disponibles"""
        metadata = cls._module_metadata.get(name, {})
        dependencies = metadata.get('dependencies', [])
        
        for dep in dependencies:
            try:
                __import__(dep)
            except ImportError:
                return False
        return True

# Décorateur pour enregistrement automatique
def security_module(name: str, description: str = "", tags: tuple = (), 
                   dependencies: list = None):
    """Décorateur pour enregistrer automatiquement un module"""
    def decorator(func: Callable):
        ModuleFactory.register_module(name, func, description, tags, dependencies)
        return func
    return decorator

# Usage
@security_module(
    name="ssl-advanced",
    description="Advanced SSL/TLS security checks",
    tags=("passive", "ssl", "crypto"),
    dependencies=["ssl", "socket"]
)
def evaluate_ssl_advanced(request: ScanRequest) -> Iterable[Finding]:
    # Implémentation du module
    pass
```

### **2. Pattern Observer pour le monitoring**
```python
# web_sentinel/core/observers.py
from abc import ABC, abstractmethod
from typing import List, Any

class ScanObserver(ABC):
    """Interface pour observer les événements de scan"""
    
    @abstractmethod
    def on_scan_started(self, request: ScanRequest):
        pass
    
    @abstractmethod
    def on_module_started(self, module_name: str):
        pass
    
    @abstractmethod
    def on_module_completed(self, module_name: str, findings: List[Finding]):
        pass
    
    @abstractmethod
    def on_scan_completed(self, result: ScanResult):
        pass
    
    @abstractmethod
    def on_error(self, error: Exception, context: str):
        pass

class ProgressObserver(ScanObserver):
    """Observer pour le suivi de progression"""
    
    def __init__(self, progress_callback: Callable[[float, str], None]):
        self.progress_callback = progress_callback
        self.total_modules = 0
        self.completed_modules = 0
    
    def on_scan_started(self, request: ScanRequest):
        self.total_modules = len(request.enabled_modules)
        self.completed_modules = 0
    
    def on_module_completed(self, module_name: str, findings: List[Finding]):
        self.completed_modules += 1
        progress = (self.completed_modules / self.total_modules) * 100
        self.progress_callback(progress, f"Completed {module_name}")

class MetricsObserver(ScanObserver):
    """Observer pour collecter des métriques"""
    
    def __init__(self):
        self.metrics = {
            'scan_duration': 0,
            'modules_executed': 0,
            'total_findings': 0,
            'errors_count': 0
        }
        self.start_time = None
    
    def on_scan_started(self, request: ScanRequest):
        self.start_time = time.time()
    
    def on_scan_completed(self, result: ScanResult):
        self.metrics['scan_duration'] = time.time() - self.start_time
        self.metrics['total_findings'] = len(result.findings)
```

### **3. Pattern Strategy pour les rapports**
```python
# web_sentinel/reporting/strategies.py
from abc import ABC, abstractmethod

class ReportStrategy(ABC):
    """Stratégie abstraite pour la génération de rapports"""
    
    @abstractmethod
    def generate_report(self, scan_result: ScanResult) -> str:
        pass
    
    @abstractmethod
    def get_file_extension(self) -> str:
        pass

class JSONReportStrategy(ReportStrategy):
    """Stratégie de rapport JSON"""
    
    def generate_report(self, scan_result: ScanResult) -> str:
        return json.dumps(scan_result.to_dict(), indent=2, ensure_ascii=False)
    
    def get_file_extension(self) -> str:
        return ".json"

class HTMLReportStrategy(ReportStrategy):
    """Stratégie de rapport HTML"""
    
    def generate_report(self, scan_result: ScanResult) -> str:
        # Template HTML avec CSS intégré
        template = """
        <!DOCTYPE html>
        <html>
        <head>
            <title>Web Sentinel Security Report</title>
            <style>
                /* CSS pour le rapport */
            </style>
        </head>
        <body>
            <!-- Contenu du rapport -->
        </body>
        </html>
        """
        return template.format(**scan_result.to_dict())
    
    def get_file_extension(self) -> str:
        return ".html"

class ReportGenerator:
    """Générateur de rapports utilisant le pattern Strategy"""
    
    def __init__(self):
        self.strategies = {
            'json': JSONReportStrategy(),
            'html': HTMLReportStrategy(),
        }
    
    def generate(self, scan_result: ScanResult, format_type: str) -> str:
        strategy = self.strategies.get(format_type)
        if not strategy:
            raise ValueError(f"Unsupported report format: {format_type}")
        
        return strategy.generate_report(scan_result)
```

---

## 🔧 Standards de développement

### **Conventions de nommage**
- **Modules** : `snake_case` (ex: `ssl_analysis.py`)
- **Classes** : `PascalCase` (ex: `ScanOrchestrator`)
- **Fonctions** : `snake_case` (ex: `evaluate_ssl_config`)
- **Constantes** : `UPPER_CASE` (ex: `DEFAULT_TIMEOUT`)
- **Variables privées** : `_leading_underscore`

### **Structure de fichiers**
```
web_sentinel/
├── __init__.py              # Version et exports principaux
├── model.py                 # Modèles de données (ScanRequest, Finding, etc.)
├── orchestrator.py          # Orchestrateur principal
├── cli.py                   # Interface ligne de commande
├── checks/                  # Modules de vérification sécurité
│   ├── __init__.py
│   ├── ssl.py              # Vérifications SSL/TLS
│   ├── headers.py          # Headers de sécurité
│   └── ...
├── interfaces/              # Interfaces utilisateur
│   ├── gui/                # Interface graphique
│   ├── mobile/             # Interface mobile
│   └── web/                # Interface web
├── core/                    # Composants centraux
│   ├── error_handling.py   # Gestion d'erreurs
│   ├── config.py           # Configuration
│   └── logging.py          # Logging
├── reporting/               # Génération de rapports
├── utils/                   # Utilitaires
└── tests/                   # Tests unitaires et d'intégration
```

### **Standards de qualité**
- **Couverture de tests** : > 90%
- **Complexité cyclomatique** : < 10 par fonction
- **Documentation** : Docstrings pour toutes les fonctions publiques
- **Type hints** : Obligatoires pour les nouvelles fonctions
- **Linting** : Conformité flake8 et mypy

---

**Cette architecture technique garantit la maintenabilité et l'extensibilité de Web Sentinel pour les années à venir !** 🏗️