test(ci): fix outbound media loader seams

This commit is contained in:
Vincent Koc
2026-04-01 01:17:08 +09:00
parent 8a563d603b
commit 6f74a572d9
2 changed files with 38 additions and 17 deletions

View File

@@ -11,6 +11,7 @@ const resolveGoogleChatAccountMock = vi.hoisted(() => vi.fn());
const resolveGoogleChatOutboundSpaceMock = vi.hoisted(() => vi.fn());
const loadWebMediaMock = vi.hoisted(() => vi.fn());
const fetchRemoteMediaMock = vi.hoisted(() => vi.fn());
const loadOutboundMediaFromUrlMock = vi.hoisted(() => vi.fn());
vi.mock("./api.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./api.js")>();
@@ -41,6 +42,8 @@ vi.mock("../runtime-api.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../runtime-api.js")>();
return {
...actual,
loadOutboundMediaFromUrl: (...args: Parameters<typeof actual.loadOutboundMediaFromUrl>) =>
loadOutboundMediaFromUrlMock(...args),
loadWebMedia: (...args: Parameters<typeof actual.loadWebMedia>) => loadWebMediaMock(...args),
fetchRemoteMedia: (...args: Parameters<typeof actual.fetchRemoteMedia>) =>
fetchRemoteMediaMock(...args),
@@ -63,6 +66,7 @@ afterEach(() => {
resolveGoogleChatOutboundSpaceMock.mockImplementation(
targetsActual.resolveGoogleChatOutboundSpace,
);
loadOutboundMediaFromUrlMock.mockImplementation(runtimeApiActual.loadOutboundMediaFromUrl);
loadWebMediaMock.mockImplementation(runtimeApiActual.loadWebMedia);
fetchRemoteMediaMock.mockImplementation(runtimeApiActual.fetchRemoteMedia);
});
@@ -84,6 +88,11 @@ function createGoogleChatCfg(): OpenClawConfig {
}
function setupRuntimeMediaMocks(params: { loadFileName: string; loadBytes: string }) {
const loadOutboundMediaFromUrl = vi.fn(async () => ({
buffer: Buffer.from(params.loadBytes),
fileName: params.loadFileName,
contentType: "image/png",
}));
const loadWebMedia = vi.fn(async () => ({
buffer: Buffer.from(params.loadBytes),
fileName: params.loadFileName,
@@ -95,10 +104,11 @@ function setupRuntimeMediaMocks(params: { loadFileName: string; loadBytes: strin
contentType: "image/png",
}));
loadOutboundMediaFromUrlMock.mockImplementation(loadOutboundMediaFromUrl);
loadWebMediaMock.mockImplementation(loadWebMedia);
fetchRemoteMediaMock.mockImplementation(fetchRemoteMedia);
return { loadWebMedia, fetchRemoteMedia };
return { loadOutboundMediaFromUrl, loadWebMedia, fetchRemoteMedia };
}
describe("googlechatPlugin outbound sendMedia", () => {
@@ -112,7 +122,7 @@ describe("googlechatPlugin outbound sendMedia", () => {
});
it("loads local media with mediaLocalRoots via runtime media loader", async () => {
const { loadWebMedia, fetchRemoteMedia } = setupRuntimeMediaMocks({
const { loadOutboundMediaFromUrl, fetchRemoteMedia } = setupRuntimeMediaMocks({
loadFileName: "image.png",
loadBytes: "image-bytes",
});
@@ -135,10 +145,10 @@ describe("googlechatPlugin outbound sendMedia", () => {
accountId: "default",
});
expect(loadWebMedia).toHaveBeenCalledWith(
expect(loadOutboundMediaFromUrl).toHaveBeenCalledWith(
"/tmp/workspace/image.png",
expect.objectContaining({
localRoots: ["/tmp/workspace"],
mediaLocalRoots: ["/tmp/workspace"],
}),
);
expect(fetchRemoteMedia).not.toHaveBeenCalled();
@@ -163,7 +173,7 @@ describe("googlechatPlugin outbound sendMedia", () => {
});
it("keeps remote URL media fetch on fetchRemoteMedia with maxBytes cap", async () => {
const { loadWebMedia, fetchRemoteMedia } = setupRuntimeMediaMocks({
const { loadOutboundMediaFromUrl, fetchRemoteMedia } = setupRuntimeMediaMocks({
loadFileName: "unused.png",
loadBytes: "should-not-be-used",
});
@@ -191,7 +201,7 @@ describe("googlechatPlugin outbound sendMedia", () => {
maxBytes: 20 * 1024 * 1024,
}),
);
expect(loadWebMedia).not.toHaveBeenCalled();
expect(loadOutboundMediaFromUrl).not.toHaveBeenCalled();
expect(uploadGoogleChatAttachmentMock).toHaveBeenCalledWith(
expect.objectContaining({
space: "spaces/AAA",
@@ -440,7 +450,7 @@ describe("googlechatPlugin outbound cfg threading", () => {
});
it("sends media without requiring Google Chat runtime initialization", async () => {
const { loadWebMedia } = setupRuntimeMediaMocks({
const { loadOutboundMediaFromUrl } = setupRuntimeMediaMocks({
loadFileName: "image.png",
loadBytes: "image-bytes",
});
@@ -469,10 +479,10 @@ describe("googlechatPlugin outbound cfg threading", () => {
chatId: "spaces/AAA",
});
expect(loadWebMedia).toHaveBeenCalledWith(
expect(loadOutboundMediaFromUrl).toHaveBeenCalledWith(
"/tmp/workspace/image.png",
expect.objectContaining({
localRoots: ["/tmp/workspace"],
mediaLocalRoots: ["/tmp/workspace"],
}),
);
});

View File

@@ -4,6 +4,14 @@ import { installSlackBlockTestMocks } from "./blocks.test-helpers.js";
// --- Module mocks (must precede dynamic import) ---
installSlackBlockTestMocks();
const loadOutboundMediaFromUrlMock = vi.hoisted(() =>
vi.fn(async (_mediaUrl: string, _options?: unknown) => ({
buffer: Buffer.from("fake-image"),
contentType: "image/png",
kind: "image",
fileName: "screenshot.png",
})),
);
const fetchWithSsrFGuard = vi.fn(
async (params: { url: string; init?: RequestInit }) =>
({
@@ -22,14 +30,16 @@ vi.mock("../../../src/infra/net/fetch-guard.js", () => ({
}),
}));
vi.mock("openclaw/plugin-sdk/web-media", () => ({
loadWebMedia: vi.fn(async () => ({
buffer: Buffer.from("fake-image"),
contentType: "image/png",
kind: "image",
fileName: "screenshot.png",
})),
}));
vi.mock("./runtime-api.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./runtime-api.js")>();
const mockedLoadOutboundMediaFromUrl =
loadOutboundMediaFromUrlMock as unknown as typeof actual.loadOutboundMediaFromUrl;
return {
...actual,
loadOutboundMediaFromUrl: (...args: Parameters<typeof actual.loadOutboundMediaFromUrl>) =>
mockedLoadOutboundMediaFromUrl(...args),
};
});
let sendMessageSlack: typeof import("./send.js").sendMessageSlack;
let clearSlackDmChannelCache: typeof import("./send.js").clearSlackDmChannelCache;
@@ -71,6 +81,7 @@ describe("sendMessageSlack file upload with user IDs", () => {
async () => new Response("ok", { status: 200 }),
) as unknown as typeof fetch;
fetchWithSsrFGuard.mockClear();
loadOutboundMediaFromUrlMock.mockClear();
clearSlackDmChannelCache();
});