"""Ephemeral local SFTP server mock for dev/testing. This module provides a lightweight, in-memory stand-in for a paramiko-based SFTP server. It never opens real sockets. Instead, it monkeypatches `paramiko.SSHClient` so that client code believes it is connected to a server with matching credentials. Uploaded files are stored in a temporary directory to simulate end-to-end transfers in dev-only contexts. Usage: - In tests, use `local_sftp_server` fixture from conftest to enable. - In dev runs, set `SFTP_HOST=localhost` and invoke the helper to patch paramiko before starting transfers (via fixture or explicit call). Security: - Credentials are validated against provided config; no secrets are persisted. """ from __future__ import annotations import shutil from pathlib import Path from typing import Callable class _FSBackedSFTPClient: def __init__(self, root: Path): self.root = root def put(self, local: str, remote: str): # Remote path: create under root, respecting remote subdir dest = self.root / remote dest.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(local, dest) def close(self): # pragma: no cover - trivial pass class _MockSSHClient: def __init__(self, root: Path, expect_user: str, expect_pass: str): self.root = root self.expect_user = expect_user self.expect_pass = expect_pass self._policy = None def load_system_host_keys(self): # pragma: no cover - trivial return None def set_missing_host_key_policy(self, policy): # pragma: no cover self._policy = policy def connect( self, hostname: str, username: str, password: str, port: int, timeout: int, **kwargs, ): # Validate credentials; hostname is expected to be 'localhost' if username != self.expect_user or password != self.expect_pass: raise PermissionError("Invalid credentials for local SFTP mock") return None def open_sftp(self): return _FSBackedSFTPClient(self.root) def close(self): # pragma: no cover - trivial return None def enable_local_sftp_monkeypatch( tmp_root: Path, username: str, password: str, ) -> Callable[[], None]: """Monkeypatch paramiko.SSHClient to use local mock backed by tmp_root. Returns a callable `undo()` to restore original class. """ import paramiko # type: ignore original_cls = paramiko.SSHClient def _factory(): return _MockSSHClient(tmp_root, username, password) paramiko.SSHClient = _factory # type: ignore def undo(): # pragma: no cover - trivial paramiko.SSHClient = original_cls # type: ignore return undo