mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-18 12:41:12 +00:00
211 lines
8.2 KiB
TypeScript
211 lines
8.2 KiB
TypeScript
import { beforeEach, describe, expect, it } from "vitest";
|
|
import type { PluginRuntime } from "../runtime-api.js";
|
|
import {
|
|
buildMSTeamsAttachmentPlaceholder,
|
|
buildMSTeamsGraphMessageUrls,
|
|
buildMSTeamsMediaPayload,
|
|
} from "./attachments.js";
|
|
import { setMSTeamsRuntime } from "./runtime.js";
|
|
|
|
const _GRAPH_HOST = "graph.microsoft.com";
|
|
const SHAREPOINT_HOST = "contoso.sharepoint.com";
|
|
const TEST_HOST = "x";
|
|
const createUrlForHost = (host: string, pathSegment: string) => `https://${host}/${pathSegment}`;
|
|
const createTestUrl = (pathSegment: string) => createUrlForHost(TEST_HOST, pathSegment);
|
|
const TEST_URL_IMAGE = createTestUrl("img");
|
|
const TEST_URL_IMAGE_PNG = createTestUrl("img.png");
|
|
const TEST_URL_IMAGE_1_PNG = createTestUrl("1.png");
|
|
const TEST_URL_IMAGE_2_JPG = createTestUrl("2.jpg");
|
|
const TEST_URL_PDF = createTestUrl("x.pdf");
|
|
const TEST_URL_PDF_1 = createTestUrl("1.pdf");
|
|
const TEST_URL_PDF_2 = createTestUrl("2.pdf");
|
|
const TEST_URL_HTML_A = createTestUrl("a.png");
|
|
const TEST_URL_HTML_B = createTestUrl("b.png");
|
|
const CONTENT_TYPE_IMAGE_PNG = "image/png";
|
|
const CONTENT_TYPE_APPLICATION_PDF = "application/pdf";
|
|
const CONTENT_TYPE_TEXT_HTML = "text/html";
|
|
const CONTENT_TYPE_TEAMS_FILE_DOWNLOAD_INFO = "application/vnd.microsoft.teams.file.download.info";
|
|
type AttachmentPlaceholderInput = Parameters<typeof buildMSTeamsAttachmentPlaceholder>[0];
|
|
type GraphMessageUrlParams = Parameters<typeof buildMSTeamsGraphMessageUrls>[0];
|
|
type MSTeamsMediaPayload = ReturnType<typeof buildMSTeamsMediaPayload>;
|
|
|
|
const runtimeStub = {
|
|
channel: {
|
|
text: {
|
|
chunkText: (text: string) => (text ? [text] : []),
|
|
},
|
|
},
|
|
} as unknown as PluginRuntime;
|
|
const MEDIA_PLACEHOLDER_IMAGE = "<media:image>";
|
|
const MEDIA_PLACEHOLDER_DOCUMENT = "<media:document>";
|
|
const formatImagePlaceholder = (count: number) =>
|
|
count > 1 ? `${MEDIA_PLACEHOLDER_IMAGE} (${count} images)` : MEDIA_PLACEHOLDER_IMAGE;
|
|
const formatDocumentPlaceholder = (count: number) =>
|
|
count > 1 ? `${MEDIA_PLACEHOLDER_DOCUMENT} (${count} files)` : MEDIA_PLACEHOLDER_DOCUMENT;
|
|
const withLabel = <T extends object>(label: string, fields: T): T & { label: string } => ({
|
|
label,
|
|
...fields,
|
|
});
|
|
const buildAttachment = <T extends Record<string, unknown>>(contentType: string, props: T) => ({
|
|
contentType,
|
|
...props,
|
|
});
|
|
const createHtmlAttachment = (content: string) =>
|
|
buildAttachment(CONTENT_TYPE_TEXT_HTML, { content });
|
|
const buildHtmlImageTag = (src: string) => `<img src="${src}" />`;
|
|
const createHtmlImageAttachments = (sources: string[], prefix = "") => [
|
|
createHtmlAttachment(`${prefix}${sources.map(buildHtmlImageTag).join("")}`),
|
|
];
|
|
const createContentUrlAttachments = (contentType: string, ...contentUrls: string[]) =>
|
|
contentUrls.map((contentUrl) => buildAttachment(contentType, { contentUrl }));
|
|
const createImageAttachments = (...contentUrls: string[]) =>
|
|
createContentUrlAttachments(CONTENT_TYPE_IMAGE_PNG, ...contentUrls);
|
|
const createPdfAttachments = (...contentUrls: string[]) =>
|
|
createContentUrlAttachments(CONTENT_TYPE_APPLICATION_PDF, ...contentUrls);
|
|
const createTeamsFileDownloadInfoAttachments = (
|
|
downloadUrl = createTestUrl("dl"),
|
|
fileType = "png",
|
|
) => [
|
|
buildAttachment(CONTENT_TYPE_TEAMS_FILE_DOWNLOAD_INFO, {
|
|
content: { downloadUrl, fileType },
|
|
}),
|
|
];
|
|
const createMediaEntriesWithType = (contentType: string, ...paths: string[]) =>
|
|
paths.map((path) => ({ path, contentType }));
|
|
const createImageMediaEntries = (...paths: string[]) =>
|
|
createMediaEntriesWithType(CONTENT_TYPE_IMAGE_PNG, ...paths);
|
|
const DEFAULT_CHANNEL_TEAM_ID = "team-id";
|
|
const DEFAULT_CHANNEL_ID = "chan-id";
|
|
const createChannelGraphMessageUrlParams = (params: {
|
|
messageId: string;
|
|
replyToId?: string;
|
|
conversationId?: string;
|
|
}) => ({
|
|
conversationType: "channel" as const,
|
|
...params,
|
|
channelData: {
|
|
team: { id: DEFAULT_CHANNEL_TEAM_ID },
|
|
channel: { id: DEFAULT_CHANNEL_ID },
|
|
},
|
|
});
|
|
const buildExpectedChannelMessagePath = (params: { messageId: string; replyToId?: string }) =>
|
|
params.replyToId
|
|
? `/teams/${DEFAULT_CHANNEL_TEAM_ID}/channels/${DEFAULT_CHANNEL_ID}/messages/${params.replyToId}/replies/${params.messageId}`
|
|
: `/teams/${DEFAULT_CHANNEL_TEAM_ID}/channels/${DEFAULT_CHANNEL_ID}/messages/${params.messageId}`;
|
|
|
|
const expectMSTeamsMediaPayload = (
|
|
payload: MSTeamsMediaPayload,
|
|
expected: { firstPath: string; paths: string[]; types: string[] },
|
|
) => {
|
|
expect(payload.MediaPath).toBe(expected.firstPath);
|
|
expect(payload.MediaUrl).toBe(expected.firstPath);
|
|
expect(payload.MediaPaths).toEqual(expected.paths);
|
|
expect(payload.MediaUrls).toEqual(expected.paths);
|
|
expect(payload.MediaTypes).toEqual(expected.types);
|
|
};
|
|
|
|
const ATTACHMENT_PLACEHOLDER_CASES = [
|
|
withLabel("returns empty string when no attachments", {
|
|
attachments: undefined as AttachmentPlaceholderInput,
|
|
expected: "",
|
|
}),
|
|
withLabel("returns empty string when attachments are empty", {
|
|
attachments: [],
|
|
expected: "",
|
|
}),
|
|
withLabel("returns image placeholder for one image attachment", {
|
|
attachments: createImageAttachments(TEST_URL_IMAGE_PNG),
|
|
expected: formatImagePlaceholder(1),
|
|
}),
|
|
withLabel("returns image placeholder with count for many image attachments", {
|
|
attachments: [
|
|
...createImageAttachments(TEST_URL_IMAGE_1_PNG),
|
|
{ contentType: "image/jpeg", contentUrl: TEST_URL_IMAGE_2_JPG },
|
|
],
|
|
expected: formatImagePlaceholder(2),
|
|
}),
|
|
withLabel("treats Teams file.download.info image attachments as images", {
|
|
attachments: createTeamsFileDownloadInfoAttachments(),
|
|
expected: formatImagePlaceholder(1),
|
|
}),
|
|
withLabel("returns document placeholder for non-image attachments", {
|
|
attachments: createPdfAttachments(TEST_URL_PDF),
|
|
expected: formatDocumentPlaceholder(1),
|
|
}),
|
|
withLabel("returns document placeholder with count for many non-image attachments", {
|
|
attachments: createPdfAttachments(TEST_URL_PDF_1, TEST_URL_PDF_2),
|
|
expected: formatDocumentPlaceholder(2),
|
|
}),
|
|
withLabel("counts one inline image in html attachments", {
|
|
attachments: createHtmlImageAttachments([TEST_URL_HTML_A], "<p>hi</p>"),
|
|
expected: formatImagePlaceholder(1),
|
|
}),
|
|
withLabel("counts many inline images in html attachments", {
|
|
attachments: createHtmlImageAttachments([TEST_URL_HTML_A, TEST_URL_HTML_B]),
|
|
expected: formatImagePlaceholder(2),
|
|
}),
|
|
];
|
|
|
|
const GRAPH_URL_EXPECTATION_CASES = [
|
|
withLabel("builds channel message urls", {
|
|
params: createChannelGraphMessageUrlParams({
|
|
conversationId: "19:thread@thread.tacv2",
|
|
messageId: "123",
|
|
}),
|
|
expectedPath: buildExpectedChannelMessagePath({ messageId: "123" }),
|
|
}),
|
|
withLabel("builds channel reply urls when replyToId is present", {
|
|
params: createChannelGraphMessageUrlParams({
|
|
messageId: "reply-id",
|
|
replyToId: "root-id",
|
|
}),
|
|
expectedPath: buildExpectedChannelMessagePath({
|
|
messageId: "reply-id",
|
|
replyToId: "root-id",
|
|
}),
|
|
}),
|
|
withLabel("builds chat message urls", {
|
|
params: {
|
|
conversationType: "groupChat" as const,
|
|
conversationId: "19:chat@thread.v2",
|
|
messageId: "456",
|
|
} satisfies GraphMessageUrlParams,
|
|
expectedPath: "/chats/19%3Achat%40thread.v2/messages/456",
|
|
}),
|
|
];
|
|
|
|
describe("msteams attachment helpers", () => {
|
|
beforeEach(() => {
|
|
setMSTeamsRuntime(runtimeStub);
|
|
});
|
|
|
|
describe("buildMSTeamsAttachmentPlaceholder", () => {
|
|
it.each(ATTACHMENT_PLACEHOLDER_CASES)("$label", ({ attachments, expected }) => {
|
|
expect(buildMSTeamsAttachmentPlaceholder(attachments)).toBe(expected);
|
|
});
|
|
});
|
|
|
|
describe("buildMSTeamsGraphMessageUrls", () => {
|
|
it.each(GRAPH_URL_EXPECTATION_CASES)("$label", ({ params, expectedPath }) => {
|
|
const urls = buildMSTeamsGraphMessageUrls(params);
|
|
expect(urls[0]).toContain(expectedPath);
|
|
});
|
|
});
|
|
|
|
describe("buildMSTeamsMediaPayload", () => {
|
|
it("returns single and multi-file fields", () => {
|
|
const payload = buildMSTeamsMediaPayload(createImageMediaEntries("/tmp/a.png", "/tmp/b.png"));
|
|
expectMSTeamsMediaPayload(payload, {
|
|
firstPath: "/tmp/a.png",
|
|
paths: ["/tmp/a.png", "/tmp/b.png"],
|
|
types: [CONTENT_TYPE_IMAGE_PNG, CONTENT_TYPE_IMAGE_PNG],
|
|
});
|
|
});
|
|
});
|
|
|
|
it("retains the expected sharepoint host fixture", () => {
|
|
expect(SHAREPOINT_HOST).toBe("contoso.sharepoint.com");
|
|
expect(TEST_URL_IMAGE).toContain(TEST_HOST);
|
|
});
|
|
});
|