test: add regression coverage for sync plugin hooks#19589
test: add regression coverage for sync plugin hooks#19589Hona merged 2 commits intoanomalyco:devfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds regression coverage in packages/opencode to ensure Plugin.trigger correctly handles both synchronous and asynchronous implementations of the experimental.chat.system.transform hook.
Changes:
- Adds a new
plugin.triggertest suite validating sync hooks don’t crash and async hooks are awaited. - Creates temporary per-test plugin projects via a generated
opencode.jsonandplugin.ts.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| { | ||
| model: { | ||
| providerID: "anthropic", | ||
| modelID: "claude-sonnet-4-6", | ||
| } as any, | ||
| }, |
There was a problem hiding this comment.
The hook type for "experimental.chat.system.transform" expects input.model to be a full SDK Model (per the Hooks definition in packages/plugin/src/index.ts), but the test passes { providerID, modelID } cast as any. This makes the regression less representative and could hide failures in plugins that legitimately read fields like model.id; consider passing a more realistic Model shape (or resolving one via the same provider/model helper used in runtime) so the test matches real inputs.
| { | ||
| model: { | ||
| providerID: "anthropic", | ||
| modelID: "claude-sonnet-4-6", | ||
| } as any, | ||
| }, |
There was a problem hiding this comment.
Same as above: this test passes a minimal { providerID, modelID } object cast as any for input.model, but the hook contract is SDK Model. Using a more realistic Model (or at least including the expected fields like model.id) will make this regression test closer to real Plugin.trigger usage and less brittle if hooks start depending on model properties.
| import { afterAll, afterEach, describe, expect, test } from "bun:test" | ||
| import fs from "fs/promises" | ||
| import os from "os" | ||
| import path from "path" | ||
| import { pathToFileURL } from "url" | ||
|
|
||
| const env = { | ||
| XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME, | ||
| XDG_DATA_HOME: process.env.XDG_DATA_HOME, | ||
| XDG_STATE_HOME: process.env.XDG_STATE_HOME, | ||
| XDG_CACHE_HOME: process.env.XDG_CACHE_HOME, | ||
| OPENCODE_TEST_HOME: process.env.OPENCODE_TEST_HOME, | ||
| OPENCODE_DISABLE_DEFAULT_PLUGINS: process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS, | ||
| } | ||
|
|
||
| const root = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-plugin-trigger-xdg-")) | ||
|
|
||
| process.env.XDG_CONFIG_HOME = path.join(root, "config") | ||
| process.env.XDG_DATA_HOME = path.join(root, "data") | ||
| process.env.XDG_STATE_HOME = path.join(root, "state") | ||
| process.env.XDG_CACHE_HOME = path.join(root, "cache") | ||
| process.env.OPENCODE_TEST_HOME = path.join(root, "home") | ||
| process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS = "1" | ||
|
|
||
| const { Plugin } = await import("../../src/plugin/index") | ||
| const { Instance } = await import("../../src/project/instance") | ||
|
|
||
| afterEach(async () => { | ||
| await Instance.disposeAll() | ||
| }) | ||
|
|
||
| afterAll(async () => { | ||
| for (const [key, value] of Object.entries(env)) { | ||
| if (value === undefined) delete process.env[key] | ||
| else process.env[key] = value | ||
| } | ||
| await fs.rm(root, { recursive: true, force: true }) |
There was a problem hiding this comment.
This test file redefines XDG_* and OPENCODE_TEST_HOME at module scope, but packages/opencode/bunfig.toml already preloads ./test/preload.ts which sets those env vars for the whole suite. Overriding them again adds global env churn and can cause cross-test interference if Bun executes files in parallel; consider relying on preload.ts (remove the XDG/OPENCODE_TEST_HOME rewrites + dynamic imports) or at least scope the env mutation to only what this test needs (e.g. just OPENCODE_DISABLE_DEFAULT_PLUGINS) with a tight beforeAll/afterAll.
| import { afterAll, afterEach, describe, expect, test } from "bun:test" | |
| import fs from "fs/promises" | |
| import os from "os" | |
| import path from "path" | |
| import { pathToFileURL } from "url" | |
| const env = { | |
| XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME, | |
| XDG_DATA_HOME: process.env.XDG_DATA_HOME, | |
| XDG_STATE_HOME: process.env.XDG_STATE_HOME, | |
| XDG_CACHE_HOME: process.env.XDG_CACHE_HOME, | |
| OPENCODE_TEST_HOME: process.env.OPENCODE_TEST_HOME, | |
| OPENCODE_DISABLE_DEFAULT_PLUGINS: process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS, | |
| } | |
| const root = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-plugin-trigger-xdg-")) | |
| process.env.XDG_CONFIG_HOME = path.join(root, "config") | |
| process.env.XDG_DATA_HOME = path.join(root, "data") | |
| process.env.XDG_STATE_HOME = path.join(root, "state") | |
| process.env.XDG_CACHE_HOME = path.join(root, "cache") | |
| process.env.OPENCODE_TEST_HOME = path.join(root, "home") | |
| process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS = "1" | |
| const { Plugin } = await import("../../src/plugin/index") | |
| const { Instance } = await import("../../src/project/instance") | |
| afterEach(async () => { | |
| await Instance.disposeAll() | |
| }) | |
| afterAll(async () => { | |
| for (const [key, value] of Object.entries(env)) { | |
| if (value === undefined) delete process.env[key] | |
| else process.env[key] = value | |
| } | |
| await fs.rm(root, { recursive: true, force: true }) | |
| import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test" | |
| import fs from "fs/promises" | |
| import os from "os" | |
| import path from "path" | |
| import { pathToFileURL } from "url" | |
| import { Plugin } from "../../src/plugin/index" | |
| import { Instance } from "../../src/project/instance" | |
| let originalDisableDefaultPlugins = process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS | |
| beforeAll(() => { | |
| originalDisableDefaultPlugins = process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS | |
| process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS = "1" | |
| }) | |
| afterEach(async () => { | |
| await Instance.disposeAll() | |
| }) | |
| afterAll(async () => { | |
| if (originalDisableDefaultPlugins === undefined) { | |
| delete process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS | |
| } else { | |
| process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS = originalDisableDefaultPlugins | |
| } |
Summary
Plugin.triggerexperimental.chat.system.transformhooksbun test \"test/plugin/trigger.test.ts\"frompackages/opencode