test: share bluebubbles media fixtures

This commit is contained in:
Peter Steinberger
2026-04-20 21:50:34 +01:00
parent 51da1f70fa
commit 5272a94a19
3 changed files with 70 additions and 74 deletions

View File

@@ -6,7 +6,6 @@ import {
sendBlueBubblesAttachment,
} from "./attachments.js";
import { fetchBlueBubblesServerInfo, getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
import type { PluginRuntime } from "./runtime-api.js";
import { setBlueBubblesRuntime } from "./runtime.js";
import {
BLUE_BUBBLES_PRIVATE_API_STATUS,
@@ -14,53 +13,27 @@ import {
mockBlueBubblesPrivateApiStatus,
mockBlueBubblesPrivateApiStatusOnce,
} from "./test-harness.js";
import {
createBlueBubblesFetchRemoteMediaMock,
createBlueBubblesRuntimeStub,
} from "./test-helpers.js";
import type { BlueBubblesAttachment } from "./types.js";
const mockFetch = vi.fn();
const fetchServerInfoMock = vi.mocked(fetchBlueBubblesServerInfo);
const fetchRemoteMediaMock = vi.fn(
async (params: {
url: string;
maxBytes?: number;
fetchImpl?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
}) => {
const fetchFn = params.fetchImpl ?? fetch;
const res = await fetchFn(params.url);
if (!res.ok) {
const text = await res.text().catch(() => "unknown");
throw new Error(
`Failed to fetch media from ${params.url}: HTTP ${res.status}; body: ${text}`,
);
}
const buffer = Buffer.from(await res.arrayBuffer());
if (typeof params.maxBytes === "number" && buffer.byteLength > params.maxBytes) {
const error = new Error(`payload exceeds maxBytes ${params.maxBytes}`) as Error & {
code?: string;
};
error.code = "max_bytes";
throw error;
}
return {
buffer,
contentType: res.headers.get("content-type") ?? undefined,
fileName: undefined,
};
const fetchRemoteMediaMock = createBlueBubblesFetchRemoteMediaMock({
createHttpError: async ({ response, url }) => {
const text = await response.text().catch(() => "unknown");
return new Error(`Failed to fetch media from ${url}: HTTP ${response.status}; body: ${text}`);
},
);
});
installBlueBubblesFetchTestHooks({
mockFetch,
privateApiStatusMock: vi.mocked(getCachedBlueBubblesPrivateApiStatus),
});
const runtimeStub = {
channel: {
media: {
fetchRemoteMedia:
fetchRemoteMediaMock as unknown as PluginRuntime["channel"]["media"]["fetchRemoteMedia"],
},
},
} as unknown as PluginRuntime;
const runtimeStub = createBlueBubblesRuntimeStub(fetchRemoteMediaMock);
describe("downloadBlueBubblesAttachment", () => {
beforeEach(() => {

View File

@@ -1,4 +1,3 @@
import type { SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import "./test-mocks.js";
import {
@@ -11,12 +10,15 @@ import {
resolveBlueBubblesClientSsrfPolicy,
} from "./client.js";
import { getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
import type { PluginRuntime } from "./runtime-api.js";
import { setBlueBubblesRuntime } from "./runtime.js";
import {
createBlueBubblesFetchGuardPassthroughInstaller,
installBlueBubblesFetchTestHooks,
} from "./test-harness.js";
import {
createBlueBubblesFetchRemoteMediaMock,
createBlueBubblesRuntimeStub,
} from "./test-helpers.js";
import type { BlueBubblesAttachment } from "./types.js";
import { _setFetchGuardForTesting } from "./types.js";
@@ -24,47 +26,16 @@ import { _setFetchGuardForTesting } from "./types.js";
const mockFetch = vi.fn();
const fetchRemoteMediaMock = vi.fn(
async (params: {
url: string;
maxBytes?: number;
ssrfPolicy?: SsrFPolicy;
fetchImpl?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
}) => {
const fetchFn = params.fetchImpl ?? fetch;
const res = await fetchFn(params.url);
if (!res.ok) {
throw new Error(`media fetch failed: HTTP ${res.status}`);
}
const buffer = Buffer.from(await res.arrayBuffer());
if (typeof params.maxBytes === "number" && buffer.byteLength > params.maxBytes) {
const error = new Error(`payload exceeds maxBytes ${params.maxBytes}`) as Error & {
code?: string;
};
error.code = "max_bytes";
throw error;
}
return {
buffer,
contentType: res.headers.get("content-type") ?? undefined,
fileName: undefined,
};
},
);
const fetchRemoteMediaMock = createBlueBubblesFetchRemoteMediaMock({
createHttpError: ({ response }) => new Error(`media fetch failed: HTTP ${response.status}`),
});
installBlueBubblesFetchTestHooks({
mockFetch,
privateApiStatusMock: vi.mocked(getCachedBlueBubblesPrivateApiStatus),
});
const runtimeStub = {
channel: {
media: {
fetchRemoteMedia:
fetchRemoteMediaMock as unknown as PluginRuntime["channel"]["media"]["fetchRemoteMedia"],
},
},
} as unknown as PluginRuntime;
const runtimeStub = createBlueBubblesRuntimeStub(fetchRemoteMediaMock);
beforeEach(() => {
fetchRemoteMediaMock.mockClear();

View File

@@ -0,0 +1,52 @@
import { vi } from "vitest";
import type { PluginRuntime } from "./runtime-api.js";
type FetchRemoteMediaParams = {
url: string;
maxBytes?: number;
ssrfPolicy?: unknown;
fetchImpl?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
};
type FetchRemoteMediaHttpErrorParams = {
response: Response;
url: string;
};
export function createBlueBubblesFetchRemoteMediaMock(options: {
createHttpError: (params: FetchRemoteMediaHttpErrorParams) => Error | Promise<Error>;
}) {
return vi.fn(async (params: FetchRemoteMediaParams) => {
const fetchFn = params.fetchImpl ?? fetch;
const res = await fetchFn(params.url);
if (!res.ok) {
throw await options.createHttpError({ response: res, url: params.url });
}
const buffer = Buffer.from(await res.arrayBuffer());
if (typeof params.maxBytes === "number" && buffer.byteLength > params.maxBytes) {
const error = new Error(`payload exceeds maxBytes ${params.maxBytes}`) as Error & {
code?: string;
};
error.code = "max_bytes";
throw error;
}
return {
buffer,
contentType: res.headers.get("content-type") ?? undefined,
fileName: undefined,
};
});
}
export function createBlueBubblesRuntimeStub(
fetchRemoteMediaMock: ReturnType<typeof createBlueBubblesFetchRemoteMediaMock>,
) {
return {
channel: {
media: {
fetchRemoteMedia:
fetchRemoteMediaMock as unknown as PluginRuntime["channel"]["media"]["fetchRemoteMedia"],
},
},
} as unknown as PluginRuntime;
}