initialize Electron desktop app and remove unused files
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
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 {
|
||||
applyAnthropicOAuthDefaultModel,
|
||||
saveAnthropicOAuthTokensForProfile,
|
||||
status as anthropicStatus,
|
||||
} from '../../packages/server/src/controllers/hermes/anthropic-auth'
|
||||
import {
|
||||
applyGeminiOAuthDefaultModel,
|
||||
resolveGeminiOAuthClientCredentials,
|
||||
saveGeminiOAuthTokensForProfile,
|
||||
status as geminiStatus,
|
||||
} from '../../packages/server/src/controllers/hermes/gemini-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('Anthropic and Gemini OAuth controllers', () => {
|
||||
const originalGeminiClientId = process.env.HERMES_GEMINI_CLIENT_ID
|
||||
const originalGeminiClientSecret = process.env.HERMES_GEMINI_CLIENT_SECRET
|
||||
|
||||
beforeEach(() => {
|
||||
hermesHome = mkdtempSync(join(tmpdir(), 'hwui-oauth-providers-'))
|
||||
process.env.HERMES_HOME = hermesHome
|
||||
delete process.env.HERMES_GEMINI_CLIENT_ID
|
||||
delete process.env.HERMES_GEMINI_CLIENT_SECRET
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env.HERMES_HOME
|
||||
if (originalGeminiClientId === undefined) delete process.env.HERMES_GEMINI_CLIENT_ID
|
||||
else process.env.HERMES_GEMINI_CLIENT_ID = originalGeminiClientId
|
||||
if (originalGeminiClientSecret === undefined) delete process.env.HERMES_GEMINI_CLIENT_SECRET
|
||||
else process.env.HERMES_GEMINI_CLIENT_SECRET = originalGeminiClientSecret
|
||||
if (hermesHome) rmSync(hermesHome, { recursive: true, force: true })
|
||||
hermesHome = ''
|
||||
})
|
||||
|
||||
it('uses provider-compatible default models when applying OAuth defaults', () => {
|
||||
expect(applyAnthropicOAuthDefaultModel({
|
||||
model: { provider: 'deepseek', default: 'deepseek-chat', base_url: 'x', api_key: 'y' },
|
||||
}).model).toEqual({ provider: 'claude-oauth', default: 'claude-sonnet-4-6' })
|
||||
|
||||
expect(applyGeminiOAuthDefaultModel({
|
||||
model: { provider: 'anthropic', default: 'claude-sonnet-4-6', base_url: 'x', api_key: 'y' },
|
||||
}).model).toEqual({ provider: 'google-gemini-cli', default: 'gemini-3.1-pro-preview' })
|
||||
})
|
||||
|
||||
it('uses the public Gemini CLI OAuth client when env credentials are not configured', () => {
|
||||
const defaults = resolveGeminiOAuthClientCredentials()
|
||||
expect(defaults.clientId).toMatch(/^681255809395-.+\.apps\.googleusercontent\.com$/)
|
||||
expect(defaults.clientSecret).toMatch(/^GOCSPX-.+$/)
|
||||
|
||||
process.env.HERMES_GEMINI_CLIENT_ID = 'custom-client-id'
|
||||
process.env.HERMES_GEMINI_CLIENT_SECRET = 'custom-client-secret'
|
||||
|
||||
expect(resolveGeminiOAuthClientCredentials()).toEqual({
|
||||
clientId: 'custom-client-id',
|
||||
clientSecret: 'custom-client-secret',
|
||||
})
|
||||
})
|
||||
|
||||
it('persists Anthropic OAuth credentials 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 saveAnthropicOAuthTokensForProfile('research', {
|
||||
access_token: 'anthropic-access-token',
|
||||
refresh_token: 'anthropic-refresh-token',
|
||||
expires_in: 3600,
|
||||
})
|
||||
|
||||
expect(existsSync(join(hermesHome, 'auth.json'))).toBe(false)
|
||||
const auth = readJson('profiles/research/auth.json')
|
||||
expect(auth.providers['claude-oauth'].tokens.access_token).toBe('anthropic-access-token')
|
||||
expect(auth.credential_pool['claude-oauth'][0].refresh_token).toBe('anthropic-refresh-token')
|
||||
expect(auth.providers.anthropic.tokens.access_token).toBe('anthropic-access-token')
|
||||
expect(auth.credential_pool.anthropic[0].refresh_token).toBe('anthropic-refresh-token')
|
||||
expect(readJson('profiles/research/.anthropic_oauth.json').accessToken).toBe('anthropic-access-token')
|
||||
expect(readYaml('config.yaml').model).toEqual({ provider: 'deepseek', default: 'deepseek-chat' })
|
||||
expect(readYaml('profiles/research/config.yaml').model).toEqual({ provider: 'claude-oauth', default: 'claude-sonnet-4-6' })
|
||||
|
||||
const ctx = makeCtx('research')
|
||||
await anthropicStatus(ctx)
|
||||
expect(ctx.body).toMatchObject({ authenticated: true })
|
||||
})
|
||||
|
||||
it('persists Gemini OAuth credentials 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 saveGeminiOAuthTokensForProfile('research', {
|
||||
access_token: 'gemini-access-token',
|
||||
refresh_token: 'gemini-refresh-token',
|
||||
expires_in: 3600,
|
||||
}, 'user@example.com')
|
||||
|
||||
expect(existsSync(join(hermesHome, 'auth.json'))).toBe(false)
|
||||
const auth = readJson('profiles/research/auth.json')
|
||||
expect(auth.providers['google-gemini-cli'].access_token).toBe('gemini-access-token')
|
||||
expect(auth.credential_pool['google-gemini-cli'][0].refresh_token).toBe('gemini-refresh-token')
|
||||
expect(readJson('profiles/research/auth/google_oauth.json')).toMatchObject({
|
||||
access: 'gemini-access-token',
|
||||
refresh: 'gemini-refresh-token',
|
||||
email: 'user@example.com',
|
||||
})
|
||||
expect(readYaml('config.yaml').model).toEqual({ provider: 'deepseek', default: 'deepseek-chat' })
|
||||
expect(readYaml('profiles/research/config.yaml').model).toEqual({ provider: 'google-gemini-cli', default: 'gemini-3.1-pro-preview' })
|
||||
|
||||
const ctx = makeCtx('research')
|
||||
await geminiStatus(ctx)
|
||||
expect(ctx.body).toMatchObject({ authenticated: true, email: 'user@example.com' })
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user