Skip to content

Commit dd5a60b

Browse files
committed
improve coverage
1 parent 13abf49 commit dd5a60b

27 files changed

Lines changed: 758 additions & 184 deletions

tests/acpremote/test_command_server.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,25 @@ def on_connect(self, conn: Any) -> None:
4949
del conn
5050

5151

52+
@pytest.mark.asyncio
53+
async def test_command_server_recording_client_stub_methods() -> None:
54+
client = _RecordingClient()
55+
56+
with pytest.raises(AssertionError, match="permission flow"):
57+
await client.request_permission([], "session-1", cast(Any, object()))
58+
with pytest.raises(AssertionError, match="extension methods"):
59+
await client.ext_method("demo.echo", {"value": 1})
60+
61+
await client.ext_notification("demo.note", {"value": 2})
62+
await client.session_update(
63+
"session-1",
64+
AgentMessageChunk(session_update="agent_message_chunk", content=text_block("ok")),
65+
source="test",
66+
)
67+
assert client.updates[0].field_meta == {"source": "test"}
68+
assert client.on_connect(object()) is None
69+
70+
5271
def _write_stdio_acp_script(tmp_path: Path, *, emit_stderr: bool = False) -> Path:
5372
stderr_line = (
5473
' import sys\n print("stderr ready", file=sys.stderr)\n' if emit_stderr else ""

tests/acpremote/test_helpers.py

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,14 @@ class _FakeRemoteMethods:
9898
calls: list[tuple[str, dict[str, Any]]] = field(default_factory=list)
9999

100100
async def initialize(self, protocol_version: int, **kwargs: Any) -> InitializeResponse:
101-
self.calls.append(("initialize", {"protocol_version": protocol_version, **kwargs}))
102-
return InitializeResponse(protocol_version=protocol_version)
101+
self.calls.append(
102+
("initialize", {"protocol_version": protocol_version, **kwargs})
103+
) # pragma: no cover
104+
return InitializeResponse(protocol_version=protocol_version) # pragma: no cover
103105

104106
async def new_session(self, **kwargs: Any) -> NewSessionResponse:
105-
self.calls.append(("new_session", kwargs))
106-
return NewSessionResponse(session_id="remote-session")
107+
self.calls.append(("new_session", kwargs)) # pragma: no cover
108+
return NewSessionResponse(session_id="remote-session") # pragma: no cover
107109

108110
async def load_session(self, **kwargs: Any) -> LoadSessionResponse | None:
109111
self.calls.append(("load_session", kwargs))
@@ -219,7 +221,7 @@ async def recv(self, decode: bool | None = None) -> str | bytes:
219221
del decode
220222
if not self.incoming:
221223
raise ConnectionClosedOK(None, None)
222-
return self.incoming.pop(0)
224+
return self.incoming.pop(0) # pragma: no cover
223225

224226
async def send(self, message: str) -> None:
225227
if self.send_error is not None:
@@ -234,6 +236,58 @@ async def wait_closed(self) -> None:
234236
self.wait_closed_calls += 1
235237

236238

239+
@pytest.mark.asyncio
240+
async def test_helper_fakes_cover_unreached_stub_paths() -> None:
241+
methods = _FakeRemoteMethods()
242+
assert isinstance(await methods.initialize(protocol_version=1), InitializeResponse)
243+
assert (await methods.new_session(cwd="/tmp")).session_id == "remote-session"
244+
assert await methods.load_session(session_id="s-1") == {"ok": True}
245+
assert await methods.list_sessions(cursor="c-1") == {"sessions": []}
246+
assert await methods.set_session_mode(session_id="s-1", mode_id="ask") == {"mode": "ask"}
247+
assert await methods.set_session_model(session_id="s-1", model_id="model-a") == {
248+
"model": "model-a"
249+
}
250+
assert await methods.set_config_option(session_id="s-1", config_id="flag") == {"config": "flag"}
251+
assert isinstance(await methods.authenticate(method_id="demo"), AuthenticateResponse)
252+
assert await methods.fork_session(session_id="s-1") == {"session_id": "s-1"}
253+
assert await methods.resume_session(session_id="s-1") == {"session_id": "s-1"}
254+
assert isinstance(await methods.close_session(session_id="s-1"), CloseSessionResponse)
255+
await methods.cancel(session_id="s-1")
256+
assert await methods.ext_method(method="demo.echo", params={"value": 1}) == {
257+
"method": "demo.echo",
258+
"params": {"value": 1},
259+
}
260+
await methods.ext_notification(method="demo.note", params={"value": 2})
261+
262+
stdin = _FakeStdin()
263+
stdin.write(b"hello")
264+
await stdin.drain()
265+
stdin.close()
266+
assert stdin.is_closing() is True
267+
await stdin.wait_closed()
268+
stdin.wait_closed_error = RuntimeError("boom")
269+
with pytest.raises(RuntimeError, match="boom"):
270+
await stdin.wait_closed()
271+
272+
stdout = _FakeStdout(lines=[])
273+
assert await stdout.readline() == b""
274+
275+
command_socket = _FakeCommandWebSocket(messages=[])
276+
with pytest.raises(ConnectionClosedOK):
277+
await command_socket.recv()
278+
await command_socket.close()
279+
assert command_socket.close_calls == 1
280+
281+
stream_socket = _FakeStreamWebSocket(incoming=["message"])
282+
assert await stream_socket.recv() == "message"
283+
with pytest.raises(ConnectionClosedOK):
284+
await stream_socket.recv()
285+
await stream_socket.close()
286+
await stream_socket.wait_closed()
287+
assert stream_socket.close_calls == 1
288+
assert stream_socket.wait_closed_calls == 1
289+
290+
237291
@pytest.mark.asyncio
238292
async def test_client_helper_paths_cover_metadata_edge_cases(
239293
monkeypatch: pytest.MonkeyPatch,
@@ -477,7 +531,7 @@ async def wait(self) -> int:
477531
await asyncio.sleep(self.wait_delay)
478532
if self.wait_error is not None:
479533
raise self.wait_error
480-
if self.returncode is None:
534+
if self.returncode is None: # pragma: no branch
481535
self.returncode = 0
482536
return self.returncode
483537

@@ -667,7 +721,7 @@ async def test_proxy_agent_helper_paths_cover_delegate_and_metadata_edges(
667721
@dataclass(slots=True)
668722
class _ClosableRemote:
669723
async def close(self) -> None:
670-
closers.append("closed")
724+
closers.append("closed") # pragma: no cover
671725

672726
proxy._client = cast(Any, object())
673727
proxy._remote = cast(RemoteClientConnection, _ClosableRemote())
@@ -699,7 +753,7 @@ async def test_proxy_agent_methods_cover_forwarding_and_connection_recheck(
699753
methods = _FakeRemoteMethods()
700754

701755
async def _close_remote() -> None:
702-
return None
756+
return None # pragma: no cover
703757

704758
remote = cast(
705759
RemoteClientConnection,

tests/acpremote/test_phase1.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,36 @@ def on_connect(self, conn: Agent) -> None:
149149
del conn
150150

151151

152+
@pytest.mark.asyncio
153+
async def test_phase1_recording_client_stub_methods_cover_error_paths() -> None:
154+
client = _RecordingClient()
155+
156+
with pytest.raises(AssertionError, match="permission flow"):
157+
await client.request_permission([], "session-1", cast(Any, object()))
158+
with pytest.raises(AssertionError, match="filesystem flow"):
159+
await client.write_text_file("content", "/tmp/demo", "session-1")
160+
with pytest.raises(AssertionError, match="filesystem flow"):
161+
await client.read_text_file("/tmp/demo", "session-1")
162+
with pytest.raises(AssertionError, match="terminal flow"):
163+
await client.create_terminal("echo hi", "session-1")
164+
with pytest.raises(AssertionError, match="terminal flow"):
165+
await client.terminal_output("session-1", "terminal-1")
166+
with pytest.raises(AssertionError, match="terminal flow"):
167+
await client.release_terminal("session-1", "terminal-1")
168+
with pytest.raises(AssertionError, match="terminal flow"):
169+
await client.wait_for_terminal_exit("session-1", "terminal-1")
170+
with pytest.raises(AssertionError, match="terminal flow"):
171+
await client.kill_terminal("session-1", "terminal-1")
172+
173+
update = AgentMessageChunk(session_update="agent_message_chunk", content=text_block("ok"))
174+
await client.session_update("session-1", update, source="phase1")
175+
assert client.updates[0].field_meta == {"source": "phase1"}
176+
with pytest.raises(RequestError):
177+
await client.ext_method("demo.missing", {})
178+
await client.ext_notification("demo.note", {"value": 1})
179+
assert client.on_connect(cast(Agent, object())) is None
180+
181+
152182
@dataclass(slots=True)
153183
class _EchoAgent:
154184
notifications_sent: list[str] = field(default_factory=list)
@@ -192,7 +222,7 @@ async def prompt(
192222
del kwargs
193223
text = "".join(block.text for block in prompt if isinstance(block, TextContentBlock))
194224
self.prompts.append(text)
195-
if self._conn is not None:
225+
if self._conn is not None: # pragma: no branch
196226
await self._conn.session_update(
197227
session_id=session_id,
198228
update=update_agent_message(text_block(text)),

tests/acpremote/test_phase2.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,25 @@ def on_connect(self, conn: Agent) -> None:
6565
del conn
6666

6767

68+
@pytest.mark.asyncio
69+
async def test_phase2_recording_client_stub_methods() -> None:
70+
client = _RecordingClient()
71+
72+
with pytest.raises(AssertionError, match="permission flow"):
73+
await client.request_permission([], "session-1", cast(Any, object()))
74+
with pytest.raises(AssertionError, match="extension methods"):
75+
await client.ext_method("demo.echo", {"value": 1})
76+
77+
await client.ext_notification("demo.note", {"value": 2})
78+
await client.session_update(
79+
"session-1",
80+
AgentMessageChunk(session_update="agent_message_chunk", content=text_block("ok")),
81+
source="phase2",
82+
)
83+
assert client.updates[0].field_meta == {"source": "phase2"}
84+
assert client.on_connect(cast(Agent, object())) is None
85+
86+
6887
@dataclass(slots=True)
6988
class _EchoAgent:
7089
_conn: Client | None = None
@@ -99,7 +118,7 @@ async def prompt(
99118
) -> PromptResponse:
100119
del kwargs
101120
text = "".join(block.text for block in prompt if hasattr(block, "text"))
102-
if self._conn is not None:
121+
if self._conn is not None: # pragma: no branch
103122
await self._conn.session_update(
104123
session_id=session_id,
105124
update=AgentMessageChunk(
@@ -204,15 +223,15 @@ async def test_phase2_websocket_path_and_bearer_auth_are_enforced() -> None:
204223
try:
205224
with pytest.raises(InvalidStatus) as missing_token:
206225
async with connect(f"ws://127.0.0.1:{port}/secure/ws"):
207-
pass
226+
raise AssertionError("unreachable") # pragma: no cover
208227
assert missing_token.value.response.status_code == 401
209228

210229
with pytest.raises(InvalidStatus) as wrong_path:
211230
async with connect(
212231
f"ws://127.0.0.1:{port}/wrong/ws",
213232
additional_headers={"Authorization": "Bearer secret-token"},
214233
):
215-
pass
234+
raise AssertionError("unreachable") # pragma: no cover
216235
assert wrong_path.value.response.status_code == 404
217236

218237
client = _RecordingClient()

tests/acpremote/test_phase3.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,25 @@ def on_connect(self, conn: Agent) -> None:
5959
del conn
6060

6161

62+
@pytest.mark.asyncio
63+
async def test_phase3_recording_client_stub_methods() -> None:
64+
client = _RecordingClient()
65+
66+
with pytest.raises(AssertionError, match="permission flow"):
67+
await client.request_permission([], "session-1", cast(Any, object()))
68+
with pytest.raises(AssertionError, match="extension methods"):
69+
await client.ext_method("demo.echo", {"value": 1})
70+
71+
await client.ext_notification("demo.note", {"value": 2})
72+
await client.session_update(
73+
"session-1",
74+
AgentMessageChunk(session_update="agent_message_chunk", content=text_block("ok")),
75+
source="phase3",
76+
)
77+
assert client.updates[0].field_meta == {"source": "phase3"}
78+
assert client.on_connect(cast(Agent, object())) is None
79+
80+
6281
@dataclass(slots=True)
6382
class _ProxyTargetAgent:
6483
ext_notifications: list[tuple[str, dict[str, Any]]] = field(default_factory=list)
@@ -116,7 +135,7 @@ async def prompt(
116135
del message_id, kwargs
117136
text = "".join(block.text for block in prompt if hasattr(block, "text"))
118137
self.prompts.append(text)
119-
if self._conn is not None:
138+
if self._conn is not None: # pragma: no branch
120139
await self._conn.session_update(
121140
session_id=session_id,
122141
update=AgentMessageChunk(
@@ -132,8 +151,8 @@ async def close_session(
132151
session_id: str,
133152
**kwargs: Any,
134153
) -> CloseSessionResponse | None:
135-
del session_id, kwargs
136-
return CloseSessionResponse()
154+
del session_id, kwargs # pragma: no cover
155+
return CloseSessionResponse() # pragma: no cover
137156

138157
async def ext_method(self, method: str, params: dict[str, Any]) -> dict[str, Any]:
139158
return {"method": method, "params": params}
@@ -152,7 +171,7 @@ async def _open_stream_pair() -> tuple[
152171
)
153172

154173
async def _handle(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
155-
if not accepted.done():
174+
if not accepted.done(): # pragma: no branch
156175
accepted.set_result((reader, writer))
157176

158177
server = await asyncio.start_server(_handle, "127.0.0.1", 0)
@@ -242,7 +261,7 @@ class _StubRemote:
242261
metadata: Any = None
243262

244263
async def close(self) -> None:
245-
return None
264+
return None # pragma: no cover
246265

247266
async def _fake_connect_remote_agent(
248267
client: Client,

tests/langchain/support.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def _stream(
102102
yield chunk
103103
return
104104

105-
if tool_calls:
105+
if tool_calls: # pragma: no branch
106106
chunk = ChatGenerationChunk(
107107
message=AIMessageChunk(
108108
content="",
@@ -272,16 +272,16 @@ def agent_message_texts(client: RecordingACPClient) -> list[str]:
272272
if not isinstance(update, AgentMessageChunk):
273273
continue
274274
message_id = update.message_id
275-
if message_id is None:
275+
if message_id is None: # pragma: no branch
276276
message_id = f"anonymous:{anonymous_message_count}"
277277
anonymous_message_count += 1
278-
if current_message_id != message_id:
278+
if current_message_id != message_id: # pragma: no branch
279279
if current_message_id is not None:
280280
messages.append(current_text)
281281
current_message_id = message_id
282282
current_text = ""
283283
current_text += update.content.text
284284

285-
if current_message_id is not None:
285+
if current_message_id is not None: # pragma: no branch
286286
messages.append(current_text)
287287
return messages

tests/langchain/test_examples.py

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -150,21 +150,20 @@ def test_deepagents_example_workspace_helpers_cover_seeded_paths(
150150
assert deepagents_graph.config.projection_maps
151151

152152

153-
def test_deepagents_example_graph_factory_requires_optional_dependency() -> None:
153+
def test_deepagents_example_graph_factory_requires_optional_dependency(
154+
monkeypatch: pytest.MonkeyPatch,
155+
) -> None:
154156
session = AcpSessionContext(
155157
session_id="example-session",
156158
cwd=Path.cwd(),
157159
created_at=utc_now(),
158160
updated_at=utc_now(),
159161
)
160162

161-
if not deepagents_graph._deepagents_available():
162-
with pytest.raises(RuntimeError, match="langchain-acp\\[deepagents\\]"):
163-
deepagents_graph.graph_from_session(session)
164-
return
163+
monkeypatch.setattr(deepagents_graph, "_deepagents_available", lambda: False)
165164

166-
graph = deepagents_graph.graph_from_session(session)
167-
assert graph is not None
165+
with pytest.raises(RuntimeError, match="langchain-acp\\[deepagents\\]"):
166+
deepagents_graph.graph_from_session(session)
168167

169168

170169
def test_deepagents_example_graph_factory_builds_graph_from_lazy_import(
@@ -198,6 +197,34 @@ def fake_create_deep_agent(**kwargs: Any) -> object:
198197
assert graph is not None
199198
assert captured["interrupt_on"] == {"write_file": True}
200199
assert captured["name"] == "deepagents-.deepagents-graph"
200+
201+
202+
def test_deepagents_example_graph_factory_builds_graph_when_dependency_is_mocked(
203+
tmp_path: Path,
204+
monkeypatch: pytest.MonkeyPatch,
205+
) -> None:
206+
fake_graph = object()
207+
captured: dict[str, Any] = {}
208+
209+
def fake_create_deep_agent(**kwargs: Any) -> object:
210+
captured.update(kwargs)
211+
return fake_graph
212+
213+
monkeypatch.setattr(deepagents_graph, "_deepagents_available", lambda: True)
214+
monkeypatch.setattr(
215+
deepagents_graph,
216+
"import_module",
217+
lambda name: cast(Any, SimpleNamespace(create_deep_agent=fake_create_deep_agent)),
218+
)
219+
220+
session = AcpSessionContext(
221+
session_id="example-session",
222+
cwd=tmp_path,
223+
created_at=utc_now(),
224+
updated_at=utc_now(),
225+
)
226+
227+
assert deepagents_graph.graph_from_session(session) is fake_graph
201228
tool_names = {tool.__name__ for tool in cast(list[Any], captured["tools"])}
202229
assert tool_names == {"list_workspace_files", "read_file", "write_file"}
203230

0 commit comments

Comments
 (0)