124 lines
4.1 KiB
TypeScript
124 lines
4.1 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'fs'
|
|
import { dirname, join } from 'path'
|
|
import { tmpdir } from 'os'
|
|
import YAML from 'js-yaml'
|
|
import { applyXaiOAuthDefaultModel, saveXaiOAuthTokensForProfile, status } from '../../packages/server/src/controllers/hermes/xai-auth'
|
|
|
|
let hermesHome = ''
|
|
|
|
function writeFile(relativePath: string, content: string) {
|
|
const target = join(hermesHome, relativePath)
|
|
mkdirSync(dirname(target), { recursive: true })
|
|
writeFileSync(target, content)
|
|
}
|
|
|
|
function readYaml(relativePath: string) {
|
|
return YAML.load(readFileSync(join(hermesHome, relativePath), 'utf-8')) as any
|
|
}
|
|
|
|
function readJson(relativePath: string) {
|
|
return JSON.parse(readFileSync(join(hermesHome, relativePath), 'utf-8'))
|
|
}
|
|
|
|
function makeCtx(profile: string): any {
|
|
return {
|
|
state: { profile: { name: profile } },
|
|
query: {},
|
|
request: { body: {} },
|
|
get: () => '',
|
|
status: 200,
|
|
body: undefined as unknown,
|
|
}
|
|
}
|
|
|
|
describe('xAI auth controller', () => {
|
|
beforeEach(() => {
|
|
hermesHome = mkdtempSync(join(tmpdir(), 'hwui-xai-auth-'))
|
|
process.env.HERMES_HOME = hermesHome
|
|
})
|
|
|
|
afterEach(() => {
|
|
delete process.env.HERMES_HOME
|
|
if (hermesHome) rmSync(hermesHome, { recursive: true, force: true })
|
|
hermesHome = ''
|
|
})
|
|
|
|
it('does not keep a non-xAI model when switching the default provider to xai-oauth', () => {
|
|
const config = applyXaiOAuthDefaultModel({
|
|
model: {
|
|
default: 'glm-5-turbo',
|
|
provider: 'custom:glm-coding-plan',
|
|
base_url: 'https://api.z.ai/api/anthropic',
|
|
api_key: 'secret',
|
|
},
|
|
})
|
|
|
|
expect(config.model).toEqual({
|
|
default: 'grok-4.3',
|
|
provider: 'xai-oauth',
|
|
})
|
|
})
|
|
|
|
it('preserves an existing Grok model when refreshing xai-oauth credentials', () => {
|
|
const config = applyXaiOAuthDefaultModel({
|
|
model: {
|
|
default: 'grok-4.20-reasoning',
|
|
provider: 'xai-oauth',
|
|
},
|
|
})
|
|
|
|
expect(config.model).toEqual({
|
|
default: 'grok-4.20-reasoning',
|
|
provider: 'xai-oauth',
|
|
})
|
|
})
|
|
|
|
it('persists OAuth credentials and default model in the request-scoped profile only', async () => {
|
|
mkdirSync(join(hermesHome, 'profiles', 'research'), { recursive: true })
|
|
writeFile('config.yaml', 'model:\n provider: deepseek\n default: deepseek-chat\n')
|
|
writeFile('profiles/research/config.yaml', 'model:\n provider: openrouter\n default: openrouter-model\n')
|
|
|
|
await saveXaiOAuthTokensForProfile(
|
|
'research',
|
|
{
|
|
discovery: { token_endpoint: 'https://auth.x.ai/oauth/token' },
|
|
redirectUri: 'http://127.0.0.1:56121/callback',
|
|
},
|
|
{
|
|
access_token: 'research-access-token',
|
|
refresh_token: 'research-refresh-token',
|
|
expires_in: 3600,
|
|
token_type: 'Bearer',
|
|
},
|
|
)
|
|
|
|
expect(existsSync(join(hermesHome, 'auth.json'))).toBe(false)
|
|
const auth = readJson('profiles/research/auth.json')
|
|
expect(auth.providers['xai-oauth'].tokens.access_token).toBe('research-access-token')
|
|
expect(auth.credential_pool['xai-oauth'][0].refresh_token).toBe('research-refresh-token')
|
|
|
|
expect(readYaml('config.yaml').model).toEqual({ provider: 'deepseek', default: 'deepseek-chat' })
|
|
expect(readYaml('profiles/research/config.yaml').model).toEqual({ provider: 'xai-oauth', default: 'grok-4.3' })
|
|
})
|
|
|
|
it('checks xAI OAuth status against the request-scoped profile', async () => {
|
|
mkdirSync(join(hermesHome, 'profiles', 'research'), { recursive: true })
|
|
writeFile('auth.json', JSON.stringify({ version: 1, providers: {}, credential_pool: {} }, null, 2))
|
|
writeFile('profiles/research/auth.json', JSON.stringify({
|
|
version: 1,
|
|
providers: {
|
|
'xai-oauth': {
|
|
last_refresh: '2026-06-02T00:00:00.000Z',
|
|
tokens: { access_token: 'research-access-token' },
|
|
},
|
|
},
|
|
}, null, 2))
|
|
|
|
const ctx = makeCtx('research')
|
|
await status(ctx)
|
|
|
|
expect(ctx.body).toEqual({ authenticated: true, last_refresh: '2026-06-02T00:00:00.000Z' })
|
|
})
|
|
})
|