Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 25 additions & 17 deletions packages/altimate-code/src/bridge/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,32 @@
import { spawn, type ChildProcess } from "child_process"
import { existsSync } from "fs"
import path from "path"
import { ensureEngine, enginePythonPath } from "./engine"
import type { BridgeMethod, BridgeMethods } from "./protocol"

/** Resolve the Python interpreter to use for the engine sidecar.
* Exported for testing — not part of the public API. */
export function resolvePython(): string {
// 1. Explicit env var
if (process.env.ALTIMATE_CLI_PYTHON) return process.env.ALTIMATE_CLI_PYTHON

// 2. Check for .venv relative to altimate-engine package (local dev)
const engineDir = path.resolve(__dirname, "..", "..", "..", "altimate-engine")
const venvPython = path.join(engineDir, ".venv", "bin", "python")
if (existsSync(venvPython)) return venvPython

// 3. Check for .venv in cwd
const cwdVenv = path.join(process.cwd(), ".venv", "bin", "python")
if (existsSync(cwdVenv)) return cwdVenv

// 4. Check the managed engine venv (created by ensureEngine)
const managedPython = enginePythonPath()
if (existsSync(managedPython)) return managedPython

// 5. Fallback
return "python3"
}

export namespace Bridge {
let child: ChildProcess | undefined
let requestId = 0
Expand Down Expand Up @@ -43,24 +67,8 @@ export namespace Bridge {
})
}

function resolvePython(): string {
// 1. Explicit env var
if (process.env.ALTIMATE_CLI_PYTHON) return process.env.ALTIMATE_CLI_PYTHON

// 2. Check for .venv relative to altimate-engine package
const engineDir = path.resolve(__dirname, "..", "..", "..", "altimate-engine")
const venvPython = path.join(engineDir, ".venv", "bin", "python")
if (existsSync(venvPython)) return venvPython

// 3. Check for .venv in cwd
const cwdVenv = path.join(process.cwd(), ".venv", "bin", "python")
if (existsSync(cwdVenv)) return cwdVenv

// 4. Fallback
return "python3"
}

async function start() {
await ensureEngine()
const pythonCmd = resolvePython()
child = spawn(pythonCmd, ["-m", "altimate_engine.server"], {
stdio: ["pipe", "pipe", "pipe"],
Expand Down
156 changes: 156 additions & 0 deletions packages/altimate-code/test/bridge/client.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { describe, expect, test, mock, afterEach } from "bun:test"
import path from "path"
import fsp from "fs/promises"
import { existsSync } from "fs"
import os from "os"

// ---------------------------------------------------------------------------
// Mock state
// ---------------------------------------------------------------------------

let ensureEngineCalls = 0
let managedPythonPath = "/nonexistent/managed-engine/venv/bin/python"

// ---------------------------------------------------------------------------
// Mock: bridge/engine (only module we mock — avoids leaking into other tests)
// ---------------------------------------------------------------------------

mock.module("../../src/bridge/engine", () => ({
ensureEngine: async () => {
ensureEngineCalls++
},
enginePythonPath: () => managedPythonPath,
}))

// ---------------------------------------------------------------------------
// Import module under test — AFTER mock.module() calls
// ---------------------------------------------------------------------------

const { resolvePython } = await import("../../src/bridge/client")

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

const tmpRoot = path.join(os.tmpdir(), "bridge-test-" + process.pid + "-" + Math.random().toString(36).slice(2))

async function createFakeFile(filePath: string) {
await fsp.mkdir(path.dirname(filePath), { recursive: true })
await fsp.writeFile(filePath, "")
}

// Paths that resolvePython() checks for dev/cwd venvs.
// From source file: __dirname is <repo>/packages/altimate-code/src/bridge/
// From test file: __dirname is <repo>/packages/altimate-code/test/bridge/
// Both resolve 3 levels up to <repo>/packages/, so the dev venv path is identical.
const devVenvPython = path.resolve(__dirname, "..", "..", "..", "altimate-engine", ".venv", "bin", "python")
const cwdVenvPython = path.join(process.cwd(), ".venv", "bin", "python")
const hasLocalDevVenv = existsSync(devVenvPython) || existsSync(cwdVenvPython)

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe("resolvePython", () => {
afterEach(async () => {
ensureEngineCalls = 0
delete process.env.ALTIMATE_CLI_PYTHON
managedPythonPath = "/nonexistent/managed-engine/venv/bin/python"
await fsp.rm(tmpRoot, { recursive: true, force: true }).catch(() => {})
})

test("prefers ALTIMATE_CLI_PYTHON env var over all other sources", () => {
process.env.ALTIMATE_CLI_PYTHON = "/custom/python3.12"
expect(resolvePython()).toBe("/custom/python3.12")
})

test("env var takes priority even when managed venv exists on disk", async () => {
const fakePython = path.join(tmpRoot, "managed", "venv", "bin", "python")
await createFakeFile(fakePython)
managedPythonPath = fakePython

process.env.ALTIMATE_CLI_PYTHON = "/override/python3"
expect(resolvePython()).toBe("/override/python3")
})

test("uses managed engine venv when it exists on disk", async () => {
if (hasLocalDevVenv) {
console.log("Skipping: local dev venv exists, can't test managed venv resolution in isolation")
return
}

const fakePython = path.join(tmpRoot, "managed", "venv", "bin", "python")
await createFakeFile(fakePython)
managedPythonPath = fakePython

expect(resolvePython()).toBe(fakePython)
})

test("falls back to python3 when no venvs exist", () => {
if (hasLocalDevVenv) {
console.log("Skipping: local dev venv exists, can't test fallback in isolation")
return
}

expect(resolvePython()).toBe("python3")
})

test("does not use managed venv when it does not exist on disk", () => {
if (hasLocalDevVenv) {
console.log("Skipping: local dev venv exists")
return
}

// managedPythonPath points to nonexistent path by default
expect(resolvePython()).toBe("python3")
})

test("checks enginePythonPath() from the engine module", async () => {
if (hasLocalDevVenv) {
console.log("Skipping: local dev venv exists")
return
}

// Initially the path doesn't exist → falls back to python3
expect(resolvePython()).toBe("python3")

// Now create the file and update the managed path
const fakePython = path.join(tmpRoot, "engine-venv", "bin", "python")
await createFakeFile(fakePython)
managedPythonPath = fakePython

// Now it should find the managed venv
expect(resolvePython()).toBe(fakePython)
})
})

describe("Bridge.start integration", () => {
// These tests verify that ensureEngine() is called by observing the
// ensureEngineCalls counter. We don't mock child_process, so start()
// will attempt a real spawn — we use /bin/echo which exists but
// won't speak JSON-RPC, causing the bridge ping to fail.

afterEach(() => {
ensureEngineCalls = 0
delete process.env.ALTIMATE_CLI_PYTHON
managedPythonPath = "/nonexistent/managed-engine/venv/bin/python"
})

test("ensureEngine is called when bridge starts", async () => {
const { Bridge } = await import("../../src/bridge/client")

// /bin/echo exists and will spawn successfully but won't respond to
// the JSON-RPC ping, so start() will eventually fail on verification.
process.env.ALTIMATE_CLI_PYTHON = "/bin/echo"

try {
await Bridge.call("ping", {} as any)
} catch {
// Expected: the bridge ping verification will fail
}

// Even though the ping failed, ensureEngine was called before the spawn attempt
expect(ensureEngineCalls).toBeGreaterThanOrEqual(1)
Bridge.stop()
})
})