Files
openclaw/extensions/mattermost/src/channel.send-loopback.test.ts

166 lines
5.2 KiB
TypeScript

// Mattermost tests cover the action-to-REST send path over loopback.
import { createPluginRuntimeMock } from "openclaw/plugin-sdk/plugin-test-runtime";
import { withServer } from "openclaw/plugin-sdk/test-env";
import { describe, expect, it, vi } from "vitest";
import { mattermostPlugin } from "./channel.js";
import type { OpenClawConfig } from "./runtime-api.js";
import { setMattermostRuntime } from "./runtime.js";
const CHANNEL_ID = "aaaaaaaaaaaaaaaaaaaaaaaaaa";
const loadOutboundMediaFromUrl = vi.hoisted(() => vi.fn());
vi.mock("./mattermost/runtime-api.js", async () => ({
...(await vi.importActual<typeof import("./mattermost/runtime-api.js")>(
"./mattermost/runtime-api.js",
)),
loadOutboundMediaFromUrl,
}));
describe("Mattermost send action loopback", () => {
it("sends text with blank attachment placeholders and rejects nonblank payloads", async () => {
const requests: Array<{ path: string; body: unknown }> = [];
await withServer(
(request, response) => {
let body = "";
request.setEncoding("utf8");
request.on("data", (chunk) => {
body += chunk;
});
request.on("end", () => {
requests.push({
path: request.url ?? "",
body: JSON.parse(body) as unknown,
});
response.writeHead(201, { "content-type": "application/json" });
response.end(JSON.stringify({ id: "post-loopback", channel_id: CHANNEL_ID }));
});
},
async (baseUrl) => {
setMattermostRuntime(createPluginRuntimeMock());
const cfg = {
channels: {
mattermost: {
botToken: ["loopback", "fixture"].join("-"),
baseUrl,
network: { dangerouslyAllowPrivateNetwork: true },
},
},
} as OpenClawConfig;
const handleAction = mattermostPlugin.actions?.handleAction;
if (!handleAction) {
throw new Error("Mattermost send action missing");
}
const result = await handleAction({
channel: "mattermost",
action: "send",
params: {
to: `channel:${CHANNEL_ID}`,
message: "loopback proof",
buffer: "",
base64: " ",
},
cfg,
accountId: "default",
});
expect(result.content).toEqual([
{
type: "text",
text: JSON.stringify({
ok: true,
channel: "mattermost",
messageId: "post-loopback",
channelId: CHANNEL_ID,
}),
},
]);
expect(requests).toEqual([
{
path: "/api/v4/posts",
body: { channel_id: CHANNEL_ID, message: "loopback proof" },
},
]);
await expect(
handleAction({
channel: "mattermost",
action: "send",
params: {
to: `channel:${CHANNEL_ID}`,
message: "must not send",
base64: "cmVwb3J0",
},
cfg,
accountId: "default",
}),
).rejects.toThrow("buffer/base64 payloads are not supported");
expect(requests).toHaveLength(1);
},
);
});
it("infers a MIME extension for unnamed uploads", async () => {
const uploads: string[] = [];
await withServer(
(request, response) => {
let body = "";
request.setEncoding("utf8");
request.on("data", (chunk) => {
body += chunk;
});
request.on("end", () => {
if (request.url === "/api/v4/files") {
uploads.push(body);
response.writeHead(201, { "content-type": "application/json" });
response.end(JSON.stringify({ file_infos: [{ id: `file-${uploads.length}` }] }));
return;
}
response.writeHead(201, { "content-type": "application/json" });
response.end(JSON.stringify({ id: "post-loopback", channel_id: CHANNEL_ID }));
});
},
async (baseUrl) => {
setMattermostRuntime(createPluginRuntimeMock());
loadOutboundMediaFromUrl.mockReset();
loadOutboundMediaFromUrl.mockResolvedValueOnce({
buffer: Buffer.from("unnamed-image"),
contentType: "image/png",
kind: "image",
});
const cfg = {
channels: {
mattermost: {
botToken: "loopback-fixture",
baseUrl,
network: { dangerouslyAllowPrivateNetwork: true },
},
},
} as OpenClawConfig;
const handleAction = mattermostPlugin.actions?.handleAction;
if (!handleAction) {
throw new Error("Mattermost send action missing");
}
await handleAction({
channel: "mattermost",
action: "send",
params: {
to: `channel:${CHANNEL_ID}`,
message: "loopback media proof",
mediaUrl: "https://media.example.test/unnamed",
},
cfg,
accountId: "default",
});
},
);
expect(uploads).toHaveLength(1);
expect(uploads[0]).toContain('filename="upload.png"');
expect(uploads[0]).toContain("Content-Type: image/png");
});
});