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.
83 lines
2.8 KiB
TypeScript
83 lines
2.8 KiB
TypeScript
import { EventEmitter } from 'events'
|
||
import { mkdir, mkdtemp, rm, writeFile } from 'fs/promises'
|
||
import { tmpdir } from 'os'
|
||
import { join } from 'path'
|
||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||
|
||
const originalEnv = { ...process.env }
|
||
const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform')
|
||
|
||
class FakeChild extends EventEmitter {
|
||
pid: number
|
||
killSignals: string[] = []
|
||
|
||
constructor(pid: number) {
|
||
super()
|
||
this.pid = pid
|
||
}
|
||
|
||
unref() { /* no-op */ }
|
||
|
||
kill(signal?: string) {
|
||
this.killSignals.push(signal || 'SIGTERM')
|
||
return true
|
||
}
|
||
}
|
||
|
||
let fakeChildren: FakeChild[] = []
|
||
|
||
vi.mock('../../packages/server/src/services/hermes/hermes-process', () => ({
|
||
execHermesWithBin: vi.fn().mockResolvedValue({ stdout: '', stderr: '' }),
|
||
spawnHermesWithBin: vi.fn(() => {
|
||
const child = new FakeChild(20000 + fakeChildren.length)
|
||
fakeChildren.push(child)
|
||
return child
|
||
}),
|
||
}))
|
||
|
||
afterEach(() => {
|
||
vi.useRealTimers()
|
||
vi.restoreAllMocks()
|
||
vi.resetModules()
|
||
process.env = { ...originalEnv }
|
||
if (originalPlatform) Object.defineProperty(process, 'platform', originalPlatform)
|
||
fakeChildren = []
|
||
})
|
||
|
||
describe('profile delete managed gateway lifecycle', () => {
|
||
it('reproduces #1633 and proves delete prep suppresses managed respawn', async () => {
|
||
vi.useFakeTimers()
|
||
vi.resetModules()
|
||
// 固定 platform:停止网关的手段随平台不同(POSIX 走 child.kill('SIGTERM'),
|
||
// Windows 走 taskkill 杀进程树)。不固定的话这个用例在 Windows 上会因为
|
||
// taskkill 是异步的而读不到 killSignals,而且会真的拿假 pid 去杀进程。
|
||
// Windows 分支由 gateway-respawn.test.ts 单独覆盖。
|
||
Object.defineProperty(process, 'platform', { value: 'linux' })
|
||
const home = await mkdtemp(join(tmpdir(), 'wui-1633-'))
|
||
process.env.HERMES_HOME = home
|
||
process.env.HERMES_BIN = '/usr/bin/hermes'
|
||
const profileDir = join(home, 'profiles', 'work')
|
||
await mkdir(profileDir, { recursive: true })
|
||
await writeFile(join(profileDir, 'config.yaml'), 'model:\n default: test\n', 'utf-8')
|
||
|
||
try {
|
||
const { startGatewayRunManaged } = await import('../../packages/server/src/services/hermes/gateway-runner')
|
||
const { prepareGatewayForProfileDelete } = await import('../../packages/server/src/services/hermes/gateway-autostart')
|
||
|
||
startGatewayRunManaged('/usr/bin/hermes', { profileDir })
|
||
expect(fakeChildren).toHaveLength(1)
|
||
|
||
const prep = prepareGatewayForProfileDelete('work')
|
||
expect(fakeChildren[0].killSignals).toEqual(['SIGTERM'])
|
||
|
||
fakeChildren[0].emit('exit', 0, 'SIGTERM')
|
||
await prep
|
||
await vi.advanceTimersByTimeAsync(6000)
|
||
|
||
expect(fakeChildren).toHaveLength(1)
|
||
} finally {
|
||
await rm(home, { recursive: true, force: true })
|
||
}
|
||
})
|
||
})
|