test: require plugin test handles

This commit is contained in:
Peter Steinberger
2026-05-08 17:06:49 +01:00
parent 4d448e4cce
commit 172158bfcb
4 changed files with 56 additions and 14 deletions

View File

@@ -5,6 +5,19 @@ async function importHookRunnerGlobalModule() {
return import("./hook-runner-global.js");
}
type HookRunnerGlobalModule = Awaited<ReturnType<typeof importHookRunnerGlobalModule>>;
type HookRunner = NonNullable<ReturnType<HookRunnerGlobalModule["getGlobalHookRunner"]>>;
function expectGlobalHookRunner(
runner: ReturnType<HookRunnerGlobalModule["getGlobalHookRunner"]>,
): HookRunner {
expect(runner).toEqual(expect.objectContaining({ hasHooks: expect.any(Function) }));
if (runner === null) {
throw new Error("Expected global hook runner");
}
return runner;
}
async function expectGlobalRunnerState(expected: { hasRunner: boolean; registry?: unknown }) {
const mod = await importHookRunnerGlobalModule();
expect(mod.getGlobalHookRunner() === null).toBe(!expected.hasRunner);
@@ -29,13 +42,16 @@ describe("hook-runner-global", () => {
it("preserves the initialized runner across module reloads", async () => {
const { modA, registry } = await createInitializedModule();
expect(modA.getGlobalHookRunner()?.hasHooks("message_received")).toBe(true);
expect(expectGlobalHookRunner(modA.getGlobalHookRunner()).hasHooks("message_received")).toBe(
true,
);
vi.resetModules();
const modB = await expectGlobalRunnerState({ hasRunner: true, registry });
expect(modB.getGlobalHookRunner()).not.toBeNull();
expect(modB.getGlobalHookRunner()?.hasHooks("message_received")).toBe(true);
expect(expectGlobalHookRunner(modB.getGlobalHookRunner()).hasHooks("message_received")).toBe(
true,
);
});
it("clears the shared state across module reloads", async () => {

View File

@@ -1,6 +1,10 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { defaultLoadOverrideModule, startLazyPluginServiceModule } from "./lazy-service-module.js";
type LazyPluginServiceHandle = NonNullable<
Awaited<ReturnType<typeof startLazyPluginServiceModule>>
>;
function createAsyncHookMock() {
return vi.fn(async () => {});
}
@@ -38,6 +42,16 @@ async function expectLifecycleStarted(params: {
});
}
function expectLazyServiceHandle(
handle: Awaited<ReturnType<typeof startLazyPluginServiceModule>>,
): LazyPluginServiceHandle {
expect(handle).toEqual(expect.objectContaining({ stop: expect.any(Function) }));
if (handle === null) {
throw new Error("Expected lazy plugin service handle");
}
return handle;
}
describe("startLazyPluginServiceModule", () => {
afterEach(() => {
delete process.env.OPENCLAW_LAZY_SERVICE_SKIP;
@@ -54,8 +68,7 @@ describe("startLazyPluginServiceModule", () => {
});
expect(lifecycle.start).toHaveBeenCalledTimes(1);
expect(handle).not.toBeNull();
await handle?.stop();
await expectLazyServiceHandle(handle).stop();
expect(lifecycle.stop).toHaveBeenCalledTimes(1);
});

View File

@@ -94,6 +94,16 @@ import type { PluginSdkResolutionPreference } from "./sdk-alias.js";
let cachedBundledTelegramDir = "";
let cachedBundledMemoryDir = "";
type GlobalHookRunner = NonNullable<ReturnType<typeof getGlobalHookRunner>>;
function expectGlobalHookRunner(runner: ReturnType<typeof getGlobalHookRunner>): GlobalHookRunner {
expect(runner).toEqual(expect.objectContaining({ hasHooks: expect.any(Function) }));
if (runner === null) {
throw new Error("Expected global hook runner");
}
return runner;
}
function createDetachedTaskRuntimeStub(id: string): DetachedTaskLifecycleRuntime {
const fail = (name: string): never => {
throw new Error(`detached runtime ${id} should not execute ${name} in this test`);
@@ -3274,14 +3284,14 @@ module.exports = { id: "throws-after-import", register() {} };`,
};
const first = loadOpenClawPlugins(options);
expect(getGlobalHookRunner()).not.toBeNull();
expectGlobalHookRunner(getGlobalHookRunner());
resetGlobalHookRunner();
expect(getGlobalHookRunner()).toBeNull();
const second = loadOpenClawPlugins(options);
expect(second).toBe(first);
expect(getGlobalHookRunner()).not.toBeNull();
expectGlobalHookRunner(getGlobalHookRunner());
resetGlobalHookRunner();
});
@@ -3322,7 +3332,7 @@ module.exports = { id: "throws-after-import", register() {} };`,
},
});
expect(getGlobalPluginRegistry()).toBe(gatewayRegistry);
expect(getGlobalHookRunner()?.hasHooks("subagent_ended")).toBe(true);
expect(expectGlobalHookRunner(getGlobalHookRunner()).hasHooks("subagent_ended")).toBe(true);
const defaultRegistry = loadOpenClawPlugins({
workspaceDir: defaultPlugin.dir,
@@ -3342,8 +3352,9 @@ module.exports = { id: "throws-after-import", register() {} };`,
expect(getActivePluginRegistry()).toBe(defaultRegistry);
expect(getGlobalPluginRegistry()).toBe(gatewayRegistry);
expect(getGlobalHookRunner()?.hasHooks("subagent_ended")).toBe(true);
expect(getGlobalHookRunner()?.hasHooks("message_sent")).toBe(false);
const globalHookRunner = expectGlobalHookRunner(getGlobalHookRunner());
expect(globalHookRunner.hasHooks("subagent_ended")).toBe(true);
expect(globalHookRunner.hasHooks("message_sent")).toBe(false);
});
it.each([

View File

@@ -331,12 +331,14 @@ describe("stageBundledPluginRuntime", () => {
]);
const match = commandsModule.matchPluginCommand("/pair now");
expect(match).not.toBeNull();
expect(match?.args).toBe("now");
expect(match).toEqual(expect.objectContaining({ args: "now" }));
if (match === null) {
throw new Error("Expected plugin command match");
}
await expect(
commandsModule.executePluginCommand({
command: match!.command,
args: match?.args,
command: match.command,
args: match.args,
}),
).resolves.toEqual({ text: "paired:now" });
});