Files
my-pal-mcp-server/tests/test_clink_claude_agent.py
Fahad 9ffca53ce5 feat! Claude Code as a CLI agent now supported. Mix and match: spawn claude code from within claude code, or claude code from within codex.
Stay in codex, plan review and fix complicated bugs, then ask it to spawn claude code and implement the plan.

This uses your current subscription instead of API tokens.
2025-10-08 11:14:22 +04:00

112 lines
3.4 KiB
Python

import asyncio
import json
import shutil
from pathlib import Path
import pytest
from clink.agents.base import CLIAgentError
from clink.agents.claude import ClaudeAgent
from clink.models import ResolvedCLIClient, ResolvedCLIRole
class DummyProcess:
def __init__(self, *, stdout: bytes = b"", stderr: bytes = b"", returncode: int = 0):
self._stdout = stdout
self._stderr = stderr
self.returncode = returncode
self.stdin_data: bytes | None = None
async def communicate(self, input_data):
self.stdin_data = input_data
return self._stdout, self._stderr
@pytest.fixture()
def claude_agent():
prompt_path = Path("systemprompts/clink/default.txt").resolve()
role = ResolvedCLIRole(name="default", prompt_path=prompt_path, role_args=[])
client = ResolvedCLIClient(
name="claude",
executable=["claude"],
internal_args=["--print", "--output-format", "json"],
config_args=["--permission-mode", "acceptEdits"],
env={},
timeout_seconds=30,
parser="claude_json",
runner="claude",
roles={"default": role},
output_to_file=None,
working_dir=None,
)
return ClaudeAgent(client), role
async def _run_agent_with_process(monkeypatch, agent, role, process, *, system_prompt="System prompt"):
async def fake_create_subprocess_exec(*_args, **_kwargs):
return process
def fake_which(executable_name):
return f"/usr/bin/{executable_name}"
monkeypatch.setattr(asyncio, "create_subprocess_exec", fake_create_subprocess_exec)
monkeypatch.setattr(shutil, "which", fake_which)
return await agent.run(
role=role,
prompt="Respond with 42",
system_prompt=system_prompt,
files=[],
images=[],
)
@pytest.mark.asyncio
async def test_claude_agent_injects_system_prompt(monkeypatch, claude_agent):
agent, role = claude_agent
stdout_payload = json.dumps(
{
"type": "result",
"subtype": "success",
"is_error": False,
"result": "42",
}
).encode()
process = DummyProcess(stdout=stdout_payload)
result = await _run_agent_with_process(monkeypatch, agent, role, process)
assert "--append-system-prompt" in result.sanitized_command
idx = result.sanitized_command.index("--append-system-prompt")
assert result.sanitized_command[idx + 1] == "System prompt"
assert process.stdin_data.decode().startswith("Respond with 42")
@pytest.mark.asyncio
async def test_claude_agent_recovers_error_payload(monkeypatch, claude_agent):
agent, role = claude_agent
stdout_payload = json.dumps(
{
"type": "result",
"subtype": "success",
"is_error": True,
"result": "API Error",
}
).encode()
process = DummyProcess(stdout=stdout_payload, returncode=2)
result = await _run_agent_with_process(monkeypatch, agent, role, process)
assert result.returncode == 2
assert result.parsed.content == "API Error"
assert result.parsed.metadata["is_error"] is True
@pytest.mark.asyncio
async def test_claude_agent_propagates_unparseable_output(monkeypatch, claude_agent):
agent, role = claude_agent
process = DummyProcess(stdout=b"", returncode=1)
with pytest.raises(CLIAgentError):
await _run_agent_with_process(monkeypatch, agent, role, process)