Files
prompt-optimizer/packages/core/tests/integration/image-adapters.test.ts

291 lines
11 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect, beforeAll, beforeEach } from 'vitest'
import { ImageService } from '../../src/services/image/service'
import { ImageModelManager } from '../../src/services/image-model/manager'
import { createImageAdapterRegistry } from '../../src/services/image/adapters/registry'
import { SeedreamImageAdapter } from '../../src/services/image/adapters/seedream'
import { OpenRouterImageAdapter } from '../../src/services/image/adapters/openrouter'
import { LocalStorageProvider } from '../../src/services/storage/localStorageProvider'
import type { ImageRequest, ImageModelConfig } from '../../src/services/image/types'
/**
* 图像适配器真实API集成测试
* 只有在相应的环境变量存在时才执行
*/
const RUN_REAL_API = process.env.RUN_REAL_API === '1'
describe.skipIf(!RUN_REAL_API)('Image Adapters Real API Integration Tests', () => {
const hasGeminiKey = !!process.env.VITE_GEMINI_API_KEY
const hasOpenAIKey = !!process.env.VITE_OPENAI_API_KEY
const hasOpenRouterKey = !!process.env.VITE_OPENROUTER_API_KEY
const hasSeedreamKey = !!(process.env.VITE_SEEDREAM_API_KEY || process.env.VITE_ARK_API_KEY || process.env.ARK_API_KEY)
let storage: LocalStorageProvider
let imageModelManager: ImageModelManager
let imageService: ImageService
let registry: ReturnType<typeof createImageAdapterRegistry>
beforeAll(() => {
if (!hasGeminiKey && !hasOpenAIKey && !hasOpenRouterKey && !hasSeedreamKey) return
})
beforeEach(async () => {
storage = new LocalStorageProvider()
registry = createImageAdapterRegistry()
imageModelManager = new ImageModelManager(storage, registry)
imageService = new ImageService(imageModelManager, registry)
await storage.clearAll()
})
describe('Gemini 图像适配器测试', () => {
const runGeminiTests = hasGeminiKey
it.runIf(runGeminiTests)('应该能使用Gemini 2.5 Flash Image生成图像', async () => {
// 添加Gemini图像模型
const geminiConfig: ImageModelConfig = {
id: 'test-gemini-fast',
name: 'Gemini 2.5 Flash Image',
providerId: 'gemini',
modelId: 'gemini-2.5-flash-image',
enabled: true,
connectionConfig: { apiKey: process.env.VITE_GEMINI_API_KEY! },
paramOverrides: { outputMimeType: 'image/png' }
} as any
await imageModelManager.addConfig(geminiConfig)
// 生成图像
const request: ImageRequest = {
prompt: 'A beautiful sunset over the ocean with calm waves',
count: 1,
configId: 'test-gemini-fast',
paramOverrides: { outputMimeType: 'image/png' }
}
const result = await imageService.generate(request)
expect(result).toBeDefined()
expect(result.images).toBeDefined()
expect(result.images.length).toBe(1)
const image = result.images[0]
expect(image).toBeDefined()
if (!image) throw new Error('Expected Gemini image result')
const b64 = image.b64
expect(b64).toBeDefined()
if (!b64) throw new Error('Expected Gemini image payload')
expect(b64.length).toBeGreaterThan(100)
expect(image.mimeType).toBe('image/png')
expect(result.metadata?.modelId).toBe('gemini-2.5-flash-image')
}, 120000)
// 当前 Gemini 适配器仅覆盖 generateContent 兼容的 Gemini image 模型
it.skipIf(!runGeminiTests)('跳过Gemini测试 - 未设置API密钥', () => {
expect(true).toBe(true)
})
})
describe('OpenRouter 图像适配器测试', () => {
const runOpenRouterTests = hasOpenRouterKey
const openrouterModelId = new OpenRouterImageAdapter().getModels()[0].id
// 测试用的小图像 base641x1 透明 PNG
const testImageBase64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='
it.runIf(runOpenRouterTests)('应该能使用OpenRouter生成图像', async () => {
// 添加OpenRouter模型
const openrouterConfig: ImageModelConfig = {
id: 'test-openrouter',
name: 'OpenRouter Image Model',
providerId: 'openrouter',
modelId: openrouterModelId,
enabled: true,
connectionConfig: { apiKey: process.env.VITE_OPENROUTER_API_KEY!, baseURL: 'https://openrouter.ai/api/v1' },
paramOverrides: {}
} as any
await imageModelManager.addConfig(openrouterConfig)
// 生成图像
const request: ImageRequest = {
prompt: 'a simple red flower',
count: 1,
configId: 'test-openrouter',
paramOverrides: {}
}
const result = await imageService.generate(request)
expect(result).toBeDefined()
expect(result.images).toBeDefined()
expect(result.images.length).toBe(1)
const image = result.images[0]
expect(image).toBeDefined()
if (!image) throw new Error('Expected OpenRouter image result')
const b64 = image.b64
expect(b64).toBeDefined()
if (!b64) throw new Error('Expected OpenRouter image payload')
expect(b64.length).toBeGreaterThan(100)
expect(image.mimeType).toBe('image/png')
expect(result.metadata?.modelId).toBe(openrouterModelId)
}, 120000)
it.runIf(runOpenRouterTests)('应该能使用OpenRouter进行图生图', async () => {
// 复用同一配置进行图生图
await imageModelManager.addConfig({
id: 'test-openrouter-i2i',
name: 'OpenRouter I2I',
providerId: 'openrouter',
modelId: openrouterModelId,
enabled: true,
connectionConfig: { apiKey: process.env.VITE_OPENROUTER_API_KEY!, baseURL: 'https://openrouter.ai/api/v1' },
paramOverrides: {}
} as any)
// 图生图请求
const request: ImageRequest = {
prompt: 'Transform this image into a vibrant watercolor painting',
count: 1,
configId: 'test-openrouter-i2i',
inputImage: {
b64: testImageBase64,
mimeType: 'image/png'
}
}
const result = await imageService.generate(request)
expect(result).toBeDefined()
expect(result.images).toBeDefined()
expect(result.images.length).toBe(1)
const image = result.images[0]
expect(image).toBeDefined()
if (!image) throw new Error('Expected OpenRouter image result')
const b64 = image.b64
expect(b64).toBeDefined()
if (!b64) throw new Error('Expected OpenRouter image payload')
expect(b64.length).toBeGreaterThan(100)
expect(result.metadata?.modelId).toBe(openrouterModelId)
}, 90000) // 图生图可能需要更长时间
it.skipIf(!runOpenRouterTests)('跳过OpenRouter测试 - 未设置API密钥', () => {
expect(true).toBe(true)
})
})
// 已不支持 DALL-E 系列模型,移除相关测试
describe('Seedream (火山方舟) 适配器测试', () => {
const runSeedreamTests = hasSeedreamKey
it.runIf(runSeedreamTests)('应该能使用Doubao Seedream 4.0生成图像', async () => {
// 添加Seedream模型 - 使用与 curl 示例匹配的参数
const seedreamModelId = new SeedreamImageAdapter().getModels()[0].id
const seedreamApiKey = process.env.VITE_SEEDREAM_API_KEY ||
process.env.VITE_ARK_API_KEY ||
process.env.ARK_API_KEY
await imageModelManager.addConfig({
id: 'test-seedream',
name: 'Doubao Seedream 4.0',
providerId: 'seedream',
modelId: seedreamModelId,
enabled: true,
connectionConfig: { apiKey: seedreamApiKey!, baseURL: 'https://ark.cn-beijing.volces.com/api/v3' },
paramOverrides: { size: '2K', watermark: false, outputMimeType: 'image/png' }
} as any)
// 生成图像 - 使用你提供的复杂科幻提示词
const request: ImageRequest = {
prompt: '星际穿越黑洞黑洞里冲出一辆快支离破碎的复古列车抢视觉冲击力电影大片末日既视感动感对比色oc渲染光线追踪动态模糊景深超现实主义深蓝画面通过细腻的丰富的色彩层次塑造主体与场景质感真实暗黑风背景的光影效果营造出氛围整体兼具艺术幻想感夸张的广角透视效果耀光反射极致的光影强引力吞噬',
count: 1,
configId: 'test-seedream',
paramOverrides: { size: '2K', watermark: false, outputMimeType: 'image/png' }
}
const result = await imageService.generate(request)
expect(result).toBeDefined()
expect(result.images).toBeDefined()
expect(result.images.length).toBe(1)
expect(result.images[0].b64).toBeTruthy()
expect(result.images[0].url).toBeFalsy()
expect(result.images[0].mimeType).toBe('image/png') // 验证MIME类型
expect(result.metadata?.modelId).toBe(seedreamModelId)
}, 120000) // 增加超时到120秒
it.skipIf(!runSeedreamTests)('跳过Seedream测试 - 未设置API密钥', () => {
expect(true).toBe(true)
})
})
describe('图像服务错误处理测试', () => {
const runErrorTests = hasGeminiKey || hasOpenAIKey || hasOpenRouterKey || hasSeedreamKey
it.runIf(runErrorTests)('应该能正确处理无效的API密钥', async () => {
// 添加一个有无效API密钥的模型
await imageModelManager.addConfig({
id: 'invalid-model',
name: 'Invalid Model',
providerId: hasGeminiKey ? 'gemini' : 'openai',
modelId: hasGeminiKey ? 'gemini-2.5-flash-image' : 'dall-e-3',
enabled: true,
connectionConfig: { apiKey: 'invalid-key', baseURL: hasGeminiKey ? undefined : 'https://api.openai.com/v1' } as any,
paramOverrides: {}
} as any)
// 尝试生成图像应该失败
const request: ImageRequest = {
prompt: 'Test image generation',
count: 1,
configId: 'invalid-model'
}
await expect(imageService.generate(request)).rejects.toThrow()
}, 30000)
it.skipIf(!runErrorTests)('跳过错误处理测试 - 未设置API密钥', () => {
expect(true).toBe(true)
})
})
describe('多图像生成测试', () => {
const runMultiImageTests = false // 已不支持多图
it.runIf(runMultiImageTests)('应该能生成多张图像', async () => {
await imageModelManager.addConfig({
id: 'test-dalle2-multi',
name: 'DALL-E 2 Multi',
providerId: 'openai',
modelId: 'dall-e-2',
enabled: true,
connectionConfig: { apiKey: process.env.VITE_OPENAI_API_KEY!, baseURL: 'https://api.openai.com/v1' },
paramOverrides: {}
} as any)
// 生成2张图像
const request: ImageRequest = {
prompt: 'A simple geometric pattern',
configId: 'test-dalle2-multi',
count: 2,
paramOverrides: {
size: '256x256' // 使用较小尺寸以节省时间和费用
}
}
const result = await imageService.generate({ ...request, count: 1 })
expect(result).toBeDefined()
expect(result.images).toBeDefined()
expect(result.images.length).toBe(1)
result.images.forEach(image => {
expect(image.b64).toBeDefined()
if (!image.b64) throw new Error('Expected OpenAI image payload')
expect(image.b64.length).toBeGreaterThan(100)
})
}, 120000) // 多图像生成需要更长时间
it.skipIf(!runMultiImageTests)('跳过多图像测试 - 未设置OpenAI API密钥', () => {
expect(true).toBe(true)
})
})
})