import { describe, expect, it } from 'vitest' import { CODEX_BRIDGE_START_BUDGET_MS, type CodexLinkDeps, type CodexLinkPaths, createCodexLink } from './codex-link' import { CODEX_BRIDGE_VERSION, CODEX_LINK_LINE, CODEX_LINK_REFRESH_URL, CODEX_LINK_STATUS_URL, CODEX_LINK_STOP_URL, codexBridgeArgs, codexBridgeConfig } from './codex-link-config' const PATHS: CodexLinkPaths = { settingsFile: 'D:\\ide\\codex-baglama.json', bridgeHome: 'D:\\ide\\codex-bridge', hermesHome: 'D:\\ide\\agent', bridgeScript: 'D:\\app\\codex-bridge\\bridge.py', pythonw: 'D:\\ide\\agent\\venv\\Scripts\\pythonw.exe', python: 'D:\\ide\\agent\\venv\\Scripts\\python.exe', codexConfig: 'D:\\home\\.codex\\config.toml' } const BRIDGE_JSON = `${PATHS.bridgeHome}\\bridge.json` const STATUS_OK = { ok: true, surum: CODEX_BRIDGE_VERSION, ide_durum: 'tamam', ide_model: 7, ozet: true } const USER_CONFIG = 'model = "gpt-6"\r\n\r\n[features]\r\nweb = true\r\n' interface World { /** What the machine looks like; mutated by the fake effects. */ files: Map /** Is something answering on the bridge port? */ listening: boolean /** Version the listening bridge reports before any spawn; a spawn brings the current one. */ staleVersion: null | string /** Does a spawn bring the bridge up? `false` = it never answers. */ spawnWorks: boolean /** Spawned bridge only starts answering after this many status probes. */ probesUntilUp: number autostart: null | string regFails: boolean /** Every observable effect, in order. */ events: string[] clock: number } function createWorld(patch: Partial & { files?: Map } = {}) { const world: World = { autostart: null, clock: 0, events: [], files: new Map([ [PATHS.codexConfig, USER_CONFIG], [PATHS.bridgeScript, '# bridge'], [PATHS.pythonw, ''], [PATHS.python, ''] ]), listening: false, staleVersion: null, probesUntilUp: 0, regFails: false, spawnWorks: true, ...patch } let spawned = false let probesSinceSpawn = 0 const deps: CodexLinkDeps = { paths: PATHS, exists: file => world.files.has(file), readText: file => world.files.get(file) ?? null, writeTextAtomic: (file, text) => { world.events.push(`write ${file}`) world.files.set(file, text) }, spawnBridge: (command, args, env) => { world.events.push(`spawn ${command} ${JSON.stringify(args)} ${JSON.stringify(env)}`) spawned = true probesSinceSpawn = 0 world.staleVersion = null }, request: async (method, url) => { if (method === 'GET' && url === CODEX_LINK_STATUS_URL) { if (spawned && world.spawnWorks && !world.listening) { probesSinceSpawn += 1 if (probesSinceSpawn > world.probesUntilUp) { world.listening = true } } if (world.listening) { world.events.push('status ok') return world.staleVersion ? { ...STATUS_OK, surum: world.staleVersion } : STATUS_OK } return null } if (method === 'POST' && url === CODEX_LINK_REFRESH_URL) { world.events.push('refresh') return world.listening ? STATUS_OK : null } if (method === 'POST' && url === CODEX_LINK_STOP_URL) { world.events.push('stop') if (!world.listening) { return null } world.listening = false return { ok: true } } throw new Error(`unexpected request ${method} ${url}`) }, setAutostart: async command => { world.events.push('autostart set') if (world.regFails) { throw new Error('reg add exited 1') } world.autostart = command }, clearAutostart: async () => { world.events.push('autostart clear') world.autostart = null }, sleep: async ms => { world.clock += ms }, now: () => world.clock, log: () => undefined } return { link: createCodexLink(deps), world } } const writes = (world: World) => world.events.filter(event => event.startsWith('write ')) const spawns = (world: World) => world.events.filter(event => event.startsWith('spawn ')) // Effects that change the machine (a status probe does not). const effects = (world: World) => world.events.filter(event => event !== 'status ok') describe('applyCodexLink', () => { it('touches nothing when Codex is not installed', async () => { const { link, world } = createWorld() world.files.delete(PATHS.codexConfig) const status = await link.apply() expect(status).toMatchObject({ enabled: true, codexFound: false, bridgeRunning: false, lastError: null }) expect(effects(world)).toEqual([]) expect(world.autostart).toBeNull() }) it('touches nothing when Codex already uses another bridge', async () => { const foreign = 'openai_base_url = "http://127.0.0.1:8799/backend-api/codex"\n[features]\n' const { link, world } = createWorld({ listening: true }) world.files.set(PATHS.codexConfig, foreign) const status = await link.apply({ sync: true }) expect(status).toMatchObject({ enabled: true, codexFound: true, foreign: true, bridgeRunning: true }) expect(effects(world)).toEqual([]) expect(world.files.get(PATHS.codexConfig)).toBe(foreign) }) it('waits for the IDE setup: no venv, no changes', async () => { const { link, world } = createWorld() world.files.delete(PATHS.pythonw) world.files.delete(PATHS.python) const status = await link.apply() expect(status).toMatchObject({ enabled: true, codexFound: true, venvReady: false, foreign: false }) expect(effects(world)).toEqual([]) }) it('brings everything up in the safe order', async () => { const { link, world } = createWorld({ probesUntilUp: 2 }) const status = await link.apply() expect(status).toEqual({ enabled: true, codexFound: true, venvReady: true, foreign: false, bridgeRunning: true, ideDurum: 'tamam', ideModel: 7, ozet: true, lastError: null }) const env = { AITURK_CODEX_BRIDGE_HOME: PATHS.bridgeHome, HERMES_HOME: PATHS.hermesHome } const args = codexBridgeArgs({ bridgeHome: PATHS.bridgeHome, bridgeScript: PATHS.bridgeScript, hermesHome: PATHS.hermesHome }) // bridge.json → start → verified answer → config.toml → login item expect(world.events).toEqual([ `write ${BRIDGE_JSON}`, `spawn ${PATHS.pythonw} ${JSON.stringify(args)} ${JSON.stringify(env)}`, 'status ok', `write ${PATHS.codexConfig}`, 'autostart set' ]) expect(JSON.parse(world.files.get(BRIDGE_JSON) ?? '')).toEqual(codexBridgeConfig()) expect(world.files.get(PATHS.codexConfig)).toBe(`${CODEX_LINK_LINE}\r\n${USER_CONFIG}`) expect(world.autostart).toBe( `"${PATHS.pythonw}" "${PATHS.bridgeScript}" --home "${PATHS.bridgeHome}" --hermes-home "${PATHS.hermesHome}"` ) }) it('never points Codex at a bridge that did not come up', async () => { const { link, world } = createWorld({ spawnWorks: false }) const status = await link.apply() expect(status).toMatchObject({ enabled: true, bridgeRunning: false, lastError: 'bridge-start-failed' }) expect(spawns(world)).toHaveLength(1) expect(world.files.get(PATHS.codexConfig)).toBe(USER_CONFIG) expect(world.autostart).toBeNull() expect(writes(world)).toEqual([`write ${BRIDGE_JSON}`]) // The start poll is bounded by its one budget. expect(world.clock).toBe(CODEX_BRIDGE_START_BUDGET_MS) }) it('takes its own line back out when the bridge can no longer be started', async () => { const { link, world } = createWorld({ spawnWorks: false }) world.files.set(PATHS.codexConfig, `${CODEX_LINK_LINE}\r\n${USER_CONFIG}`) const status = await link.apply() expect(status.lastError).toBe('bridge-start-failed') expect(world.files.get(PATHS.codexConfig)).toBe(USER_CONFIG) }) it('does not start a second bridge when one is already listening', async () => { const { link, world } = createWorld({ listening: true }) await link.apply() await link.apply({ sync: true }) expect(spawns(world)).toEqual([]) expect(world.files.get(PATHS.codexConfig)).toBe(`${CODEX_LINK_LINE}\r\n${USER_CONFIG}`) // The config is written once; the login item is refreshed on every apply. expect(writes(world).filter(event => event === `write ${PATHS.codexConfig}`)).toHaveLength(1) expect(world.events.filter(event => event === 'autostart set')).toHaveLength(2) }) it('replaces a bridge left running by an older IDE build', async () => { const { link, world } = createWorld({ listening: true, staleVersion: '0' }) const status = await link.apply() expect(world.events.indexOf('stop')).toBeGreaterThanOrEqual(0) expect(spawns(world)).toHaveLength(1) expect(world.events.indexOf('stop')).toBeLessThan(world.events.findIndex(event => event.startsWith('spawn'))) expect(status.bridgeRunning).toBe(true) expect(world.files.get(PATHS.codexConfig)).toBe(`${CODEX_LINK_LINE}\r\n${USER_CONFIG}`) }) it('asks a running bridge to re-read the IDE session on every apply', async () => { const { link, world } = createWorld({ listening: true }) await link.apply({ sync: true }) expect(world.events).toContain('refresh') expect(spawns(world)).toEqual([]) }) it('falls back to python.exe when pythonw.exe is missing', async () => { const { link, world } = createWorld() world.files.delete(PATHS.pythonw) await link.apply() expect(spawns(world)[0]).toContain(`spawn ${PATHS.python} `) expect(world.autostart?.startsWith(`"${PATHS.python}" `)).toBe(true) }) it('reports a missing bridge script instead of starting anything', async () => { const { link, world } = createWorld() world.files.delete(PATHS.bridgeScript) const status = await link.apply() expect(status.lastError).toBe('bridge-missing') expect(effects(world)).toEqual([]) }) it('surfaces a registry failure but keeps the working link', async () => { const { link, world } = createWorld({ listening: true, regFails: true }) const status = await link.apply() expect(status).toMatchObject({ bridgeRunning: true, lastError: 'autostart-failed' }) expect(world.files.get(PATHS.codexConfig)).toBe(`${CODEX_LINK_LINE}\r\n${USER_CONFIG}`) }) it('undoes everything when turned off', async () => { const { link, world } = createWorld({ listening: true }) await link.apply() world.events.length = 0 const status = await link.apply({ enabled: false }) expect(status).toMatchObject({ enabled: false, codexFound: true, bridgeRunning: false, lastError: null }) expect(world.events).toEqual([ `write ${PATHS.settingsFile}`, `write ${PATHS.codexConfig}`, 'autostart clear', 'stop' ]) expect(JSON.parse(world.files.get(PATHS.settingsFile) ?? '')).toEqual({ enabled: false, kapsam: 'ide' }) // Byte for byte what the user had before the feature touched it. expect(world.files.get(PATHS.codexConfig)).toBe(USER_CONFIG) expect(world.autostart).toBeNull() expect(world.listening).toBe(false) }) it('stays off across applies and comes back when turned on', async () => { const { link, world } = createWorld() world.files.set(PATHS.settingsFile, '{"enabled": false, "kapsam": "ide", "not": "ours"}') expect(await link.apply()).toMatchObject({ enabled: false, bridgeRunning: false }) expect(spawns(world)).toEqual([]) expect(world.files.get(PATHS.codexConfig)).toBe(USER_CONFIG) expect(await link.apply({ enabled: true })).toMatchObject({ enabled: true, bridgeRunning: true }) expect(JSON.parse(world.files.get(PATHS.settingsFile) ?? '')).toEqual({ enabled: true, kapsam: 'ide', not: 'ours' }) expect(world.files.get(PATHS.codexConfig)).toBe(`${CODEX_LINK_LINE}\r\n${USER_CONFIG}`) }) it('does not stop a bridge that serves a foreign config when turned off', async () => { const foreign = 'openai_base_url = "http://127.0.0.1:8799/backend-api/codex"\n' const { link, world } = createWorld({ listening: true }) world.files.set(PATHS.codexConfig, foreign) const status = await link.apply({ enabled: false }) expect(status).toMatchObject({ enabled: false, foreign: true, bridgeRunning: true }) expect(world.events).not.toContain('stop') expect(world.files.get(PATHS.codexConfig)).toBe(foreign) expect(world.listening).toBe(true) }) it('runs concurrent applies one after another', async () => { const { link, world } = createWorld({ probesUntilUp: 3 }) const [first, second] = await Promise.all([link.apply(), link.apply({ enabled: false })]) expect(first.enabled).toBe(true) expect(second).toMatchObject({ enabled: false, bridgeRunning: false }) expect(world.files.get(PATHS.codexConfig)).toBe(USER_CONFIG) expect(world.autostart).toBeNull() }) }) describe('getStatus', () => { it('reads without changing anything', async () => { const { link, world } = createWorld({ listening: true }) const status = await link.getStatus() expect(status).toMatchObject({ enabled: true, codexFound: true, venvReady: true, foreign: false, bridgeRunning: true, ideDurum: 'tamam', ideModel: 7, ozet: true }) expect(effects(world)).toEqual([]) }) it('reports a bridge that is not answering', async () => { const { link, world } = createWorld() expect(await link.getStatus()).toMatchObject({ bridgeRunning: false, ideDurum: null, ideModel: null, ozet: null }) expect(effects(world)).toEqual([]) }) })