"""Step definitions for lsp_hover_definitions.feature. Comprehensive tests for get_hover and get_definitions on LspClient and LspRuntime, covering all response paths and input validation. """ from __future__ import annotations from typing import Any from unittest.mock import MagicMock from behave import then, when from cleveragents.lsp.client import LspClient from cleveragents.lsp.runtime import LspRuntime def _mock_client(**send_return: Any) -> LspClient: transport = MagicMock() transport.is_alive = True client = LspClient(transport=transport) client._send_request = MagicMock(**send_return) return client def _runtime_no_init() -> LspRuntime: return LspRuntime.__new__(LspRuntime) # ── Method existence ───────────────────────────────────── @then('LspClient should have a "{method}" method for lsp_hd') def step_client_has(context: object, method: str) -> None: assert hasattr(LspClient, method) and callable(getattr(LspClient, method)) @then('LspRuntime should have a "{method}" method for lsp_hd') def step_runtime_has(context: object, method: str) -> None: assert hasattr(LspRuntime, method) and callable(getattr(LspRuntime, method)) # ── LspClient get_hover paths ──────────────────────────── @when("I call LspClient.get_hover with mock returning a dict for lsp_hd") def step_hover_dict(context: object) -> None: client = _mock_client(return_value={"contents": "**int**"}) context.lsp_hd_hover = client.get_hover("file:///t.py", 0, 0) @when("I call LspClient.get_hover with mock returning null for lsp_hd") def step_hover_null(context: object) -> None: client = _mock_client(return_value=None) context.lsp_hd_hover = client.get_hover("file:///t.py", 0, 0) @when("I call LspClient.get_hover with mock returning an integer for lsp_hd") def step_hover_int(context: object) -> None: client = _mock_client(return_value=42) context.lsp_hd_hover = client.get_hover("file:///t.py", 0, 0) @then("the hover result should be a dict for lsp_hd") def step_hover_is_dict(context: object) -> None: assert isinstance(context.lsp_hd_hover, dict) @then("the hover result should be None for lsp_hd") def step_hover_is_none(context: object) -> None: assert context.lsp_hd_hover is None # ── LspClient get_definitions paths ────────────────────── @when("I call LspClient.get_definitions with mock returning a list for lsp_hd") def step_defs_list(context: object) -> None: client = _mock_client(return_value=[{"uri": "f", "range": {}}]) context.lsp_hd_defs = client.get_definitions("f", 0, 0) @when("I call LspClient.get_definitions with mock returning a single dict for lsp_hd") def step_defs_single(context: object) -> None: client = _mock_client(return_value={"uri": "f", "range": {}}) context.lsp_hd_defs = client.get_definitions("f", 0, 0) @when("I call LspClient.get_definitions with mock returning null for lsp_hd") def step_defs_null(context: object) -> None: client = _mock_client(return_value=None) context.lsp_hd_defs = client.get_definitions("f", 0, 0) @when("I call LspClient.get_definitions with mock returning an integer for lsp_hd") def step_defs_int(context: object) -> None: client = _mock_client(return_value=42) context.lsp_hd_defs = client.get_definitions("f", 0, 0) @then("the definitions result should be a list with 1 item for lsp_hd") def step_defs_one(context: object) -> None: defs = context.lsp_hd_defs assert isinstance(defs, list) and len(defs) == 1 @then("the definitions result should be an empty list for lsp_hd") def step_defs_empty(context: object) -> None: defs = context.lsp_hd_defs assert isinstance(defs, list) and len(defs) == 0 # ── LspRuntime input validation ────────────────────────── def _call_rt(method: str, **overrides: Any) -> Exception | None: rt = _runtime_no_init() kw = {"name": "x", "file_path": "/t", "line": 1, "column": 1} kw.update(overrides) try: getattr(rt, method)(**kw) except ValueError as exc: return exc return None @when("I call LspRuntime.get_hover with empty name for lsp_hd") def step_rt_hover_name(context: object) -> None: context.lsp_hd_err = _call_rt("get_hover", name="") @when("I call LspRuntime.get_hover with empty file_path for lsp_hd") def step_rt_hover_path(context: object) -> None: context.lsp_hd_err = _call_rt("get_hover", file_path="") @when("I call LspRuntime.get_hover with line 0 for lsp_hd") def step_rt_hover_line(context: object) -> None: context.lsp_hd_err = _call_rt("get_hover", line=0) @when("I call LspRuntime.get_hover with column 0 for lsp_hd") def step_rt_hover_col(context: object) -> None: context.lsp_hd_err = _call_rt("get_hover", column=0) @when("I call LspRuntime.get_definitions with empty name for lsp_hd") def step_rt_defs_name(context: object) -> None: context.lsp_hd_err = _call_rt("get_definitions", name="") @when("I call LspRuntime.get_definitions with empty file_path for lsp_hd") def step_rt_defs_path(context: object) -> None: context.lsp_hd_err = _call_rt("get_definitions", file_path="") @when("I call LspRuntime.get_definitions with line 0 for lsp_hd") def step_rt_defs_line(context: object) -> None: context.lsp_hd_err = _call_rt("get_definitions", line=0) @when("I call LspRuntime.get_definitions with column 0 for lsp_hd") def step_rt_defs_col(context: object) -> None: context.lsp_hd_err = _call_rt("get_definitions", column=0) @then('a ValueError should be raised for lsp_hd with message "{msg}"') def step_val_err(context: object, msg: str) -> None: err = context.lsp_hd_err assert err is not None, "Expected ValueError" assert msg in str(err), f"'{msg}' not in: {err}" # ── Tool adapter ───────────────────────────────────────── @then('the tool adapter capability map should include "{cap}" for lsp_hd') def step_adapter_cap(context: object, cap: str) -> None: from cleveragents.lsp.tool_adapter import _CAPABILITY_TOOL_MAP caps = [c.value for c in _CAPABILITY_TOOL_MAP] assert cap in caps, f"'{cap}' not in: {caps}"