mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:30:43 +00:00
test: share get-reply hook fixtures
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { HookRunner } from "../../plugins/hooks.js";
|
||||
import type { MsgContext } from "../templating.js";
|
||||
import { SILENT_REPLY_TOKEN } from "../tokens.js";
|
||||
import {
|
||||
buildGetReplyGroupCtx,
|
||||
createGetReplyContinueDirectivesResult,
|
||||
createGetReplySessionState,
|
||||
registerGetReplyRuntimeOverrides,
|
||||
} from "./get-reply.test-fixtures.js";
|
||||
import { loadGetReplyModuleForTest } from "./get-reply.test-loader.js";
|
||||
import "./get-reply.test-runtime-mocks.js";
|
||||
|
||||
@@ -20,15 +25,7 @@ vi.mock("../../plugins/hook-runner-global.js", () => ({
|
||||
runBeforeAgentReply: mocks.runBeforeAgentReply,
|
||||
}) as unknown as HookRunner,
|
||||
}));
|
||||
vi.mock("./get-reply-directives.js", () => ({
|
||||
resolveReplyDirectives: (...args: unknown[]) => mocks.resolveReplyDirectives(...args),
|
||||
}));
|
||||
vi.mock("./get-reply-inline-actions.js", () => ({
|
||||
handleInlineActions: (...args: unknown[]) => mocks.handleInlineActions(...args),
|
||||
}));
|
||||
vi.mock("./session.js", () => ({
|
||||
initSessionState: (...args: unknown[]) => mocks.initSessionState(...args),
|
||||
}));
|
||||
registerGetReplyRuntimeOverrides(mocks);
|
||||
|
||||
let getReplyFromConfig: typeof import("./get-reply.js").getReplyFromConfig;
|
||||
|
||||
@@ -36,74 +33,17 @@ async function loadGetReplyRuntimeForTest() {
|
||||
({ getReplyFromConfig } = await loadGetReplyModuleForTest({ cacheKey: import.meta.url }));
|
||||
}
|
||||
|
||||
function buildCtx(overrides: Partial<MsgContext> = {}): MsgContext {
|
||||
return {
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
OriginatingChannel: "telegram",
|
||||
OriginatingTo: "telegram:-100123",
|
||||
ChatType: "group",
|
||||
Body: "hello world",
|
||||
BodyForAgent: "hello world",
|
||||
RawBody: "hello world",
|
||||
CommandBody: "hello world",
|
||||
BodyForCommands: "hello world",
|
||||
SessionKey: "agent:main:telegram:-100123",
|
||||
From: "telegram:user:42",
|
||||
To: "telegram:-100123",
|
||||
Timestamp: 1710000000000,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function createContinueDirectivesResult() {
|
||||
return {
|
||||
kind: "continue" as const,
|
||||
result: {
|
||||
commandSource: "text",
|
||||
command: {
|
||||
surface: "telegram",
|
||||
channel: "telegram",
|
||||
channelId: "telegram",
|
||||
ownerList: [],
|
||||
senderIsOwner: false,
|
||||
isAuthorizedSender: true,
|
||||
senderId: "42",
|
||||
abortKey: "agent:main:telegram:-100123",
|
||||
rawBodyNormalized: "hello world",
|
||||
commandBodyNormalized: "hello world",
|
||||
from: "telegram:user:42",
|
||||
to: "telegram:-100123",
|
||||
resetHookTriggered: false,
|
||||
},
|
||||
allowTextCommands: true,
|
||||
skillCommands: [],
|
||||
directives: {},
|
||||
cleanedBody: "hello world",
|
||||
elevatedEnabled: false,
|
||||
elevatedAllowed: false,
|
||||
elevatedFailures: [],
|
||||
defaultActivation: "always",
|
||||
resolvedThinkLevel: undefined,
|
||||
resolvedVerboseLevel: "off",
|
||||
resolvedReasoningLevel: "off",
|
||||
resolvedElevatedLevel: "off",
|
||||
execOverrides: undefined,
|
||||
blockStreamingEnabled: false,
|
||||
blockReplyChunking: undefined,
|
||||
resolvedBlockStreamingBreak: undefined,
|
||||
provider: "openai",
|
||||
model: "gpt-4o-mini",
|
||||
modelState: {
|
||||
resolveDefaultThinkingLevel: async () => undefined,
|
||||
},
|
||||
contextTokens: 0,
|
||||
inlineStatusRequested: false,
|
||||
directiveAck: undefined,
|
||||
perMessageQueueMode: undefined,
|
||||
perMessageQueueOptions: undefined,
|
||||
},
|
||||
};
|
||||
return createGetReplyContinueDirectivesResult({
|
||||
body: "hello world",
|
||||
abortKey: "agent:main:telegram:-100123",
|
||||
from: "telegram:user:42",
|
||||
to: "telegram:-100123",
|
||||
senderId: "42",
|
||||
commandSource: "text",
|
||||
senderIsOwner: false,
|
||||
resetHookTriggered: false,
|
||||
});
|
||||
}
|
||||
|
||||
describe("getReplyFromConfig before_agent_reply wiring", () => {
|
||||
@@ -116,27 +56,16 @@ describe("getReplyFromConfig before_agent_reply wiring", () => {
|
||||
mocks.hasHooks.mockReset();
|
||||
mocks.runBeforeAgentReply.mockReset();
|
||||
|
||||
mocks.initSessionState.mockResolvedValue({
|
||||
sessionCtx: buildCtx({
|
||||
OriginatingChannel: "Telegram",
|
||||
Provider: "telegram",
|
||||
mocks.initSessionState.mockResolvedValue(
|
||||
createGetReplySessionState({
|
||||
sessionCtx: buildGetReplyGroupCtx({ OriginatingChannel: "Telegram", Provider: "telegram" }),
|
||||
sessionKey: "agent:main:telegram:-100123",
|
||||
sessionScope: "per-chat",
|
||||
isGroup: true,
|
||||
triggerBodyNormalized: "hello world",
|
||||
bodyStripped: "hello world",
|
||||
}),
|
||||
sessionEntry: {},
|
||||
previousSessionEntry: {},
|
||||
sessionStore: {},
|
||||
sessionKey: "agent:main:telegram:-100123",
|
||||
sessionId: "session-1",
|
||||
isNewSession: false,
|
||||
resetTriggered: false,
|
||||
systemSent: false,
|
||||
abortedLastRun: false,
|
||||
storePath: "/tmp/sessions.json",
|
||||
sessionScope: "per-chat",
|
||||
groupResolution: undefined,
|
||||
isGroup: true,
|
||||
triggerBodyNormalized: "hello world",
|
||||
bodyStripped: "hello world",
|
||||
});
|
||||
);
|
||||
mocks.resolveReplyDirectives.mockResolvedValue(createContinueDirectivesResult());
|
||||
mocks.handleInlineActions.mockResolvedValue({
|
||||
kind: "continue",
|
||||
@@ -152,7 +81,7 @@ describe("getReplyFromConfig before_agent_reply wiring", () => {
|
||||
reply: { text: "plugin reply" },
|
||||
});
|
||||
|
||||
const result = await getReplyFromConfig(buildCtx(), undefined, {});
|
||||
const result = await getReplyFromConfig(buildGetReplyGroupCtx(), undefined, {});
|
||||
|
||||
expect(result).toEqual({ text: "plugin reply" });
|
||||
expect(mocks.runBeforeAgentReply).toHaveBeenCalledWith(
|
||||
@@ -175,7 +104,7 @@ describe("getReplyFromConfig before_agent_reply wiring", () => {
|
||||
it("falls back to NO_REPLY when the hook claims without a reply payload", async () => {
|
||||
mocks.runBeforeAgentReply.mockResolvedValue({ handled: true });
|
||||
|
||||
const result = await getReplyFromConfig(buildCtx(), undefined, {});
|
||||
const result = await getReplyFromConfig(buildGetReplyGroupCtx(), undefined, {});
|
||||
|
||||
expect(result).toEqual({ text: SILENT_REPLY_TOKEN });
|
||||
});
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { MsgContext } from "../templating.js";
|
||||
import { withFastReplyConfig } from "./get-reply-fast-path.js";
|
||||
import {
|
||||
buildGetReplyGroupCtx,
|
||||
createGetReplySessionState,
|
||||
registerGetReplyRuntimeOverrides,
|
||||
} from "./get-reply.test-fixtures.js";
|
||||
import { loadGetReplyModuleForTest } from "./get-reply.test-loader.js";
|
||||
import { registerGetReplyCommonMocks } from "./get-reply.test-mocks.js";
|
||||
|
||||
@@ -37,15 +42,7 @@ vi.mock("../../media-understanding/apply.runtime.js", () => ({
|
||||
vi.mock("./commands-core.js", () => ({
|
||||
emitResetCommandHooks: vi.fn(async () => undefined),
|
||||
}));
|
||||
vi.mock("./get-reply-directives.js", () => ({
|
||||
resolveReplyDirectives: mocks.resolveReplyDirectives,
|
||||
}));
|
||||
vi.mock("./get-reply-inline-actions.js", () => ({
|
||||
handleInlineActions: vi.fn(async () => ({ kind: "reply", reply: { text: "ok" } })),
|
||||
}));
|
||||
vi.mock("./session.js", () => ({
|
||||
initSessionState: mocks.initSessionState,
|
||||
}));
|
||||
registerGetReplyRuntimeOverrides(mocks);
|
||||
|
||||
let getReplyFromConfig: typeof import("./get-reply.js").getReplyFromConfig;
|
||||
|
||||
@@ -54,26 +51,17 @@ async function loadGetReplyRuntimeForTest() {
|
||||
}
|
||||
|
||||
function buildCtx(overrides: Partial<MsgContext> = {}): MsgContext {
|
||||
return {
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
OriginatingChannel: "telegram",
|
||||
OriginatingTo: "telegram:-100123",
|
||||
ChatType: "group",
|
||||
return buildGetReplyGroupCtx({
|
||||
Body: "<media:audio>",
|
||||
BodyForAgent: "<media:audio>",
|
||||
RawBody: "<media:audio>",
|
||||
CommandBody: "<media:audio>",
|
||||
SessionKey: "agent:main:telegram:-100123",
|
||||
From: "telegram:user:42",
|
||||
To: "telegram:-100123",
|
||||
GroupChannel: "ops",
|
||||
Timestamp: 1710000000000,
|
||||
MediaPath: "/tmp/voice.ogg",
|
||||
MediaUrl: "https://example.test/voice.ogg",
|
||||
MediaType: "audio/ogg",
|
||||
...overrides,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
describe("getReplyFromConfig message hooks", () => {
|
||||
@@ -106,24 +94,13 @@ describe("getReplyFromConfig message hooks", () => {
|
||||
);
|
||||
mocks.triggerInternalHook.mockResolvedValue(undefined);
|
||||
mocks.resolveReplyDirectives.mockResolvedValue({ kind: "reply", reply: { text: "ok" } });
|
||||
mocks.initSessionState.mockResolvedValue({
|
||||
sessionCtx: {},
|
||||
sessionEntry: {},
|
||||
previousSessionEntry: {},
|
||||
sessionStore: {},
|
||||
sessionKey: "agent:main:telegram:-100123",
|
||||
sessionId: "session-1",
|
||||
isNewSession: false,
|
||||
resetTriggered: false,
|
||||
systemSent: false,
|
||||
abortedLastRun: false,
|
||||
storePath: "/tmp/sessions.json",
|
||||
sessionScope: "per-chat",
|
||||
groupResolution: undefined,
|
||||
isGroup: true,
|
||||
triggerBodyNormalized: "",
|
||||
bodyStripped: "",
|
||||
});
|
||||
mocks.initSessionState.mockResolvedValue(
|
||||
createGetReplySessionState({
|
||||
sessionKey: "agent:main:telegram:-100123",
|
||||
sessionScope: "per-chat",
|
||||
isGroup: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("emits transcribed + preprocessed hooks with enriched context", async () => {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { MsgContext } from "../templating.js";
|
||||
import {
|
||||
buildNativeResetContext,
|
||||
createGetReplyContinueDirectivesResult,
|
||||
createGetReplySessionState,
|
||||
registerGetReplyRuntimeOverrides,
|
||||
} from "./get-reply.test-fixtures.js";
|
||||
import { loadGetReplyModuleForTest } from "./get-reply.test-loader.js";
|
||||
import "./get-reply.test-runtime-mocks.js";
|
||||
|
||||
@@ -15,15 +20,7 @@ vi.mock("./commands-core.js", () => ({
|
||||
vi.mock("./commands-core.runtime.js", () => ({
|
||||
emitResetCommandHooks: (...args: unknown[]) => mocks.emitResetCommandHooks(...args),
|
||||
}));
|
||||
vi.mock("./get-reply-directives.js", () => ({
|
||||
resolveReplyDirectives: (...args: unknown[]) => mocks.resolveReplyDirectives(...args),
|
||||
}));
|
||||
vi.mock("./get-reply-inline-actions.js", () => ({
|
||||
handleInlineActions: (...args: unknown[]) => mocks.handleInlineActions(...args),
|
||||
}));
|
||||
vi.mock("./session.js", () => ({
|
||||
initSessionState: (...args: unknown[]) => mocks.initSessionState(...args),
|
||||
}));
|
||||
registerGetReplyRuntimeOverrides(mocks);
|
||||
|
||||
let getReplyFromConfig: typeof import("./get-reply.js").getReplyFromConfig;
|
||||
|
||||
@@ -31,71 +28,17 @@ async function loadGetReplyRuntimeForTest() {
|
||||
({ getReplyFromConfig } = await loadGetReplyModuleForTest({ cacheKey: import.meta.url }));
|
||||
}
|
||||
|
||||
function buildNativeResetContext(): MsgContext {
|
||||
return {
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
ChatType: "direct",
|
||||
Body: "/new",
|
||||
RawBody: "/new",
|
||||
CommandBody: "/new",
|
||||
CommandSource: "native",
|
||||
CommandAuthorized: true,
|
||||
SessionKey: "telegram:slash:123",
|
||||
CommandTargetSessionKey: "agent:main:telegram:direct:123",
|
||||
From: "telegram:123",
|
||||
To: "slash:123",
|
||||
};
|
||||
}
|
||||
|
||||
function createContinueDirectivesResult(resetHookTriggered: boolean) {
|
||||
return {
|
||||
kind: "continue" as const,
|
||||
result: {
|
||||
commandSource: "/new",
|
||||
command: {
|
||||
surface: "telegram",
|
||||
channel: "telegram",
|
||||
channelId: "telegram",
|
||||
ownerList: [],
|
||||
senderIsOwner: true,
|
||||
isAuthorizedSender: true,
|
||||
senderId: "123",
|
||||
abortKey: "telegram:slash:123",
|
||||
rawBodyNormalized: "/new",
|
||||
commandBodyNormalized: "/new",
|
||||
from: "telegram:123",
|
||||
to: "slash:123",
|
||||
resetHookTriggered,
|
||||
},
|
||||
allowTextCommands: true,
|
||||
skillCommands: [],
|
||||
directives: {},
|
||||
cleanedBody: "/new",
|
||||
elevatedEnabled: false,
|
||||
elevatedAllowed: false,
|
||||
elevatedFailures: [],
|
||||
defaultActivation: "always",
|
||||
resolvedThinkLevel: undefined,
|
||||
resolvedVerboseLevel: "off",
|
||||
resolvedReasoningLevel: "off",
|
||||
resolvedElevatedLevel: "off",
|
||||
execOverrides: undefined,
|
||||
blockStreamingEnabled: false,
|
||||
blockReplyChunking: undefined,
|
||||
resolvedBlockStreamingBreak: undefined,
|
||||
provider: "openai",
|
||||
model: "gpt-4o-mini",
|
||||
modelState: {
|
||||
resolveDefaultThinkingLevel: async () => undefined,
|
||||
},
|
||||
contextTokens: 0,
|
||||
inlineStatusRequested: false,
|
||||
directiveAck: undefined,
|
||||
perMessageQueueMode: undefined,
|
||||
perMessageQueueOptions: undefined,
|
||||
},
|
||||
};
|
||||
return createGetReplyContinueDirectivesResult({
|
||||
body: "/new",
|
||||
abortKey: "telegram:slash:123",
|
||||
from: "telegram:123",
|
||||
to: "slash:123",
|
||||
senderId: "123",
|
||||
commandSource: "/new",
|
||||
senderIsOwner: true,
|
||||
resetHookTriggered,
|
||||
});
|
||||
}
|
||||
|
||||
describe("getReplyFromConfig reset-hook fallback", () => {
|
||||
@@ -107,24 +50,17 @@ describe("getReplyFromConfig reset-hook fallback", () => {
|
||||
mocks.emitResetCommandHooks.mockReset();
|
||||
mocks.initSessionState.mockReset();
|
||||
|
||||
mocks.initSessionState.mockResolvedValue({
|
||||
sessionCtx: buildNativeResetContext(),
|
||||
sessionEntry: {},
|
||||
previousSessionEntry: {},
|
||||
sessionStore: {},
|
||||
sessionKey: "agent:main:telegram:direct:123",
|
||||
sessionId: "session-1",
|
||||
isNewSession: true,
|
||||
resetTriggered: true,
|
||||
systemSent: false,
|
||||
abortedLastRun: false,
|
||||
storePath: "/tmp/sessions.json",
|
||||
sessionScope: "per-sender",
|
||||
groupResolution: undefined,
|
||||
isGroup: false,
|
||||
triggerBodyNormalized: "/new",
|
||||
bodyStripped: "",
|
||||
});
|
||||
mocks.initSessionState.mockResolvedValue(
|
||||
createGetReplySessionState({
|
||||
sessionCtx: buildNativeResetContext(),
|
||||
sessionKey: "agent:main:telegram:direct:123",
|
||||
isNewSession: true,
|
||||
resetTriggered: true,
|
||||
sessionScope: "per-sender",
|
||||
triggerBodyNormalized: "/new",
|
||||
bodyStripped: "",
|
||||
}),
|
||||
);
|
||||
|
||||
mocks.resolveReplyDirectives.mockResolvedValue(createContinueDirectivesResult(false));
|
||||
});
|
||||
|
||||
@@ -18,7 +18,44 @@ export function buildGetReplyCtx(overrides: Partial<MsgContext> = {}): MsgContex
|
||||
};
|
||||
}
|
||||
|
||||
export function createGetReplySessionState() {
|
||||
export function buildGetReplyGroupCtx(overrides: Partial<MsgContext> = {}): MsgContext {
|
||||
return {
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
OriginatingChannel: "telegram",
|
||||
OriginatingTo: "telegram:-100123",
|
||||
ChatType: "group",
|
||||
Body: "hello world",
|
||||
BodyForAgent: "hello world",
|
||||
RawBody: "hello world",
|
||||
CommandBody: "hello world",
|
||||
BodyForCommands: "hello world",
|
||||
SessionKey: "agent:main:telegram:-100123",
|
||||
From: "telegram:user:42",
|
||||
To: "telegram:-100123",
|
||||
Timestamp: 1710000000000,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildNativeResetContext(): MsgContext {
|
||||
return {
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
ChatType: "direct",
|
||||
Body: "/new",
|
||||
RawBody: "/new",
|
||||
CommandBody: "/new",
|
||||
CommandSource: "native",
|
||||
CommandAuthorized: true,
|
||||
SessionKey: "telegram:slash:123",
|
||||
CommandTargetSessionKey: "agent:main:telegram:direct:123",
|
||||
From: "telegram:123",
|
||||
To: "slash:123",
|
||||
};
|
||||
}
|
||||
|
||||
export function createGetReplySessionState(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
sessionCtx: {},
|
||||
sessionEntry: {},
|
||||
@@ -36,18 +73,80 @@ export function createGetReplySessionState() {
|
||||
isGroup: false,
|
||||
triggerBodyNormalized: "",
|
||||
bodyStripped: "",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
export function createGetReplyContinueDirectivesResult(params: {
|
||||
body: string;
|
||||
abortKey: string;
|
||||
from: string;
|
||||
to: string;
|
||||
senderId: string;
|
||||
commandSource: string;
|
||||
senderIsOwner: boolean;
|
||||
resetHookTriggered: boolean;
|
||||
}) {
|
||||
return {
|
||||
kind: "continue" as const,
|
||||
result: {
|
||||
commandSource: params.commandSource,
|
||||
command: {
|
||||
surface: "telegram",
|
||||
channel: "telegram",
|
||||
channelId: "telegram",
|
||||
ownerList: [],
|
||||
senderIsOwner: params.senderIsOwner,
|
||||
isAuthorizedSender: true,
|
||||
senderId: params.senderId,
|
||||
abortKey: params.abortKey,
|
||||
rawBodyNormalized: params.body,
|
||||
commandBodyNormalized: params.body,
|
||||
from: params.from,
|
||||
to: params.to,
|
||||
resetHookTriggered: params.resetHookTriggered,
|
||||
},
|
||||
allowTextCommands: true,
|
||||
skillCommands: [],
|
||||
directives: {},
|
||||
cleanedBody: params.body,
|
||||
elevatedEnabled: false,
|
||||
elevatedAllowed: false,
|
||||
elevatedFailures: [],
|
||||
defaultActivation: "always",
|
||||
resolvedThinkLevel: undefined,
|
||||
resolvedVerboseLevel: "off",
|
||||
resolvedReasoningLevel: "off",
|
||||
resolvedElevatedLevel: "off",
|
||||
execOverrides: undefined,
|
||||
blockStreamingEnabled: false,
|
||||
blockReplyChunking: undefined,
|
||||
resolvedBlockStreamingBreak: undefined,
|
||||
provider: "openai",
|
||||
model: "gpt-4o-mini",
|
||||
modelState: {
|
||||
resolveDefaultThinkingLevel: async () => undefined,
|
||||
},
|
||||
contextTokens: 0,
|
||||
inlineStatusRequested: false,
|
||||
directiveAck: undefined,
|
||||
perMessageQueueMode: undefined,
|
||||
perMessageQueueOptions: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function registerGetReplyRuntimeOverrides(handles: {
|
||||
resolveReplyDirectives: (...args: unknown[]) => unknown;
|
||||
initSessionState: (...args: unknown[]) => unknown;
|
||||
handleInlineActions?: (...args: unknown[]) => unknown;
|
||||
}): void {
|
||||
vi.doMock("./get-reply-directives.js", () => ({
|
||||
resolveReplyDirectives: (...args: unknown[]) => handles.resolveReplyDirectives(...args),
|
||||
}));
|
||||
vi.doMock("./get-reply-inline-actions.js", () => ({
|
||||
handleInlineActions: vi.fn(async () => ({ kind: "reply", reply: { text: "ok" } })),
|
||||
handleInlineActions:
|
||||
handles.handleInlineActions ?? vi.fn(async () => ({ kind: "reply", reply: { text: "ok" } })),
|
||||
}));
|
||||
vi.doMock("./session.js", () => ({
|
||||
initSessionState: (...args: unknown[]) => handles.initSessionState(...args),
|
||||
|
||||
Reference in New Issue
Block a user