import assert from 'node:assert/strict'; import { mkdtemp, rm } from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import test from 'node:test'; import { closeConnection, initializeDatabase, projectsDb, sessionsDb } from '@/modules/database/index.js'; import { sessionsService } from '@/modules/providers/services/sessions.service.js'; async function withIsolatedDatabase(runTest: () => void | Promise): Promise { const previousDatabasePath = process.env.DATABASE_PATH; const tempDirectory = await mkdtemp(path.join(os.tmpdir(), 'sessions-service-db-')); closeConnection(); process.env.DATABASE_PATH = path.join(tempDirectory, 'auth.db'); 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 }); } } test('provider session id returns the mapped native id', { concurrency: false }, async () => { await withIsolatedDatabase(() => { sessionsDb.createAppSession('app-session-id', 'codex', '/tmp/session-id-copy-project'); sessionsDb.assignProviderSessionId('app-session-id', 'codex-native-session-id'); assert.equal(sessionsService.getProviderSessionId('app-session-id'), 'codex-native-session-id'); }); }); test('app session names use at most four whole words from the initial message', { concurrency: false }, async () => { await withIsolatedDatabase(() => { const result = sessionsService.createAppSession( 'codex', '/tmp/session-name-project', ' supercalifragilisticexpialidocious\nsecond third fourth fifth ', ); assert.equal(result.sessionName, 'supercalifragilisticexpialidocious second third fourth'); assert.equal( sessionsDb.getSessionById(result.sessionId)?.custom_name, 'supercalifragilisticexpialidocious second third fourth', ); }); }); test('app sessions without message text receive a stable fallback name', { concurrency: false }, async () => { await withIsolatedDatabase(() => { const result = sessionsService.createAppSession('claude', '/tmp/attachment-only-project', ' \n '); assert.equal(result.sessionName, 'Untitled Session'); assert.equal(sessionsDb.getSessionById(result.sessionId)?.custom_name, 'Untitled Session'); }); }); test('provider session id is unavailable until the provider assigns one', { concurrency: false }, async () => { await withIsolatedDatabase(() => { sessionsDb.createAppSession('pending-app-session', 'claude', '/tmp/session-id-copy-project'); assert.throws( () => sessionsService.getProviderSessionId('pending-app-session'), (error: unknown) => { const typedError = error as { code?: string; statusCode?: number }; return typedError.code === 'PROVIDER_SESSION_ID_NOT_AVAILABLE' && typedError.statusCode === 409; }, ); }); }); test('provider session id reports a missing app session', { concurrency: false }, async () => { await withIsolatedDatabase(() => { assert.throws( () => sessionsService.getProviderSessionId('missing-session'), (error: unknown) => { const typedError = error as { code?: string; statusCode?: number }; return typedError.code === 'SESSION_NOT_FOUND' && typedError.statusCode === 404; }, ); }); }); test('recent sessions map project metadata and preserve database pagination', { concurrency: false }, async () => { await withIsolatedDatabase(() => { sessionsDb.createSession( 'older-session', 'claude', '/tmp/recent-project', 'Older conversation', '2026-08-01T08:00:00.000Z', '2026-08-01T09:00:00.000Z', ); sessionsDb.createSession( 'newer-session', 'codex', '/tmp/recent-project', 'Newer conversation', '2026-08-01T10:00:00.000Z', '2026-08-01T11:00:00.000Z', ); projectsDb.updateCustomProjectName('/tmp/recent-project', 'Recent Project'); const project = projectsDb.getProjectPath('/tmp/recent-project'); const page = sessionsService.listRecentSessions(1, 0); assert.deepEqual(page, { conversations: [{ sessionId: 'newer-session', provider: 'codex', projectId: project?.project_id ?? null, projectDisplayName: 'Recent Project', sessionTitle: 'Newer conversation', lastActivity: '2026-08-01T11:00:00.000Z', }], total: 2, hasMore: true, }); }); });