返回 oh-my-ppt
seedream.test.ts
根目录 / tests / unit / image-generation / seedream.test.ts
1 import { afterEach, describe, expect, it, vi } from 'vitest'
2 import {
3 resolveImageGenerationProvider,
4 type ResolvedImageModelConfig
5 } from '../../../src/main/agent-runtime/provider/image'
6
7 const seedreamAdapter = resolveImageGenerationProvider('seedream')
8
9 const pngBase64 =
10 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAFgwJ/lK3G1wAAAABJRU5ErkJggg=='
11 const pngBytes = Buffer.from(pngBase64, 'base64')
12
13 const createConfig = (
14 modelConfig: Record<string, unknown> = {}
15 ): ResolvedImageModelConfig => ({
16 id: 'seedream-config',
17 name: 'Seedream',
18 provider: 'seedream',
19 active: true,
20 modelConfig: {
21 apiKey: 'ark-key',
22 model: 'doubao-seedream-5-0-260128',
23 ...modelConfig
24 }
25 })
26
27 describe('seedream image generation provider', () => {
28 afterEach(() => {
29 vi.restoreAllMocks()
30 vi.unstubAllGlobals()
31 })
32
33 it('posts an Ark image generation request and reads base64 images', async () => {
34 const fetchMock = vi.fn().mockResolvedValue(
35 new Response(
36 JSON.stringify({
37 data: [{ b64_json: pngBase64 }]
38 }),
39 {
40 status: 200,
41 headers: { 'content-type': 'application/json' }
42 }
43 )
44 )
45 vi.stubGlobal('fetch', fetchMock)
46
47 const results = await seedreamAdapter.generate(
48 createConfig({ response_format: 'b64_json', sizes: ['1K', '2K', '4K'] }),
49 {
50 prompt: 'a clean presentation background',
51 size: '2K',
52 count: 1,
53 negativePrompt: 'low quality',
54 seed: 42
55 }
56 )
57
58 expect(fetchMock).toHaveBeenCalledTimes(1)
59 const [endpoint, init] = fetchMock.mock.calls[0] as [string, RequestInit]
60 expect(endpoint).toBe('https://ark.cn-beijing.volces.com/api/v3/images/generations')
61 expect(init.method).toBe('POST')
62 expect(init.headers).toMatchObject({
63 authorization: 'Bearer ark-key',
64 'content-type': 'application/json'
65 })
66 const body = JSON.parse(String(init.body))
67 expect(body).toMatchObject({
68 model: 'doubao-seedream-5-0-260128',
69 prompt: 'a clean presentation background',
70 size: '2K',
71 n: 1,
72 response_format: 'b64_json',
73 sequential_image_generation: 'disabled',
74 stream: false,
75 negative_prompt: 'low quality',
76 seed: 42
77 })
78 expect(body).not.toHaveProperty('sizes')
79 expect(results).toHaveLength(1)
80 expect(results[0].mimeType).toBe('image/png')
81 expect(results[0].extension).toBe('.png')
82 expect(results[0].bytes.length).toBeGreaterThan(0)
83 })
84
85 it('downloads images from Seedream url results and ignores returned size metadata', async () => {
86 const fetchMock = vi
87 .fn()
88 .mockResolvedValueOnce(
89 new Response(
90 JSON.stringify({
91 model: 'doubao-seedream-5-0-260128',
92 created: 1757321139,
93 data: [
94 {
95 url: 'https://example.test/seedream.png',
96 size: '3104x1312'
97 }
98 ],
99 usage: {
100 generated_images: 1,
101 output_tokens: 10,
102 total_tokens: 10
103 }
104 }),
105 { status: 200 }
106 )
107 )
108 .mockResolvedValueOnce(
109 new Response(pngBytes, {
110 status: 200,
111 headers: { 'content-type': 'image/png' }
112 })
113 )
114 vi.stubGlobal('fetch', fetchMock)
115
116 const results = await seedreamAdapter.generate(createConfig(), {
117 prompt: 'hero image',
118 size: '2K',
119 count: 1
120 })
121
122 expect(fetchMock).toHaveBeenCalledTimes(2)
123 expect(fetchMock.mock.calls[1][0]).toBe('https://example.test/seedream.png')
124 const [, init] = fetchMock.mock.calls[0] as [string, RequestInit]
125 expect(JSON.parse(String(init.body))).toMatchObject({
126 response_format: 'url',
127 size: '2K'
128 })
129 expect(results).toHaveLength(1)
130 expect(results[0].mimeType).toBe('image/png')
131 expect(results[0].bytes.equals(pngBytes)).toBe(true)
132 })
133
134 it('allows endpoint, headers, and request body overrides from model config', async () => {
135 const fetchMock = vi.fn().mockResolvedValue(
136 new Response(JSON.stringify({ data: [{ b64_json: pngBase64 }] }), { status: 200 })
137 )
138 vi.stubGlobal('fetch', fetchMock)
139
140 await seedreamAdapter.generate(
141 createConfig({
142 endpoint: 'https://example.test/images',
143 headers: { 'x-team': 'slides' },
144 requestBody: { watermark: true, size: '2048x2048' }
145 }),
146 {
147 prompt: 'hero image',
148 size: '1:1',
149 count: 2
150 }
151 )
152
153 const [endpoint, init] = fetchMock.mock.calls[0] as [string, RequestInit]
154 expect(endpoint).toBe('https://example.test/images')
155 expect(init.headers).toMatchObject({ 'x-team': 'slides' })
156 expect(JSON.parse(String(init.body))).toMatchObject({
157 size: '2048x2048',
158 n: 2,
159 watermark: true
160 })
161 })
162
163 it('treats baseUrl with a path as the full endpoint', async () => {
164 const fetchMock = vi.fn().mockResolvedValue(
165 new Response(JSON.stringify({ data: [{ b64_json: pngBase64 }] }), { status: 200 })
166 )
167 vi.stubGlobal('fetch', fetchMock)
168
169 await seedreamAdapter.generate(
170 createConfig({
171 baseUrl: 'https://ark.cn-beijing.volces.com/api/v3/images/generations',
172 model: 'doubao-seedream-4-5-251128'
173 }),
174 {
175 prompt: 'hero image',
176 size: '1:1',
177 count: 1
178 }
179 )
180
181 const [endpoint, init] = fetchMock.mock.calls[0] as [string, RequestInit]
182 expect(endpoint).toBe('https://ark.cn-beijing.volces.com/api/v3/images/generations')
183 expect(JSON.parse(String(init.body))).toMatchObject({
184 model: 'doubao-seedream-4-5-251128'
185 })
186 })
187 })
188
188 lines TYPESCRIPT