Feature: LSP Client coverage As a developer maintaining LspClient I want thorough BDD tests for every code path So that coverage for client.py exceeds 90% # ── Property: server_capabilities (line 73) ──────────────────────── Scenario: lccov access server_capabilities before initialize Given lccov I create an LspClient with a mock transport Then lccov server_capabilities should be an empty dict # ── _send_request: success with params (lines 100-109, 111, 114-117, 130, 141) ── Scenario: lccov send_request with params returns result Given lccov I create an LspClient with a mock transport And lccov the transport echoes back a successful response When lccov I call _send_request with method "test/method" and params Then lccov the request result should contain the expected data And lccov the sent message should include params # ── _send_request: success without params (lines 100-104, 106 skip) ── Scenario: lccov send_request without params omits params key Given lccov I create an LspClient with a mock transport And lccov the transport echoes back a successful response When lccov I call _send_request with method "test/noparams" and no params Then lccov the request result should contain the expected data And lccov the sent message should not include params key # ── _send_request: server returns JSON-RPC error (lines 131-138) ── Scenario: lccov send_request raises LspError on JSON-RPC error Given lccov I create an LspClient with a mock transport And lccov the transport returns a JSON-RPC error response When lccov I call _send_request expecting an error Then lccov an LspError should be stored with message containing "LSP error" # ── _send_request: server closed connection (lines 117, 121-126) ── Scenario: lccov send_request raises LspError when server dies Given lccov I create an LspClient with a mock transport And lccov the transport returns None and is not alive When lccov I call _send_request expecting an error Then lccov an LspError should be stored with message containing "closed connection" # ── _send_request: transport alive but no response yet (lines 117, 121-122 continue loop) ── Scenario: lccov send_request retries when transport alive but no data Given lccov I create an LspClient with a mock transport And lccov the transport returns None then a valid response while alive When lccov I call _send_request with method "test/retry" and no params Then lccov the request result should contain the expected data # ── _send_request: timeout (lines 146-149) ──────────────────────── Scenario: lccov send_request raises LspError on timeout Given lccov I create an LspClient with a mock transport And lccov the transport always returns None but stays alive When lccov I call _send_request with a very short timeout expecting error Then lccov an LspError should be stored with message containing "Timed out" # ── _send_request: notification while waiting (line 144) ─────────── Scenario: lccov send_request queues unrelated messages as notifications Given lccov I create an LspClient with a mock transport And lccov the transport returns a notification then the response When lccov I call _send_request with method "test/mixed" and no params Then lccov the request result should contain the expected data And lccov the pending notifications should contain the queued message # ── _send_notification: with params (lines 163-169) ──────────────── Scenario: lccov send_notification with params Given lccov I create an LspClient with a mock transport When lccov I call _send_notification with method "test/note" and params Then lccov the transport should have received a notification with params # ── _send_notification: without params (lines 163-165, 167 skip) ── Scenario: lccov send_notification without params Given lccov I create an LspClient with a mock transport When lccov I call _send_notification with method "test/note" and no params Then lccov the transport should have received a notification without params key # ── _handle_notification: publishDiagnostics (lines 173-184) ─────── Scenario: lccov handle_notification stores publishDiagnostics Given lccov I create an LspClient with a mock transport When lccov I handle a publishDiagnostics notification for "file:///test.py" Then lccov the diagnostics cache for "file:///test.py" should have 2 items # ── _handle_notification: other notification (lines 173-174, 187) ── Scenario: lccov handle_notification queues non-diagnostic notifications Given lccov I create an LspClient with a mock transport When lccov I handle a window/logMessage notification Then lccov the pending notifications deque should have 1 item # ── initialize: success without file:// prefix (lines 208, 215-268, 272 skip, 275-294) ── Scenario: lccov initialize with plain path succeeds Given lccov I create an LspClient with a mock transport And lccov the transport echoes back an initialize result with capabilities When lccov I call initialize with workspace "/tmp/project" Then lccov the client should be initialized And lccov server_capabilities should include "textDocumentSync" And lccov the workspace URI sent should start with "file://" # ── initialize: workspace already has file:// prefix (lines 215-216) ── Scenario: lccov initialize with file URI keeps prefix Given lccov I create an LspClient with a mock transport And lccov the transport echoes back an initialize result with capabilities When lccov I call initialize with workspace "file:///already/uri" Then lccov the client should be initialized And lccov the workspace URI sent should be "file:///already/uri" # ── initialize: with initialization_options (lines 272-273) ────── Scenario: lccov initialize with initialization_options includes them Given lccov I create an LspClient with a mock transport And lccov the transport echoes back an initialize result with capabilities When lccov I call initialize with workspace "/tmp/project" and init options Then lccov the client should be initialized And lccov the sent params should include initializationOptions # ── initialize: already initialized (lines 208-211) ──────────────── Scenario: lccov initialize raises error when already initialized Given lccov I create an LspClient with a mock transport And lccov the client is already marked as initialized When lccov I call initialize expecting an error Then lccov an LspError should be stored with message containing "already initialized" # ── shutdown: not initialized (lines 302-307) ────────────────────── Scenario: lccov shutdown when not initialized returns early Given lccov I create an LspClient with a mock transport When lccov I call shutdown Then lccov no request should have been sent to the transport # ── shutdown: success path (lines 302, 309, 311-312, 320-321, 323) ── Scenario: lccov shutdown sends shutdown request then exit notification Given lccov I create an LspClient with a mock transport And lccov the client is already marked as initialized And lccov the transport echoes back a shutdown response When lccov I call shutdown Then lccov the client should not be initialized after shutdown And lccov the transport should have received shutdown and exit messages # ── shutdown: shutdown request raises LspError (lines 313-317) ───── Scenario: lccov shutdown handles LspError from shutdown request gracefully Given lccov I create an LspClient with a mock transport And lccov the client is already marked as initialized And lccov the transport will raise LspError on shutdown request When lccov I call shutdown Then lccov the client should not be initialized after shutdown # ── shutdown: exit notification raises BrokenPipeError (line 320) ── Scenario: lccov shutdown suppresses BrokenPipeError on exit Given lccov I create an LspClient with a mock transport And lccov the client is already marked as initialized And lccov the transport echoes back a shutdown response but raises BrokenPipeError on exit When lccov I call shutdown Then lccov the client should not be initialized after shutdown # ── did_open (lines 342-349) ─────────────────────────────────────── Scenario: lccov did_open sends textDocument/didOpen notification Given lccov I create an LspClient with a mock transport When lccov I call did_open for "file:///test.py" with language "python" Then lccov the transport should have received a didOpen notification # ── did_close (lines 360-365) ────────────────────────────────────── Scenario: lccov did_close sends notification and clears diagnostics Given lccov I create an LspClient with a mock transport And lccov there are cached diagnostics for "file:///test.py" When lccov I call did_close for "file:///test.py" Then lccov the transport should have received a didClose notification And lccov the diagnostics cache for "file:///test.py" should be empty # ── get_diagnostics (lines 382-389) ──────────────────────────────── Scenario: lccov get_diagnostics drains messages and returns cached Given lccov I create an LspClient with a mock transport And lccov the transport has queued diagnostic notifications for drain When lccov I call get_diagnostics for "file:///test.py" Then lccov the returned diagnostics list should have 1 item Scenario: lccov get_diagnostics returns empty for unknown URI Given lccov I create an LspClient with a mock transport And lccov the transport returns None immediately for drain When lccov I call get_diagnostics for "file:///unknown.py" Then lccov the returned diagnostics list should have 0 items # ── get_completions: CompletionList (lines 407-411, 416-417) ─────── Scenario: lccov get_completions returns items from CompletionList Given lccov I create an LspClient with a mock transport And lccov the transport returns a CompletionList response When lccov I call get_completions for "file:///test.py" at line 0 char 5 Then lccov the completions should have 2 items # ── get_completions: plain list (lines 418-419) ──────────────────── Scenario: lccov get_completions returns items from plain list Given lccov I create an LspClient with a mock transport And lccov the transport returns a plain list completion response When lccov I call get_completions for "file:///test.py" at line 0 char 5 Then lccov the completions should have 1 items # ── get_completions: unexpected result type (line 420) ───────────── Scenario: lccov get_completions returns empty for unexpected result Given lccov I create an LspClient with a mock transport And lccov the transport returns a scalar completion response When lccov I call get_completions for "file:///test.py" at line 0 char 5 Then lccov the completions should have 0 items