refactor(channels): dedupe monitor message test flows
This commit is contained in:
@@ -106,6 +106,50 @@ describe("monitorSlackProvider tool results", () => {
|
||||
});
|
||||
}
|
||||
|
||||
async function runChannelMessageEvent(
|
||||
text: string,
|
||||
overrides: Partial<SlackMessageEvent> = {},
|
||||
): Promise<void> {
|
||||
await runSlackMessageOnce(monitorSlackProvider, {
|
||||
event: makeSlackMessageEvent({
|
||||
text,
|
||||
channel_type: "channel",
|
||||
...overrides,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
function setHistoryCaptureConfig(channels: Record<string, unknown>) {
|
||||
slackTestState.config = {
|
||||
messages: { ackReactionScope: "group-mentions" },
|
||||
channels: {
|
||||
slack: {
|
||||
historyLimit: 5,
|
||||
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
|
||||
channels,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function captureReplyContexts<T extends Record<string, unknown>>() {
|
||||
const contexts: T[] = [];
|
||||
replyMock.mockImplementation(async (ctx: unknown) => {
|
||||
contexts.push((ctx ?? {}) as T);
|
||||
return undefined;
|
||||
});
|
||||
return contexts;
|
||||
}
|
||||
|
||||
async function runMonitoredSlackMessages(events: SlackMessageEvent[]) {
|
||||
const { controller, run } = startSlackMonitor(monitorSlackProvider);
|
||||
const handler = await getSlackHandlerOrThrow("message");
|
||||
for (const event of events) {
|
||||
await handler({ event });
|
||||
}
|
||||
await stopSlackMonitor({ controller, run });
|
||||
}
|
||||
|
||||
function setPairingOnlyDirectMessages() {
|
||||
const currentConfig = slackTestState.config as {
|
||||
channels?: { slack?: Record<string, unknown> };
|
||||
@@ -122,6 +166,61 @@ describe("monitorSlackProvider tool results", () => {
|
||||
};
|
||||
}
|
||||
|
||||
function setOpenChannelDirectMessages(params?: {
|
||||
bindings?: Array<Record<string, unknown>>;
|
||||
groupPolicy?: "open";
|
||||
includeAckReactionConfig?: boolean;
|
||||
replyToMode?: "off" | "all" | "first";
|
||||
threadInheritParent?: boolean;
|
||||
}) {
|
||||
const slackChannelConfig: Record<string, unknown> = {
|
||||
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
|
||||
channels: { C1: { allow: true, requireMention: false } },
|
||||
...(params?.groupPolicy ? { groupPolicy: params.groupPolicy } : {}),
|
||||
...(params?.replyToMode ? { replyToMode: params.replyToMode } : {}),
|
||||
...(params?.threadInheritParent ? { thread: { inheritParent: true } } : {}),
|
||||
};
|
||||
slackTestState.config = {
|
||||
messages: params?.includeAckReactionConfig
|
||||
? {
|
||||
responsePrefix: "PFX",
|
||||
ackReaction: "👀",
|
||||
ackReactionScope: "group-mentions",
|
||||
}
|
||||
: { responsePrefix: "PFX" },
|
||||
channels: { slack: slackChannelConfig },
|
||||
...(params?.bindings ? { bindings: params.bindings } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function getFirstReplySessionCtx(): {
|
||||
SessionKey?: string;
|
||||
ParentSessionKey?: string;
|
||||
ThreadStarterBody?: string;
|
||||
ThreadLabel?: string;
|
||||
} {
|
||||
return (replyMock.mock.calls[0]?.[0] ?? {}) as {
|
||||
SessionKey?: string;
|
||||
ParentSessionKey?: string;
|
||||
ThreadStarterBody?: string;
|
||||
ThreadLabel?: string;
|
||||
};
|
||||
}
|
||||
|
||||
function expectSingleSendWithThread(threadTs: string | undefined) {
|
||||
expect(sendMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMock.mock.calls[0][2]).toMatchObject({ threadTs });
|
||||
}
|
||||
|
||||
async function runDefaultMessageAndExpectSentText(expectedText: string) {
|
||||
replyMock.mockResolvedValue({ text: expectedText.replace(/^PFX /, "") });
|
||||
await runSlackMessageOnce(monitorSlackProvider, {
|
||||
event: makeSlackMessageEvent(),
|
||||
});
|
||||
expect(sendMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMock.mock.calls[0][1]).toBe(expectedText);
|
||||
}
|
||||
|
||||
it("skips socket startup when Slack channel is disabled", async () => {
|
||||
slackTestState.config = {
|
||||
channels: {
|
||||
@@ -149,14 +248,7 @@ describe("monitorSlackProvider tool results", () => {
|
||||
});
|
||||
|
||||
it("skips tool summaries with responsePrefix", async () => {
|
||||
replyMock.mockResolvedValue({ text: "final reply" });
|
||||
|
||||
await runSlackMessageOnce(monitorSlackProvider, {
|
||||
event: makeSlackMessageEvent(),
|
||||
});
|
||||
|
||||
expect(sendMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMock.mock.calls[0][1]).toBe("PFX final reply");
|
||||
await runDefaultMessageAndExpectSentText("PFX final reply");
|
||||
});
|
||||
|
||||
it("drops events with mismatched api_app_id", async () => {
|
||||
@@ -213,127 +305,56 @@ describe("monitorSlackProvider tool results", () => {
|
||||
},
|
||||
};
|
||||
|
||||
replyMock.mockResolvedValue({ text: "final reply" });
|
||||
|
||||
await runSlackMessageOnce(monitorSlackProvider, {
|
||||
event: makeSlackMessageEvent(),
|
||||
});
|
||||
|
||||
expect(sendMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMock.mock.calls[0][1]).toBe("final reply");
|
||||
await runDefaultMessageAndExpectSentText("final reply");
|
||||
});
|
||||
|
||||
it("preserves RawBody without injecting processed room history", async () => {
|
||||
slackTestState.config = {
|
||||
messages: { ackReactionScope: "group-mentions" },
|
||||
channels: {
|
||||
slack: {
|
||||
historyLimit: 5,
|
||||
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
|
||||
channels: { "*": { requireMention: false } },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let capturedCtx: { Body?: string; RawBody?: string; CommandBody?: string } = {};
|
||||
replyMock.mockImplementation(async (ctx: unknown) => {
|
||||
capturedCtx = ctx ?? {};
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const { controller, run } = startSlackMonitor(monitorSlackProvider);
|
||||
const handler = await getSlackHandlerOrThrow("message");
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
type: "message",
|
||||
user: "U1",
|
||||
text: "first",
|
||||
ts: "123",
|
||||
channel: "C1",
|
||||
channel_type: "channel",
|
||||
},
|
||||
});
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
type: "message",
|
||||
user: "U2",
|
||||
text: "second",
|
||||
ts: "124",
|
||||
channel: "C1",
|
||||
channel_type: "channel",
|
||||
},
|
||||
});
|
||||
|
||||
await stopSlackMonitor({ controller, run });
|
||||
setHistoryCaptureConfig({ "*": { requireMention: false } });
|
||||
const capturedCtx = captureReplyContexts<{
|
||||
Body?: string;
|
||||
RawBody?: string;
|
||||
CommandBody?: string;
|
||||
}>();
|
||||
await runMonitoredSlackMessages([
|
||||
makeSlackMessageEvent({ user: "U1", text: "first", ts: "123", channel_type: "channel" }),
|
||||
makeSlackMessageEvent({ user: "U2", text: "second", ts: "124", channel_type: "channel" }),
|
||||
]);
|
||||
|
||||
expect(replyMock).toHaveBeenCalledTimes(2);
|
||||
expect(capturedCtx.Body).not.toContain(HISTORY_CONTEXT_MARKER);
|
||||
expect(capturedCtx.Body).not.toContain(CURRENT_MESSAGE_MARKER);
|
||||
expect(capturedCtx.Body).not.toContain("first");
|
||||
expect(capturedCtx.RawBody).toBe("second");
|
||||
expect(capturedCtx.CommandBody).toBe("second");
|
||||
const latestCtx = capturedCtx.at(-1) ?? {};
|
||||
expect(latestCtx.Body).not.toContain(HISTORY_CONTEXT_MARKER);
|
||||
expect(latestCtx.Body).not.toContain(CURRENT_MESSAGE_MARKER);
|
||||
expect(latestCtx.Body).not.toContain("first");
|
||||
expect(latestCtx.RawBody).toBe("second");
|
||||
expect(latestCtx.CommandBody).toBe("second");
|
||||
});
|
||||
|
||||
it("scopes thread history to the thread by default", async () => {
|
||||
slackTestState.config = {
|
||||
messages: { ackReactionScope: "group-mentions" },
|
||||
channels: {
|
||||
slack: {
|
||||
historyLimit: 5,
|
||||
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
|
||||
channels: { C1: { allow: true, requireMention: true } },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const capturedCtx: Array<{ Body?: string }> = [];
|
||||
replyMock.mockImplementation(async (ctx: unknown) => {
|
||||
capturedCtx.push(ctx ?? {});
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const { controller, run } = startSlackMonitor(monitorSlackProvider);
|
||||
const handler = await getSlackHandlerOrThrow("message");
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
type: "message",
|
||||
setHistoryCaptureConfig({ C1: { allow: true, requireMention: true } });
|
||||
const capturedCtx = captureReplyContexts<{ Body?: string }>();
|
||||
await runMonitoredSlackMessages([
|
||||
makeSlackMessageEvent({
|
||||
user: "U1",
|
||||
text: "thread-a-one",
|
||||
ts: "200",
|
||||
thread_ts: "100",
|
||||
channel: "C1",
|
||||
channel_type: "channel",
|
||||
},
|
||||
});
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
type: "message",
|
||||
}),
|
||||
makeSlackMessageEvent({
|
||||
user: "U1",
|
||||
text: "<@bot-user> thread-a-two",
|
||||
ts: "201",
|
||||
thread_ts: "100",
|
||||
channel: "C1",
|
||||
channel_type: "channel",
|
||||
},
|
||||
});
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
type: "message",
|
||||
}),
|
||||
makeSlackMessageEvent({
|
||||
user: "U2",
|
||||
text: "<@bot-user> thread-b-one",
|
||||
ts: "301",
|
||||
thread_ts: "300",
|
||||
channel: "C1",
|
||||
channel_type: "channel",
|
||||
},
|
||||
});
|
||||
|
||||
await stopSlackMonitor({ controller, run });
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(replyMock).toHaveBeenCalledTimes(2);
|
||||
expect(capturedCtx[0]?.Body).toContain("thread-a-one");
|
||||
@@ -438,13 +459,7 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
it("treats control commands as mentions for group bypass", async () => {
|
||||
replyMock.mockResolvedValue({ text: "ok" });
|
||||
|
||||
await runSlackMessageOnce(monitorSlackProvider, {
|
||||
event: makeSlackMessageEvent({
|
||||
text: "/elevated off",
|
||||
channel_type: "channel",
|
||||
}),
|
||||
});
|
||||
await runChannelMessageEvent("/elevated off");
|
||||
|
||||
expect(replyMock).toHaveBeenCalledTimes(1);
|
||||
expect(firstReplyCtx().WasMentioned).toBe(true);
|
||||
@@ -452,25 +467,14 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
it("threads replies when incoming message is in a thread", async () => {
|
||||
replyMock.mockResolvedValue({ text: "thread reply" });
|
||||
slackTestState.config = {
|
||||
messages: {
|
||||
responsePrefix: "PFX",
|
||||
ackReaction: "👀",
|
||||
ackReactionScope: "group-mentions",
|
||||
},
|
||||
channels: {
|
||||
slack: {
|
||||
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
|
||||
groupPolicy: "open",
|
||||
replyToMode: "off",
|
||||
channels: { C1: { allow: true, requireMention: false } },
|
||||
},
|
||||
},
|
||||
};
|
||||
setOpenChannelDirectMessages({
|
||||
includeAckReactionConfig: true,
|
||||
groupPolicy: "open",
|
||||
replyToMode: "off",
|
||||
});
|
||||
await runChannelThreadReplyEvent();
|
||||
|
||||
expect(sendMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMock.mock.calls[0][2]).toMatchObject({ threadTs: "111.222" });
|
||||
expectSingleSendWithThread("111.222");
|
||||
});
|
||||
|
||||
it("ignores replyToId directive when replyToMode is off", async () => {
|
||||
@@ -497,8 +501,7 @@ describe("monitorSlackProvider tool results", () => {
|
||||
}),
|
||||
});
|
||||
|
||||
expect(sendMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMock.mock.calls[0][2]).toMatchObject({ threadTs: undefined });
|
||||
expectSingleSendWithThread(undefined);
|
||||
});
|
||||
|
||||
it("keeps replyToId directive threading when replyToMode is all", async () => {
|
||||
@@ -511,8 +514,7 @@ describe("monitorSlackProvider tool results", () => {
|
||||
}),
|
||||
});
|
||||
|
||||
expect(sendMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMock.mock.calls[0][2]).toMatchObject({ threadTs: "555" });
|
||||
expectSingleSendWithThread("555");
|
||||
});
|
||||
|
||||
it("reacts to mention-gated room messages when ackReaction is enabled", async () => {
|
||||
@@ -581,8 +583,7 @@ describe("monitorSlackProvider tool results", () => {
|
||||
setDirectMessageReplyMode("all");
|
||||
await runDirectMessageEvent("123");
|
||||
|
||||
expect(sendMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMock.mock.calls[0][2]).toMatchObject({ threadTs: "123" });
|
||||
expectSingleSendWithThread("123");
|
||||
});
|
||||
|
||||
it("treats parent_user_id as a thread reply even when thread_ts matches ts", async () => {
|
||||
@@ -596,27 +597,14 @@ describe("monitorSlackProvider tool results", () => {
|
||||
});
|
||||
|
||||
expect(replyMock).toHaveBeenCalledTimes(1);
|
||||
const ctx = replyMock.mock.calls[0]?.[0] as {
|
||||
SessionKey?: string;
|
||||
ParentSessionKey?: string;
|
||||
};
|
||||
const ctx = getFirstReplySessionCtx();
|
||||
expect(ctx.SessionKey).toBe("agent:main:main:thread:123");
|
||||
expect(ctx.ParentSessionKey).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps thread parent inheritance opt-in", async () => {
|
||||
replyMock.mockResolvedValue({ text: "thread reply" });
|
||||
|
||||
slackTestState.config = {
|
||||
messages: { responsePrefix: "PFX" },
|
||||
channels: {
|
||||
slack: {
|
||||
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
|
||||
channels: { C1: { allow: true, requireMention: false } },
|
||||
thread: { inheritParent: true },
|
||||
},
|
||||
},
|
||||
};
|
||||
setOpenChannelDirectMessages({ threadInheritParent: true });
|
||||
|
||||
await runSlackMessageOnce(monitorSlackProvider, {
|
||||
event: makeSlackMessageEvent({
|
||||
@@ -626,10 +614,7 @@ describe("monitorSlackProvider tool results", () => {
|
||||
});
|
||||
|
||||
expect(replyMock).toHaveBeenCalledTimes(1);
|
||||
const ctx = replyMock.mock.calls[0]?.[0] as {
|
||||
SessionKey?: string;
|
||||
ParentSessionKey?: string;
|
||||
};
|
||||
const ctx = getFirstReplySessionCtx();
|
||||
expect(ctx.SessionKey).toBe("agent:main:slack:channel:c1:thread:111.222");
|
||||
expect(ctx.ParentSessionKey).toBe("agent:main:slack:channel:c1");
|
||||
});
|
||||
@@ -649,25 +634,12 @@ describe("monitorSlackProvider tool results", () => {
|
||||
});
|
||||
}
|
||||
|
||||
slackTestState.config = {
|
||||
messages: { responsePrefix: "PFX" },
|
||||
channels: {
|
||||
slack: {
|
||||
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
|
||||
channels: { C1: { allow: true, requireMention: false } },
|
||||
},
|
||||
},
|
||||
};
|
||||
setOpenChannelDirectMessages();
|
||||
|
||||
await runChannelThreadReplyEvent();
|
||||
|
||||
expect(replyMock).toHaveBeenCalledTimes(1);
|
||||
const ctx = replyMock.mock.calls[0]?.[0] as {
|
||||
SessionKey?: string;
|
||||
ParentSessionKey?: string;
|
||||
ThreadStarterBody?: string;
|
||||
ThreadLabel?: string;
|
||||
};
|
||||
const ctx = getFirstReplySessionCtx();
|
||||
expect(ctx.SessionKey).toBe("agent:main:slack:channel:c1:thread:111.222");
|
||||
expect(ctx.ParentSessionKey).toBeUndefined();
|
||||
expect(ctx.ThreadStarterBody).toContain("starter message");
|
||||
@@ -676,16 +648,9 @@ describe("monitorSlackProvider tool results", () => {
|
||||
|
||||
it("scopes thread session keys to the routed agent", async () => {
|
||||
replyMock.mockResolvedValue({ text: "ok" });
|
||||
slackTestState.config = {
|
||||
messages: { responsePrefix: "PFX" },
|
||||
channels: {
|
||||
slack: {
|
||||
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
|
||||
channels: { C1: { allow: true, requireMention: false } },
|
||||
},
|
||||
},
|
||||
setOpenChannelDirectMessages({
|
||||
bindings: [{ agentId: "support", match: { channel: "slack", teamId: "T1" } }],
|
||||
};
|
||||
});
|
||||
|
||||
const client = getSlackClient();
|
||||
if (client?.auth?.test) {
|
||||
@@ -703,10 +668,7 @@ describe("monitorSlackProvider tool results", () => {
|
||||
await runChannelThreadReplyEvent();
|
||||
|
||||
expect(replyMock).toHaveBeenCalledTimes(1);
|
||||
const ctx = replyMock.mock.calls[0]?.[0] as {
|
||||
SessionKey?: string;
|
||||
ParentSessionKey?: string;
|
||||
};
|
||||
const ctx = getFirstReplySessionCtx();
|
||||
expect(ctx.SessionKey).toBe("agent:support:slack:channel:c1:thread:111.222");
|
||||
expect(ctx.ParentSessionKey).toBeUndefined();
|
||||
});
|
||||
@@ -716,8 +678,7 @@ describe("monitorSlackProvider tool results", () => {
|
||||
setDirectMessageReplyMode("off");
|
||||
await runDirectMessageEvent("789");
|
||||
|
||||
expect(sendMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMock.mock.calls[0][2]).toMatchObject({ threadTs: undefined });
|
||||
expectSingleSendWithThread(undefined);
|
||||
});
|
||||
|
||||
it("threads first reply when replyToMode is first and message is not threaded", async () => {
|
||||
@@ -725,8 +686,6 @@ describe("monitorSlackProvider tool results", () => {
|
||||
setDirectMessageReplyMode("first");
|
||||
await runDirectMessageEvent("789");
|
||||
|
||||
expect(sendMock).toHaveBeenCalledTimes(1);
|
||||
// First reply starts a thread under the incoming message
|
||||
expect(sendMock.mock.calls[0][2]).toMatchObject({ threadTs: "789" });
|
||||
expectSingleSendWithThread("789");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user