mirror of
https://github.com/farcasclaudiu/openclaw.git
synced 2026-06-28 19:01:47 +03:00
perf(test): reuse temp root in slack prepare contract suite
This commit is contained in:
@@ -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 () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user