100 lines
5.6 KiB
TypeScript
100 lines
5.6 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import { analyzeAliyunVideo, isAliyunVideoModel, supportsVideoSound } from "@/lib/video-canvas/aliyun-video-analysis";
|
|
|
|
test("only official Alibaba video-understanding models use direct video input", () => {
|
|
expect(isAliyunVideoModel("qwen3.8-max", "alibaba")).toBe(true);
|
|
expect(isAliyunVideoModel("qwen3.8-omni-flash", "alibaba")).toBe(true);
|
|
expect(isAliyunVideoModel("qwen3-vl-plus", "alibaba")).toBe(false);
|
|
expect(isAliyunVideoModel("qwen3.8-max", "custom")).toBe(false);
|
|
expect(supportsVideoSound("qwen3.8-max")).toBe(false);
|
|
expect(supportsVideoSound("qwen3.8-omni-flash")).toBe(true);
|
|
});
|
|
|
|
test("direct visual analysis sends the full video to the official endpoint", async () => {
|
|
const original = globalThis.fetch;
|
|
let body: any;
|
|
globalThis.fetch = async (input, init) => {
|
|
expect(String(input)).toBe("https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions");
|
|
body = JSON.parse(String(init?.body));
|
|
return Response.json({ choices: [{ message: { content: '{"summary":"已解析真实画面"}' } }] });
|
|
};
|
|
try {
|
|
const result = await analyzeAliyunVideo({ apiKey: "test-key", baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1", modelId: "qwen3.8-max", prompt: "按时间分析", videoUrl: "https://example.com/test.mp4" });
|
|
expect(result).toContain("已解析真实画面");
|
|
expect(body.messages[0].content[0]).toMatchObject({ type: "video_url", video_url: { url: "https://example.com/test.mp4" } });
|
|
expect(body.stream).toBe(false);
|
|
} finally { globalThis.fetch = original; }
|
|
});
|
|
|
|
test("Omni parses streaming video and sound response", async () => {
|
|
const original = globalThis.fetch;
|
|
globalThis.fetch = async (_input, init) => {
|
|
const body = JSON.parse(String(init?.body));
|
|
expect(body.model).toBe("qwen3.8-omni-flash");
|
|
expect(body.stream).toBe(true);
|
|
expect(body.modalities).toEqual(["text"]);
|
|
return new Response('data: {"choices":[{"delta":{"content":"有人声"}}]}\n\ndata: [DONE]\n', { status: 200 });
|
|
};
|
|
try {
|
|
expect(await analyzeAliyunVideo({ apiKey: "test-key", baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1", modelId: "qwen3.8-omni-flash", prompt: "听声音", videoUrl: "https://example.com/test.mp4" })).toBe("有人声");
|
|
} finally { globalThis.fetch = original; }
|
|
});
|
|
|
|
test("native highlight analysis uses temporal video input with adjacent shot context", async () => {
|
|
const original = globalThis.fetch;
|
|
globalThis.fetch = async (_input, init) => {
|
|
const body = JSON.parse(String(init?.body));
|
|
expect(body.messages[0].content).toEqual([
|
|
{ type: "video_url", video_url: { url: "https://example.com/shot.mp4" }, fps: 2 },
|
|
{ type: "image_url", image_url: { url: "data:image/jpeg;base64,bmVpZ2hib3I=" } },
|
|
{ type: "text", text: "挑选动作高光,并检查衔接" },
|
|
]);
|
|
return Response.json({ choices: [{ message: { content: '{"startSeconds":2,"matchScore":90}' } }] });
|
|
};
|
|
try {
|
|
expect(await analyzeAliyunVideo({ apiKey: "test-key", baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
modelId: "qwen3.8-max", prompt: "挑选动作高光,并检查衔接", videoUrl: "https://example.com/shot.mp4",
|
|
contextImages: ["data:image/jpeg;base64,bmVpZ2hib3I="], fps: 2 })).toContain("startSeconds");
|
|
} finally { globalThis.fetch = original; }
|
|
});
|
|
|
|
test("rejects private video URLs and unofficial provider endpoints", async () => {
|
|
await expect(analyzeAliyunVideo({ apiKey: "test-key", baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1", modelId: "qwen3.8-max", prompt: "分析", videoUrl: "http://127.0.0.1/test.mp4" })).rejects.toThrow("公网访问");
|
|
await expect(analyzeAliyunVideo({ apiKey: "test-key", baseUrl: "https://example.com/v1", modelId: "qwen3.8-max", prompt: "分析", videoUrl: "https://example.com/test.mp4" })).rejects.toThrow("官方地址");
|
|
});
|
|
|
|
test("highlight requests enforce provider JSON output without changing free-text analysis", async () => {
|
|
const original = globalThis.fetch;
|
|
const bodies: any[] = [];
|
|
globalThis.fetch = async (_input, init) => {
|
|
bodies.push(JSON.parse(String(init?.body)));
|
|
return Response.json({ choices: [{ finish_reason: "stop", message: { content: '{"startSeconds":0}' } }] });
|
|
};
|
|
const input = { apiKey: "test-key", baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
modelId: "qwen3.8-max" as const, prompt: "返回 JSON", videoUrl: "https://example.com/shot.mp4" };
|
|
try {
|
|
await analyzeAliyunVideo({ ...input, jsonOutput: true });
|
|
await analyzeAliyunVideo(input);
|
|
expect(bodies[0].response_format).toEqual({ type: "json_object" });
|
|
expect(bodies[1].response_format).toBeUndefined();
|
|
} finally { globalThis.fetch = original; }
|
|
});
|
|
|
|
for (const modelId of ["qwen3.8-max", "qwen3.8-omni-flash"] as const) {
|
|
test(`${modelId} rejects truncated analysis even when the partial text is valid JSON`, async () => {
|
|
const original = globalThis.fetch;
|
|
let calls = 0;
|
|
globalThis.fetch = async () => {
|
|
calls++;
|
|
return modelId === "qwen3.8-max"
|
|
? Response.json({ choices: [{ finish_reason: "length", message: { content: '{"matchScore":99}' } }] })
|
|
: new Response('data: {"choices":[{"delta":{"content":"{\"matchScore\":99}"}}]}\n\ndata: {"choices":[{"delta":{},"finish_reason":"length"}]}\n\ndata: [DONE]\n');
|
|
};
|
|
try {
|
|
await expect(analyzeAliyunVideo({ apiKey: "test-key", baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
modelId, prompt: "返回 JSON", videoUrl: "https://example.com/shot.mp4", jsonOutput: true })).rejects.toThrow("被截断");
|
|
expect(calls).toBe(1);
|
|
} finally { globalThis.fetch = original; }
|
|
});
|
|
}
|