-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathtest_connection_token.py
More file actions
163 lines (133 loc) · 5.77 KB
/
test_connection_token.py
File metadata and controls
163 lines (133 loc) · 5.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
"""E2E Connection Token Tests
Tests for the optional TCP ``connect`` token handshake. Mirrors the Node SDK's
``connection_token.test.ts``.
"""
import os
import shutil
import tempfile
import pytest
import pytest_asyncio
from copilot import CopilotClient, RuntimeConnection
from copilot.session import PermissionHandler
from .testharness.proxy import CapiProxy
pytestmark = pytest.mark.asyncio(loop_scope="module")
class ConnectionTokenContext:
"""Spawns a TCP CLI server with an explicit connection token."""
def __init__(self, token: str | None):
self.token = token
self.cli_path: str = ""
self.home_dir: str = ""
self.work_dir: str = ""
self.proxy_url: str = ""
self._proxy: CapiProxy | None = None
self._client: CopilotClient | None = None
async def setup(self):
from .testharness.context import get_cli_path_for_tests
self.cli_path = get_cli_path_for_tests()
self.home_dir = tempfile.mkdtemp(prefix="copilot-token-config-")
self.work_dir = tempfile.mkdtemp(prefix="copilot-token-work-")
self._proxy = CapiProxy()
self.proxy_url = await self._proxy.start()
github_token = (
"fake-token-for-e2e-tests" if os.environ.get("GITHUB_ACTIONS") == "true" else None
)
self._client = CopilotClient(
connection=RuntimeConnection.for_tcp(path=self.cli_path, connection_token=self.token),
working_directory=self.work_dir,
env=self.get_env(),
github_token=github_token,
)
# Trigger the spawn + connect handshake so the server is listening.
await self._client.start()
async def teardown(self):
if self._client:
try:
await self._client.stop()
except Exception:
# Best-effort cleanup; ignore stop errors during teardown.
pass
self._client = None
if self._proxy:
await self._proxy.stop(skip_writing_cache=True)
self._proxy = None
if self.home_dir and os.path.exists(self.home_dir):
shutil.rmtree(self.home_dir, ignore_errors=True)
if self.work_dir and os.path.exists(self.work_dir):
shutil.rmtree(self.work_dir, ignore_errors=True)
def get_env(self) -> dict:
env = os.environ.copy()
env.update(
{
"COPILOT_API_URL": self.proxy_url,
"COPILOT_HOME": self.home_dir,
"XDG_CONFIG_HOME": self.home_dir,
"XDG_STATE_HOME": self.home_dir,
}
)
return env
@property
def client(self) -> CopilotClient:
if not self._client:
raise RuntimeError("Context not set up")
return self._client
@pytest_asyncio.fixture(scope="module", loop_scope="module")
async def explicit_token_ctx():
ctx = ConnectionTokenContext(token="right-token")
await ctx.setup()
yield ctx
await ctx.teardown()
@pytest_asyncio.fixture(scope="module", loop_scope="module")
async def auto_token_ctx():
ctx = ConnectionTokenContext(token=None)
await ctx.setup()
yield ctx
await ctx.teardown()
class TestConnectionToken:
async def test_explicit_token_round_trips(self, explicit_token_ctx: ConnectionTokenContext):
"""Client started with an explicit token can ping successfully."""
# Sanity-check that the token was forwarded to the spawned CLI and the
# `connect` handshake succeeded; a real ping must round-trip.
response = await explicit_token_ctx.client.ping("hi")
assert response.message == "pong: hi"
# Bonus: a fresh session round-trip also exercises the live connection.
session = await explicit_token_ctx.client.create_session(
on_permission_request=PermissionHandler.approve_all
)
await session.disconnect()
async def test_auto_generated_token_round_trips(self, auto_token_ctx: ConnectionTokenContext):
"""When the SDK spawns its own CLI in TCP mode without an explicit token,
the auto-generated UUID is forwarded and the `connect` handshake succeeds."""
response = await auto_token_ctx.client.ping("hi")
assert response.message == "pong: hi"
async def test_wrong_token_is_rejected(self, explicit_token_ctx: ConnectionTokenContext):
"""A sibling client connecting with the wrong token is rejected."""
port = explicit_token_ctx.client.runtime_port
assert port is not None
wrong = CopilotClient(
connection=RuntimeConnection.for_uri(f"localhost:{port}", connection_token="wrong")
)
try:
with pytest.raises(Exception, match="AUTHENTICATION_FAILED"):
await wrong.start()
finally:
try:
await wrong.force_stop()
except Exception:
# Best-effort cleanup; client startup is expected to fail above,
# so force_stop may raise if no process/session was established.
pass
async def test_missing_token_is_rejected(self, explicit_token_ctx: ConnectionTokenContext):
"""A sibling client with no token is rejected when the server requires one."""
port = explicit_token_ctx.client.runtime_port
assert port is not None
no_token = CopilotClient(connection=RuntimeConnection.for_uri(f"localhost:{port}"))
try:
with pytest.raises(Exception, match="AUTHENTICATION_FAILED"):
await no_token.start()
finally:
try:
await no_token.force_stop()
except Exception:
# Best-effort cleanup; client startup is expected to fail above,
# so force_stop may raise if no process/session was established.
pass