"""Shared helper functions for LSP transport BDD step definitions. Extracted common mock-building utilities so individual step files stay under the 500-line limit (see CONTRIBUTING.md file-size guideline). """ from __future__ import annotations import json import subprocess from typing import Any from unittest.mock import MagicMock def make_mock_process( *, poll_return: int | None = None, returncode: int = 0, stdin: object | None = "auto", stdout: object | None = "auto", stderr: object | None = "auto", pid: int = 12345, ) -> MagicMock: """Create a ``MagicMock`` resembling ``subprocess.Popen``. Args: poll_return: Value for :attr:`~MagicMock.poll` return value. ``None`` means the process is still running. returncode: The subprocess exit code when ``poll()`` returns. stdin: Stdin mock object (or ``"auto"`` for auto-generated). stdout: Stdout mock object (or ``"auto"`` for auto-generated). stderr: Stderr mock object (or ``"auto"`` for auto-generated). pid: Process ID to report. Returns: A fully configured ``MagicMock`` with ``subprocess.Popen`` spec. """ proc = MagicMock(spec=subprocess.Popen) proc.pid = pid proc.poll.return_value = poll_return proc.returncode = returncode if stdin == "auto": proc.stdin = MagicMock() else: proc.stdin = stdin if stdout == "auto": proc.stdout = MagicMock() else: proc.stdout = stdout if stderr == "auto": proc.stderr = MagicMock() else: proc.stderr = stderr return proc def build_lsp_frame(body_dict: dict[str, Any]) -> bytes: """Encode a dict as a Content-Length framed LSP message. Args: body_dict: The JSON-serialisable message body. Returns: Raw bytes including the ``Content-Length`` header and empty line prefix. """ payload = json.dumps(body_dict, separators=(",", ":")).encode("utf-8") header = f"Content-Length: {len(payload)}\r\n\r\n".encode("ascii") return header + payload