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
5 changes: 5 additions & 0 deletions packages/opencode/src/altimate/fingerprint/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export namespace Fingerprint {
return cached
}

/** Reset the fingerprint cache (exported for testing) */
export function reset(): void {
cached = undefined
}

export async function refresh(): Promise<Result> {
const previousCwd = cached?.cwd ?? process.cwd()
cached = undefined
Expand Down
19 changes: 18 additions & 1 deletion packages/opencode/test/altimate/adversarial.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,24 @@
* concurrent access, and error recovery paths.
*/

import { describe, expect, test, beforeEach, beforeAll, afterAll } from "bun:test"
import { describe, expect, test, beforeEach, beforeAll, afterAll, mock } from "bun:test"

// Mock DuckDB driver so tests don't require the native duckdb package
mock.module("@altimateai/drivers/duckdb", () => ({
connect: async (config: any) => ({
execute: async (sql: string) => ({
columns: [],
rows: [],
row_count: 0,
truncated: false,
}),
connect: async () => {},
close: async () => {},
schemas: async () => [],
tables: async () => [],
columns: async () => [],
}),
}))

// Disable telemetry via env var instead of mock.module
beforeAll(() => { process.env.ALTIMATE_TELEMETRY_DISABLED = "true" })
Expand Down
33 changes: 14 additions & 19 deletions packages/opencode/test/altimate/connections.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,20 +323,22 @@ describe("Connection dispatcher registration", () => {
// DuckDB driver (in-memory, actual queries)
// ---------------------------------------------------------------------------

describe("DuckDB driver (in-memory)", () => {
// altimate_change start - check DuckDB availability synchronously to avoid flaky async race conditions
let duckdbAvailable = false
try {
require.resolve("duckdb")
duckdbAvailable = true
} catch {
// DuckDB native driver not installed — skip all tests in this block
}

describe.skipIf(!duckdbAvailable)("DuckDB driver (in-memory)", () => {
let connector: any

beforeEach(async () => {
try {
const { connect } = await import(
"@altimateai/drivers/duckdb"
)
connector = await connect({ type: "duckdb", path: ":memory:" })
await connector.connect()
} catch (e) {
// DuckDB might not be installed in test env
connector = null
}
const { connect } = await import("@altimateai/drivers/duckdb")
connector = await connect({ type: "duckdb", path: ":memory:" })
await connector.connect()
})

afterEach(async () => {
Expand All @@ -346,8 +348,6 @@ describe("DuckDB driver (in-memory)", () => {
})

test("execute SELECT 1", async () => {
if (!connector) return // skip if duckdb not installed

const result = await connector.execute("SELECT 1 AS num")
expect(result.columns).toEqual(["num"])
expect(result.rows).toEqual([[1]])
Expand All @@ -356,8 +356,6 @@ describe("DuckDB driver (in-memory)", () => {
})

test("execute with limit truncation", async () => {
if (!connector) return

// Generate 5 rows, limit to 3
const result = await connector.execute(
"SELECT * FROM generate_series(1, 5)",
Expand All @@ -368,15 +366,11 @@ describe("DuckDB driver (in-memory)", () => {
})

test("listSchemas returns schemas", async () => {
if (!connector) return

const schemas = await connector.listSchemas()
expect(schemas).toContain("main")
})

test("listTables and describeTable", async () => {
if (!connector) return

await connector.execute(
"CREATE TABLE test_table (id INTEGER NOT NULL, name VARCHAR, active BOOLEAN)",
)
Expand All @@ -394,3 +388,4 @@ describe("DuckDB driver (in-memory)", () => {
expect(columns[1].nullable).toBe(true)
})
})
// altimate_change end
35 changes: 34 additions & 1 deletion packages/opencode/test/altimate/dbt-first-execution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,45 @@
* Set DBT_TEST_PROJECT_ROOT env var to override the project path.
*/

import { describe, expect, test, beforeAll, afterAll, beforeEach } from "bun:test"
import { describe, expect, test, beforeAll, afterAll, beforeEach, mock } from "bun:test"
import { existsSync, readFileSync } from "fs"
import { join } from "path"
import { homedir } from "os"
import type { Connector } from "@altimateai/drivers/types"

// Mock DuckDB driver so tests don't require the native duckdb package
mock.module("@altimateai/drivers/duckdb", () => ({
connect: async (config: any) => ({
execute: async (sql: string) => {
// Simple mock: parse SELECT literals
const match = sql.match(/SELECT\s+'([^']+)'\s+AS\s+(\w+)/i)
if (match) {
return {
columns: [{ name: match[2], type: "varchar" }],
rows: [[match[1]]],
row_count: 1,
truncated: false,
}
}
const numMatch = sql.match(/SELECT\s+(\d+)\s+AS\s+(\w+)/i)
if (numMatch) {
return {
columns: [{ name: numMatch[2], type: "integer" }],
rows: [[Number(numMatch[1])]],
row_count: 1,
truncated: false,
}
}
return { columns: [], rows: [], row_count: 0, truncated: false }
},
connect: async () => {},
close: async () => {},
schemas: async () => [],
tables: async () => [],
columns: async () => [],
}),
}))

// ---------------------------------------------------------------------------
// Detect dbt project for testing
// ---------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,15 +223,15 @@ describe("Orphaned generation — endTrace with unclosed generation", () => {
test("snapshot mid-generation shows 'running', endTrace shows 'completed'", async () => {
const tracer = Tracer.withExporters([new FileExporter(tmpDir)])
tracer.startTrace("s-run-complete", { prompt: "test" })
await new Promise((r) => setTimeout(r, 200)) // wait for initial snapshot
await new Promise((r) => setTimeout(r, 50)) // wait for initial snapshot
tracer.logStepStart({ id: "1" })
tracer.logToolCall({
tool: "bash", callID: "c1",
state: { status: "completed", input: {}, output: "ok", time: { start: 1, end: 2 } },
})

// Wait for snapshot — should be "running"
await new Promise((r) => setTimeout(r, 200))
await new Promise((r) => setTimeout(r, 50))
const snap = JSON.parse(await fs.readFile(tracer.getTracePath()!, "utf-8")) as TraceFile
expect(snap.summary.status).toBe("running")

Expand Down Expand Up @@ -294,7 +294,7 @@ describe("Worker race — events after endTrace", () => {
}

// Wait for endTrace to complete
await new Promise((r) => setTimeout(r, 300))
await new Promise((r) => setTimeout(r, 50))

// Verify the late event was NOT added to the trace
const filePath = path.join(tmpDir, "race-session.json")
Expand Down Expand Up @@ -403,7 +403,7 @@ describe("buildTraceFile — status transitions", () => {
const path1 = tracer.getTracePath()!

// Wait for initial snapshot — should be "completed" (no active generation)
await new Promise((r) => setTimeout(r, 200))
await new Promise((r) => setTimeout(r, 50))
const snap0 = JSON.parse(await fs.readFile(path1, "utf-8")) as TraceFile
expect(snap0.summary.status).toBe("completed")

Expand All @@ -413,13 +413,13 @@ describe("buildTraceFile — status transitions", () => {
tool: "bash", callID: "c1",
state: { status: "completed", input: {}, output: "ok", time: { start: 1, end: 2 } },
})
await new Promise((r) => setTimeout(r, 200))
await new Promise((r) => setTimeout(r, 50))
const snap1 = JSON.parse(await fs.readFile(path1, "utf-8")) as TraceFile
expect(snap1.summary.status).toBe("running")

// Finish generation — should go back to "completed"
tracer.logStepFinish(ZERO_STEP)
await new Promise((r) => setTimeout(r, 200))
await new Promise((r) => setTimeout(r, 50))
const snap2 = JSON.parse(await fs.readFile(path1, "utf-8")) as TraceFile
expect(snap2.summary.status).toBe("completed")

Expand All @@ -429,7 +429,7 @@ describe("buildTraceFile — status transitions", () => {
tool: "read", callID: "c2",
state: { status: "completed", input: {}, output: "ok", time: { start: 3, end: 4 } },
})
await new Promise((r) => setTimeout(r, 200))
await new Promise((r) => setTimeout(r, 50))
const snap3 = JSON.parse(await fs.readFile(path1, "utf-8")) as TraceFile
expect(snap3.summary.status).toBe("running")

Expand Down Expand Up @@ -501,7 +501,7 @@ describe("Snapshot debounce under load", () => {
}

// Wait for all snapshots to settle
await new Promise((r) => setTimeout(r, 500))
await new Promise((r) => setTimeout(r, 100))

const filePath = await tracer.endTrace()
const trace: TraceFile = JSON.parse(await fs.readFile(filePath!, "utf-8"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe("buildTraceFile — snapshot isolation", () => {
})

// Wait for snapshot to write
await new Promise((r) => setTimeout(r, 200))
await new Promise((r) => setTimeout(r, 50))

// Read the snapshot
const snap1 = JSON.parse(await fs.readFile(tracer.getTracePath()!, "utf-8")) as TraceFile
Expand Down Expand Up @@ -84,7 +84,7 @@ describe("buildTraceFile — snapshot isolation", () => {
})

// Wait for snapshot
await new Promise((r) => setTimeout(r, 200))
await new Promise((r) => setTimeout(r, 50))
const snap1 = JSON.parse(await fs.readFile(tracer.getTracePath()!, "utf-8")) as TraceFile
const span1Count = snap1.spans.length

Expand All @@ -96,7 +96,7 @@ describe("buildTraceFile — snapshot isolation", () => {
})

// Wait for second snapshot
await new Promise((r) => setTimeout(r, 200))
await new Promise((r) => setTimeout(r, 50))
const snap2 = JSON.parse(await fs.readFile(tracer.getTracePath()!, "utf-8")) as TraceFile

// Second snapshot should have more spans
Expand All @@ -111,7 +111,7 @@ describe("buildTraceFile — snapshot isolation", () => {
const tracer = Tracer.withExporters([new FileExporter(tmpDir)])
tracer.startTrace("s-running", { prompt: "test" })
// Wait for initial snapshot to complete
await new Promise((r) => setTimeout(r, 200))
await new Promise((r) => setTimeout(r, 50))

tracer.logStepStart({ id: "1" })
tracer.logToolCall({
Expand All @@ -121,13 +121,13 @@ describe("buildTraceFile — snapshot isolation", () => {
})

// Wait for snapshot — should show "running" since generation is in progress
await new Promise((r) => setTimeout(r, 200))
await new Promise((r) => setTimeout(r, 50))
const snap = JSON.parse(await fs.readFile(tracer.getTracePath()!, "utf-8")) as TraceFile
expect(snap.summary.status).toBe("running")

// After finishing generation, should show "completed"
tracer.logStepFinish(ZERO_STEP)
await new Promise((r) => setTimeout(r, 200))
await new Promise((r) => setTimeout(r, 50))
const snap2 = JSON.parse(await fs.readFile(tracer.getTracePath()!, "utf-8")) as TraceFile
expect(snap2.summary.status).toBe("completed")

Expand Down Expand Up @@ -155,7 +155,7 @@ describe("snapshot — debouncing and atomicity", () => {
}

// Wait for all snapshots to settle
await new Promise((r) => setTimeout(r, 500))
await new Promise((r) => setTimeout(r, 100))

// Check for leftover .tmp files
const files = await fs.readdir(tmpDir)
Expand All @@ -182,7 +182,7 @@ describe("snapshot — debouncing and atomicity", () => {
})

// Should not crash — snapshot failure is silently swallowed
await new Promise((r) => setTimeout(r, 200))
await new Promise((r) => setTimeout(r, 50))

tracer.logStepFinish(ZERO_STEP)
// endTrace will also fail to write, but should return undefined gracefully
Expand Down Expand Up @@ -231,7 +231,7 @@ describe("snapshot — debouncing and atomicity", () => {
state: { status: "completed", input: {}, output: "ok", time: { start: 1, end: 2 } },
})

await new Promise((r) => setTimeout(r, 300))
await new Promise((r) => setTimeout(r, 50))

// The file may have been overwritten by a snapshot, but the spans
// array was already mutated (spans are still pushed to the array
Expand Down Expand Up @@ -485,7 +485,7 @@ describe("Live trace viewer — /api/trace", () => {

try {
// startTrace writes initial snapshot — file should exist immediately
await new Promise((r) => setTimeout(r, 200))
await new Promise((r) => setTimeout(r, 50))
const r1 = await fetch(`http://localhost:${server.port}/api/trace`)
expect(r1.status).toBe(200)
const data1 = await r1.json() as TraceFile
Expand All @@ -497,7 +497,7 @@ describe("Live trace viewer — /api/trace", () => {
tool: "bash", callID: "c1",
state: { status: "completed", input: {}, output: "ok", time: { start: 1, end: 2 } },
})
await new Promise((r) => setTimeout(r, 300))
await new Promise((r) => setTimeout(r, 50))

const r2 = await fetch(`http://localhost:${server.port}/api/trace`)
expect(r2.status).toBe(200)
Expand All @@ -509,7 +509,7 @@ describe("Live trace viewer — /api/trace", () => {
tool: "read", callID: "c2",
state: { status: "completed", input: {}, output: "content", time: { start: 3, end: 4 } },
})
await new Promise((r) => setTimeout(r, 300))
await new Promise((r) => setTimeout(r, 50))

const r3 = await fetch(`http://localhost:${server.port}/api/trace`)
const data3 = await r3.json() as TraceFile
Expand Down Expand Up @@ -564,7 +564,7 @@ describe("Snapshot with non-serializable data in spans", () => {
state: { status: "completed", input: {}, output: "ok", time: { start: 1, end: 2 } },
})
// Wait for the tool snapshot to settle first
await new Promise((r) => setTimeout(r, 200))
await new Promise((r) => setTimeout(r, 50))

// Now add attributes (after snapshot)
tracer.setSpanAttributes({
Expand All @@ -577,7 +577,7 @@ describe("Snapshot with non-serializable data in spans", () => {
tool: "read", callID: "c2",
state: { status: "completed", input: {}, output: "ok", time: { start: 3, end: 4 } },
})
await new Promise((r) => setTimeout(r, 200))
await new Promise((r) => setTimeout(r, 50))

const snap = JSON.parse(await fs.readFile(tracer.getTracePath()!, "utf-8")) as TraceFile
// The first tool span should now have the attributes (from the second snapshot)
Expand All @@ -593,12 +593,12 @@ describe("Snapshot with non-serializable data in spans", () => {
test("snapshot handles span with undefined output gracefully", async () => {
const tracer = Tracer.withExporters([new FileExporter(tmpDir)])
tracer.startTrace("s-undef-output", { prompt: "test" })
await new Promise((r) => setTimeout(r, 200)) // wait for initial snapshot
await new Promise((r) => setTimeout(r, 50)) // wait for initial snapshot
tracer.logStepStart({ id: "1" })
// Generation with no text and no tool calls — output will be undefined
tracer.logStepFinish(ZERO_STEP)

await new Promise((r) => setTimeout(r, 200))
await new Promise((r) => setTimeout(r, 50))

const snap = JSON.parse(await fs.readFile(tracer.getTracePath()!, "utf-8")) as TraceFile
// undefined output becomes null or is omitted in JSON
Expand Down
Loading
Loading