import assert from 'node:assert/strict'; import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import test from 'node:test'; import { closeConnection, initializeDatabase, sessionsDb } from '@/modules/database/index.js'; import { CodexSessionSynchronizer } from '@/modules/providers/list/codex/codex-session-synchronizer.provider.js'; const patchHomeDir = (nextHomeDir: string) => { const original = os.homedir; (os as any).homedir = () => nextHomeDir; return () => { (os as any).homedir = original; }; }; async function withIsolatedDatabase(runTest: () => void | Promise): Promise { const previousDatabasePath = process.env.DATABASE_PATH; const tempDirectory = await mkdtemp(path.join(os.tmpdir(), 'codex-provider-db-')); const databasePath = path.join(tempDirectory, 'auth.db'); closeConnection(); process.env.DATABASE_PATH = databasePath; await initializeDatabase(); try { await runTest(); } finally { closeConnection(); if (previousDatabasePath === undefined) { delete process.env.DATABASE_PATH; } else { process.env.DATABASE_PATH = previousDatabasePath; } await rm(tempDirectory, { recursive: true, force: true }); } } /** * Writes one Codex rollout transcript. `firstUserMessage` mirrors the * `event_msg`/`user_message` payload the runtime records for the prompt the * user typed; omitting it produces a transcript with no user turn. */ const writeCodexTranscript = async ( homeDir: string, codexSessionId: string, workspacePath: string, firstUserMessage?: string, ): Promise => { const sessionsDir = path.join(homeDir, '.codex', 'sessions', '2026', '07', '07'); await mkdir(sessionsDir, { recursive: true }); const lines: string[] = [ JSON.stringify({ type: 'session_meta', payload: { id: codexSessionId, cwd: workspacePath } }), ]; if (firstUserMessage !== undefined) { lines.push(JSON.stringify({ type: 'event_msg', payload: { type: 'user_message', message: firstUserMessage } })); } const filePath = path.join(sessionsDir, `rollout-${codexSessionId}.jsonl`); await writeFile(filePath, `${lines.join('\n')}\n`, 'utf8'); return filePath; }; test('Codex synchronizer titles app-created sessions from the first user message', { concurrency: false }, async () => { const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'codex-session-sync-app-')); const workspacePath = path.join(tempRoot, 'workspace'); await mkdir(workspacePath, { recursive: true }); const restoreHomeDir = patchHomeDir(tempRoot); try { await writeCodexTranscript(tempRoot, 'codex-app-1', workspacePath, 'Fix the login redirect bug'); await withIsolatedDatabase(async () => { // The app allocates its own id and later maps the provider id onto it, // exactly as a message sent from cloudcli does. sessionsDb.createAppSession('app-1', 'codex', workspacePath); sessionsDb.assignProviderSessionId('app-1', 'codex-app-1'); const synchronizer = new CodexSessionSynchronizer(); await synchronizer.synchronize(); assert.equal(sessionsDb.getSessionById('app-1')?.custom_name, 'Fix the login redirect bug'); }); } finally { restoreHomeDir(); await rm(tempRoot, { recursive: true, force: true }); } }); test('Codex synchronizer leaves indexed sessions untitled when no name is available', { concurrency: false }, async () => { const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'codex-session-sync-indexed-')); const workspacePath = path.join(tempRoot, 'workspace'); await mkdir(workspacePath, { recursive: true }); const restoreHomeDir = patchHomeDir(tempRoot); try { // A CLI-created session has no app row; its first user message must NOT be // used as the title, preserving the existing indexing behavior. await writeCodexTranscript(tempRoot, 'codex-indexed-1', workspacePath, 'This prompt should be ignored'); await withIsolatedDatabase(async () => { const synchronizer = new CodexSessionSynchronizer(); await synchronizer.synchronize(); assert.equal(sessionsDb.getSessionById('codex-indexed-1')?.custom_name, 'Untitled Codex Session'); }); } finally { restoreHomeDir(); await rm(tempRoot, { recursive: true, force: true }); } });