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.
341 lines
9.4 KiB
341 lines
9.4 KiB
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
import type { NextRequest } from "next/server";
|
|
import {
|
|
POPI_IMAGE_SUBTYPE_REPAINT,
|
|
POPI_VIDEO_SUBTYPE_ENHANCE,
|
|
POPI_VIDEO_SUBTYPES,
|
|
} from "@/constants/generationTask";
|
|
import {
|
|
buildPopiModelParameters,
|
|
fetchPopiModelDetail,
|
|
fetchPopiModels,
|
|
getPopiModelExtensionFields,
|
|
getPopiModelSupportParameterDimensions,
|
|
popiModelToProviderModel,
|
|
} from "../_popiserverModels";
|
|
|
|
const originalFetch = global.fetch;
|
|
const originalPopiartApiBaseUrl = process.env.POPIART_API_BASE_URL;
|
|
|
|
function makeRequest(): NextRequest {
|
|
return {
|
|
nextUrl: new URL("http://localhost:3000/api/models"),
|
|
headers: new Headers({ Authorization: "Bearer login-token" }),
|
|
} as unknown as NextRequest;
|
|
}
|
|
|
|
describe("popiserver model helpers", () => {
|
|
beforeEach(() => {
|
|
process.env.POPIART_API_BASE_URL = "http://localhost:3000";
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: () => Promise.resolve({
|
|
status: "0000",
|
|
message: "ok",
|
|
data: {
|
|
list: [
|
|
{
|
|
id: 15,
|
|
code: "viduq2-pro",
|
|
name: "Vidu Q2 Pro",
|
|
aiModelCodeAlias: "viduq2-pro",
|
|
categories: [{ taskSubType: 203 }],
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
}) as unknown as typeof fetch;
|
|
});
|
|
|
|
afterEach(() => {
|
|
global.fetch = originalFetch;
|
|
process.env.POPIART_API_BASE_URL = originalPopiartApiBaseUrl;
|
|
});
|
|
|
|
it("passes only the model subType filter when listing models", async () => {
|
|
const params = new URLSearchParams({ subType: `${POPI_VIDEO_SUBTYPES.join(",")},205` });
|
|
|
|
await fetchPopiModels(makeRequest(), "login-token", undefined, params);
|
|
|
|
expect(global.fetch).toHaveBeenCalledTimes(1);
|
|
const url = String((global.fetch as unknown as ReturnType<typeof vi.fn>).mock.calls[0][0]);
|
|
const searchParams = new URL(url).searchParams;
|
|
expect(searchParams.get("subType")).toBe("203,202,204,206,205");
|
|
expect(searchParams.get("origin")).toBe("canvas");
|
|
expect(searchParams.has("type")).toBe(false);
|
|
expect(searchParams.has("subtype")).toBe(false);
|
|
});
|
|
|
|
it("infers image-to-video capability for video enhance subtype", () => {
|
|
const providerModel = popiModelToProviderModel({
|
|
id: 109,
|
|
code: "video-enhance",
|
|
name: "Video Enhance",
|
|
categories: [{ taskSubType: POPI_VIDEO_SUBTYPE_ENHANCE }],
|
|
});
|
|
|
|
expect(providerModel.capabilities).toContain("image-to-video");
|
|
expect(providerModel.metadata?.subType).toBe(POPI_VIDEO_SUBTYPE_ENHANCE);
|
|
});
|
|
|
|
it("infers image-to-image capability for repaint subtype", () => {
|
|
const providerModel = popiModelToProviderModel({
|
|
id: 116,
|
|
code: "image-repaint",
|
|
name: "Image Repaint",
|
|
categories: [{ taskSubType: POPI_IMAGE_SUBTYPE_REPAINT }],
|
|
});
|
|
|
|
expect(providerModel.capabilities).toContain("image-to-image");
|
|
expect(providerModel.metadata?.subType).toBe(POPI_IMAGE_SUBTYPE_REPAINT);
|
|
});
|
|
|
|
it("passes id when fetching model detail", async () => {
|
|
vi.mocked(global.fetch).mockResolvedValueOnce({
|
|
ok: true,
|
|
json: () => Promise.resolve({
|
|
status: "0000",
|
|
message: "ok",
|
|
data: {
|
|
id: 37,
|
|
code: "speech-2.8-hd",
|
|
name: "Speech 2.8 HD",
|
|
categories: [],
|
|
isSupportAudios: true,
|
|
},
|
|
}),
|
|
} as Response);
|
|
|
|
await expect(fetchPopiModelDetail(makeRequest(), "login-token", "37")).resolves.toMatchObject({
|
|
id: 37,
|
|
code: "speech-2.8-hd",
|
|
});
|
|
|
|
const url = String((global.fetch as unknown as ReturnType<typeof vi.fn>).mock.calls[0][0]);
|
|
const searchParams = new URL(url).searchParams;
|
|
expect(searchParams.get("id")).toBe("37");
|
|
expect(searchParams.has("aiModelId")).toBe(false);
|
|
});
|
|
|
|
it("preserves popiserver task metadata on provider models", () => {
|
|
const model = popiModelToProviderModel({
|
|
id: 30,
|
|
code: "speech-2.8-hd",
|
|
name: "Speech 2.8 HD",
|
|
aiModelCodeAlias: "speech-2.8-hd",
|
|
billingMethod: 1,
|
|
categories: [{ taskSubType: 301 }],
|
|
});
|
|
|
|
expect(model.provider).toBe("popiserver");
|
|
expect(model.metadata).toMatchObject({
|
|
type: 3,
|
|
subType: 301,
|
|
aiModelId: 30,
|
|
aiModelCode: "speech-2.8-hd",
|
|
aiModelCodeAlias: "speech-2.8-hd",
|
|
billingMethod: 1,
|
|
});
|
|
});
|
|
|
|
it("does not read popiserver extension fields from metadata", () => {
|
|
const model = popiModelToProviderModel({
|
|
id: 65,
|
|
code: "kling-video-o1",
|
|
name: "Kling Video O1",
|
|
categories: [{ taskSubType: 202 }],
|
|
metadata: JSON.stringify({
|
|
generate_audio: {
|
|
default: true,
|
|
label: "生成音频",
|
|
name: "generate_audio",
|
|
required: true,
|
|
type: "boolean",
|
|
},
|
|
}),
|
|
});
|
|
|
|
expect(model.metadata).not.toHaveProperty("extensionFields");
|
|
});
|
|
|
|
it("reads popiserver extension fields from otherShowData arrays", () => {
|
|
expect(getPopiModelExtensionFields({
|
|
id: 65,
|
|
otherShowData: JSON.stringify([
|
|
{
|
|
default: true,
|
|
label: "生成音频",
|
|
name: "generate_audio",
|
|
options: [
|
|
{ value: true, label: "开启" },
|
|
{ value: false, label: "关闭" },
|
|
],
|
|
required: true,
|
|
type: "boolean",
|
|
},
|
|
]),
|
|
})).toEqual([
|
|
{
|
|
default: true,
|
|
label: "生成音频",
|
|
name: "generate_audio",
|
|
options: [
|
|
{ value: true, label: "开启" },
|
|
{ value: false, label: "关闭" },
|
|
],
|
|
required: true,
|
|
type: "boolean",
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("parses support parameter dimensions from JSON strings", () => {
|
|
expect(getPopiModelSupportParameterDimensions({
|
|
id: 59,
|
|
uploadImageLimit: [1, 10],
|
|
uploadVideoLimit: [],
|
|
uploadAudioLimit: [1],
|
|
supportParameterDimensions: JSON.stringify({
|
|
image: {
|
|
formats: ["png", "jpg"],
|
|
aspectRatio: { min: null, max: null },
|
|
width: { min: 300, max: 1500 },
|
|
height: { min: "300", max: "1500" },
|
|
totalPixels: { min: null, max: null },
|
|
},
|
|
video: {
|
|
formats: [""],
|
|
width: { min: null, max: null },
|
|
},
|
|
audio: {
|
|
formats: ["", "MP3"],
|
|
},
|
|
}),
|
|
})).toEqual({
|
|
image: {
|
|
formats: ["png", "jpg"],
|
|
count: { min: 1, max: 10 },
|
|
width: { min: 300, max: 1500 },
|
|
height: { min: 300, max: 1500 },
|
|
},
|
|
audio: {
|
|
formats: ["mp3"],
|
|
count: { min: 1 },
|
|
},
|
|
});
|
|
});
|
|
|
|
it("creates count-only media rules from upload limits", () => {
|
|
expect(getPopiModelSupportParameterDimensions({
|
|
id: 60,
|
|
uploadVideoLimit: [2, 4],
|
|
uploadAudioLimit: [],
|
|
supportParameterDimensions: JSON.stringify({
|
|
image: {
|
|
formats: [""],
|
|
},
|
|
}),
|
|
})).toEqual({
|
|
video: {
|
|
count: { min: 2, max: 4 },
|
|
},
|
|
});
|
|
});
|
|
|
|
it("ignores invalid support parameter dimensions JSON", () => {
|
|
expect(getPopiModelSupportParameterDimensions({
|
|
id: 59,
|
|
supportParameterDimensions: "{invalid",
|
|
})).toBeUndefined();
|
|
});
|
|
|
|
it("uses displayDimensions label/value options for model parameters", () => {
|
|
const parameters = buildPopiModelParameters({
|
|
id: 37,
|
|
code: "ernie-image-turbo",
|
|
displayDimensions: JSON.stringify({
|
|
dimensions: {
|
|
ratio: {
|
|
name: "Ratio",
|
|
options: [
|
|
{ value: "16:9", label: "16:9" },
|
|
{ value: "9:16", label: "9:16" },
|
|
],
|
|
},
|
|
resolution: {
|
|
name: "Resolution",
|
|
options: [
|
|
{ value: "720", label: "720p" },
|
|
{ value: "1024", label: "1024p" },
|
|
],
|
|
},
|
|
duration: {
|
|
name: "Duration",
|
|
options: [
|
|
{ value: 5, label: "5" },
|
|
{ value: 4, label: "4" },
|
|
],
|
|
},
|
|
},
|
|
}),
|
|
ratio: ["1:1"],
|
|
resolution: ["480"],
|
|
duration: [8],
|
|
});
|
|
|
|
expect(parameters).toEqual([
|
|
expect.objectContaining({
|
|
name: "ratio",
|
|
label: "Ratio",
|
|
options: [
|
|
{ value: "16:9", label: "16:9" },
|
|
{ value: "9:16", label: "9:16" },
|
|
],
|
|
default: "16:9",
|
|
}),
|
|
expect.objectContaining({
|
|
name: "resolution",
|
|
label: "Resolution",
|
|
options: [
|
|
{ value: "720", label: "720p" },
|
|
{ value: "1024", label: "1024p" },
|
|
],
|
|
default: "720",
|
|
}),
|
|
expect.objectContaining({
|
|
name: "duration",
|
|
label: "Duration",
|
|
options: [
|
|
{ value: 5, label: "5" },
|
|
{ value: 4, label: "4" },
|
|
],
|
|
default: 5,
|
|
}),
|
|
]);
|
|
});
|
|
|
|
it("uses displayDimensions name as the request parameter name", () => {
|
|
const parameters = buildPopiModelParameters({
|
|
id: 38,
|
|
code: "model-with-aspect-ratio-key",
|
|
displayDimensions: JSON.stringify({
|
|
dimensions: {
|
|
aspectRatio: {
|
|
name: "ratio",
|
|
options: [
|
|
{ value: "16:9", label: "16:9" },
|
|
],
|
|
},
|
|
},
|
|
}),
|
|
});
|
|
|
|
expect(parameters).toEqual([
|
|
expect.objectContaining({
|
|
name: "ratio",
|
|
options: [{ value: "16:9", label: "16:9" }],
|
|
default: "16:9",
|
|
}),
|
|
]);
|
|
});
|
|
});
|
|
|