test: share auto reply trigger fixtures

This commit is contained in:
Peter Steinberger
2026-04-20 16:13:46 +01:00
parent c705720d87
commit df05668f8b

View File

@@ -24,6 +24,14 @@ type GetReplyFromConfig = typeof import("./reply.js").getReplyFromConfig;
const TEST_PRIMARY_PROFILE_ID = "openai-codex:primary@example.test";
const TEST_SECONDARY_PROFILE_ID = "openai-codex:secondary@example.test";
const TEST_TIME_ZONE = "America/Chicago";
const TELEGRAM_DIRECT_MESSAGE = {
From: "telegram:111",
To: "telegram:111",
ChatType: "direct",
Provider: "telegram",
Surface: "telegram",
} as const;
vi.mock("./reply/agent-runner.runtime.js", () => ({
runReplyAgent: async (params: {
@@ -115,6 +123,111 @@ function mockEmbeddedOkPayload() {
return mockRunEmbeddedPiAgentOk("ok");
}
function mockRunEmbeddedPiAgentText(text: string, durationMs: number) {
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
runEmbeddedPiAgentMock.mockReset();
runEmbeddedPiAgentMock.mockResolvedValue({
payloads: [{ text }],
meta: {
durationMs,
agentMeta: { sessionId: "s", provider: "p", model: "m" },
},
});
return runEmbeddedPiAgentMock;
}
async function writeDailyMemoryNotes(
workspaceDir: string,
notes: Array<{ stamp: string; text: string }>,
) {
const memoryDir = join(workspaceDir, "memory");
await fs.mkdir(memoryDir, { recursive: true });
for (const note of notes) {
await fs.writeFile(join(memoryDir, `${note.stamp}.md`), note.text, "utf-8");
}
}
async function seedTargetSession(storePath: string, targetSessionKey: string) {
await fs.writeFile(
storePath,
JSON.stringify({
[targetSessionKey]: {
sessionId: "session-target",
updatedAt: Date.now(),
},
}),
);
}
function makeNativeTelegramCommandMessage(params: {
body: string;
slashSessionKey: string;
targetSessionKey: string;
}) {
return {
Body: params.body,
...TELEGRAM_DIRECT_MESSAGE,
SessionKey: params.slashSessionKey,
CommandSource: "native",
CommandTargetSessionKey: params.targetSessionKey,
CommandAuthorized: true,
};
}
function makeTelegramSessionMessage(body: string, sessionKey: string) {
return {
Body: body,
...TELEGRAM_DIRECT_MESSAGE,
SessionKey: sessionKey,
};
}
function makeAuthorizedSmsCommandMessage(body: string) {
return {
Body: body,
From: "+1003",
To: "+2000",
CommandAuthorized: true,
};
}
function makeStartupContextCfg(home: string, startupContext?: { applyOn: Array<"new" | "reset"> }) {
const cfg = makeCfg(home);
cfg.agents ??= {};
cfg.agents.defaults ??= {};
cfg.agents.defaults.userTimezone = TEST_TIME_ZONE;
if (startupContext) {
cfg.agents.defaults.startupContext = startupContext;
}
return cfg;
}
async function runAuthorizedSmsCommand(body: string, cfg: ReturnType<typeof makeCfg>) {
return await getReplyFromConfig(makeAuthorizedSmsCommandMessage(body), {}, cfg);
}
async function expectNextRunUsesTargetSession(
params: {
cfg: ReturnType<typeof makeCfg>;
targetSessionKey: string;
runEmbeddedPiAgentMock: ReturnType<typeof getRunEmbeddedPiAgentMock>;
},
expected: Record<string, unknown>,
) {
mockRunEmbeddedPiAgentText("ok", 5);
await getReplyFromConfig(
makeTelegramSessionMessage("hi", params.targetSessionKey),
{},
params.cfg,
);
expect(params.runEmbeddedPiAgentMock).toHaveBeenCalledOnce();
expect(params.runEmbeddedPiAgentMock.mock.calls[0]?.[0]).toEqual(
expect.objectContaining(expected),
);
}
async function writeStoredModelOverride(cfg: ReturnType<typeof makeCfg>): Promise<void> {
await fs.writeFile(
requireSessionStorePath(cfg),
@@ -264,50 +377,21 @@ describe("trigger handling", () => {
it("prepends runtime-loaded daily memory context on bare /new", async () => {
await withTempHome(async (home) => {
const workspaceDir = join(home, "openclaw");
const timeZone = "America/Chicago";
const nowMs = Date.now();
const todayStamp = formatDateStampForZone(nowMs, timeZone);
const yesterdayStamp = formatDateStampForZone(nowMs - 24 * 60 * 60 * 1000, timeZone);
await fs.mkdir(join(workspaceDir, "memory"), { recursive: true });
await fs.writeFile(
join(workspaceDir, "memory", `${todayStamp}.md`),
"today startup note",
"utf-8",
);
await fs.writeFile(
join(workspaceDir, "memory", `${yesterdayStamp}.md`),
"yesterday startup note",
"utf-8",
);
const todayStamp = formatDateStampForZone(nowMs, TEST_TIME_ZONE);
const yesterdayStamp = formatDateStampForZone(nowMs - 24 * 60 * 60 * 1000, TEST_TIME_ZONE);
await writeDailyMemoryNotes(workspaceDir, [
{ stamp: todayStamp, text: "today startup note" },
{ stamp: yesterdayStamp, text: "yesterday startup note" },
]);
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
runEmbeddedPiAgentMock.mockReset();
runEmbeddedPiAgentMock.mockResolvedValue({
payloads: [{ text: "hello" }],
meta: {
durationMs: 1,
agentMeta: { sessionId: "s", provider: "p", model: "m" },
},
});
const runEmbeddedPiAgentMock = mockRunEmbeddedPiAgentText("hello", 1);
const cfg = makeCfg(home);
cfg.agents ??= {};
cfg.agents.defaults ??= {};
cfg.agents.defaults.userTimezone = timeZone;
const cfg = makeStartupContextCfg(home);
const res = await getReplyFromConfig(
{
Body: "/new",
From: "+1003",
To: "+2000",
CommandAuthorized: true,
},
{},
cfg,
);
const res = await runAuthorizedSmsCommand("/new", cfg);
const text = Array.isArray(res) ? res[0]?.text : res?.text;
expect(text).toBe("hello");
expect(maybeReplyText(res)).toBe("hello");
const prompt = runEmbeddedPiAgentMock.mock.calls.at(-1)?.[0]?.prompt ?? "";
expect(prompt).toContain("[Startup context loaded by runtime]");
expect(prompt).toContain(`[Untrusted daily memory: memory/${todayStamp}.md]`);
@@ -321,47 +405,19 @@ describe("trigger handling", () => {
it("treats normalized /RESET as reset for startupContext.applyOn", async () => {
await withTempHome(async (home) => {
const workspaceDir = join(home, "openclaw");
const timeZone = "America/Chicago";
const nowMs = Date.now();
const todayStamp = formatDateStampForZone(nowMs, timeZone);
await fs.mkdir(join(workspaceDir, "memory"), { recursive: true });
await fs.writeFile(
join(workspaceDir, "memory", `${todayStamp}.md`),
"reset startup note",
"utf-8",
);
const todayStamp = formatDateStampForZone(nowMs, TEST_TIME_ZONE);
await writeDailyMemoryNotes(workspaceDir, [
{ stamp: todayStamp, text: "reset startup note" },
]);
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
runEmbeddedPiAgentMock.mockReset();
runEmbeddedPiAgentMock.mockResolvedValue({
payloads: [{ text: "hello" }],
meta: {
durationMs: 1,
agentMeta: { sessionId: "s", provider: "p", model: "m" },
},
});
const runEmbeddedPiAgentMock = mockRunEmbeddedPiAgentText("hello", 1);
const cfg = makeCfg(home);
cfg.agents ??= {};
cfg.agents.defaults ??= {};
cfg.agents.defaults.userTimezone = timeZone;
cfg.agents.defaults.startupContext = {
applyOn: ["reset"],
};
const cfg = makeStartupContextCfg(home, { applyOn: ["reset"] });
const res = await getReplyFromConfig(
{
Body: "/RESET",
From: "+1003",
To: "+2000",
CommandAuthorized: true,
},
{},
cfg,
);
const res = await runAuthorizedSmsCommand("/RESET", cfg);
const text = Array.isArray(res) ? res[0]?.text : res?.text;
expect(text).toBe("hello");
expect(maybeReplyText(res)).toBe("hello");
const prompt = runEmbeddedPiAgentMock.mock.calls.at(-1)?.[0]?.prompt ?? "";
expect(prompt).toContain(`[Untrusted daily memory: memory/${todayStamp}.md]`);
expect(prompt).toContain("reset startup note");
@@ -594,77 +650,35 @@ describe("trigger handling", () => {
cfg.session = { ...cfg.session, store: join(home, "native-model.sessions.json") };
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
runEmbeddedPiAgentMock.mockReset();
const storePath = cfg.session?.store;
if (!storePath) {
throw new Error("missing session store path");
}
const storePath = requireSessionStorePath(cfg);
const slashSessionKey = "telegram:slash:111";
const targetSessionKey = MAIN_SESSION_KEY;
await fs.writeFile(
storePath,
JSON.stringify({
[targetSessionKey]: {
sessionId: "session-target",
updatedAt: Date.now(),
},
}),
);
await seedTargetSession(storePath, targetSessionKey);
const res = await getReplyFromConfig(
{
Body: "/model openai/gpt-4.1-mini",
From: "telegram:111",
To: "telegram:111",
ChatType: "direct",
Provider: "telegram",
Surface: "telegram",
SessionKey: slashSessionKey,
CommandSource: "native",
CommandTargetSessionKey: targetSessionKey,
CommandAuthorized: true,
},
makeNativeTelegramCommandMessage({
body: "/model openai/gpt-4.1-mini",
slashSessionKey,
targetSessionKey,
}),
{},
cfg,
);
const text = Array.isArray(res) ? res[0]?.text : res?.text;
expect(text).toContain("Model set to openai/gpt-4.1-mini");
expect(maybeReplyText(res)).toContain("Model set to openai/gpt-4.1-mini");
const store = loadSessionStore(storePath);
expect(store[targetSessionKey]?.providerOverride).toBe("openai");
expect(store[targetSessionKey]?.modelOverride).toBe("gpt-4.1-mini");
expect(store[slashSessionKey]).toBeUndefined();
runEmbeddedPiAgentMock.mockReset();
runEmbeddedPiAgentMock.mockResolvedValue({
payloads: [{ text: "ok" }],
meta: {
durationMs: 5,
agentMeta: { sessionId: "s", provider: "p", model: "m" },
},
});
await getReplyFromConfig(
await expectNextRunUsesTargetSession(
{ cfg, targetSessionKey, runEmbeddedPiAgentMock },
{
Body: "hi",
From: "telegram:111",
To: "telegram:111",
ChatType: "direct",
Provider: "telegram",
Surface: "telegram",
SessionKey: targetSessionKey,
},
{},
cfg,
);
expect(runEmbeddedPiAgentMock).toHaveBeenCalledOnce();
expect(runEmbeddedPiAgentMock.mock.calls[0]?.[0]).toEqual(
expect.objectContaining({
provider: "openai",
model: "gpt-4.1-mini",
}),
},
);
});
});
@@ -675,10 +689,7 @@ describe("trigger handling", () => {
cfg.session = { ...cfg.session, store: join(home, "native-model-auth.sessions.json") };
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
runEmbeddedPiAgentMock.mockReset();
const storePath = cfg.session?.store;
if (!storePath) {
throw new Error("missing session store path");
}
const storePath = requireSessionStorePath(cfg);
const authDir = join(home, ".openclaw", "agents", "main", "agent");
await fs.mkdir(authDir, { recursive: true });
await fs.writeFile(
@@ -720,72 +731,33 @@ describe("trigger handling", () => {
const slashSessionKey = "telegram:slash:111";
const targetSessionKey = MAIN_SESSION_KEY;
await fs.writeFile(
storePath,
JSON.stringify({
[targetSessionKey]: {
sessionId: "session-target",
updatedAt: Date.now(),
},
}),
);
await seedTargetSession(storePath, targetSessionKey);
const res = await getReplyFromConfig(
{
Body: `/model openai-codex/gpt-5.4@${TEST_SECONDARY_PROFILE_ID}`,
From: "telegram:111",
To: "telegram:111",
ChatType: "direct",
Provider: "telegram",
Surface: "telegram",
SessionKey: slashSessionKey,
CommandSource: "native",
CommandTargetSessionKey: targetSessionKey,
CommandAuthorized: true,
},
makeNativeTelegramCommandMessage({
body: `/model openai-codex/gpt-5.4@${TEST_SECONDARY_PROFILE_ID}`,
slashSessionKey,
targetSessionKey,
}),
{},
cfg,
);
const text = Array.isArray(res) ? res[0]?.text : res?.text;
expect(text).toContain(`Auth profile set to ${TEST_SECONDARY_PROFILE_ID}`);
expect(maybeReplyText(res)).toContain(`Auth profile set to ${TEST_SECONDARY_PROFILE_ID}`);
const store = loadSessionStore(storePath);
expect(store[targetSessionKey]?.authProfileOverride).toBe(TEST_SECONDARY_PROFILE_ID);
expect(store[targetSessionKey]?.authProfileOverrideSource).toBe("user");
expect(store[slashSessionKey]).toBeUndefined();
runEmbeddedPiAgentMock.mockReset();
runEmbeddedPiAgentMock.mockResolvedValue({
payloads: [{ text: "ok" }],
meta: {
durationMs: 5,
agentMeta: { sessionId: "s", provider: "p", model: "m" },
},
});
await getReplyFromConfig(
await expectNextRunUsesTargetSession(
{ cfg, targetSessionKey, runEmbeddedPiAgentMock },
{
Body: "hi",
From: "telegram:111",
To: "telegram:111",
ChatType: "direct",
Provider: "telegram",
Surface: "telegram",
SessionKey: targetSessionKey,
},
{},
cfg,
);
expect(runEmbeddedPiAgentMock).toHaveBeenCalledOnce();
expect(runEmbeddedPiAgentMock.mock.calls[0]?.[0]).toEqual(
expect.objectContaining({
provider: "openai-codex",
model: "gpt-5.4",
authProfileId: TEST_SECONDARY_PROFILE_ID,
authProfileIdSource: "user",
}),
},
);
});
});