mirror of
https://github.com/EKKOLearnAI/hermes-web-ui.git
synced 2026-05-25 13:30:14 +00:00
5df8734495
* fix: update tests for new batch delete and update mechanism changes **sessions-routes.test.ts:** - Add missing batchRemove mock to controller mock - Fix "No batchRemove export defined" error **update-controller.test.ts:** - Update test to expect direct npm/npm.cmd calls instead of dirname(process.execPath) - Update timeout from 120000 to 10 * 60 * 1000 (10 minutes) - Update spawn path check to use dynamic global prefix (expect.any) Tests now match the refactored update mechanism that uses npm prefix -g for reliable path resolution. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: add speechSynthesis mock to message-item-highlight tests * test: fix all failing tests - Add approvals mock to session-settings test - Fix NSwitch stub to properly emit events - Update usage stats test expectations for new field structure - Mock getDb in model-context tests to avoid database lock errors - Add speechSynthesis API mock to message-item-highlight tests Related to v0.5.12 feature changes --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
116 lines
3.3 KiB
TypeScript
116 lines
3.3 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { dirname, join } from 'path'
|
|
|
|
type ChildProcessMocks = {
|
|
execFileSync: ReturnType<typeof vi.fn>
|
|
spawn: ReturnType<typeof vi.fn>
|
|
unref: ReturnType<typeof vi.fn>
|
|
}
|
|
|
|
async function loadUpdateController(overrides: Partial<ChildProcessMocks> = {}) {
|
|
const execFileSync = overrides.execFileSync ?? vi.fn().mockReturnValue('updated')
|
|
const unref = overrides.unref ?? vi.fn()
|
|
const spawn = overrides.spawn ?? vi.fn(() => ({ unref }))
|
|
|
|
vi.resetModules()
|
|
vi.doMock('child_process', () => ({ execFileSync, spawn }))
|
|
|
|
const mod = await import('../../packages/server/src/controllers/update')
|
|
return {
|
|
...mod,
|
|
mocks: { execFileSync, spawn, unref },
|
|
}
|
|
}
|
|
|
|
function createMockCtx() {
|
|
return {
|
|
status: 200,
|
|
body: null as unknown,
|
|
}
|
|
}
|
|
|
|
describe('update controller', () => {
|
|
const originalPort = process.env.PORT
|
|
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => undefined) as never)
|
|
|
|
beforeEach(() => {
|
|
vi.useFakeTimers()
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers()
|
|
if (originalPort === undefined) {
|
|
delete process.env.PORT
|
|
} else {
|
|
process.env.PORT = originalPort
|
|
}
|
|
})
|
|
|
|
it('updates using npm from PATH and restarts via global prefix', async () => {
|
|
process.env.PORT = '9129'
|
|
const { handleUpdate, mocks } = await loadUpdateController()
|
|
const ctx = createMockCtx()
|
|
|
|
await handleUpdate(ctx)
|
|
|
|
expect(mocks.execFileSync).toHaveBeenCalledWith(
|
|
process.platform === 'win32' ? 'npm.cmd' : 'npm',
|
|
['install', '-g', 'hermes-web-ui@latest'],
|
|
{
|
|
encoding: 'utf-8',
|
|
timeout: 10 * 60 * 1000,
|
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
},
|
|
)
|
|
expect(ctx.body).toEqual({ success: true, message: 'updated' })
|
|
|
|
vi.runAllTimers()
|
|
|
|
// Note: spawn is called with getGlobalCliBin() result
|
|
expect(mocks.spawn).toHaveBeenCalledWith(
|
|
expect.any(String), // Dynamic path based on npm prefix -g
|
|
['restart', '--port', '9129'],
|
|
{
|
|
detached: true,
|
|
stdio: 'ignore',
|
|
windowsHide: true,
|
|
},
|
|
)
|
|
expect(mocks.unref).toHaveBeenCalledOnce()
|
|
expect(exitSpy).toHaveBeenCalledWith(0)
|
|
})
|
|
|
|
it('falls back to the default port when PORT is not set', async () => {
|
|
delete process.env.PORT
|
|
const { handleUpdate, mocks } = await loadUpdateController()
|
|
const ctx = createMockCtx()
|
|
|
|
await handleUpdate(ctx)
|
|
vi.runAllTimers()
|
|
|
|
expect(mocks.spawn).toHaveBeenCalledWith(
|
|
expect.any(String),
|
|
['restart', '--port', '8648'],
|
|
expect.objectContaining({ detached: true, stdio: 'ignore', windowsHide: true }),
|
|
)
|
|
})
|
|
|
|
it('returns a 500 with stderr when installation fails', async () => {
|
|
const execFileSync = vi.fn(() => {
|
|
const error = new Error('install failed') as Error & { stderr?: string }
|
|
error.stderr = 'engine mismatch'
|
|
throw error
|
|
})
|
|
const { handleUpdate, mocks } = await loadUpdateController({ execFileSync })
|
|
const ctx = createMockCtx()
|
|
|
|
await handleUpdate(ctx)
|
|
|
|
expect(ctx.status).toBe(500)
|
|
expect(ctx.body).toEqual({ success: false, message: 'engine mismatch' })
|
|
expect(mocks.spawn).not.toHaveBeenCalled()
|
|
expect(exitSpy).not.toHaveBeenCalled()
|
|
})
|
|
})
|