Skip to content

Commit 41850ee

Browse files
All e2e tests
1 parent 6018054 commit 41850ee

6 files changed

Lines changed: 79 additions & 193 deletions

File tree

go/client_test.go

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -509,47 +509,6 @@ func TestOverridesBuiltInTool(t *testing.T) {
509509
})
510510
}
511511

512-
func TestSkipPermission(t *testing.T) {
513-
t.Run("SkipPermission is serialized in tool definition", func(t *testing.T) {
514-
tool := Tool{
515-
Name: "my_tool",
516-
Description: "A tool that skips permission",
517-
SkipPermission: true,
518-
Handler: func(_ ToolInvocation) (ToolResult, error) { return ToolResult{}, nil },
519-
}
520-
data, err := json.Marshal(tool)
521-
if err != nil {
522-
t.Fatalf("failed to marshal: %v", err)
523-
}
524-
var m map[string]any
525-
if err := json.Unmarshal(data, &m); err != nil {
526-
t.Fatalf("failed to unmarshal: %v", err)
527-
}
528-
if v, ok := m["skipPermission"]; !ok || v != true {
529-
t.Errorf("expected skipPermission=true, got %v", m)
530-
}
531-
})
532-
533-
t.Run("SkipPermission omitted when false", func(t *testing.T) {
534-
tool := Tool{
535-
Name: "custom_tool",
536-
Description: "A custom tool",
537-
Handler: func(_ ToolInvocation) (ToolResult, error) { return ToolResult{}, nil },
538-
}
539-
data, err := json.Marshal(tool)
540-
if err != nil {
541-
t.Fatalf("failed to marshal: %v", err)
542-
}
543-
var m map[string]any
544-
if err := json.Unmarshal(data, &m); err != nil {
545-
t.Fatalf("failed to unmarshal: %v", err)
546-
}
547-
if _, ok := m["skipPermission"]; ok {
548-
t.Errorf("expected skipPermission to be omitted, got %v", m)
549-
}
550-
})
551-
}
552-
553512
func TestClient_CreateSession_RequiresPermissionHandler(t *testing.T) {
554513
t.Run("returns error when config is nil", func(t *testing.T) {
555514
client := NewClient(nil)

go/internal/e2e/tools_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,44 @@ func TestTools(t *testing.T) {
264264
}
265265
})
266266

267+
t.Run("skipPermission sent in tool definition", func(t *testing.T) {
268+
ctx.ConfigureForTest(t)
269+
270+
type LookupParams struct {
271+
ID string `json:"id" jsonschema:"ID to look up"`
272+
}
273+
274+
safeLookupTool := copilot.DefineTool("safe_lookup", "A safe lookup that skips permission",
275+
func(params LookupParams, inv copilot.ToolInvocation) (string, error) {
276+
return "RESULT: " + params.ID, nil
277+
})
278+
safeLookupTool.SkipPermission = true
279+
280+
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
281+
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
282+
Tools: []copilot.Tool{
283+
safeLookupTool,
284+
},
285+
})
286+
if err != nil {
287+
t.Fatalf("Failed to create session: %v", err)
288+
}
289+
290+
_, err = session.Send(t.Context(), copilot.MessageOptions{Prompt: "Use safe_lookup to look up 'test123'"})
291+
if err != nil {
292+
t.Fatalf("Failed to send message: %v", err)
293+
}
294+
295+
answer, err := testharness.GetFinalAssistantMessage(t.Context(), session)
296+
if err != nil {
297+
t.Fatalf("Failed to get assistant message: %v", err)
298+
}
299+
300+
if answer.Data.Content == nil || !strings.Contains(*answer.Data.Content, "RESULT: test123") {
301+
t.Errorf("Expected answer to contain 'RESULT: test123', got %v", answer.Data.Content)
302+
}
303+
})
304+
267305
t.Run("overrides built-in tool with custom tool", func(t *testing.T) {
268306
ctx.ConfigureForTest(t)
269307

nodejs/test/client.test.ts

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -378,64 +378,6 @@ describe("CopilotClient", () => {
378378
});
379379
});
380380

