test(core): reuse permission service stub (#42205)

This commit is contained in:
Kit Langton 2026-08-12 21:26:58 -04:00 committed by GitHub
parent bac3631ded
commit a78c8d8972
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 147 additions and 250 deletions

View file

@ -0,0 +1,8 @@
import { Permission } from "@opencode-ai/core/permission"
import { Effect, Layer } from "effect"
export const permissionLayer = (overrides: Partial<Permission.Interface> = {}) =>
Layer.mock(Permission.Service, {
allowsAll: () => Effect.succeed(false),
...overrides,
})

View file

@ -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]])

View file

@ -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" },

View file

@ -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<Record<string, ToolInfo>>, options?: Tool.Options) =>
registry.transform((draft) =>
Object.entries(tools).forEach(([name, tool]) => draft.add({ ...tool, name, options: options ?? tool.options })),

View file

@ -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<void> => Effect.void
let formatFile = (_target: string): Effect.Effect<boolean> => 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),

View file

@ -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<void> => Effect.void
let formatFile = (_target: string): Effect.Effect<boolean> => 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),

View file

@ -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({

View file

@ -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(

View file

@ -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 = <A, E, R>(
],
[
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)
}),
}),
],
]),
),

View file

@ -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<string>()
let denyAction: string | undefined
let afterPermission = (_input: Permission.AssertInput): Effect.Effect<void> => 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

View file

@ -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({

View file

@ -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],

View file

@ -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({

View file

@ -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<boolean> => 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),