summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-04-30 12:51:32 -0400
committerGitHub <[email protected]>2026-04-30 12:51:32 -0400
commitce63ca4d7a3552a5e35f98e698967c0d499b2c1d (patch)
tree92e235f5d6d6eac945dc8994f04fe4ac0f070a95 /packages
parentfef79819425714c1cbd969dd2139b8c4b96e0d16 (diff)
downloadopencode-ce63ca4d7a3552a5e35f98e698967c0d499b2c1d.tar.gz
opencode-ce63ca4d7a3552a5e35f98e698967c0d499b2c1d.zip
test: use testEffect for system prompt test (#25047)
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/skill/index.ts12
-rw-r--r--packages/opencode/test/cli/tui/thread.test.ts55
-rw-r--r--packages/opencode/test/session/system.test.ts124
3 files changed, 109 insertions, 82 deletions
diff --git a/packages/opencode/src/skill/index.ts b/packages/opencode/src/skill/index.ts
index 9750742f9..a4e3fb6d9 100644
--- a/packages/opencode/src/skill/index.ts
+++ b/packages/opencode/src/skill/index.ts
@@ -1,4 +1,3 @@
-import os from "os"
import path from "path"
import { pathToFileURL } from "url"
import z from "zod"
@@ -148,6 +147,7 @@ const discoverSkills = Effect.fnUntraced(function* (
config: Config.Interface,
discovery: Discovery.Interface,
fsys: AppFileSystem.Interface,
+ global: Global.Interface,
directory: string,
worktree: string,
) {
@@ -159,7 +159,7 @@ const discoverSkills = Effect.fnUntraced(function* (
externalDirs.push(AGENTS_EXTERNAL_DIR)
for (const dir of externalDirs) {
- const root = path.join(Global.Path.home, dir)
+ const root = path.join(global.home, dir)
if (!(yield* fsys.isDir(root))) continue
yield* scan(state, root, EXTERNAL_SKILL_PATTERN, { dot: true, scope: "global" })
}
@@ -180,7 +180,7 @@ const discoverSkills = Effect.fnUntraced(function* (
const cfg = yield* config.get()
for (const item of cfg.skills?.paths ?? []) {
- const expanded = item.startsWith("~/") ? path.join(os.homedir(), item.slice(2)) : item
+ const expanded = item.startsWith("~/") ? path.join(global.home, item.slice(2)) : item
const dir = path.isAbsolute(expanded) ? expanded : path.join(directory, expanded)
if (!(yield* fsys.isDir(dir))) {
log.warn("skill path not found", { path: dir })
@@ -221,13 +221,14 @@ export const layer = Layer.effect(
const config = yield* Config.Service
const bus = yield* Bus.Service
const fsys = yield* AppFileSystem.Service
+ const global = yield* Global.Service
const discovered = yield* InstanceState.make(
Effect.fn("Skill.discovery")(function* (ctx) {
- return yield* discoverSkills(config, discovery, fsys, ctx.directory, ctx.worktree)
+ return yield* discoverSkills(config, discovery, fsys, global, ctx.directory, ctx.worktree)
}),
)
const state = yield* InstanceState.make(
- Effect.fn("Skill.state")(function* (ctx) {
+ Effect.fn("Skill.state")(function* () {
const s: State = { skills: {}, dirs: new Set() }
yield* loadSkills(s, yield* InstanceState.get(discovered), bus)
return s
@@ -264,6 +265,7 @@ export const defaultLayer = layer.pipe(
Layer.provide(Config.defaultLayer),
Layer.provide(Bus.layer),
Layer.provide(AppFileSystem.defaultLayer),
+ Layer.provide(Global.layer),
)
export function fmt(list: Info[], opts: { verbose: boolean }) {
diff --git a/packages/opencode/test/cli/tui/thread.test.ts b/packages/opencode/test/cli/tui/thread.test.ts
index e2bd9d7bc..b74355655 100644
--- a/packages/opencode/test/cli/tui/thread.test.ts
+++ b/packages/opencode/test/cli/tui/thread.test.ts
@@ -3,18 +3,44 @@ import fs from "fs/promises"
import path from "path"
import { tmpdir } from "../../fixture/fixture"
import * as App from "../../../src/cli/cmd/tui/app"
-import { Rpc } from "@/util/rpc"
import { UI } from "../../../src/cli/ui"
import * as Timeout from "../../../src/util/timeout"
import * as Network from "../../../src/cli/network"
import * as Win32 from "../../../src/cli/cmd/tui/win32"
-import { TuiConfig } from "../../../src/cli/cmd/tui/config/tui"
const stop = new Error("stop")
+const packageRoot = path.resolve(import.meta.dir, "../../..")
const seen = {
tui: [] as string[],
}
+class TestWorker extends EventTarget {
+ onerror: Worker["onerror"] = null
+ onmessage: Worker["onmessage"] = null
+ onmessageerror: Worker["onmessageerror"] = null
+
+ postMessage(data: string) {
+ const parsed = JSON.parse(data)
+ if (!parsed || typeof parsed !== "object" || !("method" in parsed) || !("id" in parsed)) return
+ if (typeof parsed.method !== "string" || typeof parsed.id !== "number") return
+ const result =
+ parsed.method === "fetch"
+ ? { status: 200, headers: {}, body: "" }
+ : parsed.method === "server"
+ ? { url: "http://127.0.0.1" }
+ : parsed.method === "snapshot"
+ ? ""
+ : undefined
+ queueMicrotask(() => {
+ this.onmessage?.(
+ new MessageEvent("message", { data: JSON.stringify({ type: "rpc.result", result, id: parsed.id }) }),
+ )
+ })
+ }
+
+ terminate() {}
+}
+
function setup() {
// Intentionally avoid mock.module() here: Bun keeps module overrides in cache
// and mock.restore() does not reset mock.module values. If this switches back
@@ -25,10 +51,6 @@ function setup() {
if (input.directory) seen.tui.push(input.directory)
throw stop
})
- spyOn(Rpc, "client").mockImplementation(() => ({
- call: async () => ({ url: "http://127.0.0.1" }) as never,
- on: () => () => {},
- }))
spyOn(UI, "error").mockImplementation(() => {})
spyOn(Timeout, "withTimeout").mockImplementation((input) => input)
spyOn(Network, "resolveNetworkOptions").mockResolvedValue({
@@ -71,7 +93,6 @@ describe("tui thread", () => {
async function check(project?: string) {
setup()
- const cwd = process.cwd()
const pwd = process.env.PWD
const worker = globalThis.Worker
const tty = Object.getOwnPropertyDescriptor(process.stdin, "isTTY")
@@ -85,26 +106,26 @@ describe("tui thread", () => {
configurable: true,
value: true,
})
- globalThis.Worker = class extends EventTarget {
- onerror = null
- onmessage = null
- onmessageerror = null
- postMessage() {}
- terminate() {}
- } as unknown as typeof Worker
+ Object.defineProperty(globalThis, "Worker", { configurable: true, value: TestWorker })
try {
process.chdir(tmp.path)
process.env.PWD = link
- await expect(call(project)).rejects.toBe(stop)
+ let error: unknown
+ try {
+ await call(project)
+ } catch (caught) {
+ error = caught
+ }
+ expect(error).toBe(stop)
expect(seen.tui[0]).toBe(tmp.path)
} finally {
- process.chdir(cwd)
+ process.chdir(packageRoot)
if (pwd === undefined) delete process.env.PWD
else process.env.PWD = pwd
if (tty) Object.defineProperty(process.stdin, "isTTY", tty)
else delete (process.stdin as { isTTY?: boolean }).isTTY
- globalThis.Worker = worker
+ Object.defineProperty(globalThis, "Worker", { configurable: true, value: worker })
await fs.rm(link, { recursive: true, force: true }).catch(() => undefined)
}
}
diff --git a/packages/opencode/test/session/system.test.ts b/packages/opencode/test/session/system.test.ts
index 33123acce..6e5439da5 100644
--- a/packages/opencode/test/session/system.test.ts
+++ b/packages/opencode/test/session/system.test.ts
@@ -1,69 +1,73 @@
-import { describe, expect, test } from "bun:test"
-import path from "path"
-import { Effect } from "effect"
-import { Agent } from "../../src/agent/agent"
-import { Instance } from "../../src/project/instance"
+import { describe, expect } from "bun:test"
+import { Effect, Layer } from "effect"
+import type { Agent } from "../../src/agent/agent"
+import { NamedError } from "@opencode-ai/core/util/error"
+import { Skill } from "../../src/skill"
+import { Permission } from "../../src/permission"
import { SystemPrompt } from "../../src/session/system"
-import { provideInstance, tmpdir } from "../fixture/fixture"
+import { testEffect } from "../lib/effect"
-function load<A>(dir: string, fn: (svc: Agent.Interface) => Effect.Effect<A>) {
- return Effect.runPromise(provideInstance(dir)(Agent.Service.use(fn)).pipe(Effect.provide(Agent.defaultLayer)))
-}
-
-describe("session.system", () => {
- test("skills output is sorted by name and stable across calls", async () => {
- await using tmp = await tmpdir({
- git: true,
- init: async (dir) => {
- for (const [name, description] of [
- ["zeta-skill", "Zeta skill."],
- ["alpha-skill", "Alpha skill."],
- ["middle-skill", "Middle skill."],
- ]) {
- const skillDir = path.join(dir, ".opencode", "skill", name)
- await Bun.write(
- path.join(skillDir, "SKILL.md"),
- `---
-name: ${name}
-description: ${description}
----
+const skills: Skill.Info[] = [
+ {
+ name: "zeta-skill",
+ description: "Zeta skill.",
+ location: "/tmp/zeta-skill/SKILL.md",
+ content: "# zeta-skill",
+ },
+ {
+ name: "alpha-skill",
+ description: "Alpha skill.",
+ location: "/tmp/alpha-skill/SKILL.md",
+ content: "# alpha-skill",
+ },
+ {
+ name: "middle-skill",
+ description: "Middle skill.",
+ location: "/tmp/middle-skill/SKILL.md",
+ content: "# middle-skill",
+ },
+]
-# ${name}
-`,
- )
- }
- },
- })
-
- const home = process.env.OPENCODE_TEST_HOME
- process.env.OPENCODE_TEST_HOME = tmp.path
+const build: Agent.Info = {
+ name: "build",
+ mode: "primary",
+ permission: Permission.fromConfig({ "*": "allow" }),
+ options: {},
+}
- try {
- await Instance.provide({
- directory: tmp.path,
- fn: async () => {
- const build = await load(tmp.path, (svc) => svc.get("build"))
- const runSkills = Effect.gen(function* () {
- const svc = yield* SystemPrompt.Service
- return yield* svc.skills(build!)
- }).pipe(Effect.provide(SystemPrompt.defaultLayer))
+const it = testEffect(
+ SystemPrompt.layer.pipe(
+ Layer.provide(
+ Layer.succeed(
+ Skill.Service,
+ Skill.Service.of({
+ get: (name) => Effect.succeed(skills.find((skill) => skill.name === name)),
+ all: () => Effect.succeed(skills),
+ dirs: () => Effect.succeed([]),
+ available: () => Effect.succeed(skills),
+ }),
+ ),
+ ),
+ ),
+)
- const first = await Effect.runPromise(runSkills)
- const second = await Effect.runPromise(runSkills)
+describe("session.system", () => {
+ it.effect("skills output is sorted by name and stable across calls", () =>
+ Effect.gen(function* () {
+ const prompt = yield* SystemPrompt.Service
+ const first = yield* prompt.skills(build)
+ const second = yield* prompt.skills(build)
+ const output = first ?? (yield* Effect.fail(new NamedError.Unknown({ message: "missing skills output" })))
- expect(first).toBe(second)
+ expect(first).toBe(second)
- const alpha = first!.indexOf("<name>alpha-skill</name>")
- const middle = first!.indexOf("<name>middle-skill</name>")
- const zeta = first!.indexOf("<name>zeta-skill</name>")
+ const alpha = output.indexOf("<name>alpha-skill</name>")
+ const middle = output.indexOf("<name>middle-skill</name>")
+ const zeta = output.indexOf("<name>zeta-skill</name>")
- expect(alpha).toBeGreaterThan(-1)
- expect(middle).toBeGreaterThan(alpha)
- expect(zeta).toBeGreaterThan(middle)
- },
- })
- } finally {
- process.env.OPENCODE_TEST_HOME = home
- }
- })
+ expect(alpha).toBeGreaterThan(-1)
+ expect(middle).toBeGreaterThan(alpha)
+ expect(zeta).toBeGreaterThan(middle)
+ }),
+ )
})