179 lines
5.6 KiB
TypeScript
179 lines
5.6 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import {
|
|
createDefaultProviderRegistry,
|
|
MockImageProviderAdapter,
|
|
normalizeImageAssets,
|
|
normalizeProviderError,
|
|
OpenAICompatibleImageProviderAdapter,
|
|
SiliconFlowImageProviderAdapter
|
|
} from "../index";
|
|
|
|
describe("provider adapter registry", () => {
|
|
it("creates implemented and reserved adapters", () => {
|
|
const registry = createDefaultProviderRegistry();
|
|
|
|
expect(registry.mock.provider).toBe("mock");
|
|
expect(registry["openai-compatible"].provider).toBe("openai-compatible");
|
|
expect(registry.siliconflow.provider).toBe("siliconflow");
|
|
expect(registry.dashscope.provider).toBe("dashscope");
|
|
expect(registry.volcengine.provider).toBe("volcengine");
|
|
expect(registry.zhipu.provider).toBe("zhipu");
|
|
expect(registry["custom-template"].provider).toBe("custom-template");
|
|
});
|
|
});
|
|
|
|
describe("mock provider", () => {
|
|
it("returns deterministic URL assets with mapped dimensions", async () => {
|
|
const adapter = new MockImageProviderAdapter();
|
|
const result = await adapter.generateTextToImage(
|
|
{
|
|
model: "mock-wallpaper",
|
|
prompt: "misty mountain wallpaper",
|
|
size: { aspectRatio: "16:9", resolution: "2k" },
|
|
seed: 42
|
|
},
|
|
{}
|
|
);
|
|
|
|
expect(result.assets[0]).toMatchObject({
|
|
kind: "url",
|
|
width: 2560,
|
|
height: 1440,
|
|
seed: 42
|
|
});
|
|
expect(result.usage?.imageCount).toBe(1);
|
|
});
|
|
|
|
it("can return base64 assets for local tests", async () => {
|
|
const adapter = new MockImageProviderAdapter();
|
|
const result = await adapter.generateTextToImage(
|
|
{
|
|
model: "mock-wallpaper",
|
|
prompt: "phone wallpaper",
|
|
responseFormat: "base64",
|
|
size: { aspectRatio: "9:16", resolution: "1k" }
|
|
},
|
|
{}
|
|
);
|
|
|
|
expect(result.assets[0]?.kind).toBe("base64");
|
|
expect(result.assets[0]?.mimeType).toBe("image/png");
|
|
expect(result.assets[0]?.width).toBe(720);
|
|
expect(result.assets[0]?.height).toBe(1280);
|
|
});
|
|
});
|
|
|
|
describe("OpenAI compatible provider", () => {
|
|
it("posts to configurable image endpoint and normalizes base64 response", async () => {
|
|
const fetch = vi.fn(async (_url: URL | RequestInfo, init?: RequestInit) => {
|
|
expect(_url).toBe("https://new-api.example.com/v1/images/generations");
|
|
expect(JSON.parse(String(init?.body))).toMatchObject({
|
|
model: "gpt-image-1",
|
|
prompt: "desktop wallpaper",
|
|
n: 1,
|
|
size: "1536x1024",
|
|
response_format: "b64_json"
|
|
});
|
|
return jsonResponse({
|
|
data: [{ b64_json: "data:image/png;base64,abc123" }],
|
|
usage: { input_tokens: 12, output_tokens: 0 }
|
|
});
|
|
});
|
|
const adapter = new OpenAICompatibleImageProviderAdapter();
|
|
|
|
const result = await adapter.generateTextToImage(
|
|
{
|
|
baseUrl: "https://new-api.example.com",
|
|
model: "gpt-image-1",
|
|
prompt: "desktop wallpaper",
|
|
responseFormat: "base64",
|
|
size: { aspectRatio: "16:9", resolution: "4k" }
|
|
},
|
|
{ auth: { apiKey: "test-key" }, fetch }
|
|
);
|
|
|
|
expect(result.assets[0]).toMatchObject({
|
|
kind: "base64",
|
|
value: "abc123",
|
|
mimeType: "image/png",
|
|
width: 1536,
|
|
height: 1024
|
|
});
|
|
expect(result.usage?.inputTokens).toBe(12);
|
|
});
|
|
});
|
|
|
|
describe("SiliconFlow provider", () => {
|
|
it("uses image_size and normalizes URL response", async () => {
|
|
const fetch = vi.fn(async (_url: URL | RequestInfo, init?: RequestInit) => {
|
|
expect(_url).toBe("https://api.siliconflow.cn/v1/images/generations");
|
|
expect(JSON.parse(String(init?.body))).toMatchObject({
|
|
model: "black-forest-labs/FLUX.2-pro",
|
|
prompt: "portrait wallpaper",
|
|
image_size: "720x1280",
|
|
batch_size: 1,
|
|
seed: 7
|
|
});
|
|
return jsonResponse({ data: [{ url: "https://cdn.example.com/result.png" }] });
|
|
});
|
|
const adapter = new SiliconFlowImageProviderAdapter();
|
|
|
|
const result = await adapter.generateTextToImage(
|
|
{
|
|
model: "black-forest-labs/FLUX.2-pro",
|
|
prompt: "portrait wallpaper",
|
|
size: { aspectRatio: "9:16", resolution: "1k" },
|
|
seed: 7
|
|
},
|
|
{ auth: { apiKey: "sf-key" }, fetch }
|
|
);
|
|
|
|
expect(result.assets[0]).toMatchObject({
|
|
kind: "url",
|
|
value: "https://cdn.example.com/result.png",
|
|
width: 720,
|
|
height: 1280
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("normalization helpers", () => {
|
|
it("normalizes common provider errors", () => {
|
|
expect(normalizeProviderError("siliconflow", { status: 429, message: "rate limit exceeded" })).toMatchObject({
|
|
category: "rate_limit",
|
|
retryable: true,
|
|
statusCode: 429
|
|
});
|
|
|
|
expect(normalizeProviderError("openai-compatible", { status: 401, message: "invalid api key" })).toMatchObject({
|
|
category: "authentication",
|
|
retryable: false
|
|
});
|
|
|
|
expect(normalizeProviderError("openai-compatible", { status: 504, message: "gateway timeout" })).toMatchObject({
|
|
category: "timeout",
|
|
retryable: true
|
|
});
|
|
});
|
|
|
|
it("normalizes URL and base64 image payloads", () => {
|
|
expect(normalizeImageAssets({ data: [{ url: "https://example.com/a.png" }] })[0]).toMatchObject({
|
|
kind: "url",
|
|
value: "https://example.com/a.png"
|
|
});
|
|
|
|
expect(normalizeImageAssets({ images: [{ image_base64: "data:image/jpeg;base64,zzz" }] })[0]).toMatchObject({
|
|
kind: "base64",
|
|
value: "zzz",
|
|
mimeType: "image/jpeg"
|
|
});
|
|
});
|
|
});
|
|
|
|
function jsonResponse(body: unknown, status = 200): Response {
|
|
return new Response(JSON.stringify(body), {
|
|
status,
|
|
headers: { "content-type": "application/json" }
|
|
});
|
|
}
|