Files
openclaw/extensions/moonshot/media-understanding-provider.test.ts
T
NIO 765d05c2e4 fix(moonshot): bound video description JSON response reads (#96502)
* fix(moonshot): bound video description JSON response reads

The Moonshot video description endpoint used an unbounded await res.json()
to parse the media understanding response. Route through
readProviderJsonResponse (16 MiB cap) to match the bound already in
place for other media understanding providers (xai, openrouter).

AI-assisted.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(moonshot): add bounds and malformed-JSON coverage for video description

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 12:12:46 -07:00

165 lines
5.2 KiB
TypeScript

// Moonshot tests cover media understanding provider plugin behavior.
import {
createRequestCaptureJsonFetch,
installPinnedHostnameTestHooks,
} from "openclaw/plugin-sdk/test-env";
import { describe, expect, it } from "vitest";
import { describeMoonshotVideo } from "./media-understanding-provider.js";
installPinnedHostnameTestHooks();
function oversizedJsonResponse(params: { chunkCount: number; chunkSize: number }): {
response: Response;
getReadCount: () => number;
wasCanceled: () => boolean;
} {
const chunk = new Uint8Array(params.chunkSize);
let readCount = 0;
let canceled = false;
return {
response: new Response(
new ReadableStream<Uint8Array>({
pull(controller) {
if (readCount >= params.chunkCount) {
controller.close();
return;
}
readCount += 1;
controller.enqueue(chunk);
},
cancel() {
canceled = true;
},
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
},
),
getReadCount: () => readCount,
wasCanceled: () => canceled,
};
}
describe("describeMoonshotVideo", () => {
it("builds an OpenAI-compatible video request", async () => {
const { fetchFn, getRequest } = createRequestCaptureJsonFetch({
choices: [{ message: { content: "video ok" } }],
});
const result = await describeMoonshotVideo({
buffer: Buffer.from("video-bytes"),
fileName: "clip.mp4",
apiKey: "moonshot-test",
timeoutMs: 1500,
baseUrl: "https://api.moonshot.ai/v1/",
model: "kimi-k2.6",
headers: { "X-Trace": "1" },
fetchFn,
});
const { url, init } = getRequest();
expect(result.text).toBe("video ok");
expect(result.model).toBe("kimi-k2.6");
expect(url).toBe("https://api.moonshot.ai/v1/chat/completions");
if (!init) {
throw new Error("expected Moonshot request init");
}
expect(init.method).toBe("POST");
expect(init.signal).toBeInstanceOf(AbortSignal);
const headers = new Headers(init.headers);
expect(headers.get("authorization")).toBe("Bearer moonshot-test");
expect(headers.get("content-type")).toBe("application/json");
expect(headers.get("x-trace")).toBe("1");
expect(init.body).toBeTypeOf("string");
if (typeof init.body !== "string") {
throw new Error("expected Moonshot JSON request body");
}
const body = JSON.parse(init.body) as {
model?: string;
messages?: Array<{
content?: Array<{ type?: string; text?: string; video_url?: { url?: string } }>;
}>;
};
expect(body.model).toBe("kimi-k2.6");
const content = body.messages?.[0]?.content;
if (!content) {
throw new Error("expected Moonshot user content");
}
const [textContent] = content;
if (!textContent) {
throw new Error("expected Moonshot text content");
}
expect(textContent.type).toBe("text");
expect(textContent.text).toBe("Describe the video.");
const videoContent = content[1];
if (!videoContent) {
throw new Error("expected Moonshot video content");
}
expect(videoContent.type).toBe("video_url");
if (!videoContent.video_url) {
throw new Error("expected Moonshot video URL payload");
}
expect(videoContent.video_url.url).toBe(
`data:video/mp4;base64,${Buffer.from("video-bytes").toString("base64")}`,
);
});
it("falls back to reasoning_content when content is empty", async () => {
const { fetchFn } = createRequestCaptureJsonFetch({
choices: [{ message: { content: "", reasoning_content: "reasoned answer" } }],
});
const result = await describeMoonshotVideo({
buffer: Buffer.from("video"),
fileName: "clip.mp4",
apiKey: "moonshot-test",
timeoutMs: 1000,
fetchFn,
});
expect(result.text).toBe("reasoned answer");
expect(result.model).toBe("kimi-k2.6");
});
it("bounds successful Moonshot video JSON bodies instead of buffering the whole response", async () => {
const streamed = oversizedJsonResponse({ chunkCount: 64, chunkSize: 1024 * 1024 });
await expect(
describeMoonshotVideo({
buffer: Buffer.from("video-bytes"),
fileName: "clip.mp4",
mime: "video/mp4",
apiKey: "test-key",
timeoutMs: 1500,
baseUrl: "https://example.com/v1",
fetchFn: async () => streamed.response,
}),
).rejects.toThrow("Moonshot video description failed: JSON response exceeds 16777216 bytes");
expect(streamed.getReadCount()).toBeLessThan(64);
expect(streamed.wasCanceled()).toBe(true);
});
it("reports malformed Moonshot video JSON with a provider-owned error", async () => {
const response = new Response("not-json{", {
status: 200,
headers: { "Content-Type": "application/json" },
});
await expect(
describeMoonshotVideo({
buffer: Buffer.from("video-bytes"),
fileName: "clip.mp4",
mime: "video/mp4",
apiKey: "test-key",
timeoutMs: 1500,
baseUrl: "https://example.com/v1",
fetchFn: async () => response,
}),
).rejects.toThrow("Moonshot video description failed: malformed JSON response");
});
});