import { describe, it, expect, beforeEach, afterEach } from 'bun:test' import * as fs from 'node:fs' import * as path from 'node:path' import * as os from 'node:os' import { SessionStore } from '../session-store.js' describe('SessionStore', () => { let tmpDir: string let store: SessionStore beforeEach(() => { tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'session-store-')) store = new SessionStore(path.join(tmpDir, 'sessions.json')) }) afterEach(() => { fs.rmSync(tmpDir, { recursive: true, force: true }) }) it('returns null for unknown chatId', () => { expect(store.get('unknown')).toBeNull() }) it('stores and retrieves a session', () => { store.set('chat-1', 'uuid-aaa', '/path/to/project') const entry = store.get('chat-1') expect(entry).not.toBeNull() expect(entry!.sessionId).toBe('uuid-aaa') expect(entry!.workDir).toBe('/path/to/project') }) it('overwrites existing entry on set', () => { store.set('chat-1', 'uuid-aaa', '/old') store.set('chat-1', 'uuid-bbb', '/new') expect(store.get('chat-1')!.sessionId).toBe('uuid-bbb') }) it('deletes an entry', () => { store.set('chat-1', 'uuid-aaa', '/path') store.delete('chat-1') expect(store.get('chat-1')).toBeNull() }) it('deletes every chat entry bound to a sessionId', () => { store.set('chat-1', 'uuid-shared', '/project-a') store.set('chat-2', 'uuid-other', '/project-b') store.set('chat-3', 'uuid-shared', '/project-c') const removed = store.deleteBySessionId('uuid-shared') expect(removed.sort()).toEqual(['chat-1', 'chat-3']) expect(store.get('chat-1')).toBeNull() expect(store.get('chat-3')).toBeNull() expect(store.get('chat-2')!.sessionId).toBe('uuid-other') const reloaded = new SessionStore(path.join(tmpDir, 'sessions.json')) expect(reloaded.get('chat-1')).toBeNull() expect(reloaded.get('chat-3')).toBeNull() expect(reloaded.get('chat-2')!.sessionId).toBe('uuid-other') }) it('refreshes from disk before reading so running adapters do not reuse deleted mappings', () => { store.set('chat-1', 'uuid-stale', '/project') const serverSideStore = new SessionStore(path.join(tmpDir, 'sessions.json')) expect(serverSideStore.deleteBySessionId('uuid-stale')).toEqual(['chat-1']) expect(store.get('chat-1')).toBeNull() expect(store.listAll()).toEqual([]) }) it('returns an empty list when deleting an unknown sessionId', () => { store.set('chat-1', 'uuid-aaa', '/project') expect(store.deleteBySessionId('uuid-missing')).toEqual([]) expect(store.get('chat-1')!.sessionId).toBe('uuid-aaa') }) it('persists to disk and reloads', () => { store.set('chat-1', 'uuid-aaa', '/path') const store2 = new SessionStore(path.join(tmpDir, 'sessions.json')) expect(store2.get('chat-1')!.sessionId).toBe('uuid-aaa') }) it('handles missing file gracefully', () => { const store2 = new SessionStore(path.join(tmpDir, 'nonexistent.json')) expect(store2.get('anything')).toBeNull() }) it('uses CLAUDE_CONFIG_DIR for the default session store path', () => { const previousConfigDir = process.env.CLAUDE_CONFIG_DIR process.env.CLAUDE_CONFIG_DIR = tmpDir try { const defaultStore = new SessionStore() defaultStore.set('chat-default', 'uuid-default', '/default-project') const savedPath = path.join(tmpDir, 'adapter-sessions.json') expect(fs.existsSync(savedPath)).toBe(true) expect(JSON.parse(fs.readFileSync(savedPath, 'utf8'))['chat-default'].sessionId).toBe('uuid-default') } finally { if (previousConfigDir === undefined) { delete process.env.CLAUDE_CONFIG_DIR } else { process.env.CLAUDE_CONFIG_DIR = previousConfigDir } } }) it('lists all entries', () => { store.set('chat-1', 'uuid-1', '/a') store.set('chat-2', 'uuid-2', '/b') const all = store.listAll() expect(all).toHaveLength(2) }) })