From 67f3c934fec7e118de90fe845c262a4d75ce03ba Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 6 Nov 2025 11:42:46 -0500 Subject: [PATCH] fix tests --- packages/opencode/src/permission/index.ts | 38 ++++++++++++++++++++--- packages/opencode/test/tool/patch.test.ts | 25 ++++++++++++--- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index eb541049..fbcda6cf 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -75,12 +75,23 @@ export namespace Permission { async (state) => { for (const pending of Object.values(state.pending)) { for (const item of Object.values(pending)) { - item.reject(new RejectedError(item.info.sessionID, item.info.id, item.info.callID, item.info.metadata)) + item.reject( + new RejectedError( + item.info.sessionID, + item.info.id, + item.info.callID, + item.info.metadata, + ), + ) } } }, ) + export function pending() { + return state().pending + } + export async function ask(input: { type: Info["type"] title: Info["title"] @@ -139,7 +150,11 @@ export namespace Permission { export const Response = z.enum(["once", "always", "reject"]) export type Response = z.infer - export function respond(input: { sessionID: Info["sessionID"]; permissionID: Info["id"]; response: Response }) { + export function respond(input: { + sessionID: Info["sessionID"] + permissionID: Info["id"] + response: Response + }) { log.info("response", input) const { pending, approved } = state() const match = pending[input.sessionID]?.[input.permissionID] @@ -151,7 +166,14 @@ export namespace Permission { response: input.response, }) if (input.response === "reject") { - match.reject(new RejectedError(input.sessionID, input.permissionID, match.info.callID, match.info.metadata)) + match.reject( + new RejectedError( + input.sessionID, + input.permissionID, + match.info.callID, + match.info.metadata, + ), + ) return } match.resolve() @@ -166,7 +188,11 @@ export namespace Permission { for (const item of Object.values(items)) { const itemKeys = toKeys(item.info.pattern, item.info.type) if (covered(itemKeys, approved[input.sessionID])) { - respond({ sessionID: item.info.sessionID, permissionID: item.info.id, response: input.response }) + respond({ + sessionID: item.info.sessionID, + permissionID: item.info.id, + response: input.response, + }) } } } @@ -179,7 +205,9 @@ export namespace Permission { public readonly toolCallID?: string, public readonly metadata?: Record, ) { - super(`The user rejected permission to use this specific tool call. You may try again with different parameters.`) + super( + `The user rejected permission to use this specific tool call. You may try again with different parameters.`, + ) } } } diff --git a/packages/opencode/test/tool/patch.test.ts b/packages/opencode/test/tool/patch.test.ts index 78d1fecb..141759db 100644 --- a/packages/opencode/test/tool/patch.test.ts +++ b/packages/opencode/test/tool/patch.test.ts @@ -3,6 +3,7 @@ import path from "path" import { PatchTool } from "../../src/tool/patch" import { Instance } from "../../src/project/instance" import { tmpdir } from "../fixture/fixture" +import { Permission } from "../../src/permission" import * as fs from "fs/promises" const ctx = { @@ -21,9 +22,7 @@ describe("tool.patch", () => { await Instance.provide({ directory: "/tmp", fn: async () => { - await expect(patchTool.execute({ patchText: "" }, ctx)).rejects.toThrow( - "patchText is required", - ) + expect(patchTool.execute({ patchText: "" }, ctx)).rejects.toThrow("patchText is required") }, }) }) @@ -32,7 +31,7 @@ describe("tool.patch", () => { await Instance.provide({ directory: "/tmp", fn: async () => { - await expect(patchTool.execute({ patchText: "invalid patch" }, ctx)).rejects.toThrow( + expect(patchTool.execute({ patchText: "invalid patch" }, ctx)).rejects.toThrow( "Failed to parse patch", ) }, @@ -46,13 +45,29 @@ describe("tool.patch", () => { const emptyPatch = `*** Begin Patch *** End Patch` - await expect(patchTool.execute({ patchText: emptyPatch }, ctx)).rejects.toThrow( + expect(patchTool.execute({ patchText: emptyPatch }, ctx)).rejects.toThrow( "No file changes found in patch", ) }, }) }) + test("should ask permission for files outside working directory", async () => { + await Instance.provide({ + directory: "/tmp", + fn: async () => { + const maliciousPatch = `*** Begin Patch +*** Add File: /etc/passwd ++malicious content +*** End Patch` + patchTool.execute({ patchText: maliciousPatch }, ctx) + // TODO: this sucks + await new Promise((resolve) => setTimeout(resolve, 100)) + expect(Permission.pending()[ctx.sessionID]).toBeDefined() + }, + }) + }) + test("should handle simple add file operation", async () => { await using fixture = await tmpdir()