c200fe0f86
CI / lint (push) Successful in 18s
CI / unit_tests (push) Has been cancelled
CI / e2e_tests (push) Has been cancelled
CI / benchmark-publish (push) Has been cancelled
CI / build (push) Has been cancelled
CI / typecheck (push) Has been cancelled
CI / security (push) Has been cancelled
CI / integration_tests (push) Has been cancelled
CI / helm (push) Has been cancelled
CI / quality (push) Has been cancelled
CI / coverage (push) Has been cancelled
CI / status-check (push) Has been cancelled
CI / benchmark-regression (push) Has been cancelled
CI / docker (push) Has been cancelled
Add hover and definition support to LspClient and LspRuntime. - LspClient.get_hover(): sends textDocument/hover, returns Hover dict or None - LspClient.get_definitions(): sends textDocument/definition, handles Location, Location[], and LocationLink[] responses - LspRuntime wrappers: input validation, file reading, language detection, 1-based to 0-based line/column conversion - try/finally for did_close() safety on both new methods - Tool adapter: HOVER and DEFINITIONS dispatch to runtime instead of raising LspNotAvailableError - Updated lsp_tool_adapter_coverage test (HOVER -> REFERENCES) - 21 Behave BDD scenarios with full path coverage Closes #1243 Co-authored-by: Hamza Khyari <hamza.khyari@cleverthis.com> Co-committed-by: Hamza Khyari <hamza.khyari@cleverthis.com>
183 lines
6.3 KiB
Python
183 lines
6.3 KiB
Python
"""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}"
|