f3d80b4628
Auto Release / auto-release (push) Failing after 1s
CI / lint (push) Failing after 0s
CI / screenshot-quality (push) Failing after 3s
CI / pr-policy (push) Has been skipped
CI / test (3.11) (push) Failing after 0s
CI / test (3.12) (push) Failing after 0s
CI / test (3.13) (push) Failing after 3s
CI / coverage (push) Failing after 1s
Legibility / legibility (push) Failing after 3s
177 lines
6.5 KiB
Python
177 lines
6.5 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from claude_tap import parse_args
|
|
from claude_tap.cli import CLIENT_CONFIGS, run_client
|
|
from claude_tap.cursor_transcript import import_cursor_transcripts
|
|
from claude_tap.trace import TraceWriter
|
|
|
|
|
|
class _DummyProc:
|
|
def __init__(self) -> None:
|
|
self.pid = 12345
|
|
self.returncode: int | None = None
|
|
|
|
async def wait(self) -> int:
|
|
self.returncode = 0
|
|
return 0
|
|
|
|
def terminate(self) -> None:
|
|
self.returncode = 0
|
|
|
|
def kill(self) -> None:
|
|
self.returncode = -9
|
|
|
|
|
|
def test_cursor_registered_in_client_configs() -> None:
|
|
cfg = CLIENT_CONFIGS["cursor"]
|
|
assert cfg.cmd == "cursor-agent"
|
|
assert cfg.default_target == "https://api2.cursor.sh"
|
|
assert cfg.default_proxy_mode == "forward"
|
|
|
|
|
|
def test_parse_args_cursor_defaults_to_forward_mode() -> None:
|
|
args = parse_args(["--tap-client", "cursor"])
|
|
assert args.client == "cursor"
|
|
assert args.proxy_mode == "forward"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_run_client_cursor_forward_sets_proxy_ca_and_no_proxy(monkeypatch) -> None:
|
|
captured: dict[str, object] = {}
|
|
ca_path = Path("/tmp/test-ca.pem")
|
|
|
|
async def fake_create_subprocess_exec(*cmd, **kwargs):
|
|
captured["cmd"] = cmd
|
|
captured["env"] = kwargs["env"]
|
|
return _DummyProc()
|
|
|
|
monkeypatch.setenv("NO_PROXY", "example.com")
|
|
monkeypatch.setattr("claude_tap.cli.shutil.which", lambda _: "/tmp/cursor-agent")
|
|
monkeypatch.setattr(asyncio, "create_subprocess_exec", fake_create_subprocess_exec)
|
|
monkeypatch.setattr("sys.stdin.isatty", lambda: False)
|
|
|
|
code = await run_client(
|
|
43123,
|
|
["-p", "--trust", "--model", "auto", "hello"],
|
|
client="cursor",
|
|
proxy_mode="forward",
|
|
ca_cert_path=ca_path,
|
|
)
|
|
|
|
assert code == 0
|
|
assert captured["cmd"] == ("/tmp/cursor-agent", "-p", "--trust", "--model", "auto", "hello")
|
|
env = captured["env"]
|
|
assert env["HTTPS_PROXY"] == "http://127.0.0.1:43123"
|
|
assert env["NODE_EXTRA_CA_CERTS"] == str(ca_path)
|
|
assert "example.com" in env["NO_PROXY"]
|
|
assert "localhost" in env["NO_PROXY"]
|
|
assert "127.0.0.1" in env["NO_PROXY"]
|
|
assert "::1" in env["NO_PROXY"]
|
|
assert env["no_proxy"] == env["NO_PROXY"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_import_cursor_transcripts_appends_viewer_friendly_records(trace_db, tmp_path: Path) -> None:
|
|
session_id = "session-123"
|
|
transcript = (
|
|
tmp_path / ".cursor" / "projects" / "project-one" / "agent-transcripts" / session_id / f"{session_id}.jsonl"
|
|
)
|
|
transcript.parent.mkdir(parents=True)
|
|
rows = [
|
|
{
|
|
"role": "user",
|
|
"message": {
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": "<timestamp>now</timestamp>\n<user_query>\nhello cursor\n</user_query>",
|
|
}
|
|
]
|
|
},
|
|
},
|
|
{"role": "assistant", "message": {"content": [{"type": "text", "text": "hello back"}]}},
|
|
{"role": "user", "message": {"content": [{"type": "text", "text": "second turn"}]}},
|
|
{"role": "assistant", "message": {"content": [{"type": "text", "text": "second answer"}]}},
|
|
]
|
|
transcript.write_text("\n".join(json.dumps(row) for row in rows), encoding="utf-8")
|
|
|
|
from claude_tap.trace_store import get_trace_store
|
|
|
|
store = get_trace_store()
|
|
session_id = store.create_session(client="cursor", proxy_mode="reverse")
|
|
writer = TraceWriter(session_id, store=store, metadata={"client": "cursor", "proxy_mode": "reverse"})
|
|
imported = await import_cursor_transcripts(writer, since=0, home=tmp_path)
|
|
writer.close()
|
|
|
|
assert imported == 2
|
|
records = store.load_records(session_id)
|
|
assert records[0]["transport"] == "cursor-transcript"
|
|
assert records[0]["request"]["body"]["messages"][0]["content"] == "hello cursor"
|
|
assert records[0]["response"]["body"]["content"][0]["text"] == "hello back"
|
|
assert records[1]["request"]["body"]["messages"][0]["content"] == "second turn"
|
|
assert records[1]["response"]["body"]["content"][0]["text"] == "second answer"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_import_cursor_transcripts_preserves_tool_uses(trace_db, tmp_path: Path) -> None:
|
|
session_id = "tool-session"
|
|
transcript = (
|
|
tmp_path / ".cursor" / "projects" / "project-one" / "agent-transcripts" / session_id / f"{session_id}.jsonl"
|
|
)
|
|
transcript.parent.mkdir(parents=True)
|
|
rows = [
|
|
{"role": "user", "message": {"content": [{"type": "text", "text": "inspect files"}]}},
|
|
{
|
|
"role": "assistant",
|
|
"message": {
|
|
"content": [
|
|
{"type": "text", "text": "I will inspect the workspace."},
|
|
{
|
|
"type": "tool_use",
|
|
"name": "Shell",
|
|
"input": {"command": "pwd && ls", "working_directory": "/tmp/work"},
|
|
},
|
|
]
|
|
},
|
|
},
|
|
{
|
|
"role": "assistant",
|
|
"message": {
|
|
"content": [{"type": "tool_use", "name": "ReadFile", "input": {"path": "/tmp/work/sample.txt"}}]
|
|
},
|
|
},
|
|
{"role": "assistant", "message": {"content": [{"type": "text", "text": "done"}]}},
|
|
]
|
|
transcript.write_text("\n".join(json.dumps(row) for row in rows), encoding="utf-8")
|
|
|
|
from claude_tap.trace_store import get_trace_store
|
|
|
|
store = get_trace_store()
|
|
session_id = store.create_session(client="cursor", proxy_mode="reverse")
|
|
writer = TraceWriter(session_id, store=store, metadata={"client": "cursor", "proxy_mode": "reverse"})
|
|
imported = await import_cursor_transcripts(writer, since=0, home=tmp_path)
|
|
writer.close()
|
|
|
|
assert imported == 3
|
|
records = store.load_records(session_id)
|
|
|
|
assert records[0]["request"]["path"].endswith("/turn/1/step/1")
|
|
assert records[1]["request"]["path"].endswith("/turn/1/step/2")
|
|
assert records[2]["request"]["path"].endswith("/turn/1/step/3")
|
|
assert records[1]["request"]["body"]["messages"][0]["content"] == "inspect files"
|
|
|
|
content = records[0]["response"]["body"]["content"]
|
|
assert content[0] == {"type": "text", "text": "I will inspect the workspace."}
|
|
assert content[1]["type"] == "tool_use"
|
|
assert content[1]["name"] == "Shell"
|
|
assert content[1]["id"] == "cursor_tool_1_2"
|
|
|
|
assert records[1]["response"]["body"]["content"][0]["name"] == "ReadFile"
|
|
assert records[2]["response"]["body"]["content"] == [{"type": "text", "text": "done"}]
|