Files
openclaw/extensions/qwen/video-generation-provider.test.ts
2026-04-05 23:47:10 +01:00

186 lines
5.6 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import { buildQwenVideoGenerationProvider } from "./video-generation-provider.js";
const {
resolveApiKeyForProviderMock,
postJsonRequestMock,
fetchWithTimeoutMock,
assertOkOrThrowHttpErrorMock,
resolveProviderHttpRequestConfigMock,
} = vi.hoisted(() => ({
resolveApiKeyForProviderMock: vi.fn(async () => ({ apiKey: "qwen-key" })),
postJsonRequestMock: vi.fn(),
fetchWithTimeoutMock: vi.fn(),
assertOkOrThrowHttpErrorMock: vi.fn(async () => {}),
resolveProviderHttpRequestConfigMock: vi.fn((params) => ({
baseUrl: params.baseUrl ?? params.defaultBaseUrl,
allowPrivateNetwork: false,
headers: new Headers(params.defaultHeaders),
dispatcherPolicy: undefined,
})),
}));
vi.mock("openclaw/plugin-sdk/provider-auth-runtime", () => ({
resolveApiKeyForProvider: resolveApiKeyForProviderMock,
}));
vi.mock("openclaw/plugin-sdk/provider-http", () => ({
assertOkOrThrowHttpError: assertOkOrThrowHttpErrorMock,
fetchWithTimeout: fetchWithTimeoutMock,
postJsonRequest: postJsonRequestMock,
resolveProviderHttpRequestConfig: resolveProviderHttpRequestConfigMock,
}));
describe("qwen video generation provider", () => {
afterEach(() => {
resolveApiKeyForProviderMock.mockClear();
postJsonRequestMock.mockReset();
fetchWithTimeoutMock.mockReset();
assertOkOrThrowHttpErrorMock.mockClear();
resolveProviderHttpRequestConfigMock.mockClear();
});
it("submits async Wan generation, polls task status, and downloads the resulting video", async () => {
postJsonRequestMock.mockResolvedValue({
response: {
json: async () => ({
request_id: "req-1",
output: {
task_id: "task-1",
},
}),
},
release: vi.fn(async () => {}),
});
fetchWithTimeoutMock
.mockResolvedValueOnce({
json: async () => ({
output: {
task_status: "SUCCEEDED",
results: [{ video_url: "https://example.com/out.mp4" }],
},
}),
headers: new Headers(),
})
.mockResolvedValueOnce({
arrayBuffer: async () => Buffer.from("mp4-bytes"),
headers: new Headers({ "content-type": "video/mp4" }),
});
const provider = buildQwenVideoGenerationProvider();
const result = await provider.generateVideo({
provider: "qwen",
model: "wan2.6-r2v-flash",
prompt: "animate this shot",
cfg: {},
inputImages: [{ url: "https://example.com/ref.png" }],
durationSeconds: 6,
audio: true,
});
expect(postJsonRequestMock).toHaveBeenCalledWith(
expect.objectContaining({
url: "https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/video-generation/video-synthesis",
body: expect.objectContaining({
model: "wan2.6-r2v-flash",
input: expect.objectContaining({
prompt: "animate this shot",
img_url: "https://example.com/ref.png",
}),
}),
}),
);
expect(fetchWithTimeoutMock).toHaveBeenNthCalledWith(
1,
"https://dashscope-intl.aliyuncs.com/api/v1/tasks/task-1",
expect.objectContaining({ method: "GET" }),
120000,
fetch,
);
expect(result.videos).toHaveLength(1);
expect(result.videos[0]?.mimeType).toBe("video/mp4");
expect(result.metadata).toEqual(
expect.objectContaining({
requestId: "req-1",
taskId: "task-1",
taskStatus: "SUCCEEDED",
}),
);
});
it("fails fast when reference inputs are local buffers instead of remote URLs", async () => {
const provider = buildQwenVideoGenerationProvider();
await expect(
provider.generateVideo({
provider: "qwen",
model: "wan2.6-i2v",
prompt: "animate this local frame",
cfg: {},
inputImages: [{ buffer: Buffer.from("png-bytes"), mimeType: "image/png" }],
}),
).rejects.toThrow(
"Qwen video generation currently requires remote http(s) URLs for reference images/videos.",
);
expect(postJsonRequestMock).not.toHaveBeenCalled();
});
it("preserves dedicated coding endpoints for dedicated API keys", async () => {
postJsonRequestMock.mockResolvedValue({
response: {
json: async () => ({
request_id: "req-2",
output: {
task_id: "task-2",
},
}),
},
release: vi.fn(async () => {}),
});
fetchWithTimeoutMock
.mockResolvedValueOnce({
json: async () => ({
output: {
task_status: "SUCCEEDED",
results: [{ video_url: "https://example.com/out.mp4" }],
},
}),
headers: new Headers(),
})
.mockResolvedValueOnce({
arrayBuffer: async () => Buffer.from("mp4-bytes"),
headers: new Headers({ "content-type": "video/mp4" }),
});
const provider = buildQwenVideoGenerationProvider();
await provider.generateVideo({
provider: "qwen",
model: "wan2.6-t2v",
prompt: "animate this shot",
cfg: {
models: {
providers: {
qwen: {
baseUrl: "https://coding-intl.dashscope.aliyuncs.com/v1",
models: [],
},
},
},
},
});
expect(postJsonRequestMock).toHaveBeenCalledWith(
expect.objectContaining({
url: "https://coding-intl.dashscope.aliyuncs.com/api/v1/services/aigc/video-generation/video-synthesis",
}),
);
expect(fetchWithTimeoutMock).toHaveBeenNthCalledWith(
1,
"https://coding-intl.dashscope.aliyuncs.com/api/v1/tasks/task-2",
expect.objectContaining({ method: "GET" }),
120000,
fetch,
);
});
});