test(signal): isolate tool-result harness lifecycle (#110720)

This commit is contained in:
Peter Steinberger
2026-07-18 15:06:19 +01:00
committed by GitHub
parent e8566596e0
commit 3f976a949a
3 changed files with 84 additions and 14 deletions
@@ -8,6 +8,7 @@ import {
createChannelIngressQueueForTests,
} from "openclaw/plugin-sdk/plugin-state-test-runtime";
import type { MockFn } from "openclaw/plugin-sdk/plugin-test-runtime";
import { closeOpenClawAgentDatabasesForTest } from "openclaw/plugin-sdk/sqlite-runtime-testing";
import { afterEach, beforeEach, vi } from "vitest";
import type { SignalDaemonHandle } from "./daemon.js";
import { setSignalRuntime } from "./runtime.js";
@@ -40,9 +41,7 @@ const streamMock = vi.hoisted(() => vi.fn()) as unknown as MockFn;
const signalCheckMock = vi.hoisted(() => vi.fn()) as unknown as MockFn;
const signalRpcRequestMock = vi.hoisted(() => vi.fn()) as unknown as MockFn;
const spawnSignalDaemonMock = vi.hoisted(() => vi.fn()) as unknown as MockFn;
const signalToolResultSessionStorePath = vi.hoisted(
() => `/tmp/openclaw-signal-tool-result-sessions-${process.pid}.json`,
);
const signalToolResultSessionStore = vi.hoisted(() => ({ path: "" }));
let signalToolResultStateDir: string | undefined;
let signalToolResultIngressQueue: ReturnType<typeof createChannelIngressQueueForTests> | undefined;
@@ -149,7 +148,7 @@ vi.mock("openclaw/plugin-sdk/session-store-runtime", async () => {
);
return {
...actual,
resolveStorePath: vi.fn(() => signalToolResultSessionStorePath),
resolveStorePath: vi.fn(() => signalToolResultSessionStore.path),
updateLastRoute: (...args: unknown[]) => updateLastRouteMock(...args),
readSessionUpdatedAt: vi.fn(() => undefined),
recordSessionMetaFromInbound: vi.fn().mockResolvedValue(undefined),
@@ -175,7 +174,10 @@ vi.mock("openclaw/plugin-sdk/channel-inbound", async () => {
}
return {
...resolved,
replyResolver: (...replyArgs: unknown[]) => replyMock(...replyArgs),
replyResolver: async (...replyArgs: unknown[]) => {
await resolved.replyOptions?.turnAdoptionLifecycle?.onAdopted();
return await replyMock(...replyArgs);
},
} as typeof resolved;
},
},
@@ -264,6 +266,7 @@ export function installSignalToolResultTestHooks() {
);
const stateDir = await fs.realpath(createdStateDir);
signalToolResultStateDir = stateDir;
signalToolResultSessionStore.path = path.join(stateDir, "sessions.json");
signalToolResultIngressQueue = undefined;
setSignalRuntime({
logging: {
@@ -294,7 +297,7 @@ export function installSignalToolResultTestHooks() {
} as unknown as PluginRuntime);
config = {
messages: { responsePrefix: "PFX" },
session: { store: signalToolResultSessionStorePath },
session: { store: signalToolResultSessionStore.path },
channels: {
signal: { autoStart: false, dmPolicy: "open", allowFrom: ["*"] },
},
@@ -318,10 +321,12 @@ export function installSignalToolResultTestHooks() {
afterEach(async () => {
clearSignalRuntimeForTest();
signalToolResultIngressQueue = undefined;
closeOpenClawAgentDatabasesForTest();
closeOpenClawStateDatabaseForTest();
if (signalToolResultStateDir) {
await fs.rm(signalToolResultStateDir, { recursive: true, force: true });
signalToolResultStateDir = undefined;
}
signalToolResultSessionStore.path = "";
});
}
@@ -0,0 +1,68 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const getLoadedChannelPluginForRead = vi.hoisted(() => vi.fn());
const getChannelPlugin = vi.hoisted(() => vi.fn());
vi.mock("./index.js", () => ({
getChannelPlugin,
normalizeChannelId: (raw?: string | null) => raw?.trim().toLowerCase() || null,
}));
vi.mock("./registry-loaded.js", () => ({
getLoadedChannelPluginForRead,
}));
import { resolveExplicitDeliveryTargetCompat } from "./target-parsing-loaded.js";
describe("resolveExplicitDeliveryTargetCompat", () => {
beforeEach(() => {
getLoadedChannelPluginForRead.mockReset();
getChannelPlugin.mockReset();
});
it("keeps unloaded channels on generic parsing without activating a plugin", () => {
getLoadedChannelPluginForRead.mockReturnValue(undefined);
expect(
resolveExplicitDeliveryTargetCompat({
channel: " LegacyChat ",
rawTarget: " room-a ",
fallbackThreadId: "77",
}),
).toEqual({
channel: "legacychat",
rawTo: "room-a",
to: "room-a",
threadId: "77",
chatType: undefined,
});
expect(getLoadedChannelPluginForRead).toHaveBeenCalledWith("legacychat");
expect(getChannelPlugin).not.toHaveBeenCalled();
});
it("preserves the deprecated parser contract for an already-loaded plugin", () => {
const parseExplicitTarget = vi.fn(() => ({
to: "room-a",
threadId: 42,
chatType: "group" as const,
}));
getLoadedChannelPluginForRead.mockReturnValue({
messaging: { parseExplicitTarget },
});
expect(
resolveExplicitDeliveryTargetCompat({
channel: "legacychat",
rawTarget: "room-a:topic:42",
}),
).toEqual({
channel: "legacychat",
rawTo: "room-a:topic:42",
to: "room-a",
threadId: 42,
chatType: "group",
});
expect(parseExplicitTarget).toHaveBeenCalledWith({ raw: "room-a:topic:42" });
expect(getChannelPlugin).not.toHaveBeenCalled();
});
});
@@ -4,7 +4,7 @@ import {
normalizeOptionalThreadValue,
} from "@openclaw/normalization-core/string-coerce";
import type { ChannelRouteParsedTarget } from "../../plugin-sdk/channel-route.js";
import { getChannelPlugin, normalizeChannelId } from "./index.js";
import { normalizeChannelId } from "./index.js";
import { getLoadedChannelPluginForRead } from "./registry-loaded.js";
/** Preserves the shipped `parseExplicitTarget` SDK contract until its deprecation window ends. */
@@ -19,13 +19,10 @@ export function resolveExplicitDeliveryTargetCompat(params: {
return null;
}
const normalizedChannel = normalizeChannelId(channel) ?? channel;
const parsed =
getLoadedChannelPluginForRead(normalizedChannel)?.messaging?.parseExplicitTarget?.({
raw: rawTo,
}) ??
getChannelPlugin(normalizedChannel)?.messaging?.parseExplicitTarget?.({
raw: rawTo,
});
// This deprecated hook belongs to the active plugin. Source-loading a bundled
// plugin here turns every target parse into broad runtime discovery.
const plugin = getLoadedChannelPluginForRead(normalizedChannel);
const parsed = plugin?.messaging?.parseExplicitTarget?.({ raw: rawTo });
return {
channel,
rawTo,