381-
describe("skipPermission in tool definitions", () => {
382-
it("sends skipPermission in tool definition on session.create", async () => {
383-
const client = new CopilotClient();
384-
await client.start();
385-
onTestFinished(() => client.forceStop());
386-
387-
const spy = vi.spyOn((client as any).connection!, "sendRequest");
388-
await client.createSession({
389-
onPermissionRequest: approveAll,
390-
tools: [
391-
{
392-
name: "my_tool",
393-
description: "a tool that skips permission",
394-
handler: async () => "ok",
395-
skipPermission: true,
396-
},
397-
],
398-
});
399-
400-
const payload = spy.mock.calls.find((c) => c[0] === "session.create")![1] as any;
401-
expect(payload.tools).toEqual([
402-
expect.objectContaining({ name: "my_tool", skipPermission: true }),
403-
]);
404-
});
405-
406-
it("sends skipPermission in tool definition on session.resume", async () => {
407-
const client = new CopilotClient();
408-
await client.start();
409-
onTestFinished(() => client.forceStop());
410-
411-
const session = await client.createSession({ onPermissionRequest: approveAll });
412-
// Mock sendRequest to capture the call without hitting the runtime
413-
const spy = vi
414-
.spyOn((client as any).connection!, "sendRequest")
415-
.mockImplementation(async (method: string, params: any) => {
416-
if (method === "session.resume") return { sessionId: params.sessionId };
417-
throw new Error(`Unexpected method: ${method}`);
418-
});
419-
await client.resumeSession(session.sessionId, {
420-
onPermissionRequest: approveAll,
421-
tools: [
422-
{
423-
name: "my_tool",
424-
description: "a tool that skips permission",
425-
handler: async () => "ok",
426-
skipPermission: true,
427-
},
428-
],
429-
});
430-
431-
const payload = spy.mock.calls.find((c) => c[0] === "session.resume")![1] as any;
432-
expect(payload.tools).toEqual([
433-
expect.objectContaining({ name: "my_tool", skipPermission: true }),
434-
]);
435-
spy.mockRestore();
436-
});
437-
});
438-
439381
describe("agent parameter in session creation", () => {
440382
it("forwards agent in session.create request", async () => {
441383
const client = new CopilotClient();

nodejs/test/e2e/tools.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,27 @@ describe("Custom tools", async () => {
159159
expect(customToolRequests[0].toolName).toBe("encrypt_string");
160160
});
161161

162+
it("skipPermission sent in tool definition", async () => {
163+
const session = await client.createSession({
164+
onPermissionRequest: approveAll,
165+
tools: [
166+
defineTool("safe_lookup", {
167+
description: "A safe lookup that skips permission",
168+
parameters: z.object({
169+
id: z.string().describe("ID to look up"),
170+
}),
171+
handler: ({ id }) => `RESULT: ${id}`,
172+
skipPermission: true,
173+
}),
174+
],
175+
});
176+
177+
const assistantMessage = await session.sendAndWait({
178+
prompt: "Use safe_lookup to look up 'test123'",
179+
});
180+
expect(assistantMessage?.data.content).toContain("RESULT: test123");
181+
});
182+
162183
it("overrides built-in tool with custom tool", async () => {
163184
const session = await client.createSession({
164185
onPermissionRequest: approveAll,

python/e2e/test_tools.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,26 @@ def db_query(params: DbQueryParams, invocation: ToolInvocation) -> list[City]:
138138
assert "135460" in response_content.replace(",", "")
139139
assert "204356" in response_content.replace(",", "")
140140

141+
async def test_skippermission_sent_in_tool_definition(self, ctx: E2ETestContext):
142+
class LookupParams(BaseModel):
143+
id: str = Field(description="ID to look up")
144+
145+
@define_tool(
146+
"safe_lookup",
147+
description="A safe lookup that skips permission",
148+
skip_permission=True,
149+
)
150+
def safe_lookup(params: LookupParams, invocation: ToolInvocation) -> str:
151+
return f"RESULT: {params.id}"
152+
153+
session = await ctx.client.create_session(
154+
{"tools": [safe_lookup], "on_permission_request": PermissionHandler.approve_all}
155+
)
156+
157+
await session.send({"prompt": "Use safe_lookup to look up 'test123'"})
158+
assistant_message = await get_final_assistant_message(session)
159+
assert "RESULT: test123" in assistant_message.data.content
160+
141161
async def test_overrides_built_in_tool_with_custom_tool(self, ctx: E2ETestContext):
142162
class GrepParams(BaseModel):
143163
query: str = Field(description="Search query")

python/test_client.py

Lines changed: 0 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -237,100 +237,6 @@ def grep(params) -> str:
237237
await client.force_stop()
238238

239239

240-
class TestSkipPermission:
241-
@pytest.mark.asyncio
242-
async def test_skip_permission_sent_in_tool_definition(self):
243-
client = CopilotClient({"cli_path": CLI_PATH})
244-
await client.start()
245-
246-
try:
247-
captured = {}
248-
original_request = client._client.request
249-
250-
async def mock_request(method, params):
251-
captured[method] = params
252-
return await original_request(method, params)
253-
254-
client._client.request = mock_request
255-
256-
@define_tool(description="Safe lookup", skip_permission=True)
257-
def safe_lookup(params) -> str:
258-
return "ok"
259-
260-
await client.create_session(
261-
{"tools": [safe_lookup], "on_permission_request": PermissionHandler.approve_all}
262-
)
263-
tool_defs = captured["session.create"]["tools"]
264-
assert len(tool_defs) == 1
265-
assert tool_defs[0]["name"] == "safe_lookup"
266-
assert tool_defs[0]["skipPermission"] is True
267-
assert "overridesBuiltInTool" not in tool_defs[0]
268-
finally:
269-
await client.force_stop()
270-
271-
@pytest.mark.asyncio
272-
async def test_resume_session_sends_skip_permission(self):
273-
client = CopilotClient({"cli_path": CLI_PATH})
274-
await client.start()
275-
276-
try:
277-
session = await client.create_session(
278-
{"on_permission_request": PermissionHandler.approve_all}
279-
)
280-
281-
captured = {}
282-
original_request = client._client.request
283-
284-
async def mock_request(method, params):
285-
captured[method] = params
286-
return await original_request(method, params)
287-
288-
client._client.request = mock_request
289-
290-
@define_tool(description="Safe lookup", skip_permission=True)
291-
def safe_lookup(params) -> str:
292-
return "ok"
293-
294-
await client.resume_session(
295-
session.session_id,
296-
{"tools": [safe_lookup], "on_permission_request": PermissionHandler.approve_all},
297-
)
298-
tool_defs = captured["session.resume"]["tools"]
299-
assert len(tool_defs) == 1
300-
assert tool_defs[0]["skipPermission"] is True
301-
assert "overridesBuiltInTool" not in tool_defs[0]
302-
finally:
303-
await client.force_stop()
304-
305-
@pytest.mark.asyncio
306-
async def test_skip_permission_omitted_when_false(self):
307-
client = CopilotClient({"cli_path": CLI_PATH})
308-
await client.start()
309-
310-
try:
311-
captured = {}
312-
original_request = client._client.request
313-
314-
async def mock_request(method, params):
315-
captured[method] = params
316-
return await original_request(method, params)
317-
318-
client._client.request = mock_request
319-
320-
@define_tool(description="Normal tool")
321-
def normal_tool(params) -> str:
322-
return "ok"
323-
324-
await client.create_session(
325-
{"tools": [normal_tool], "on_permission_request": PermissionHandler.approve_all}
326-
)
327-
tool_defs = captured["session.create"]["tools"]
328-
assert len(tool_defs) == 1
329-
assert "skipPermission" not in tool_defs[0]
330-
finally:
331-
await client.force_stop()
332-
333-
334240
class TestOnListModels:
335241
@pytest.mark.asyncio
336242
async def test_list_models_with_custom_handler(self):

0 commit comments

Comments
 (0)