perf(test): reuse temp root in slack prepare contract suite

This commit is contained in:
Peter Steinberger
2026-02-15 00:32:55 +00:00
parent 97cde14819
commit 8181f51dbd
@@ -2,7 +2,7 @@ import type { App } from "@slack/bolt";
import fs from "node:fs"; import fs from "node:fs";
import os from "node:os"; import os from "node:os";
import path from "node:path"; import path from "node:path";
import { describe, expect, it, vi } from "vitest"; import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../../config/config.js"; import type { OpenClawConfig } from "../../../config/config.js";
import type { RuntimeEnv } from "../../../runtime.js"; import type { RuntimeEnv } from "../../../runtime.js";
import type { ResolvedSlackAccount } from "../../accounts.js"; import type { ResolvedSlackAccount } from "../../accounts.js";
@@ -14,6 +14,29 @@ import { createSlackMonitorContext } from "../context.js";
import { prepareSlackMessage } from "./prepare.js"; import { prepareSlackMessage } from "./prepare.js";
describe("slack prepareSlackMessage inbound contract", () => { describe("slack prepareSlackMessage inbound contract", () => {
let fixtureRoot = "";
let caseId = 0;
function makeTmpStorePath() {
if (!fixtureRoot) {
throw new Error("fixtureRoot missing");
}
const dir = path.join(fixtureRoot, `case-${caseId++}`);
fs.mkdirSync(dir);
return { dir, storePath: path.join(dir, "sessions.json") };
}
beforeAll(() => {
fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-slack-thread-"));
});
afterAll(() => {
if (fixtureRoot) {
fs.rmSync(fixtureRoot, { recursive: true, force: true });
fixtureRoot = "";
}
});
function createDefaultSlackCtx() { function createDefaultSlackCtx() {
const slackCtx = createSlackMonitorContext({ const slackCtx = createSlackMonitorContext({
cfg: { cfg: {
@@ -301,119 +324,109 @@ describe("slack prepareSlackMessage inbound contract", () => {
}); });
it("marks first thread turn and injects thread history for a new thread session", async () => { it("marks first thread turn and injects thread history for a new thread session", async () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-slack-thread-")); const { storePath } = makeTmpStorePath();
const storePath = path.join(tmpDir, "sessions.json"); const replies = vi
try { .fn()
const replies = vi .mockResolvedValueOnce({
.fn() messages: [{ text: "starter", user: "U2", ts: "100.000" }],
.mockResolvedValueOnce({ })
messages: [{ text: "starter", user: "U2", ts: "100.000" }], .mockResolvedValueOnce({
}) messages: [
.mockResolvedValueOnce({ { text: "starter", user: "U2", ts: "100.000" },
messages: [ { text: "assistant reply", bot_id: "B1", ts: "100.500" },
{ text: "starter", user: "U2", ts: "100.000" }, { text: "follow-up question", user: "U1", ts: "100.800" },
{ text: "assistant reply", bot_id: "B1", ts: "100.500" }, { text: "current message", user: "U1", ts: "101.000" },
{ text: "follow-up question", user: "U1", ts: "100.800" }, ],
{ text: "current message", user: "U1", ts: "101.000" }, response_metadata: { next_cursor: "" },
],
response_metadata: { next_cursor: "" },
});
const slackCtx = createThreadSlackCtx({
cfg: {
session: { store: storePath },
channels: { slack: { enabled: true, replyToMode: "all", groupPolicy: "open" } },
} as OpenClawConfig,
replies,
}); });
slackCtx.resolveUserName = async (id: string) => ({ const slackCtx = createThreadSlackCtx({
name: id === "U1" ? "Alice" : "Bob", cfg: {
}); session: { store: storePath },
slackCtx.resolveChannelName = async () => ({ name: "general", type: "channel" }); channels: { slack: { enabled: true, replyToMode: "all", groupPolicy: "open" } },
} as OpenClawConfig,
replies,
});
slackCtx.resolveUserName = async (id: string) => ({
name: id === "U1" ? "Alice" : "Bob",
});
slackCtx.resolveChannelName = async () => ({ name: "general", type: "channel" });
const account = createThreadAccount(); const account = createThreadAccount();
const message: SlackMessageEvent = { const message: SlackMessageEvent = {
channel: "C123", channel: "C123",
channel_type: "channel", channel_type: "channel",
user: "U1", user: "U1",
text: "current message", text: "current message",
ts: "101.000", ts: "101.000",
thread_ts: "100.000", thread_ts: "100.000",
} as SlackMessageEvent; } as SlackMessageEvent;
const prepared = await prepareSlackMessage({ const prepared = await prepareSlackMessage({
ctx: slackCtx, ctx: slackCtx,
account, account,
message, message,
opts: { source: "message" }, opts: { source: "message" },
}); });
expect(prepared).toBeTruthy(); expect(prepared).toBeTruthy();
expect(prepared!.ctxPayload.IsFirstThreadTurn).toBe(true); expect(prepared!.ctxPayload.IsFirstThreadTurn).toBe(true);
expect(prepared!.ctxPayload.ThreadHistoryBody).toContain("assistant reply"); expect(prepared!.ctxPayload.ThreadHistoryBody).toContain("assistant reply");
expect(prepared!.ctxPayload.ThreadHistoryBody).toContain("follow-up question"); expect(prepared!.ctxPayload.ThreadHistoryBody).toContain("follow-up question");
expect(prepared!.ctxPayload.ThreadHistoryBody).not.toContain("current message"); expect(prepared!.ctxPayload.ThreadHistoryBody).not.toContain("current message");
expect(replies).toHaveBeenCalledTimes(2); expect(replies).toHaveBeenCalledTimes(2);
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
}); });
it("does not mark first thread turn when thread session already exists in store", async () => { it("does not mark first thread turn when thread session already exists in store", async () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-slack-thread-")); const { storePath } = makeTmpStorePath();
const storePath = path.join(tmpDir, "sessions.json"); const cfg = {
try { session: { store: storePath },
const cfg = { channels: { slack: { enabled: true, replyToMode: "all", groupPolicy: "open" } },
session: { store: storePath }, } as OpenClawConfig;
channels: { slack: { enabled: true, replyToMode: "all", groupPolicy: "open" } }, const route = resolveAgentRoute({
} as OpenClawConfig; cfg,
const route = resolveAgentRoute({ channel: "slack",
cfg, accountId: "default",
channel: "slack", teamId: "T1",
accountId: "default", peer: { kind: "channel", id: "C123" },
teamId: "T1", });
peer: { kind: "channel", id: "C123" }, const threadKeys = resolveThreadSessionKeys({
}); baseSessionKey: route.sessionKey,
const threadKeys = resolveThreadSessionKeys({ threadId: "200.000",
baseSessionKey: route.sessionKey, });
threadId: "200.000", fs.writeFileSync(
}); storePath,
fs.writeFileSync( JSON.stringify({ [threadKeys.sessionKey]: { updatedAt: Date.now() } }, null, 2),
storePath, );
JSON.stringify({ [threadKeys.sessionKey]: { updatedAt: Date.now() } }, null, 2),
);
const replies = vi.fn().mockResolvedValue({ const replies = vi.fn().mockResolvedValue({
messages: [{ text: "starter", user: "U2", ts: "200.000" }], messages: [{ text: "starter", user: "U2", ts: "200.000" }],
}); });
const slackCtx = createThreadSlackCtx({ cfg, replies }); const slackCtx = createThreadSlackCtx({ cfg, replies });
slackCtx.resolveUserName = async () => ({ name: "Alice" }); slackCtx.resolveUserName = async () => ({ name: "Alice" });
slackCtx.resolveChannelName = async () => ({ name: "general", type: "channel" }); slackCtx.resolveChannelName = async () => ({ name: "general", type: "channel" });
const account = createThreadAccount(); const account = createThreadAccount();
const message: SlackMessageEvent = { const message: SlackMessageEvent = {
channel: "C123", channel: "C123",
channel_type: "channel", channel_type: "channel",
user: "U1", user: "U1",
text: "reply in old thread", text: "reply in old thread",
ts: "201.000", ts: "201.000",
thread_ts: "200.000", thread_ts: "200.000",
} as SlackMessageEvent; } as SlackMessageEvent;
const prepared = await prepareSlackMessage({ const prepared = await prepareSlackMessage({
ctx: slackCtx, ctx: slackCtx,
account, account,
message, message,
opts: { source: "message" }, opts: { source: "message" },
}); });
expect(prepared).toBeTruthy(); expect(prepared).toBeTruthy();
expect(prepared!.ctxPayload.IsFirstThreadTurn).toBeUndefined(); expect(prepared!.ctxPayload.IsFirstThreadTurn).toBeUndefined();
expect(prepared!.ctxPayload.ThreadHistoryBody).toBeUndefined(); expect(prepared!.ctxPayload.ThreadHistoryBody).toBeUndefined();
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
}); });
it("includes thread_ts and parent_user_id metadata in thread replies", async () => { it("includes thread_ts and parent_user_id metadata in thread replies", async () => {