Fix bridge profile environment isolation (#796)

This commit is contained in:
ekko
2026-05-16 20:27:23 +08:00
committed by GitHub
parent 7d7c8b7321
commit 8357c8ed84
8 changed files with 602 additions and 133 deletions
+51 -18
View File
@@ -1,4 +1,8 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { existsSync, readFileSync } from 'fs'
import { mkdir, mkdtemp, rm, writeFile } from 'fs/promises'
import { tmpdir } from 'os'
import { join } from 'path'
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
// Mock hermes-cli
vi.mock('../../packages/server/src/services/hermes/hermes-cli', () => ({
@@ -19,27 +23,17 @@ vi.mock('../../packages/server/src/services/hermes/hermes-cli', () => ({
import * as hermesCli from '../../packages/server/src/services/hermes/hermes-cli'
describe('Profile Routes', () => {
const originalHermesHome = process.env.HERMES_HOME
const tempHomes: string[] = []
beforeEach(() => {
vi.clearAllMocks()
})
describe('ensureApiServerConfig (via active profile switch)', () => {
it('should inject api_server config when missing', async () => {
// This tests the logic that profiles.ts ensures api_server config exists
// We test the ensureApiServerConfig behavior indirectly through the module
const { existsSync, readFileSync, writeFileSync } = await import('fs')
vi.mock('fs', () => ({
existsSync: vi.fn().mockReturnValue(true),
readFileSync: vi.fn().mockReturnValue('platforms: {}'),
writeFileSync: vi.fn(),
createReadStream: vi.fn(),
unlinkSync: vi.fn(),
mkdirSync: vi.fn(),
copyFileSync: vi.fn(),
mkdir: vi.fn(),
writeFile: vi.fn(),
}))
})
afterEach(async () => {
if (originalHermesHome === undefined) delete process.env.HERMES_HOME
else process.env.HERMES_HOME = originalHermesHome
await Promise.all(tempHomes.splice(0).map(dir => rm(dir, { recursive: true, force: true })))
})
describe('hermes-cli wrapper', () => {
@@ -84,4 +78,43 @@ describe('Profile Routes', () => {
expect(hermesCli.renameProfile).toHaveBeenCalledWith('old', 'new')
})
})
describe('profile deletion fallback', () => {
it('removes a reserved profile directory when Hermes CLI refuses to delete it', async () => {
const hermesHome = await mkdtemp(join(tmpdir(), 'hermes-profile-delete-'))
tempHomes.push(hermesHome)
process.env.HERMES_HOME = hermesHome
const badProfileDir = join(hermesHome, 'profiles', 'hermes')
await mkdir(badProfileDir, { recursive: true })
await writeFile(join(badProfileDir, 'config.yaml'), 'model:\n default: bad\n', 'utf-8')
await writeFile(join(hermesHome, 'active_profile'), 'hermes\n', 'utf-8')
vi.mocked(hermesCli.deleteProfile).mockResolvedValue(false)
const { remove } = await import('../../packages/server/src/controllers/hermes/profiles')
const ctx: any = { params: { name: 'hermes' }, status: 200, body: undefined }
await remove(ctx)
expect(ctx.status).toBe(200)
expect(ctx.body).toEqual({ success: true, fallback: 'removed_reserved_profile_from_disk' })
expect(existsSync(badProfileDir)).toBe(false)
expect(readFileSync(join(hermesHome, 'active_profile'), 'utf-8')).toBe('default\n')
})
it('does not bypass Hermes CLI failures for normal profile names', async () => {
const hermesHome = await mkdtemp(join(tmpdir(), 'hermes-profile-delete-'))
tempHomes.push(hermesHome)
process.env.HERMES_HOME = hermesHome
const profileDir = join(hermesHome, 'profiles', 'work')
await mkdir(profileDir, { recursive: true })
vi.mocked(hermesCli.deleteProfile).mockResolvedValue(false)
const { remove } = await import('../../packages/server/src/controllers/hermes/profiles')
const ctx: any = { params: { name: 'work' }, status: 200, body: undefined }
await remove(ctx)
expect(ctx.status).toBe(500)
expect(ctx.body).toEqual({ error: 'Failed to delete profile' })
expect(existsSync(profileDir)).toBe(true)
})
})
})