Files
hermes-ui/tests/server/media-controller.test.ts
toor a1fff482bf make the test suite and openapi generator platform-independent
Windows checkouts turned LF into CRLF (core.autocrlf=true), and several
tests plus the openapi generator hardcoded LF or POSIX path separators.
The result was a suite that was green on Linux CI but red on Windows, and
an openapi.json whose /api/hermes/* routes silently disappeared when
generated on Windows.

Pin the repository to LF via .gitattributes, strip shebangs before
Vite's SSR transform, and key route files by their posix path. Add a
manually triggered macOS verification workflow so the same checks run
on a self-hosted macOS runner.
2026-10-20 11:17:34 +08:00

105 lines
4.1 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 { 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
}
})
})