You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

347 lines
10 KiB

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" },
]);
}
});
});