openclaw/src/plugins/services.test.ts
Harold Hunt aa1454d1a8
Plugins: broaden plugin surface for Codex App Server (#45318)
* Plugins: add inbound claim and Telegram interaction seams

* Plugins: add Discord interaction surface

* Chore: fix formatting after plugin rebase

* fix(hooks): preserve observers after inbound claim

* test(hooks): cover claimed inbound observer delivery

* fix(plugins): harden typing lease refreshes

* fix(discord): pass real auth to plugin interactions

* fix(plugins): remove raw session binding runtime exposure

* fix(plugins): tighten interactive callback handling

* Plugins: gate conversation binding with approvals

* Plugins: migrate legacy plugin binding records

* Plugins/phone-control: update test command context

* Plugins: migrate legacy binding ids

* Plugins: migrate legacy codex session bindings

* Discord: fix plugin interaction handling

* Discord: support direct plugin conversation binds

* Plugins: preserve Discord command bind targets

* Tests: fix plugin binding and interactive fallout

* Discord: stabilize directory lookup tests

* Discord: route bound DMs to plugins

* Discord: restore plugin bindings after restart

* Telegram: persist detached plugin bindings

* Plugins: limit binding APIs to Telegram and Discord

* Plugins: harden bound conversation routing

* Plugins: fix extension target imports

* Plugins: fix Telegram runtime extension imports

* Plugins: format rebased binding handlers

* Discord: bind group DM interactions by channel

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-15 16:06:11 -07:00

135 lines
3.7 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import { createEmptyPluginRegistry } from "./registry.js";
import type { OpenClawPluginService, OpenClawPluginServiceContext } from "./types.js";
const mockedLogger = vi.hoisted(() => ({
info: vi.fn<(msg: string) => void>(),
warn: vi.fn<(msg: string) => void>(),
error: vi.fn<(msg: string) => void>(),
debug: vi.fn<(msg: string) => void>(),
}));
vi.mock("../logging/subsystem.js", () => ({
createSubsystemLogger: () => mockedLogger,
}));
import { STATE_DIR } from "../config/paths.js";
import { startPluginServices } from "./services.js";
function createRegistry(services: OpenClawPluginService[]) {
const registry = createEmptyPluginRegistry();
for (const service of services) {
registry.services.push({
pluginId: "plugin:test",
service,
source: "test",
rootDir: "/plugins/test-plugin",
});
}
return registry;
}
describe("startPluginServices", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("starts services and stops them in reverse order", async () => {
const starts: string[] = [];
const stops: string[] = [];
const contexts: OpenClawPluginServiceContext[] = [];
const serviceA: OpenClawPluginService = {
id: "service-a",
start: (ctx) => {
starts.push("a");
contexts.push(ctx);
},
stop: () => {
stops.push("a");
},
};
const serviceB: OpenClawPluginService = {
id: "service-b",
start: (ctx) => {
starts.push("b");
contexts.push(ctx);
},
};
const serviceC: OpenClawPluginService = {
id: "service-c",
start: (ctx) => {
starts.push("c");
contexts.push(ctx);
},
stop: () => {
stops.push("c");
},
};
const config = {} as Parameters<typeof startPluginServices>[0]["config"];
const handle = await startPluginServices({
registry: createRegistry([serviceA, serviceB, serviceC]),
config,
workspaceDir: "/tmp/workspace",
});
await handle.stop();
expect(starts).toEqual(["a", "b", "c"]);
expect(stops).toEqual(["c", "a"]);
expect(contexts).toHaveLength(3);
for (const ctx of contexts) {
expect(ctx.config).toBe(config);
expect(ctx.workspaceDir).toBe("/tmp/workspace");
expect(ctx.stateDir).toBe(STATE_DIR);
expect(ctx.logger).toBeDefined();
expect(typeof ctx.logger.info).toBe("function");
expect(typeof ctx.logger.warn).toBe("function");
expect(typeof ctx.logger.error).toBe("function");
}
});
it("logs start/stop failures and continues", async () => {
const stopOk = vi.fn();
const stopThrows = vi.fn(() => {
throw new Error("stop failed");
});
const handle = await startPluginServices({
registry: createRegistry([
{
id: "service-start-fail",
start: () => {
throw new Error("start failed");
},
stop: vi.fn(),
},
{
id: "service-ok",
start: () => undefined,
stop: stopOk,
},
{
id: "service-stop-fail",
start: () => undefined,
stop: stopThrows,
},
]),
config: {} as Parameters<typeof startPluginServices>[0]["config"],
});
await handle.stop();
expect(mockedLogger.error).toHaveBeenCalledWith(
expect.stringContaining(
"plugin service failed (service-start-fail, plugin=plugin:test, root=/plugins/test-plugin):",
),
);
expect(mockedLogger.warn).toHaveBeenCalledWith(
expect.stringContaining("plugin service stop failed (service-stop-fail):"),
);
expect(stopOk).toHaveBeenCalledOnce();
expect(stopThrows).toHaveBeenCalledOnce();
});
});