mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-06 04:21:33 +00:00
* fix(slack): preserve rendered presentations across cloning * test(slack): cover serialized presentation provenance * test(slack): prove restart fallback for rendered payloads - Reload the Slack adapter to simulate a runtime restart. - Verify stale rendered provenance falls back to text without blocks. * fix(slack): fail closed on invalid rendered metadata --------- Co-authored-by: Pavonis <pavonis@martian.engineering> Co-authored-by: Peter Steinberger <steipete@gmail.com>
101 lines
3.5 KiB
TypeScript
101 lines
3.5 KiB
TypeScript
// Covers Slack question delivery capture and Block Kit final edit.
|
|
import { describe, expect, it, vi } from "vitest";
|
|
|
|
const hoisted = vi.hoisted(() => ({
|
|
update: vi.fn(),
|
|
registration: undefined as
|
|
| { finalize: (statusLine: string) => void | Promise<void>; deliveryId: string }
|
|
| undefined,
|
|
}));
|
|
vi.mock("openclaw/plugin-sdk/question-gateway-runtime", async (importOriginal) => {
|
|
const original =
|
|
await importOriginal<typeof import("openclaw/plugin-sdk/question-gateway-runtime")>();
|
|
return {
|
|
...original,
|
|
questionGatewayRuntime: {
|
|
...original.questionGatewayRuntime,
|
|
registerChannelDelivery: (registration: typeof hoisted.registration) => {
|
|
hoisted.registration = registration;
|
|
},
|
|
},
|
|
};
|
|
});
|
|
vi.mock("./send.js", () => ({ updateMessageSlack: hoisted.update }));
|
|
|
|
import { slackOutbound } from "./outbound-adapter.js";
|
|
|
|
function jsonRoundTrip<T>(value: T): T {
|
|
// oxlint-disable-next-line unicorn/prefer-structured-clone -- This test exercises JSON transport.
|
|
return JSON.parse(JSON.stringify(value)) as T;
|
|
}
|
|
|
|
describe("Slack question finalization", () => {
|
|
it("removes action blocks and appends terminal context", async () => {
|
|
const questionId = "ask_0123456789abcdef0123456789abcdef";
|
|
const headers = Array.from({ length: 21 }, (_value, index) => `Column ${String(index)}`);
|
|
const payload = {
|
|
channelData: { askUser: { questionId } },
|
|
presentation: {
|
|
blocks: [
|
|
{
|
|
type: "table" as const,
|
|
caption: "Option metadata",
|
|
headers,
|
|
rows: [headers.map((_header, index) => `Value ${String(index)}`)],
|
|
},
|
|
{ type: "text" as const, text: "Pick one" },
|
|
{
|
|
type: "buttons" as const,
|
|
buttons: ["One", "Two"].map((label) => ({
|
|
label,
|
|
action: { type: "question" as const, questionId, optionValue: label },
|
|
})),
|
|
},
|
|
],
|
|
},
|
|
};
|
|
const rendered = await slackOutbound.renderPresentation?.({
|
|
payload,
|
|
presentation: payload.presentation,
|
|
ctx: { cfg: {}, to: "C123", text: "Pick one", payload },
|
|
});
|
|
expect(rendered).not.toBeNull();
|
|
const renderedAfterTransport = jsonRoundTrip(rendered);
|
|
const renderedSegments = (
|
|
renderedAfterTransport?.channelData?.slack as
|
|
| { renderedPresentationSegments?: unknown[] }
|
|
| undefined
|
|
)?.renderedPresentationSegments;
|
|
expect(renderedSegments?.map((segment) => (segment as { kind?: unknown }).kind)).toEqual([
|
|
"text",
|
|
"blocks",
|
|
]);
|
|
await slackOutbound.afterDeliverPayload?.({
|
|
cfg: {},
|
|
target: { channel: "slack", to: "C123", accountId: "default" },
|
|
payload: renderedAfterTransport!,
|
|
results: [
|
|
{ channel: "slack", messageId: "44", channelId: "C123" },
|
|
{ channel: "slack", messageId: "55", channelId: "C123" },
|
|
],
|
|
});
|
|
|
|
await hoisted.registration?.finalize("Answered: <!channel>");
|
|
expect(hoisted.update).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
channelId: "C123",
|
|
messageTs: "55",
|
|
text: expect.stringContaining("Answered: <!channel>"),
|
|
blocks: expect.arrayContaining([
|
|
{
|
|
type: "context",
|
|
elements: [{ type: "mrkdwn", text: "Answered: <!channel>" }],
|
|
},
|
|
]),
|
|
}),
|
|
);
|
|
const blocks = hoisted.update.mock.calls[0]?.[0]?.blocks as Array<{ type?: string }>;
|
|
expect(blocks.some((block) => block.type === "actions")).toBe(false);
|
|
});
|
|
});
|