Fix nonblocking preview actions (#1188)
This commit is contained in:
@@ -2,26 +2,45 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { delimiter, dirname, join } from 'path'
|
||||
|
||||
type UpdateControllerMocks = {
|
||||
execFile: ReturnType<typeof vi.fn>
|
||||
execFileSync: ReturnType<typeof vi.fn>
|
||||
spawn: ReturnType<typeof vi.fn>
|
||||
unref: ReturnType<typeof vi.fn>
|
||||
existsSync: ReturnType<typeof vi.fn>
|
||||
readFileSync: ReturnType<typeof vi.fn>
|
||||
appendFileSync: ReturnType<typeof vi.fn>
|
||||
}
|
||||
|
||||
async function loadUpdateController(overrides: Partial<UpdateControllerMocks> = {}) {
|
||||
const execFile = overrides.execFile ?? vi.fn((_command: string, _args: string[], _options: any, callback: any) => callback(null, '', ''))
|
||||
const execFileSync = overrides.execFileSync ?? vi.fn().mockReturnValue('updated')
|
||||
const unref = overrides.unref ?? vi.fn()
|
||||
const spawn = overrides.spawn ?? vi.fn(() => ({ unref, on: vi.fn() }))
|
||||
const existsSync = overrides.existsSync ?? vi.fn(() => true)
|
||||
const readFileSync = overrides.readFileSync ?? vi.fn(() => JSON.stringify({
|
||||
name: 'hermes-web-ui',
|
||||
version: '0.0.0',
|
||||
repository: { url: 'https://github.com/EKKOLearnAI/hermes-web-ui.git' },
|
||||
}))
|
||||
const appendFileSync = overrides.appendFileSync ?? vi.fn()
|
||||
|
||||
vi.resetModules()
|
||||
vi.doMock('child_process', () => ({ execFileSync, spawn }))
|
||||
vi.doMock('fs', () => ({ existsSync }))
|
||||
vi.doMock('child_process', () => ({ execFile, execFileSync, spawn }))
|
||||
vi.doMock('fs', () => ({
|
||||
appendFileSync,
|
||||
closeSync: vi.fn(),
|
||||
existsSync,
|
||||
mkdirSync: vi.fn(),
|
||||
openSync: vi.fn(() => 1),
|
||||
readFileSync,
|
||||
rmSync: vi.fn(),
|
||||
writeFileSync: vi.fn(),
|
||||
}))
|
||||
|
||||
const mod = await import('../../packages/server/src/controllers/update')
|
||||
return {
|
||||
...mod,
|
||||
mocks: { execFileSync, spawn, unref, existsSync },
|
||||
mocks: { execFile, execFileSync, spawn, unref, existsSync, readFileSync, appendFileSync },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,11 +85,13 @@ describe('update controller', () => {
|
||||
vi.useRealTimers()
|
||||
vi.doUnmock('child_process')
|
||||
vi.doUnmock('fs')
|
||||
vi.unstubAllGlobals()
|
||||
if (originalPort === undefined) {
|
||||
delete process.env.PORT
|
||||
} else {
|
||||
process.env.PORT = originalPort
|
||||
}
|
||||
delete process.env.HERMES_WEB_UI_PREVIEW_REPO
|
||||
})
|
||||
|
||||
it('updates and restarts through the running Node executable, not PATH shims', async () => {
|
||||
@@ -188,4 +209,101 @@ describe('update controller', () => {
|
||||
expect(exitSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('loads preview tags through async git with a short timeout', async () => {
|
||||
process.env.HERMES_WEB_UI_PREVIEW_REPO = 'https://github.com/EKKOLearnAI/hermes-web-ui'
|
||||
const execFile = vi.fn((_command: string, _args: string[], _options: any, callback: any) => {
|
||||
callback(null, [
|
||||
'abc123\trefs/tags/v0.6.6',
|
||||
'def456\trefs/tags/v0.6.7',
|
||||
].join('\n'), '')
|
||||
})
|
||||
const execFileSync = vi.fn(() => 'git version 2.0.0')
|
||||
const { previewTags, mocks } = await loadUpdateController({ execFile, execFileSync })
|
||||
const ctx = createMockCtx()
|
||||
|
||||
await previewTags(ctx)
|
||||
|
||||
expect(ctx.status).toBe(200)
|
||||
expect(ctx.body).toEqual({
|
||||
tags: [
|
||||
{ name: 'main', sha: '' },
|
||||
{ name: 'v0.6.7', sha: 'def456' },
|
||||
{ name: 'v0.6.6', sha: 'abc123' },
|
||||
],
|
||||
})
|
||||
expect(mocks.execFile).toHaveBeenCalledWith(
|
||||
'git',
|
||||
['ls-remote', '--tags', '--refs', 'https://github.com/EKKOLearnAI/hermes-web-ui.git'],
|
||||
expect.objectContaining({ timeout: 8000 }),
|
||||
expect.any(Function),
|
||||
)
|
||||
})
|
||||
|
||||
it('falls back to GitHub API when async git tag loading fails', async () => {
|
||||
process.env.HERMES_WEB_UI_PREVIEW_REPO = 'https://github.com/EKKOLearnAI/hermes-web-ui'
|
||||
const execFile = vi.fn((_command: string, _args: string[], _options: any, callback: any) => {
|
||||
callback(new Error('git timeout'), '', '')
|
||||
})
|
||||
const execFileSync = vi.fn(() => 'git version 2.0.0')
|
||||
const fetchMock = vi.fn(async () => ({
|
||||
ok: true,
|
||||
json: async () => [
|
||||
{ name: 'v0.6.7', commit: { sha: 'def456' } },
|
||||
{ name: 'v0.6.6', commit: { sha: 'abc123' } },
|
||||
],
|
||||
}))
|
||||
vi.stubGlobal('fetch', fetchMock)
|
||||
const { previewTags } = await loadUpdateController({ execFile, execFileSync })
|
||||
const ctx = createMockCtx()
|
||||
|
||||
await previewTags(ctx)
|
||||
|
||||
expect(ctx.status).toBe(200)
|
||||
expect(ctx.body).toEqual({
|
||||
tags: [
|
||||
{ name: 'main', sha: '' },
|
||||
{ name: 'v0.6.7', sha: 'def456' },
|
||||
{ name: 'v0.6.6', sha: 'abc123' },
|
||||
],
|
||||
})
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
'https://api.github.com/repos/EKKOLearnAI/hermes-web-ui/tags?per_page=100',
|
||||
expect.objectContaining({
|
||||
headers: { 'User-Agent': 'hermes-web-ui-preview' },
|
||||
signal: expect.any(AbortSignal),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('runs preview npm install through async execFile', async () => {
|
||||
const npmCli = getNpmCliPath()
|
||||
const execFile = vi.fn((_command: string, _args: string[], _options: any, callback: any) => {
|
||||
callback(null, 'installed', '')
|
||||
})
|
||||
const execFileSync = vi.fn(() => '')
|
||||
const { installPreview, mocks } = await loadUpdateController({ execFile, execFileSync })
|
||||
const ctx = createMockCtx()
|
||||
|
||||
await installPreview(ctx)
|
||||
|
||||
expect(ctx.status).toBe(202)
|
||||
expect((ctx.body as any).success).toBe(true)
|
||||
expect((ctx.body as any).accepted).toBe(true)
|
||||
expect((ctx.body as any).active_action).toBe('install')
|
||||
expect(mocks.execFile).toHaveBeenCalledWith(
|
||||
process.execPath,
|
||||
[npmCli, 'install', '--include=dev', '--ignore-scripts'],
|
||||
expect.objectContaining({
|
||||
timeout: 15 * 60 * 1000,
|
||||
cwd: expect.any(String),
|
||||
}),
|
||||
expect.any(Function),
|
||||
)
|
||||
expect(mocks.execFileSync).not.toHaveBeenCalledWith(
|
||||
process.execPath,
|
||||
[npmCli, 'install', '--include=dev', '--ignore-scripts'],
|
||||
expect.any(Object),
|
||||
)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user