Initial WallMuse project
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
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" }
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user