open-design/apps/daemon/tests/media-openai-compatible-providers.test.ts
Quang Do 13c8bc4193
feat(daemon): add OpenAI-compatible media providers (#1712)
* feat(daemon): add openai-compatible media providers

* fix(web): sync media registry with routed providers
2026-05-15 23:05:03 +08:00

247 lines
8.5 KiB
TypeScript

import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { generateMedia } from '../src/media.js';
const PNG_BASE64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+X2uoAAAAASUVORK5CYII=';
const VIDEO_BASE64 = Buffer.from([0, 0, 0, 24, 102, 116, 121, 112]).toString('base64');
describe('OpenAI-compatible media providers', () => {
let root: string;
let projectRoot: string;
let projectsRoot: string;
const realFetch = globalThis.fetch;
const originalImageRouterKey = process.env.OD_IMAGEROUTER_API_KEY;
const originalCustomImageKey = process.env.OD_CUSTOM_IMAGE_API_KEY;
const originalMediaConfigDir = process.env.OD_MEDIA_CONFIG_DIR;
const originalDataDir = process.env.OD_DATA_DIR;
beforeEach(async () => {
root = await mkdtemp(path.join(tmpdir(), 'od-openai-compatible-media-'));
projectRoot = path.join(root, 'project-root');
projectsRoot = path.join(projectRoot, '.od', 'projects');
await mkdir(projectsRoot, { recursive: true });
delete process.env.OD_IMAGEROUTER_API_KEY;
delete process.env.OD_CUSTOM_IMAGE_API_KEY;
delete process.env.OD_MEDIA_CONFIG_DIR;
delete process.env.OD_DATA_DIR;
});
afterEach(async () => {
globalThis.fetch = realFetch;
vi.unstubAllGlobals();
if (originalImageRouterKey == null) {
delete process.env.OD_IMAGEROUTER_API_KEY;
} else {
process.env.OD_IMAGEROUTER_API_KEY = originalImageRouterKey;
}
if (originalCustomImageKey == null) {
delete process.env.OD_CUSTOM_IMAGE_API_KEY;
} else {
process.env.OD_CUSTOM_IMAGE_API_KEY = originalCustomImageKey;
}
if (originalMediaConfigDir == null) {
delete process.env.OD_MEDIA_CONFIG_DIR;
} else {
process.env.OD_MEDIA_CONFIG_DIR = originalMediaConfigDir;
}
if (originalDataDir == null) {
delete process.env.OD_DATA_DIR;
} else {
process.env.OD_DATA_DIR = originalDataDir;
}
await rm(root, { recursive: true, force: true });
});
async function writeConfig(data: unknown) {
const file = path.join(projectRoot, '.od', 'media-config.json');
await mkdir(path.dirname(file), { recursive: true });
await writeFile(file, JSON.stringify(data), 'utf8');
}
it('renders custom /v1/images/generations providers with configured base URL and model', async () => {
await writeConfig({
providers: {
'custom-image': {
baseUrl: 'https://images.example.test/v1',
model: 'acme-image-model',
},
},
});
const fetchMock = vi.fn(async (input: unknown, init?: RequestInit) => {
expect(String(input)).toBe('https://images.example.test/v1/images/generations');
expect(init?.method).toBe('POST');
expect(init?.headers).toMatchObject({
'content-type': 'application/json',
});
expect(init?.headers).not.toHaveProperty('authorization');
expect(JSON.parse(String(init?.body))).toEqual({
prompt: 'A product render on white seamless paper',
model: 'acme-image-model',
n: 1,
size: '1024x1024',
});
return new Response(JSON.stringify({
data: [{ b64_json: PNG_BASE64 }],
}), {
status: 200,
headers: { 'content-type': 'application/json' },
});
});
vi.stubGlobal('fetch', fetchMock);
const result = await generateMedia({
projectRoot,
projectsRoot,
projectId: 'project-1',
surface: 'image',
model: 'custom-image',
prompt: 'A product render on white seamless paper',
output: 'custom.png',
});
expect(result.providerId).toBe('custom-image');
expect(result.providerNote).toContain('custom-image/acme-image-model');
expect(fetchMock).toHaveBeenCalledTimes(1);
const bytes = await readFile(path.join(projectsRoot, 'project-1', 'custom.png'));
expect(bytes.length).toBeGreaterThan(0);
});
it('routes matching OpenAI image catalog ids through the configured custom provider', async () => {
await writeConfig({
providers: {
'custom-image': {
apiKey: 'proxy-test-key',
baseUrl: 'https://proxy.example.test/v1/images/generations',
model: 'gpt-image-2',
},
},
});
const fetchMock = vi.fn(async (input: unknown, init?: RequestInit) => {
expect(String(input)).toBe('https://proxy.example.test/v1/images/generations');
expect(init?.method).toBe('POST');
expect(init?.headers).toMatchObject({
authorization: 'Bearer proxy-test-key',
'content-type': 'application/json',
});
expect(JSON.parse(String(init?.body))).toEqual({
prompt: 'A clean app icon with glass material',
model: 'gpt-image-2',
n: 1,
size: '1024x1024',
});
return new Response(JSON.stringify({
data: [{ b64_json: PNG_BASE64 }],
}), {
status: 200,
headers: { 'content-type': 'application/json' },
});
});
vi.stubGlobal('fetch', fetchMock);
const result = await generateMedia({
projectRoot,
projectsRoot,
projectId: 'project-1',
surface: 'image',
model: 'gpt-image-2',
prompt: 'A clean app icon with glass material',
output: 'proxy.png',
});
expect(result.providerId).toBe('custom-image');
expect(result.providerNote).toContain('custom-image/gpt-image-2');
expect(fetchMock).toHaveBeenCalledTimes(1);
});
it('renders ImageRouter images through the OpenAI-compatible JSON endpoint', async () => {
process.env.OD_IMAGEROUTER_API_KEY = 'ir-test-key';
const fetchMock = vi.fn(async (input: unknown, init?: RequestInit) => {
expect(String(input)).toBe('https://api.imagerouter.io/v1/openai/images/generations');
expect(init?.method).toBe('POST');
expect(init?.headers).toMatchObject({
authorization: 'Bearer ir-test-key',
'content-type': 'application/json',
});
expect(JSON.parse(String(init?.body))).toEqual({
prompt: 'A cinematic vertical poster',
model: 'openai/gpt-image-2',
quality: 'auto',
size: '576x1024',
response_format: 'b64_json',
output_format: 'png',
});
return new Response(JSON.stringify({
data: [{ b64_json: PNG_BASE64 }],
}), {
status: 200,
headers: { 'content-type': 'application/json' },
});
});
vi.stubGlobal('fetch', fetchMock);
const result = await generateMedia({
projectRoot,
projectsRoot,
projectId: 'project-1',
surface: 'image',
model: 'openai/gpt-image-2',
prompt: 'A cinematic vertical poster',
aspect: '9:16',
output: 'imagerouter.png',
});
expect(result.providerId).toBe('imagerouter');
expect(result.providerNote).toContain('imagerouter/openai/gpt-image-2');
});
it('renders ImageRouter videos through the OpenAI-compatible JSON endpoint', async () => {
process.env.OD_IMAGEROUTER_API_KEY = 'ir-test-key';
const fetchMock = vi.fn(async (input: unknown, init?: RequestInit) => {
expect(String(input)).toBe('https://api.imagerouter.io/v1/openai/videos/generations');
expect(init?.method).toBe('POST');
expect(init?.headers).toMatchObject({
authorization: 'Bearer ir-test-key',
'content-type': 'application/json',
});
expect(JSON.parse(String(init?.body))).toEqual({
prompt: 'A short cinematic camera push through a neon market',
model: 'xAI/grok-imagine-video',
size: '1024x576',
seconds: 8,
response_format: 'b64_json',
});
return new Response(JSON.stringify({
data: [{ b64_json: VIDEO_BASE64 }],
}), {
status: 200,
headers: { 'content-type': 'application/json' },
});
});
vi.stubGlobal('fetch', fetchMock);
const result = await generateMedia({
projectRoot,
projectsRoot,
projectId: 'project-1',
surface: 'video',
model: 'xAI/grok-imagine-video',
prompt: 'A short cinematic camera push through a neon market',
aspect: '16:9',
length: 8,
output: 'imagerouter.mp4',
});
expect(result.providerId).toBe('imagerouter');
expect(result.name).toBe('imagerouter.mp4');
expect(result.providerNote).toContain('imagerouter/xAI/grok-imagine-video');
const bytes = await readFile(path.join(projectsRoot, 'project-1', 'imagerouter.mp4'));
expect(bytes.length).toBeGreaterThan(0);
});
});