import { test } from 'node:test'; import assert from 'node:assert/strict'; import { createRequire } from 'node:module'; import { mkdirSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import Module from 'node:module'; const require = createRequire(import.meta.url); async function loadMainForWindowFlags(flags) { const originalArgv = process.argv; const originalHome = process.env.HOME; const originalLoad = Module._load; const mainPath = require.resolve('../app/main.js'); const home = join(tmpdir(), `obelisk-window-flags-${Date.now()}-${Math.random()}`); mkdirSync(join(home, '.obelisk'), { recursive: true }); writeFileSync(join(home, '.obelisk', 'obelisk.sqlite'), ''); delete require.cache[mainPath]; process.env.HOME = home; process.argv = [originalArgv[0] || 'node', originalArgv[1] || 'electron', ...flags]; const windows = []; class FakeDatabase { pragma() {} close() {} prepare() { return { get: () => null, all: () => [], run: () => ({}) }; } } class FakeBrowserWindow { constructor(options) { this.options = options; this.loadedURL = null; this.loadedFile = null; this.devToolsOpened = false; this.webContents = { on() {}, setZoomLevel() {}, openDevTools: () => { this.devToolsOpened = true; }, send() {}, }; windows.push(this); } loadFile(filePath) { this.loadedFile = filePath; } loadURL(url) { this.loadedURL = url; return Promise.resolve(); } close() {} static getAllWindows() { return windows; } static fromWebContents() { return null; } } Module._load = function patchedLoad(request, parent, isMain) { if (request === 'electron') { return { app: { whenReady: () => Promise.resolve(), on() {}, quit() {}, }, BrowserWindow: FakeBrowserWindow, ipcMain: { handle() {} }, clipboard: {}, dialog: {}, nativeImage: {}, }; } if (request === 'better-sqlite3') return FakeDatabase; if (request === './indexer') return { writeHeartbeat() {} }; if (request === './indexer-service') { return { createIndexerService: () => ({ start() {}, stop() {}, idle: async () => {}, runBuildNow() { return Promise.resolve(); }, }), }; } if (request === './indexer-worker-client') { return { createWorkerBuildIndex: () => ({ buildIndex: async () => ({ files: 0, affectedSessionIds: [] }), stop() {}, }), }; } if (request === 'chokidar') { return { watch: () => ({ on() { return this; }, close() {} }) }; } return originalLoad.call(this, request, parent, isMain); }; try { require('../app/main.js'); await new Promise(resolve => setImmediate(resolve)); return windows; } finally { Module._load = originalLoad; delete require.cache[mainPath]; process.argv = originalArgv; process.env.HOME = originalHome; rmSync(home, { recursive: true, force: true }); } } test('dev mode does not open DevTools unless explicitly requested', async () => { const packagedWindows = await loadMainForWindowFlags([]); assert.equal(packagedWindows.length, 1); assert.equal(packagedWindows[0].loadedURL, null); assert.equal(packagedWindows[0].options.webPreferences.devTools, false); assert.equal(packagedWindows[0].devToolsOpened, false); const devWindows = await loadMainForWindowFlags(['--dev']); assert.equal(devWindows.length, 1); assert.equal(devWindows[0].loadedURL, 'http://localhost:5173'); assert.equal(devWindows[0].options.webPreferences.devTools, true); assert.equal(devWindows[0].devToolsOpened, false); const devtoolsWindows = await loadMainForWindowFlags(['--dev', '--devtools']); assert.equal(devtoolsWindows.length, 1); assert.equal(devtoolsWindows[0].loadedURL, 'http://localhost:5173'); assert.equal(devtoolsWindows[0].devToolsOpened, true); }); test('main process watches Codex sessions directory instead of Codex root', async () => { const originalLoad = Module._load; const originalHome = process.env.HOME; const home = join(tmpdir(), `obelisk-main-watch-dirs-${Date.now()}`); const claudeDir = join(home, '.claude'); const codexDir = join(home, '.codex'); mkdirSync(join(claudeDir, 'projects'), { recursive: true }); mkdirSync(join(codexDir, 'sessions'), { recursive: true }); mkdirSync(join(home, '.obelisk'), { recursive: true }); writeFileSync(join(home, '.obelisk', 'obelisk.sqlite'), ''); process.env.HOME = home; const serviceOptions = []; const mainPath = require.resolve('../app/main.js'); delete require.cache[mainPath]; class FakeDatabase { pragma() {} close() {} prepare() { return { get: () => null, all: () => [], run: () => ({}) }; } } class FakeBrowserWindow { constructor() { this.webContents = { on() {}, setZoomLevel() {}, openDevTools() {}, send() {} }; } loadFile() {} loadURL() {} close() {} static getAllWindows() { return []; } static fromWebContents() { return null; } } Module._load = function patchedLoad(request, parent, isMain) { if (request === 'electron') { return { app: { whenReady: () => Promise.resolve(), on() {}, quit() {}, }, BrowserWindow: FakeBrowserWindow, ipcMain: { handle() {} }, clipboard: {}, dialog: {}, nativeImage: {}, }; } if (request === 'better-sqlite3') return FakeDatabase; if (request === './indexer') return { writeHeartbeat() {} }; if (request === './indexer-service') { return { createIndexerService: (options) => { serviceOptions.push(options); return { start() {}, stop() {}, idle: async () => {}, runBuildNow() { return Promise.resolve(); }, }; }, }; } if (request === './indexer-worker-client') { return { createWorkerBuildIndex: () => ({ buildIndex: async () => ({ files: 0, affectedSessionIds: [] }), stop() {}, }), }; } if (request === 'chokidar') return { watch: () => ({ on() { return this; }, close() {} }) }; return originalLoad.call(this, request, parent, isMain); }; try { require('../app/main.js'); await new Promise(resolve => setImmediate(resolve)); assert.equal(serviceOptions.length, 1); assert.deepEqual(serviceOptions[0].watchDirs, [ join(claudeDir, 'projects'), join(codexDir, 'sessions'), ]); assert.equal(serviceOptions[0].watchDirs.includes(codexDir), false); } finally { Module._load = originalLoad; delete require.cache[mainPath]; process.env.HOME = originalHome; rmSync(home, { recursive: true, force: true }); } }); test('session IPC hides Codex rows by default and supports explicit source opt-in', async () => { const originalLoad = Module._load; const originalHome = process.env.HOME; const home = join(tmpdir(), `obelisk-main-source-filter-${Date.now()}`); mkdirSync(join(home, '.obelisk'), { recursive: true }); writeFileSync(join(home, '.obelisk', 'obelisk.sqlite'), ''); process.env.HOME = home; const ipcHandlers = new Map(); const queries = []; const mainPath = require.resolve('../app/main.js'); delete require.cache[mainPath]; class FakeDatabase { pragma() {} close() {} prepare(sql) { return { all: (...params) => { queries.push({ sql, params }); return []; }, get: (...params) => { queries.push({ sql, params }); return null; }, run: () => ({}), }; } } class FakeBrowserWindow { constructor() { this.webContents = { on() {}, setZoomLevel() {}, openDevTools() {}, send() {} }; } loadFile() {} loadURL() {} close() {} static getAllWindows() { return []; } static fromWebContents() { return null; } } Module._load = function patchedLoad(request, parent, isMain) { if (request === 'electron') { return { app: { whenReady: () => Promise.resolve(), on() {}, quit() {}, }, BrowserWindow: FakeBrowserWindow, ipcMain: { handle(channel, handler) { ipcHandlers.set(channel, handler); }, }, clipboard: {}, dialog: {}, nativeImage: {}, }; } if (request === 'better-sqlite3') return FakeDatabase; if (request === './indexer') return { writeHeartbeat() {} }; if (request === './indexer-service') { return { createIndexerService: () => ({ start() {}, stop() {}, idle: async () => {}, runBuildNow() { return Promise.resolve(); }, }), }; } if (request === './indexer-worker-client') { return { createWorkerBuildIndex: () => ({ buildIndex: async () => ({ files: 0, affectedSessionIds: [] }), stop() {}, }), }; } if (request === 'chokidar') return { watch: () => ({ on() { return this; }, close() {} }) }; return originalLoad.call(this, request, parent, isMain); }; try { require('../app/main.js'); await new Promise(resolve => setImmediate(resolve)); ipcHandlers.get('db:getSessions')(null, {}); assert.match(queries.at(-1).sql, /COALESCE\(source, 'claude'\) = 'claude'/); ipcHandlers.get('db:getProjects')(null, {}); assert.match(queries.at(-1).sql, /COALESCE\(source, 'claude'\) = 'claude'/); ipcHandlers.get('db:getSessions')(null, { includeCodex: true }); assert.doesNotMatch(queries.at(-1).sql, /COALESCE\(source, 'claude'\) = 'claude'/); ipcHandlers.get('db:getSessions')(null, { source: 'codex' }); assert.match(queries.at(-1).sql, /COALESCE\(source, 'claude'\) = \?/); assert.ok(queries.at(-1).params.includes('codex')); await ipcHandlers.get('settings:get')(); assert.ok( queries.some(q => /COUNT\(\*\) as c FROM sessions WHERE COALESCE\(source, 'claude'\) = 'claude'/.test(q.sql)), ); assert.ok( queries.some(q => /MAX\(started_at\) as t FROM sessions WHERE COALESCE\(source, 'claude'\) = 'claude'/.test(q.sql)), ); } finally { Module._load = originalLoad; delete require.cache[mainPath]; process.env.HOME = originalHome; rmSync(home, { recursive: true, force: true }); } }); test('main process migrates an existing app database before source-filtered IPC queries', async () => { const originalLoad = Module._load; const originalHome = process.env.HOME; const home = join(tmpdir(), `obelisk-main-db-migration-${Date.now()}`); const obeliskDir = join(home, '.obelisk'); mkdirSync(obeliskDir, { recursive: true }); process.env.HOME = home; const { DatabaseSync } = require('node:sqlite'); const dbPath = join(obeliskDir, 'obelisk.sqlite'); const legacy = new DatabaseSync(dbPath); legacy.exec(` CREATE TABLE sessions ( id TEXT PRIMARY KEY, title TEXT, project TEXT, project_path TEXT, started_at TEXT, ended_at TEXT, git_branch TEXT, version TEXT, message_count INTEGER DEFAULT 0, jsonl_path TEXT ); CREATE TABLE messages ( uuid TEXT PRIMARY KEY, session_id TEXT, type TEXT, parent_uuid TEXT, timestamp TEXT, role TEXT, text TEXT, content_type TEXT, is_meta INTEGER DEFAULT 0, model TEXT, is_sidechain INTEGER DEFAULT 0, agent_id TEXT, input_tokens INTEGER, output_tokens INTEGER, cwd TEXT, skill TEXT, turn_duration_ms INTEGER ); CREATE TABLE memories ( id TEXT PRIMARY KEY, session_id TEXT, project TEXT, message_start TEXT, message_end TEXT, path TEXT, anchors TEXT, summary TEXT, created_at TEXT, deleted_at TEXT, deleted_reason TEXT ); INSERT INTO sessions (id, title, project, started_at, message_count) VALUES ('legacy-session', 'Legacy session', 'quiet-zero', '2026-06-10T10:00:00Z', 1); `); legacy.close(); const ipcHandlers = new Map(); const mainPath = require.resolve('../app/main.js'); delete require.cache[mainPath]; class FakeBrowserWindow { constructor() { this.webContents = { on() {}, setZoomLevel() {}, openDevTools() {}, send() {} }; } loadFile() {} loadURL() {} close() {} static getAllWindows() { return []; } static fromWebContents() { return null; } } Module._load = function patchedLoad(request, parent, isMain) { if (request === 'electron') { return { app: { whenReady: () => Promise.resolve(), on() {}, quit() {}, }, BrowserWindow: FakeBrowserWindow, ipcMain: { handle(channel, handler) { ipcHandlers.set(channel, handler); }, }, clipboard: {}, dialog: {}, nativeImage: {}, }; } if (request === 'better-sqlite3') { return class SqliteCompatDatabase { constructor(dbFile) { this.db = new DatabaseSync(dbFile); } pragma(statement) { this.db.exec(`PRAGMA ${statement}`); } exec(sql) { return this.db.exec(sql); } close() { return this.db.close(); } prepare(sql) { const stmt = this.db.prepare(sql); return { all: (...params) => stmt.all(...params), get: (...params) => stmt.get(...params), run: (...params) => stmt.run(...params), }; } }; } if (request === './indexer') return { writeHeartbeat() {} }; if (request === './indexer-service') { return { createIndexerService: () => ({ start() {}, stop() {}, idle: async () => {}, runBuildNow() { return Promise.resolve(); }, }), }; } if (request === './indexer-worker-client') { return { createWorkerBuildIndex: () => ({ buildIndex: async () => ({ files: 0, affectedSessionIds: [] }), stop() {}, }), }; } if (request === 'chokidar') return { watch: () => ({ on() { return this; }, close() {} }) }; return originalLoad.call(this, request, parent, isMain); }; try { require('../app/main.js'); await new Promise(resolve => setImmediate(resolve)); const sessions = ipcHandlers.get('db:getSessions')(null, {}); assert.equal(sessions[0].id, 'legacy-session'); assert.equal(sessions[0].source, 'claude'); assert.deepEqual(ipcHandlers.get('db:getStats')(null, {}), { sessions: 1, memories: 0, memoriesArchived: 0, }); } finally { Module._load = originalLoad; delete require.cache[mainPath]; process.env.HOME = originalHome; rmSync(home, { recursive: true, force: true }); } }); test('closing the last macOS window releases background resources until activation', async () => { const originalLoad = Module._load; const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform'); const originalHome = process.env.HOME; const home = join(tmpdir(), `obelisk-main-window-${Date.now()}`); mkdirSync(join(home, '.obelisk'), { recursive: true }); writeFileSync(join(home, '.obelisk', 'obelisk.sqlite'), ''); const mainPath = require.resolve('../app/main.js'); delete require.cache[mainPath]; process.env.HOME = home; Object.defineProperty(process, 'platform', { value: 'darwin' }); const appHandlers = new Map(); const serviceEvents = []; const workers = []; const watchers = []; const windows = []; let quitCalled = false; class FakeDatabase { pragma() {} close() { serviceEvents.push('db-close'); } prepare() { return { get: () => null, all: () => [], run: () => ({}) }; } } class FakeBrowserWindow { constructor() { this.webContents = { on() {}, setZoomLevel() {}, openDevTools() {}, send() {} }; windows.push(this); } loadFile() {} loadURL() {} close() {} static getAllWindows() { return windows; } static fromWebContents() { return null; } } Module._load = function patchedLoad(request, parent, isMain) { if (request === 'electron') { return { app: { whenReady: () => Promise.resolve(), on(event, handler) { appHandlers.set(event, handler); }, quit() { quitCalled = true; }, }, BrowserWindow: FakeBrowserWindow, ipcMain: { handle() {} }, clipboard: {}, dialog: {}, nativeImage: {}, }; } if (request === 'better-sqlite3') return FakeDatabase; if (request === './indexer') return { writeHeartbeat() {} }; if (request === './indexer-service') { return { createIndexerService: () => ({ start() { serviceEvents.push('service-start'); }, stop() { serviceEvents.push('service-stop'); }, idle: async () => { serviceEvents.push('service-idle'); }, runBuildNow() { serviceEvents.push('service-build'); return Promise.resolve(); }, }), }; } if (request === './indexer-worker-client') { return { createWorkerBuildIndex: () => { const worker = { stop() { serviceEvents.push('worker-stop'); } }; workers.push(worker); return worker; }, }; } if (request === 'chokidar') { return { watch: () => { const watcher = { on() { return this; }, close() { serviceEvents.push('watcher-close'); } }; watchers.push(watcher); return watcher; }, }; } return originalLoad.call(this, request, parent, isMain); }; try { require('../app/main.js'); await new Promise(resolve => setImmediate(resolve)); assert.equal(windows.length, 1); assert.equal(workers.length, 1); assert.equal(watchers.length, 1); windows.length = 0; appHandlers.get('window-all-closed')(); await new Promise(resolve => setImmediate(resolve)); assert.equal(quitCalled, false); assert.ok(serviceEvents.includes('service-stop')); assert.ok(serviceEvents.includes('worker-stop')); assert.ok(serviceEvents.includes('watcher-close')); assert.ok(serviceEvents.includes('db-close')); appHandlers.get('activate')(); await new Promise(resolve => setImmediate(resolve)); assert.equal(windows.length, 1); assert.equal(workers.length, 2); assert.equal(watchers.length, 2); assert.equal(serviceEvents.filter(e => e === 'service-start').length, 2); } finally { Module._load = originalLoad; delete require.cache[mainPath]; process.env.HOME = originalHome; if (originalPlatform) Object.defineProperty(process, 'platform', originalPlatform); rmSync(home, { recursive: true, force: true }); } }); test('settings rebuild reopens the database from the configured Claude path', async () => { const home = join(tmpdir(), `obelisk-main-settings-${Date.now()}`); const defaultClaudeDir = join(home, '.claude'); const customClaudeDir = join(home, 'custom-claude'); const customCodexDir = join(home, 'custom-codex'); mkdirSync(defaultClaudeDir, { recursive: true }); mkdirSync(customClaudeDir, { recursive: true }); mkdirSync(customCodexDir, { recursive: true }); mkdirSync(join(home, '.obelisk'), { recursive: true }); writeFileSync(join(defaultClaudeDir, 'obelisk.sqlite'), ''); writeFileSync(join(customClaudeDir, 'obelisk.sqlite'), 'legacy custom db'); writeFileSync(join(home, '.obelisk', 'settings.json'), JSON.stringify({ claudeDir: customClaudeDir, codexDir: customCodexDir, })); const originalHome = process.env.HOME; process.env.HOME = home; const ipcHandlers = new Map(); const openedDbPaths = []; const buildCalls = []; const serviceEvents = []; const originalLoad = Module._load; const mainPath = require.resolve('../app/main.js'); delete require.cache[mainPath]; class FakeDatabase { constructor(dbPath) { openedDbPaths.push(dbPath); } pragma() {} close() {} prepare() { return { get: () => null, all: () => [], run: () => ({}) }; } } class FakeBrowserWindow { constructor() { this.webContents = { on() {}, setZoomLevel() {}, openDevTools() {}, send() {}, }; } loadFile() {} loadURL() {} close() {} static getAllWindows() { return []; } static fromWebContents() { return null; } } Module._load = function patchedLoad(request, parent, isMain) { if (request === 'electron') { return { app: { whenReady: () => Promise.resolve(), on() {}, quit() {}, }, BrowserWindow: FakeBrowserWindow, ipcMain: { handle(channel, handler) { ipcHandlers.set(channel, handler); }, }, clipboard: {}, dialog: {}, nativeImage: {}, }; } if (request === 'better-sqlite3') return FakeDatabase; if (request === './indexer') return { writeHeartbeat() {} }; if (request === './indexer-service') { return { createIndexerService: () => ({ start() { serviceEvents.push('start'); }, stop() { serviceEvents.push('stop'); }, idle: async () => { serviceEvents.push('idle'); }, runBuildNow() { serviceEvents.push('runBuildNow'); return Promise.resolve(); }, }), }; } if (request === './indexer-worker-client') { return { createWorkerBuildIndex: () => ({ buildIndex: async (args) => { serviceEvents.push('build'); buildCalls.push(args); writeFileSync(args.dbPath, 'rebuilt temp db'); return { files: 2, affectedSessionIds: ['session-1', 'session-2'] }; }, stop() { return Promise.resolve(); }, }), }; } if (request === 'chokidar') { return { watch: () => ({ on() { return this; }, close() {} }) }; } return originalLoad.call(this, request, parent, isMain); }; try { require('../app/main.js'); await new Promise(resolve => setImmediate(resolve)); const rebuild = ipcHandlers.get('settings:rebuildIndex'); assert.equal(typeof rebuild, 'function'); await rebuild(); assert.equal(buildCalls.at(-1).claudeDir, customClaudeDir); assert.equal(buildCalls.at(-1).projectsDir, join(customClaudeDir, 'projects')); assert.equal(buildCalls.at(-1).codexDir, customCodexDir); assert.notEqual(buildCalls.at(-1).dbPath, join(home, '.obelisk', 'obelisk.sqlite')); assert.equal(buildCalls.at(-1).preserveDbPath, join(home, '.obelisk', 'obelisk.sqlite')); assert.equal(openedDbPaths.at(-1), join(home, '.obelisk', 'obelisk.sqlite')); assert.equal( require('node:fs').readFileSync(join(home, '.obelisk', 'obelisk.sqlite'), 'utf8'), 'rebuilt temp db', ); assert.ok(serviceEvents.indexOf('build') > serviceEvents.indexOf('stop')); } finally { Module._load = originalLoad; delete require.cache[mainPath]; process.env.HOME = originalHome; rmSync(home, { recursive: true, force: true }); } }); test('settings rebuild keeps the existing database after a worker failure', async () => { const home = join(tmpdir(), `obelisk-main-settings-rebuild-failure-${Date.now()}`); const customClaudeDir = join(home, 'custom-claude'); const customCodexDir = join(home, 'custom-codex'); mkdirSync(customClaudeDir, { recursive: true }); mkdirSync(customCodexDir, { recursive: true }); mkdirSync(join(home, '.obelisk'), { recursive: true }); writeFileSync(join(customClaudeDir, 'obelisk.sqlite'), 'legacy custom db'); writeFileSync(join(home, '.obelisk', 'settings.json'), JSON.stringify({ claudeDir: customClaudeDir, codexDir: customCodexDir, })); const originalHome = process.env.HOME; process.env.HOME = home; const ipcHandlers = new Map(); const openedDbPaths = []; const closedDbPaths = []; const serviceEvents = []; const originalLoad = Module._load; const mainPath = require.resolve('../app/main.js'); delete require.cache[mainPath]; class FakeDatabase { constructor(dbPath) { this.dbPath = dbPath; openedDbPaths.push(dbPath); } pragma() {} close() { closedDbPaths.push(this.dbPath); } prepare() { return { get: () => null, all: () => [], run: () => ({}) }; } } class FakeBrowserWindow { constructor() { this.webContents = { on() {}, setZoomLevel() {}, openDevTools() {}, send() {}, }; } loadFile() {} loadURL() {} close() {} static getAllWindows() { return []; } static fromWebContents() { return null; } } Module._load = function patchedLoad(request, parent, isMain) { if (request === 'electron') { return { app: { whenReady: () => Promise.resolve(), on() {}, quit() {}, }, BrowserWindow: FakeBrowserWindow, ipcMain: { handle(channel, handler) { ipcHandlers.set(channel, handler); }, }, clipboard: {}, dialog: {}, nativeImage: {}, }; } if (request === 'better-sqlite3') return FakeDatabase; if (request === './indexer') return { writeHeartbeat() {} }; if (request === './indexer-service') { return { createIndexerService: () => ({ start() { serviceEvents.push('start'); }, stop() { serviceEvents.push('stop'); }, idle: async () => { serviceEvents.push('idle'); }, runBuildNow() { serviceEvents.push('runBuildNow'); return Promise.resolve(); }, }), }; } if (request === './indexer-worker-client') { return { createWorkerBuildIndex: () => ({ buildIndex: async () => { serviceEvents.push('build'); throw new Error('worker exploded'); }, stop() {}, }), }; } if (request === 'chokidar') { return { watch: () => ({ on() { return this; }, close() {} }) }; } return originalLoad.call(this, request, parent, isMain); }; try { require('../app/main.js'); await new Promise(resolve => setImmediate(resolve)); const rebuild = ipcHandlers.get('settings:rebuildIndex'); const openCountBeforeRebuild = openedDbPaths.length; await assert.rejects(() => rebuild(), /worker exploded/); const expectedDbPath = join(home, '.obelisk', 'obelisk.sqlite'); assert.equal(openedDbPaths.at(-1), expectedDbPath); assert.equal(openedDbPaths.length, openCountBeforeRebuild); assert.equal(closedDbPaths.includes(expectedDbPath), false); assert.equal( require('node:fs').readFileSync(expectedDbPath, 'utf8'), 'legacy custom db', ); assert.ok(serviceEvents.indexOf('build') > serviceEvents.indexOf('stop')); assert.ok(serviceEvents.lastIndexOf('start') > serviceEvents.indexOf('build')); } finally { Module._load = originalLoad; delete require.cache[mainPath]; process.env.HOME = originalHome; rmSync(home, { recursive: true, force: true }); } }); test('settings rebuild cancels an in-flight background build instead of waiting for it', async () => { const home = join(tmpdir(), `obelisk-main-settings-rebuild-cancel-${Date.now()}`); const customClaudeDir = join(home, 'custom-claude'); const customCodexDir = join(home, 'custom-codex'); mkdirSync(customClaudeDir, { recursive: true }); mkdirSync(customCodexDir, { recursive: true }); mkdirSync(join(home, '.obelisk'), { recursive: true }); writeFileSync(join(customClaudeDir, 'obelisk.sqlite'), 'legacy custom db'); writeFileSync(join(home, '.obelisk', 'settings.json'), JSON.stringify({ claudeDir: customClaudeDir, codexDir: customCodexDir, })); const originalHome = process.env.HOME; process.env.HOME = home; const ipcHandlers = new Map(); const serviceEvents = []; const originalLoad = Module._load; const mainPath = require.resolve('../app/main.js'); delete require.cache[mainPath]; class FakeDatabase { pragma() {} close() {} prepare() { return { get: () => null, all: () => [], run: () => ({}) }; } } class FakeBrowserWindow { constructor() { this.webContents = { on() {}, setZoomLevel() {}, openDevTools() {}, send() {}, }; } loadFile() {} loadURL() {} close() {} static getAllWindows() { return []; } static fromWebContents() { return null; } } Module._load = function patchedLoad(request, parent, isMain) { if (request === 'electron') { return { app: { whenReady: () => Promise.resolve(), on() {}, quit() {}, }, BrowserWindow: FakeBrowserWindow, ipcMain: { handle(channel, handler) { ipcHandlers.set(channel, handler); }, }, clipboard: {}, dialog: {}, nativeImage: {}, }; } if (request === 'better-sqlite3') return FakeDatabase; if (request === './indexer') return { writeHeartbeat() {} }; if (request === './indexer-service') { return { createIndexerService: () => ({ start() { serviceEvents.push('start'); }, stop() { serviceEvents.push('stop'); }, idle: async () => new Promise(() => {}), runBuildNow() { serviceEvents.push('runBuildNow'); return Promise.resolve(); }, }), }; } if (request === './indexer-worker-client') { let buildIndexCalls = 0; return { createWorkerBuildIndex: () => ({ buildIndex: async (args) => { serviceEvents.push(`build-${++buildIndexCalls}`); writeFileSync(args.dbPath, 'rebuilt temp db'); return { files: 2, affectedSessionIds: [] }; }, stop() { serviceEvents.push('worker-stop'); return Promise.resolve(); }, }), }; } if (request === 'chokidar') { return { watch: () => ({ on() { return this; }, close() {} }) }; } return originalLoad.call(this, request, parent, isMain); }; try { require('../app/main.js'); await new Promise(resolve => setImmediate(resolve)); const rebuild = ipcHandlers.get('settings:rebuildIndex'); const outcome = await Promise.race([ rebuild().then(() => 'done'), new Promise(resolve => setTimeout(() => resolve('timeout'), 20)), ]); assert.equal(outcome, 'done'); assert.ok(serviceEvents.indexOf('worker-stop') > serviceEvents.indexOf('stop')); assert.ok(serviceEvents.some(event => event.startsWith('build-'))); } finally { Module._load = originalLoad; delete require.cache[mainPath]; process.env.HOME = originalHome; rmSync(home, { recursive: true, force: true }); } });