Files
hermes-web-ui/tests/server/hermes-plugins-config.test.ts
2026-07-02 09:17:47 +08:00

142 lines
4.4 KiB
TypeScript

import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'fs'
import { tmpdir } from 'os'
import { join } from 'path'
import YAML from 'js-yaml'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
describe('Hermes plugin configuration', () => {
const originalEnv = { ...process.env }
let tempDir = ''
beforeEach(() => {
vi.resetModules()
tempDir = mkdtempSync(join(tmpdir(), 'hermes-plugins-config-'))
process.env = { ...originalEnv }
process.env.HERMES_HOME = join(tempDir, 'home')
installFakeHermes()
})
afterEach(() => {
process.env = { ...originalEnv }
if (tempDir) rmSync(tempDir, { recursive: true, force: true })
})
function installFakeHermes() {
const agentRoot = join(tempDir, 'agent')
const venvBin = join(agentRoot, '.venv', 'bin')
const hermesCliDir = join(agentRoot, 'hermes_cli')
const fakePython = join(venvBin, 'python')
const fakeHermes = join(venvBin, 'hermes')
mkdirSync(venvBin, { recursive: true })
mkdirSync(hermesCliDir, { recursive: true })
writeFileSync(join(agentRoot, 'run_agent.py'), '')
writeFileSync(join(hermesCliDir, 'plugins.py'), '')
writeFileSync(fakePython, [
'#!/bin/sh',
'printf "%s\\n" "$PLUGIN_JSON"',
'',
].join('\n'))
chmodSync(fakePython, 0o755)
writeFileSync(fakeHermes, `#!${fakePython}\n`)
chmodSync(fakeHermes, 0o755)
process.env.HERMES_BIN = fakeHermes
process.env.PLUGIN_JSON = JSON.stringify({
plugins: [{
key: 'local-plugin',
name: 'local-plugin',
kind: 'standalone',
source: 'user',
configStatus: 'not-enabled',
effectiveStatus: 'inactive',
version: '',
description: '',
author: '',
path: join(process.env.HERMES_HOME!, 'plugins', 'local-plugin'),
providesTools: [],
providesHooks: [],
requiresEnv: [],
}],
warnings: [],
metadata: {
hermesAgentRoot: agentRoot,
pythonExecutable: fakePython,
cwd: process.cwd(),
projectPluginsEnabled: false,
},
})
}
function readConfig() {
return YAML.load(readFileSync(join(process.env.HERMES_HOME!, 'config.yaml'), 'utf-8')) as any
}
it('enables standalone user plugins in the active profile config', async () => {
mkdirSync(process.env.HERMES_HOME!, { recursive: true })
writeFileSync(join(process.env.HERMES_HOME!, 'config.yaml'), [
'plugins:',
' disabled:',
' - local-plugin',
'',
].join('\n'))
const { setHermesPluginEnabled } = await import('../../packages/server/src/services/hermes/plugins')
await expect(setHermesPluginEnabled(undefined, 'local-plugin', true)).resolves.toEqual({
key: 'local-plugin',
enabled: true,
})
expect(readConfig().plugins).toEqual({
enabled: ['local-plugin'],
disabled: [],
})
})
it('disables standalone user plugins in the active profile config', async () => {
mkdirSync(process.env.HERMES_HOME!, { recursive: true })
writeFileSync(join(process.env.HERMES_HOME!, 'config.yaml'), [
'plugins:',
' enabled:',
' - local-plugin',
'',
].join('\n'))
const { setHermesPluginEnabled } = await import('../../packages/server/src/services/hermes/plugins')
await expect(setHermesPluginEnabled(undefined, 'local-plugin', false)).resolves.toEqual({
key: 'local-plugin',
enabled: false,
})
expect(readConfig().plugins).toEqual({
enabled: [],
disabled: ['local-plugin'],
})
})
it('rejects bundled plugins because they are managed by Hermes Agent', async () => {
process.env.PLUGIN_JSON = JSON.stringify({
plugins: [{
key: 'bundled-plugin',
name: 'bundled-plugin',
kind: 'standalone',
source: 'bundled',
configStatus: 'auto',
effectiveStatus: 'auto-active',
version: '',
description: '',
author: '',
path: '',
providesTools: [],
providesHooks: [],
requiresEnv: [],
}],
warnings: [],
metadata: { hermesAgentRoot: '', pythonExecutable: '', cwd: '', projectPluginsEnabled: false },
})
const { setHermesPluginEnabled } = await import('../../packages/server/src/services/hermes/plugins')
await expect(setHermesPluginEnabled(undefined, 'bundled-plugin', false)).rejects.toThrow('cannot be managed')
})
})