Skip to content

Commit 7d9172d

Browse files
committed
perf: speed up slow extension tests
1 parent bcea5e7 commit 7d9172d

3 files changed

Lines changed: 219 additions & 248 deletions

File tree

extensions/slack/src/monitor/message-handler/prepare.test.ts

Lines changed: 198 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ import {
1010
} from "openclaw/plugin-sdk/conversation-runtime";
1111
import { resolveAgentRoute } from "openclaw/plugin-sdk/routing";
1212
import { resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
13-
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
13+
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
1414
import type { ResolvedSlackAccount } from "../../accounts.js";
1515
import type { SlackMessageEvent } from "../../types.js";
1616
import type { SlackMonitorContext } from "../context.js";
17+
import { resetSlackThreadStarterCacheForTest } from "../media.js";
1718
import { resolveSlackMessageContent } from "./prepare-content.js";
1819
import { prepareSlackMessage } from "./prepare.js";
1920
import {
@@ -29,6 +30,10 @@ describe("slack prepareSlackMessage inbound contract", () => {
2930
storeFixture.setup();
3031
});
3132

33+
beforeEach(() => {
34+
resetSlackThreadStarterCacheForTest();
35+
});
36+
3237
afterAll(() => {
3338
storeFixture.cleanup();
3439
});
@@ -126,6 +131,102 @@ describe("slack prepareSlackMessage inbound contract", () => {
126131
return prepareMessageWith(ctx, createThreadAccount(), createThreadReplyMessage(overrides));
127132
}
128133

134+
type ThreadContextAllowlistCaseParams = {
135+
channel: string;
136+
channelType: SlackMessageEvent["channel_type"];
137+
user: string;
138+
userName: string;
139+
starterText: string;
140+
followUpText: string;
141+
startTs: string;
142+
replyTs: string;
143+
followUpTs: string;
144+
currentTs: string;
145+
channelsConfig?: Parameters<typeof createInboundSlackCtx>[0]["channelsConfig"];
146+
resolveChannelName?: (channelId: string) => Promise<{
147+
name?: string;
148+
type?: SlackMessageEvent["channel_type"];
149+
topic?: string;
150+
purpose?: string;
151+
}>;
152+
};
153+
154+
async function prepareThreadContextAllowlistCase(params: ThreadContextAllowlistCaseParams) {
155+
const { storePath } = storeFixture.makeTmpStorePath();
156+
const replies = vi
157+
.fn()
158+
.mockResolvedValueOnce({
159+
messages: [{ text: params.starterText, user: params.user, ts: params.startTs }],
160+
})
161+
.mockResolvedValueOnce({
162+
messages: [
163+
{ text: params.starterText, user: params.user, ts: params.startTs },
164+
{ text: "assistant reply", bot_id: "B1", ts: params.replyTs },
165+
{ text: params.followUpText, user: params.user, ts: params.followUpTs },
166+
{ text: "current message", user: params.user, ts: params.currentTs },
167+
],
168+
response_metadata: { next_cursor: "" },
169+
});
170+
const ctx = createInboundSlackCtx({
171+
cfg: {
172+
session: { store: storePath },
173+
channels: {
174+
slack: {
175+
enabled: true,
176+
replyToMode: "all",
177+
groupPolicy: "open",
178+
contextVisibility: "allowlist",
179+
},
180+
},
181+
} as OpenClawConfig,
182+
appClient: { conversations: { replies } } as unknown as App["client"],
183+
defaultRequireMention: false,
184+
replyToMode: "all",
185+
channelsConfig: params.channelsConfig,
186+
});
187+
ctx.allowFrom = ["u-owner"];
188+
ctx.resolveUserName = async (id: string) => ({
189+
name: id === params.user ? params.userName : "Owner",
190+
});
191+
if (params.resolveChannelName) {
192+
ctx.resolveChannelName = params.resolveChannelName;
193+
}
194+
195+
const prepared = await prepareSlackMessage({
196+
ctx,
197+
account: createSlackAccount({
198+
replyToMode: "all",
199+
thread: { initialHistoryLimit: 20 },
200+
}),
201+
message: {
202+
channel: params.channel,
203+
channel_type: params.channelType,
204+
user: params.user,
205+
text: "current message",
206+
ts: params.currentTs,
207+
thread_ts: params.startTs,
208+
} as SlackMessageEvent,
209+
opts: { source: "message" },
210+
});
211+
212+
return { prepared, replies };
213+
}
214+
215+
function expectThreadContextAllowsHumanHistory(
216+
prepared: Awaited<ReturnType<typeof prepareSlackMessage>>,
217+
replies: ReturnType<typeof vi.fn>,
218+
starterText: string,
219+
followUpText: string,
220+
) {
221+
expect(prepared).toBeTruthy();
222+
expect(prepared!.ctxPayload.ThreadStarterBody).toBe(starterText);
223+
expect(prepared!.ctxPayload.ThreadHistoryBody).toContain(starterText);
224+
expect(prepared!.ctxPayload.ThreadHistoryBody).toContain(followUpText);
225+
expect(prepared!.ctxPayload.ThreadHistoryBody).not.toContain("assistant reply");
226+
expect(prepared!.ctxPayload.ThreadHistoryBody).not.toContain("current message");
227+
expect(replies).toHaveBeenCalledTimes(2);
228+
}
229+
129230
function createDmScopeMainSlackCtx(): SlackMonitorContext {
130231
const slackCtx = createInboundSlackCtx({
131232
cfg: {
@@ -502,6 +603,102 @@ describe("slack prepareSlackMessage inbound contract", () => {
502603
expect(replies).toHaveBeenCalledTimes(2);
503604
});
504605

606+
it("uses room users allowlist for thread context filtering", async () => {
607+
const { prepared, replies } = await prepareThreadContextAllowlistCase({
608+
channel: "C123",
609+
channelType: "channel",
610+
user: "U1",
611+
userName: "Alice",
612+
starterText: "starter from room user",
613+
followUpText: "allowed follow-up",
614+
startTs: "100.000",
615+
replyTs: "100.500",
616+
followUpTs: "100.800",
617+
currentTs: "101.000",
618+
channelsConfig: {
619+
C123: {
620+
users: ["U1"],
621+
requireMention: false,
622+
},
623+
},
624+
resolveChannelName: async () => ({ name: "general", type: "channel" }),
625+
});
626+
627+
expectThreadContextAllowsHumanHistory(
628+
prepared,
629+
replies,
630+
"starter from room user",
631+
"allowed follow-up",
632+
);
633+
});
634+
635+
it("does not apply the owner allowlist to open-room thread context", async () => {
636+
const { prepared, replies } = await prepareThreadContextAllowlistCase({
637+
channel: "C124",
638+
channelType: "channel",
639+
user: "U2",
640+
userName: "Bob",
641+
starterText: "starter from open room",
642+
followUpText: "open-room follow-up",
643+
startTs: "200.000",
644+
replyTs: "200.500",
645+
followUpTs: "200.800",
646+
currentTs: "201.000",
647+
channelsConfig: {
648+
C124: {
649+
requireMention: false,
650+
},
651+
},
652+
resolveChannelName: async () => ({ name: "general", type: "channel" }),
653+
});
654+
655+
expectThreadContextAllowsHumanHistory(
656+
prepared,
657+
replies,
658+
"starter from open room",
659+
"open-room follow-up",
660+
);
661+
});
662+
663+
it("does not apply the owner allowlist to open DMs when dmPolicy is open", async () => {
664+
const { prepared, replies } = await prepareThreadContextAllowlistCase({
665+
channel: "D300",
666+
channelType: "im",
667+
user: "U3",
668+
userName: "Dana",
669+
starterText: "starter from open dm",
670+
followUpText: "dm follow-up",
671+
startTs: "300.000",
672+
replyTs: "300.500",
673+
followUpTs: "300.800",
674+
currentTs: "301.000",
675+
});
676+
677+
expectThreadContextAllowsHumanHistory(
678+
prepared,
679+
replies,
680+
"starter from open dm",
681+
"dm follow-up",
682+
);
683+
});
684+
685+
it("does not apply the owner allowlist to MPIM thread context", async () => {
686+
const { prepared, replies } = await prepareThreadContextAllowlistCase({
687+
channel: "G400",
688+
channelType: "mpim",
689+
user: "U4",
690+
userName: "Evan",
691+
starterText: "starter from mpim",
692+
followUpText: "mpim follow-up",
693+
startTs: "400.000",
694+
replyTs: "400.500",
695+
followUpTs: "400.800",
696+
currentTs: "401.000",
697+
});
698+
699+
expectThreadContextAllowsHumanHistory(prepared, replies, "starter from mpim", "mpim follow-up");
700+
});
701+
505702
it("skips loading thread history when thread session already exists in store (bloat fix)", async () => {
506703
const { storePath } = storeFixture.makeTmpStorePath();
507704
const cfg = {

0 commit comments

Comments
 (0)