Files
hapi/hub/src/web/routes/codexDesktop.test.ts
T
f9ef3a4489 feat(codex): import local Codex sessions into Hapi (#796)
* local: add Codex Desktop session sync controls

* feat(codex): import local Codex sessions into Hapi

---------

Co-authored-by: Codex Local <codex-local@example.invalid>
2026-06-04 17:53:12 +08:00

150 lines
5.0 KiB
TypeScript

import { afterEach, describe, expect, it } from 'bun:test'
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { Hono } from 'hono'
import { AGENT_MESSAGE_PAYLOAD_TYPE } from '@hapi/protocol'
import { Store } from '../../store'
import type { SyncEngine } from '../../sync/syncEngine'
import type { WebAppEnv } from '../middleware/auth'
import { createCodexDesktopRoutes, importSelectedCodexSessions } from './codexDesktop'
const originalCodexHome = process.env.CODEX_HOME
function createTranscript(codexHome: string, sessionId: string): void {
const sessionDir = join(codexHome, 'sessions', '2026', '06', '04')
mkdirSync(sessionDir, { recursive: true })
const transcriptPath = join(sessionDir, `rollout-${sessionId}.jsonl`)
const lines = [
{
type: 'session_meta',
payload: {
id: sessionId,
cwd: 'C:\\work\\project',
originator: 'codex_cli_rs',
cli_version: '0.0.0-test'
}
},
{
type: 'response_item',
payload: {
type: 'message',
role: 'user',
content: [{ type: 'input_text', text: 'normal user message' }]
}
},
{
type: 'response_item',
payload: {
type: 'message',
role: 'assistant',
content: [{ type: 'output_text', text: 'normal assistant message' }]
}
}
]
writeFileSync(transcriptPath, `${lines.map((line) => JSON.stringify(line)).join('\n')}\n`, 'utf-8')
}
function createRoutesApp(namespace: string): Hono<WebAppEnv> {
const app = new Hono<WebAppEnv>()
app.use('*', async (c, next) => {
c.set('namespace', namespace)
await next()
})
app.route('/api', createCodexDesktopRoutes({
store: new Store(':memory:'),
getSyncEngine: () => null
}))
return app
}
describe('Codex Desktop import routes', () => {
afterEach(() => {
if (originalCodexHome === undefined) {
delete process.env.CODEX_HOME
} else {
process.env.CODEX_HOME = originalCodexHome
}
})
it('imports normal response_item chat messages', async () => {
const codexHome = mkdtempSync(join(tmpdir(), 'hapi-codex-home-test-'))
const store = new Store(':memory:')
const codexSessionId = '11111111-1111-4111-8111-111111111111'
process.env.CODEX_HOME = codexHome
try {
createTranscript(codexHome, codexSessionId)
const result = await importSelectedCodexSessions({
codexSessionIds: [codexSessionId],
store,
namespace: 'default',
getSyncEngine: () => null
})
expect(result.success).toBe(true)
const session = store.sessions.getSessionsByNamespace('default')[0]
expect(session).toBeDefined()
const messages = store.messages.getAllMessages(session.id)
expect(messages).toHaveLength(2)
expect(messages[0].content).toEqual({
role: 'user',
content: {
type: 'text',
text: 'normal user message'
},
meta: {
sentFrom: 'cli'
}
})
expect(messages[1].content).toEqual({
role: 'agent',
content: {
type: AGENT_MESSAGE_PAYLOAD_TYPE,
data: {
type: 'message',
message: 'normal assistant message',
id: expect.any(String)
}
},
meta: {
sentFrom: 'cli'
}
})
} finally {
store.close()
rmSync(codexHome, { recursive: true, force: true })
}
})
it('rejects Codex transcript endpoints outside the default namespace', async () => {
const app = createRoutesApp('team-a')
const response = await app.request('/api/codex/sessions')
expect(response.status).toBe(403)
expect(await response.json()).toEqual({
success: false,
error: 'Codex transcript import is not available outside the default namespace'
})
})
it('allows Codex transcript endpoints in the default namespace', async () => {
const codexHome = mkdtempSync(join(tmpdir(), 'hapi-codex-home-route-test-'))
process.env.CODEX_HOME = codexHome
try {
const app = createRoutesApp('default')
const response = await app.request('/api/codex/sessions')
expect(response.status).toBe(200)
expect(await response.json()).toEqual({
success: true,
sessions: []
})
} finally {
rmSync(codexHome, { recursive: true, force: true })
}
})
})