105 lines
4.1 KiB
TypeScript
105 lines
4.1 KiB
TypeScript
import { join, resolve } from 'path'
|
||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||
|
||
const originalWebUiHome = process.env.HERMES_WEB_UI_HOME
|
||
const originalWebuiStateDir = process.env.HERMES_WEBUI_STATE_DIR
|
||
|
||
afterEach(() => {
|
||
vi.doUnmock('../../packages/server/src/services/hermes/hermes-profile')
|
||
vi.doUnmock('../../packages/server/src/services/config-helpers')
|
||
vi.clearAllMocks()
|
||
vi.unstubAllEnvs()
|
||
vi.resetModules()
|
||
if (originalWebUiHome === undefined) delete process.env.HERMES_WEB_UI_HOME
|
||
else process.env.HERMES_WEB_UI_HOME = originalWebUiHome
|
||
if (originalWebuiStateDir === undefined) delete process.env.HERMES_WEBUI_STATE_DIR
|
||
else process.env.HERMES_WEBUI_STATE_DIR = originalWebuiStateDir
|
||
})
|
||
|
||
describe('media controller', () => {
|
||
it('uses Hermes Web UI media directory as the default generated video output path', async () => {
|
||
process.env.HERMES_WEB_UI_HOME = '/tmp/hermes-ui-test-home'
|
||
const { defaultImageOutputPath, defaultMediaOutputPath } = await import('../../packages/server/src/controllers/hermes/media')
|
||
|
||
// 生产侧 getWebUiHome() 用 resolve() 落地 appHome,Windows 上 resolve('/tmp/x')
|
||
// = 'D:\tmp\x',而 join('/tmp/x') = '\tmp\x'(join 不补盘符)。断言必须跟着
|
||
// 生产语义走,否则只在 Windows 上红、Linux CI 上却是绿的。
|
||
const mediaRoot = join(resolve('/tmp/hermes-ui-test-home'), 'media')
|
||
expect(defaultMediaOutputPath('req_123')).toBe(join(mediaRoot, 'req_123.mp4'))
|
||
expect(defaultMediaOutputPath('bad/request:id')).toBe(join(mediaRoot, 'bad_request_id.mp4'))
|
||
expect(defaultImageOutputPath('img_123')).toBe(join(mediaRoot, 'img_123.png'))
|
||
expect(defaultImageOutputPath('bad/request:id', 1)).toBe(join(mediaRoot, 'bad_request_id-2.png'))
|
||
})
|
||
|
||
it('generates images through the requested configured custom provider', async () => {
|
||
vi.stubEnv('AGNES_API_KEY', 'agnes-secret')
|
||
vi.doMock('../../packages/server/src/services/hermes/hermes-profile', () => ({
|
||
getActiveProfileName: () => 'default',
|
||
getProfileDir: () => '/tmp/hermes-ui-test-profile',
|
||
listProfileNamesFromDisk: () => ['default'],
|
||
}))
|
||
vi.doMock('../../packages/server/src/services/config-helpers', () => ({
|
||
readConfigYamlForProfile: vi.fn(async () => ({
|
||
custom_providers: [{
|
||
name: 'agnes',
|
||
base_url: 'https://agnes.example/v1',
|
||
api_key_env: 'AGNES_API_KEY',
|
||
model: 'agnes-image-2.1-flash',
|
||
}],
|
||
})),
|
||
}))
|
||
const fetchMock = vi.fn(async () => new Response(
|
||
'data: {"data":[{"b64_json":"aW1hZ2UtYnl0ZXM="}]}\n\n',
|
||
{ status: 200, headers: { 'Content-Type': 'text/event-stream' } },
|
||
))
|
||
const originalFetch = globalThis.fetch
|
||
globalThis.fetch = fetchMock as any
|
||
try {
|
||
const { apiKeyImageGenerate } = await import('../../packages/server/src/controllers/hermes/media')
|
||
const ctx: any = {
|
||
state: { serverTokenAuth: true },
|
||
query: {},
|
||
request: {
|
||
body: {
|
||
provider: 'agnes',
|
||
mode: 'text',
|
||
prompt: 'make an icon',
|
||
output_path: '/tmp/hermes-ui-agnes-image.png',
|
||
},
|
||
},
|
||
get: vi.fn(() => ''),
|
||
status: 200,
|
||
body: undefined,
|
||
}
|
||
|
||
await apiKeyImageGenerate(ctx)
|
||
|
||
expect(ctx.status).toBe(200)
|
||
expect(ctx.body).toMatchObject({
|
||
ok: true,
|
||
mode: 'text',
|
||
provider: 'agnes',
|
||
base_url: 'https://agnes.example/v1',
|
||
profile: 'default',
|
||
})
|
||
expect(fetchMock).toHaveBeenCalledWith(
|
||
'https://agnes.example/v1/images/generations',
|
||
expect.objectContaining({
|
||
method: 'POST',
|
||
headers: expect.objectContaining({
|
||
Authorization: 'Bearer agnes-secret',
|
||
'Content-Type': 'application/json',
|
||
}),
|
||
}),
|
||
)
|
||
const requestInit = fetchMock.mock.calls[0][1] as RequestInit
|
||
expect(JSON.parse(String(requestInit.body))).toMatchObject({
|
||
model: 'gpt-image-2',
|
||
prompt: 'make an icon',
|
||
})
|
||
} finally {
|
||
globalThis.fetch = originalFetch
|
||
}
|
||
})
|
||
})
|