Files
cleveragents-core/features/lsp_client_coverage.feature
freemo 31472b5413
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 21s
CI / helm (pull_request) Successful in 22s
CI / lint (pull_request) Successful in 3m20s
CI / quality (pull_request) Successful in 3m43s
CI / typecheck (pull_request) Successful in 3m58s
CI / security (pull_request) Successful in 4m8s
CI / integration_tests (pull_request) Successful in 9m33s
CI / unit_tests (pull_request) Successful in 10m12s
CI / docker (pull_request) Successful in 1m28s
CI / coverage (pull_request) Successful in 12m18s
CI / e2e_tests (pull_request) Successful in 19m51s
CI / status-check (pull_request) Successful in 1s
CI / build (push) Successful in 15s
CI / helm (push) Successful in 22s
CI / lint (push) Successful in 3m18s
CI / quality (push) Successful in 3m41s
CI / typecheck (push) Successful in 3m57s
CI / benchmark-regression (push) Has been skipped
CI / security (push) Successful in 4m10s
CI / unit_tests (push) Failing after 6m58s
CI / docker (push) Has been skipped
CI / integration_tests (push) Successful in 9m15s
CI / coverage (push) Successful in 12m26s
CI / e2e_tests (push) Successful in 23m11s
CI / status-check (push) Failing after 1s
CI / benchmark-publish (push) Successful in 28m31s
CI / benchmark-regression (pull_request) Successful in 54m53s
test(coverage): add Behave scenarios for 39 under-covered modules
Add Behave feature/step pairs that exercise uncovered branches across handlers, LSP, CLI, and service layers to reach the coverage gate.

ISSUES CLOSED: #1232
2026-03-31 21:47:12 +00:00

223 lines
12 KiB
Gherkin

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