From a78c8d897291f3bb33018e44b7e1a7b37b386c57 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 12 Aug 2026 21:26:58 -0400 Subject: [PATCH] test(core): reuse permission service stub (#42205) --- packages/core/test/lib/permission.ts | 8 ++++ .../core/test/session-instructions.test.ts | 14 +----- .../core/test/session-runner-recorded.test.ts | 14 +----- packages/core/test/session-runner.test.ts | 14 +----- packages/core/test/tool-edit.test.ts | 40 +++++++--------- packages/core/test/tool-patch.test.ts | 48 ++++++++----------- packages/core/test/tool-question.test.ts | 40 +++++++--------- packages/core/test/tool-read.test.ts | 44 +++++++---------- packages/core/test/tool-search.test.ts | 22 +++------ packages/core/test/tool-shell.test.ts | 43 +++++++---------- packages/core/test/tool-skill.test.ts | 40 +++++++--------- packages/core/test/tool-webfetch.test.ts | 14 +----- packages/core/test/tool-websearch.test.ts | 16 ++----- packages/core/test/tool-write.test.ts | 40 +++++++--------- 14 files changed, 147 insertions(+), 250 deletions(-) create mode 100644 packages/core/test/lib/permission.ts diff --git a/packages/core/test/lib/permission.ts b/packages/core/test/lib/permission.ts new file mode 100644 index 00000000000..292d118112c --- /dev/null +++ b/packages/core/test/lib/permission.ts @@ -0,0 +1,8 @@ +import { Permission } from "@opencode-ai/core/permission" +import { Effect, Layer } from "effect" + +export const permissionLayer = (overrides: Partial = {}) => + Layer.mock(Permission.Service, { + allowsAll: () => Effect.succeed(false), + ...overrides, + }) diff --git a/packages/core/test/session-instructions.test.ts b/packages/core/test/session-instructions.test.ts index 8e20c8df78d..77036b77ef6 100644 --- a/packages/core/test/session-instructions.test.ts +++ b/packages/core/test/session-instructions.test.ts @@ -33,6 +33,7 @@ import { Tool } from "@opencode-ai/core/tool" import { tempLocationLayer } from "./fixture/location" import { makeLocationNode } from "@opencode-ai/util/effect/app-node" import { testEffect } from "./lib/effect" +import { permissionLayer } from "./lib/permission" import { executeTool, registerToolPlugin } from "./lib/tool" const readToolNode = makeLocationNode({ @@ -58,18 +59,7 @@ const projects = Layer.succeed( directories: () => Effect.succeed([]), }), ) -const permission = Layer.succeed( - Permission.Service, - Permission.Service.of({ - allowsAll: () => Effect.succeed(false), - assert: () => Effect.void, - ask: () => Effect.die("unused"), - reply: () => Effect.die("unused"), - get: () => Effect.die("unused"), - forSession: () => Effect.die("unused"), - list: () => Effect.die("unused"), - }), -) +const permission = permissionLayer({ assert: () => Effect.void }) const config = Config.testLayer() const imageLayer = AppNodeBuilder.build(Image.node, [[Config.node, config]]) diff --git a/packages/core/test/session-runner-recorded.test.ts b/packages/core/test/session-runner-recorded.test.ts index 95a8fea11e0..1533d8e20a1 100644 --- a/packages/core/test/session-runner-recorded.test.ts +++ b/packages/core/test/session-runner-recorded.test.ts @@ -44,6 +44,7 @@ import { Effect, Layer, Stream } from "effect" import { HttpClient, HttpClientResponse } from "effect/unstable/http" import path from "node:path" import { testEffect } from "./lib/effect" +import { permissionLayer } from "./lib/permission" import { agentHost, catalogHost, host } from "./plugin/host" const cassetteName = "session-runner/openai-chat-streams-text" @@ -55,18 +56,7 @@ if (process.env.RECORD === "true") { const cassette = HttpRecorder.layerFetch(cassetteName, { directory: cassetteDirectory }) const executor = RequestExecutor.layer.pipe(Layer.provide(cassette)) const client = LLMClient.layer.pipe(Layer.provide(executor)) -const permission = Layer.succeed( - Permission.Service, - Permission.Service.of({ - allowsAll: () => Effect.succeed(false), - assert: () => Effect.die("unused"), - ask: () => Effect.die("unused"), - reply: () => Effect.die("unused"), - get: () => Effect.die("unused"), - forSession: () => Effect.die("unused"), - list: () => Effect.die("unused"), - }), -) +const permission = permissionLayer() const model = OpenAIChat.route .with({ endpoint: { baseURL: "https://api.openai.com/v1" }, diff --git a/packages/core/test/session-runner.test.ts b/packages/core/test/session-runner.test.ts index 667d4db2841..a5cdbe1efc1 100644 --- a/packages/core/test/session-runner.test.ts +++ b/packages/core/test/session-runner.test.ts @@ -74,6 +74,7 @@ import { Cause, DateTime, Deferred, Effect, Exit, Fiber, Layer, Schema, Scope, S import { TestClock } from "effect/testing" import { asc, desc, eq } from "drizzle-orm" import { testEffect } from "./lib/effect" +import { permissionLayer } from "./lib/permission" import { agentHost, catalogHost, host } from "./plugin/host" import PROMPT_DEFAULT from "../src/session/runner/prompt/base.txt" import { CodeModeInstructions } from "@opencode-ai/core/codemode/instructions" @@ -218,18 +219,7 @@ const permissionFail = { }), }), } -const permission = Layer.succeed( - Permission.Service, - Permission.Service.of({ - allowsAll: () => Effect.succeed(false), - assert: () => Effect.die("unused"), - ask: () => Effect.die("unused"), - reply: () => Effect.die("unused"), - get: () => Effect.die("unused"), - forSession: () => Effect.die("unused"), - list: () => Effect.die("unused"), - }), -) +const permission = permissionLayer() const transformTools = (registry: Tool.Interface, tools: Readonly>, options?: Tool.Options) => registry.transform((draft) => Object.entries(tools).forEach(([name, tool]) => draft.add({ ...tool, name, options: options ?? tool.options })), diff --git a/packages/core/test/tool-edit.test.ts b/packages/core/test/tool-edit.test.ts index da66c895231..80b5e4ee806 100644 --- a/packages/core/test/tool-edit.test.ts +++ b/packages/core/test/tool-edit.test.ts @@ -19,6 +19,7 @@ import { location } from "./fixture/location" import { tmpdir } from "./fixture/tmpdir" import { makeLocationNode } from "@opencode-ai/util/effect/app-node" import { testEffect } from "./lib/effect" +import { permissionLayer } from "./lib/permission" import { toolIdentity, executeTool, registerToolPlugin, toolDefinitions } from "./lib/tool" const editToolNode = makeLocationNode({ @@ -43,31 +44,22 @@ let denyAction: string | undefined let afterRead = (_target: string, _content: Uint8Array): Effect.Effect => Effect.void let formatFile = (_target: string): Effect.Effect => Effect.succeed(false) -const permission = Layer.succeed( - Permission.Service, - Permission.Service.of({ - allowsAll: () => Effect.succeed(false), - assert: (input) => - Effect.sync(() => assertions.push(input)).pipe( - Effect.andThen( - input.action === denyAction - ? Effect.fail( - new Permission.BlockedError({ - rules: [], - permission: input.action, - resources: input.resources, - }), - ) - : Effect.void, - ), +const permission = permissionLayer({ + assert: (input) => + Effect.sync(() => assertions.push(input)).pipe( + Effect.andThen( + input.action === denyAction + ? Effect.fail( + new Permission.BlockedError({ + rules: [], + permission: input.action, + resources: input.resources, + }), + ) + : Effect.void, ), - ask: () => Effect.die("unused"), - reply: () => Effect.die("unused"), - get: () => Effect.die("unused"), - forSession: () => Effect.die("unused"), - list: () => Effect.die("unused"), - }), -) + ), +}) const formatter = Layer.mock(Formatter.Service, { file: (target) => formatFile(target), diff --git a/packages/core/test/tool-patch.test.ts b/packages/core/test/tool-patch.test.ts index 95d1064bf36..959ddd7a124 100644 --- a/packages/core/test/tool-patch.test.ts +++ b/packages/core/test/tool-patch.test.ts @@ -19,6 +19,7 @@ import { location } from "./fixture/location" import { tmpdir } from "./fixture/tmpdir" import { makeLocationNode } from "@opencode-ai/util/effect/app-node" import { testEffect } from "./lib/effect" +import { permissionLayer } from "./lib/permission" import { toolIdentity, executeTool, registerToolPlugin, toolDefinitions } from "./lib/tool" const patchToolNode = makeLocationNode({ @@ -38,35 +39,26 @@ let editApproved = false let afterEditApproval = (): Effect.Effect => Effect.void let formatFile = (_target: string): Effect.Effect => Effect.succeed(false) -const permission = Layer.succeed( - Permission.Service, - Permission.Service.of({ - allowsAll: () => Effect.succeed(false), - assert: (input) => - Effect.sync(() => { - assertions.push(input) - if (input.action === "edit") editApproved = true - }).pipe( - Effect.andThen(input.action === "edit" ? Effect.suspend(afterEditApproval) : Effect.void), - Effect.andThen( - input.action === denyAction - ? Effect.fail( - new Permission.BlockedError({ - rules: [], - permission: input.action, - resources: input.resources, - }), - ) - : Effect.void, - ), +const permission = permissionLayer({ + assert: (input) => + Effect.sync(() => { + assertions.push(input) + if (input.action === "edit") editApproved = true + }).pipe( + Effect.andThen(input.action === "edit" ? Effect.suspend(afterEditApproval) : Effect.void), + Effect.andThen( + input.action === denyAction + ? Effect.fail( + new Permission.BlockedError({ + rules: [], + permission: input.action, + resources: input.resources, + }), + ) + : Effect.void, ), - ask: () => Effect.die("unused"), - reply: () => Effect.die("unused"), - get: () => Effect.die("unused"), - forSession: () => Effect.die("unused"), - list: () => Effect.die("unused"), - }), -) + ), +}) const formatter = Layer.mock(Formatter.Service, { file: (target) => formatFile(target), diff --git a/packages/core/test/tool-question.test.ts b/packages/core/test/tool-question.test.ts index 01efc6d2d69..d8a78fe3d7e 100644 --- a/packages/core/test/tool-question.test.ts +++ b/packages/core/test/tool-question.test.ts @@ -10,6 +10,7 @@ import { QuestionTool } from "@opencode-ai/core/tool/plugin/question" import { Image } from "@opencode-ai/core/image" import { testEffect } from "./lib/effect" import { imagePassthrough } from "./lib/image" +import { permissionLayer } from "./lib/permission" import { makeLocationNode } from "@opencode-ai/util/effect/app-node" import { toolIdentity, executeTool, registerToolPlugin, toolDefinitions } from "./lib/tool" @@ -28,31 +29,22 @@ const questionInput = { }, ], } -const permission = Layer.succeed( - Permission.Service, - Permission.Service.of({ - allowsAll: () => Effect.succeed(false), - assert: (input) => - Effect.sync(() => assertions.push(input)).pipe( - Effect.andThen( - deny - ? Effect.fail( - new Permission.BlockedError({ - rules: [], - permission: input.action, - resources: input.resources, - }), - ) - : Effect.void, - ), +const permission = permissionLayer({ + assert: (input) => + Effect.sync(() => assertions.push(input)).pipe( + Effect.andThen( + deny + ? Effect.fail( + new Permission.BlockedError({ + rules: [], + permission: input.action, + resources: input.resources, + }), + ) + : Effect.void, ), - ask: () => Effect.die("unused"), - reply: () => Effect.die("unused"), - get: () => Effect.die("unused"), - forSession: () => Effect.die("unused"), - list: () => Effect.die("unused"), - }), -) + ), +}) const form = Layer.succeed( Form.Service, Form.Service.of({ diff --git a/packages/core/test/tool-read.test.ts b/packages/core/test/tool-read.test.ts index 81874257154..f00c1711e4e 100644 --- a/packages/core/test/tool-read.test.ts +++ b/packages/core/test/tool-read.test.ts @@ -23,6 +23,7 @@ import { makeLocationNode } from "@opencode-ai/util/effect/app-node" import { SessionInstructions } from "@opencode-ai/core/session/instructions" import { Environment } from "@opencode-ai/core/environment/index" import { testEffect } from "./lib/effect" +import { permissionLayer } from "./lib/permission" import { toolIdentity, executeTool, registerToolPlugin, toolDefinitions } from "./lib/tool" const readToolNode = makeLocationNode({ @@ -70,33 +71,24 @@ const reader = Layer.succeed( }), ) let allow = true -const permission = Layer.succeed( - Permission.Service, - Permission.Service.of({ - allowsAll: () => Effect.succeed(false), - assert: (input) => - Effect.sync(() => { - assertions.push(input) - }).pipe( - Effect.andThen( - allow - ? Effect.void - : Effect.fail( - new Permission.BlockedError({ - rules: [], - permission: input.action, - resources: input.resources, - }), - ), - ), +const permission = permissionLayer({ + assert: (input) => + Effect.sync(() => { + assertions.push(input) + }).pipe( + Effect.andThen( + allow + ? Effect.void + : Effect.fail( + new Permission.BlockedError({ + rules: [], + permission: input.action, + resources: input.resources, + }), + ), ), - ask: () => Effect.die("unused"), - reply: () => Effect.die("unused"), - get: () => Effect.die("unused"), - forSession: () => Effect.die("unused"), - list: () => Effect.die("unused"), - }), -) + ), +}) const config = Config.testLayer() const imageLayer = AppNodeBuilder.build(Image.node, [[Config.node, config]]) const testFileSystem = Layer.effect( diff --git a/packages/core/test/tool-search.test.ts b/packages/core/test/tool-search.test.ts index 9d5d5bfc1fd..5c04f81573f 100644 --- a/packages/core/test/tool-search.test.ts +++ b/packages/core/test/tool-search.test.ts @@ -19,6 +19,7 @@ import { Tool } from "@opencode-ai/core/tool" import { location } from "./fixture/location" import { tmpdir } from "./fixture/tmpdir" import { testEffect } from "./lib/effect" +import { permissionLayer } from "./lib/permission" import { executeTool, registerToolPlugin, toolIdentity } from "./lib/tool" const globToolNode = makeLocationNode({ @@ -49,21 +50,12 @@ const withTools = ( ], [ Permission.node, - Layer.succeed( - Permission.Service, - Permission.Service.of({ - allowsAll: () => Effect.succeed(false), - assert: (input) => - Effect.sync(() => { - assertions?.push(input) - }), - ask: () => Effect.die("unused"), - reply: () => Effect.die("unused"), - get: () => Effect.die("unused"), - forSession: () => Effect.die("unused"), - list: () => Effect.die("unused"), - }), - ), + permissionLayer({ + assert: (input) => + Effect.sync(() => { + assertions?.push(input) + }), + }), ], ]), ), diff --git a/packages/core/test/tool-shell.test.ts b/packages/core/test/tool-shell.test.ts index a90e72bd604..d203479e893 100644 --- a/packages/core/test/tool-shell.test.ts +++ b/packages/core/test/tool-shell.test.ts @@ -39,6 +39,7 @@ import { Tool } from "@opencode-ai/core/tool" import { tmpdir } from "./fixture/tmpdir" import { tempGlobalLayer } from "./fixture/global" import { testEffect } from "./lib/effect" +import { permissionLayer } from "./lib/permission" import { toolIdentity, executeTool, registerToolPlugin, toolDefinitions } from "./lib/tool" const sessionID = Session.ID.make("ses_shell_tool_test") @@ -48,32 +49,24 @@ const allowedActions = new Set() let denyAction: string | undefined let afterPermission = (_input: Permission.AssertInput): Effect.Effect => Effect.void -const permission = Layer.succeed( - Permission.Service, - Permission.Service.of({ - allowsAll: (input) => Effect.succeed(allowedActions.has(input.action)), - assert: (input) => - Effect.sync(() => assertions.push(input)).pipe( - Effect.andThen(Effect.suspend(() => afterPermission(input))), - Effect.andThen( - input.action === denyAction - ? Effect.fail( - new Permission.BlockedError({ - rules: [], - permission: input.action, - resources: input.resources, - }), - ) - : Effect.void, - ), +const permission = permissionLayer({ + allowsAll: (input) => Effect.succeed(allowedActions.has(input.action)), + assert: (input) => + Effect.sync(() => assertions.push(input)).pipe( + Effect.andThen(Effect.suspend(() => afterPermission(input))), + Effect.andThen( + input.action === denyAction + ? Effect.fail( + new Permission.BlockedError({ + rules: [], + permission: input.action, + resources: input.resources, + }), + ) + : Effect.void, ), - ask: () => Effect.die("unused"), - reply: () => Effect.die("unused"), - get: () => Effect.die("unused"), - forSession: () => Effect.die("unused"), - list: () => Effect.die("unused"), - }), -) + ), +}) const reset = () => { assertions.length = 0 diff --git a/packages/core/test/tool-skill.test.ts b/packages/core/test/tool-skill.test.ts index 174c11a540c..c3c65a14153 100644 --- a/packages/core/test/tool-skill.test.ts +++ b/packages/core/test/tool-skill.test.ts @@ -14,6 +14,7 @@ import { tmpdir } from "./fixture/tmpdir" import { Image } from "@opencode-ai/core/image" import { it } from "./lib/effect" import { imagePassthrough } from "./lib/image" +import { permissionLayer } from "./lib/permission" import { makeLocationNode } from "@opencode-ai/util/effect/app-node" import { FSUtil } from "@opencode-ai/util/fs-util" import { toolIdentity, executeTool, registerToolPlugin, toolDefinitions } from "./lib/tool" @@ -52,31 +53,22 @@ describe("SkillTool", () => { let current = [info] const assertions: Permission.AssertInput[] = [] let deny = false - const permission = Layer.succeed( - Permission.Service, - Permission.Service.of({ - allowsAll: () => Effect.succeed(false), - assert: (input) => - Effect.sync(() => assertions.push(input)).pipe( - Effect.andThen( - deny - ? Effect.fail( - new Permission.BlockedError({ - rules: [], - permission: input.action, - resources: input.resources, - }), - ) - : Effect.void, - ), + const permission = permissionLayer({ + assert: (input) => + Effect.sync(() => assertions.push(input)).pipe( + Effect.andThen( + deny + ? Effect.fail( + new Permission.BlockedError({ + rules: [], + permission: input.action, + resources: input.resources, + }), + ) + : Effect.void, ), - ask: () => Effect.die("unused"), - reply: () => Effect.die("unused"), - get: () => Effect.die("unused"), - forSession: () => Effect.die("unused"), - list: () => Effect.die("unused"), - }), - ) + ), + }) const skills = Layer.succeed( Skill.Service, Skill.Service.of({ diff --git a/packages/core/test/tool-webfetch.test.ts b/packages/core/test/tool-webfetch.test.ts index d94b698a8a7..bac467da1f8 100644 --- a/packages/core/test/tool-webfetch.test.ts +++ b/packages/core/test/tool-webfetch.test.ts @@ -13,6 +13,7 @@ import { makeLocationNode } from "@opencode-ai/util/effect/app-node" import { Image } from "@opencode-ai/core/image" import { testEffect } from "./lib/effect" import { imagePassthrough } from "./lib/image" +import { permissionLayer } from "./lib/permission" import { toolIdentity, executeTool, registerToolPlugin, toolDefinitions } from "./lib/tool" const webFetchToolNode = makeLocationNode({ @@ -36,18 +37,7 @@ const http = Layer.succeed( ), ), ) -const permission = Layer.succeed( - Permission.Service, - Permission.Service.of({ - allowsAll: () => Effect.succeed(false), - assert: (input) => Effect.sync(() => assertions.push(input)), - ask: () => Effect.die("unused"), - reply: () => Effect.die("unused"), - get: () => Effect.die("unused"), - forSession: () => Effect.die("unused"), - list: () => Effect.die("unused"), - }), -) +const permission = permissionLayer({ assert: (input) => Effect.sync(() => assertions.push(input)) }) const toolLayer = (replacements: LayerNode.Replacements = []) => AppNodeBuilder.build(LayerNode.group([Tool.node, webFetchToolNode]), [ [Permission.node, permission], diff --git a/packages/core/test/tool-websearch.test.ts b/packages/core/test/tool-websearch.test.ts index 8316d142f2c..61ecbe7efe7 100644 --- a/packages/core/test/tool-websearch.test.ts +++ b/packages/core/test/tool-websearch.test.ts @@ -15,6 +15,7 @@ import { makeLocationNode } from "@opencode-ai/util/effect/app-node" import { Image } from "@opencode-ai/core/image" import { testEffect } from "./lib/effect" import { imagePassthrough } from "./lib/image" +import { permissionLayer } from "./lib/permission" import { toolIdentity, executeTool, registerToolPlugin, toolDefinitions } from "./lib/tool" import { webSearchHost } from "./plugin/host" @@ -66,18 +67,9 @@ beforeEach(() => { }) }) -const permission = Layer.succeed( - Permission.Service, - Permission.Service.of({ - allowsAll: () => Effect.succeed(false), - assert: (input) => Effect.sync(() => assertions.push(input)), - ask: () => Effect.die("unused"), - reply: () => Effect.die("unused"), - get: () => Effect.die("unused"), - forSession: () => Effect.die("unused"), - list: () => Effect.die("unused"), - }), -) +const permission = permissionLayer({ + assert: (input) => Effect.sync(() => assertions.push(input)), +}) const websearch = Layer.succeed( WebSearch.Service, WebSearch.Service.of({ diff --git a/packages/core/test/tool-write.test.ts b/packages/core/test/tool-write.test.ts index 68f45d53d00..563e02d2e1c 100644 --- a/packages/core/test/tool-write.test.ts +++ b/packages/core/test/tool-write.test.ts @@ -19,6 +19,7 @@ import { location } from "./fixture/location" import { tmpdir } from "./fixture/tmpdir" import { makeLocationNode } from "@opencode-ai/util/effect/app-node" import { testEffect } from "./lib/effect" +import { permissionLayer } from "./lib/permission" import { toolIdentity, executeTool, registerToolPlugin, toolDefinitions } from "./lib/tool" const writeToolNode = makeLocationNode({ @@ -33,31 +34,22 @@ const writes: string[] = [] let formatFile = (_target: string): Effect.Effect => Effect.succeed(false) let denyAction: string | undefined -const permission = Layer.succeed( - Permission.Service, - Permission.Service.of({ - allowsAll: () => Effect.succeed(false), - assert: (input) => - Effect.sync(() => assertions.push(input)).pipe( - Effect.andThen( - input.action === denyAction - ? Effect.fail( - new Permission.BlockedError({ - rules: [], - permission: input.action, - resources: input.resources, - }), - ) - : Effect.void, - ), +const permission = permissionLayer({ + assert: (input) => + Effect.sync(() => assertions.push(input)).pipe( + Effect.andThen( + input.action === denyAction + ? Effect.fail( + new Permission.BlockedError({ + rules: [], + permission: input.action, + resources: input.resources, + }), + ) + : Effect.void, ), - ask: () => Effect.die("unused"), - reply: () => Effect.die("unused"), - get: () => Effect.die("unused"), - forSession: () => Effect.die("unused"), - list: () => Effect.die("unused"), - }), -) + ), +}) const formatter = Layer.mock(Formatter.Service, { file: (target) => formatFile(target),