import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import type { NextRequest } from "next/server"; import { checkPopiTaskOnce, submitPopiTask } from "../popiserver"; import type { GenerationInput } from "@/lib/providers/types"; const originalFetch = global.fetch; function makeRequest(): NextRequest { return { nextUrl: new URL("http://localhost:3000/api/generate"), headers: new Headers({ Authorization: "Bearer login-token" }), } as unknown as NextRequest; } function makeInput(): GenerationInput { return { model: { id: "15", name: "Vidu Q2 Pro", provider: "popiserver", capabilities: ["image-to-video"], description: null, metadata: { type: 2, subType: 203, aiModelId: 15, aiModelCode: "viduq2-pro", aiModelCodeAlias: "viduq2-pro", }, }, prompt: "@角色1 和 @角色2 在一起吃火锅", images: ["https://example.com/1.png", "https://example.com/2.png"], parameters: { ratio: "16:9", resolution: "720", videoRatio: 10, }, }; } function makeAudioInput(): GenerationInput { return { model: { id: "30", name: "Speech 2.8 HD", provider: "popiserver", capabilities: ["text-to-audio"], description: null, metadata: { type: 3, subType: 301, aiModelId: 30, aiModelCode: "speech-2.8-hd", aiModelCodeAlias: "speech-2.8-hd", }, }, prompt: "我是一个小白兔,白白的小白兔", parameters: {}, }; } describe("popiserver generation provider", () => { beforeEach(() => { global.fetch = vi.fn() as unknown as typeof fetch; }); afterEach(() => { global.fetch = originalFetch; }); it("submits a unified async task create request", async () => { vi.mocked(global.fetch).mockResolvedValue({ ok: true, json: () => Promise.resolve({ status: "0000", message: "ok", data: { id: 1189 }, }), } as Response); const result = await submitPopiTask(makeRequest(), "login-token", makeInput()); expect(result.taskId).toBe("1189"); const [, init] = vi.mocked(global.fetch).mock.calls[0]; expect(init?.method).toBe("POST"); expect(JSON.parse(String(init?.body))).toMatchObject({ type: 2, subType: 203, aiPlatform: "GATEWAY", origin: "canvas", aiModelId: 15, aiModelCode: "viduq2-pro", aiModelCodeAlias: "viduq2-pro", batchSize: 1, ratio: "16:9", resolution: "720", videoRatio: 10, }); }); it("submits audio task create with the gateway audio request shape", async () => { vi.mocked(global.fetch).mockResolvedValue({ ok: true, json: () => Promise.resolve({ status: "0000", message: "ok", data: { id: 1190 }, }), } as Response); const result = await submitPopiTask(makeRequest(), "login-token", makeAudioInput()); expect(result.taskId).toBe("1190"); const [, init] = vi.mocked(global.fetch).mock.calls[0]; expect(JSON.parse(String(init?.body))).toEqual({ type: 3, chatPrompt: "我是一个小白兔,白白的小白兔", model: "speech-2.8-hd", origin: "canvas", aiPlatform: "GATEWAY", aiModelId: 30, aiModelCode: "speech-2.8-hd", aiModelCodeAlias: "speech-2.8-hd", subType: 301, batchSize: 1, }); }); it("uploads task input media in one batch before creating the task", async () => { vi.mocked(global.fetch) .mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ status: "0000", message: "ok", data: [ { url: "https://cdn.popi.art/input-image.png" }, { url: "https://cdn.popi.art/input-video.mp4" }, { url: "https://cdn.popi.art/input-audio.mp3" }, ], }), } as Response) .mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ status: "0000", message: "ok", data: { id: 1191 }, }), } as Response); const input = makeInput(); input.images = ["data:image/png;base64,aW1hZ2U="]; input.dynamicInputs = { videos: ["data:video/mp4;base64,dmlkZW8="], audios: ["data:audio/mpeg;base64,YXVkaW8="], }; const result = await submitPopiTask(makeRequest(), "login-token", input); expect(result.taskId).toBe("1191"); expect(vi.mocked(global.fetch)).toHaveBeenCalledTimes(2); const [uploadUrl, uploadInit] = vi.mocked(global.fetch).mock.calls[0]; expect(String(uploadUrl)).toContain("/api_client/media/upload"); expect(uploadInit?.method).toBe("POST"); expect(uploadInit?.body).toBeInstanceOf(FormData); const uploadBody = uploadInit?.body as FormData; expect(uploadBody.get("multiple")).toBe("true"); expect(uploadBody.getAll("file")).toHaveLength(3); const [createUrl, createInit] = vi.mocked(global.fetch).mock.calls[1]; expect(String(createUrl)).toContain("/api_client/anime/task/create"); const createBody = JSON.parse(String(createInit?.body)); expect(createBody.images).toEqual(["https://cdn.popi.art/input-image.png"]); expect(createBody.videos).toEqual(["https://cdn.popi.art/input-video.mp4"]); expect(createBody.audios).toEqual(["https://cdn.popi.art/input-audio.mp3"]); }); it("accepts single-object media upload responses and reuses duplicate inputs", async () => { vi.mocked(global.fetch) .mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ status: "0000", message: "ok", data: { id: 5120, path: "/media/2026/0527/5120.png", url: "https://cdn.popi.art/5120.png", }, }), } as Response) .mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ status: "0000", message: "ok", data: { id: 1192 }, }), } as Response); const duplicateImage = "data:image/png;base64,aW1hZ2U="; const input = makeInput(); input.images = [duplicateImage]; input.dynamicInputs = { images: [duplicateImage], }; const result = await submitPopiTask(makeRequest(), "login-token", input); expect(result.taskId).toBe("1192"); expect(vi.mocked(global.fetch)).toHaveBeenCalledTimes(2); const [, uploadInit] = vi.mocked(global.fetch).mock.calls[0]; const uploadBody = uploadInit?.body as FormData; expect(uploadBody.getAll("file")).toHaveLength(1); const [, createInit] = vi.mocked(global.fetch).mock.calls[1]; const createBody = JSON.parse(String(createInit?.body)); expect(createBody.images).toEqual(["https://cdn.popi.art/5120.png"]); }); it("preserves direct URL positions when uploading mixed media inputs", async () => { vi.mocked(global.fetch) .mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ status: "0000", message: "ok", data: [ { url: "https://cdn.popi.art/uploaded-image.png" }, { url: "https://cdn.popi.art/uploaded-video.mp4" }, ], }), } as Response) .mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ status: "0000", message: "ok", data: { id: 1193 }, }), } as Response); const input = makeInput(); input.images = [ "https://already.cdn/source.png", "data:image/png;base64,aW1hZ2U=", ]; input.dynamicInputs = { videos: [ "https://already.cdn/source.mp4", "data:video/mp4;base64,dmlkZW8=", ], }; const result = await submitPopiTask(makeRequest(), "login-token", input); expect(result.taskId).toBe("1193"); const [, uploadInit] = vi.mocked(global.fetch).mock.calls[0]; const uploadBody = uploadInit?.body as FormData; expect(uploadBody.getAll("file")).toHaveLength(2); const [, createInit] = vi.mocked(global.fetch).mock.calls[1]; const createBody = JSON.parse(String(createInit?.body)); expect(createBody.images).toEqual([ "https://already.cdn/source.png", "https://cdn.popi.art/uploaded-image.png", ]); expect(createBody.videos).toEqual([ "https://already.cdn/source.mp4", "https://cdn.popi.art/uploaded-video.mp4", ]); }); it("extracts completed image task output from task list", async () => { vi.mocked(global.fetch).mockResolvedValue({ ok: true, json: () => Promise.resolve({ status: "0000", message: "ok", data: { list: [ { id: 1189, status: 2, resultList: [ { images: ["https://example.com/result.png"], }, ], }, ], }, }), } as Response); const result = await checkPopiTaskOnce(makeRequest(), "login-token", "1189", "image"); expect(result.status).toBe("completed"); const [url] = vi.mocked(global.fetch).mock.calls[0]; const searchParams = new URL(String(url)).searchParams; expect(searchParams.get("ids")).toBe("1189"); expect(searchParams.get("origin")).toBe("canvas"); if (result.status === "completed") { expect(result.output.outputs?.[0]).toMatchObject({ type: "image", url: "https://example.com/result.png", }); } }); it("extracts all completed image task outputs from batched result list", async () => { vi.mocked(global.fetch).mockResolvedValue({ ok: true, json: () => Promise.resolve({ status: "0000", message: "ok", data: { list: [ { id: 1191, status: 2, resultList: [ { images: ["https://example.com/result-1.png"] }, { images: ["https://example.com/result-2.png"] }, { images: ["https://example.com/result-3.png"] }, ], }, ], }, }), } as Response); const result = await checkPopiTaskOnce(makeRequest(), "login-token", "1191", "image"); expect(result.status).toBe("completed"); if (result.status === "completed") { expect(result.output.outputs).toEqual([ { type: "image", data: "", url: "https://example.com/result-1.png" }, { type: "image", data: "", url: "https://example.com/result-2.png" }, { type: "image", data: "", url: "https://example.com/result-3.png" }, ]); } }); });