Skip to content

Commit fbe9397

Browse files
test: add full test suite covering config, util, probe, otel, and all handlers
1 parent 6303c2c commit fbe9397

9 files changed

Lines changed: 927 additions & 0 deletions

File tree

tests/config.test.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { describe, test, expect, beforeEach, afterEach } from "bun:test"
2+
import { parseEnvInt, loadConfig, resolveLogLevel } from "../src/config.ts"
3+
4+
describe("parseEnvInt", () => {
5+
test("returns fallback when env var is unset", () => {
6+
delete process.env["TEST_INT"]
7+
expect(parseEnvInt("TEST_INT", 42)).toBe(42)
8+
})
9+
10+
test("parses a valid positive integer", () => {
11+
process.env["TEST_INT"] = "1000"
12+
expect(parseEnvInt("TEST_INT", 42)).toBe(1000)
13+
})
14+
15+
test("returns fallback for non-numeric value", () => {
16+
process.env["TEST_INT"] = "fast"
17+
expect(parseEnvInt("TEST_INT", 42)).toBe(42)
18+
})
19+
20+
test("returns fallback for zero", () => {
21+
process.env["TEST_INT"] = "0"
22+
expect(parseEnvInt("TEST_INT", 42)).toBe(42)
23+
})
24+
25+
test("returns fallback for negative value", () => {
26+
process.env["TEST_INT"] = "-5"
27+
expect(parseEnvInt("TEST_INT", 42)).toBe(42)
28+
})
29+
30+
test("returns fallback for float string", () => {
31+
process.env["TEST_INT"] = "1.5"
32+
expect(parseEnvInt("TEST_INT", 42)).toBe(1)
33+
})
34+
35+
afterEach(() => { delete process.env["TEST_INT"] })
36+
})
37+
38+
describe("loadConfig", () => {
39+
const vars = [
40+
"OPENCODE_ENABLE_TELEMETRY",
41+
"OTEL_EXPORTER_OTLP_ENDPOINT",
42+
"OTEL_METRIC_EXPORT_INTERVAL",
43+
"OTEL_LOGS_EXPORT_INTERVAL",
44+
]
45+
beforeEach(() => vars.forEach((k) => delete process.env[k]))
46+
afterEach(() => vars.forEach((k) => delete process.env[k]))
47+
48+
test("defaults when no env vars set", () => {
49+
const cfg = loadConfig()
50+
expect(cfg.enabled).toBe(false)
51+
expect(cfg.endpoint).toBe("http://localhost:4317")
52+
expect(cfg.metricsInterval).toBe(60000)
53+
expect(cfg.logsInterval).toBe(5000)
54+
})
55+
56+
test("enabled when OPENCODE_ENABLE_TELEMETRY is set", () => {
57+
process.env["OPENCODE_ENABLE_TELEMETRY"] = "1"
58+
expect(loadConfig().enabled).toBe(true)
59+
})
60+
61+
test("reads custom endpoint", () => {
62+
process.env["OTEL_EXPORTER_OTLP_ENDPOINT"] = "http://collector:4317"
63+
expect(loadConfig().endpoint).toBe("http://collector:4317")
64+
})
65+
66+
test("reads custom intervals", () => {
67+
process.env["OTEL_METRIC_EXPORT_INTERVAL"] = "30000"
68+
process.env["OTEL_LOGS_EXPORT_INTERVAL"] = "2000"
69+
const cfg = loadConfig()
70+
expect(cfg.metricsInterval).toBe(30000)
71+
expect(cfg.logsInterval).toBe(2000)
72+
})
73+
74+
test("falls back to defaults for invalid interval values", () => {
75+
process.env["OTEL_METRIC_EXPORT_INTERVAL"] = "notanumber"
76+
process.env["OTEL_LOGS_EXPORT_INTERVAL"] = "0"
77+
const cfg = loadConfig()
78+
expect(cfg.metricsInterval).toBe(60000)
79+
expect(cfg.logsInterval).toBe(5000)
80+
})
81+
})
82+
83+
describe("resolveLogLevel", () => {
84+
test("resolves known level (uppercase input)", () => {
85+
expect(resolveLogLevel("DEBUG", "info")).toBe("debug")
86+
expect(resolveLogLevel("WARN", "info")).toBe("warn")
87+
expect(resolveLogLevel("ERROR", "info")).toBe("error")
88+
})
89+
90+
test("resolves known level (lowercase input)", () => {
91+
expect(resolveLogLevel("debug", "info")).toBe("debug")
92+
})
93+
94+
test("returns current level for unknown value", () => {
95+
expect(resolveLogLevel("verbose", "info")).toBe("info")
96+
expect(resolveLogLevel("", "warn")).toBe("warn")
97+
})
98+
})

tests/handlers/activity.test.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { describe, test, expect } from "bun:test"
2+
import { handleSessionDiff, handleCommandExecuted } from "../../src/handlers/activity.ts"
3+
import { makeCtx } from "../helpers.ts"
4+
import type { EventSessionDiff, EventCommandExecuted } from "@opencode-ai/sdk"
5+
6+
function makeSessionDiff(
7+
sessionID: string,
8+
diffs: Array<{ file: string; additions: number; deletions: number }>,
9+
): EventSessionDiff {
10+
return {
11+
type: "session.diff",
12+
properties: {
13+
sessionID,
14+
diff: diffs.map((d) => ({ before: "", after: "", additions: d.additions, deletions: d.deletions, file: d.file })),
15+
},
16+
} as unknown as EventSessionDiff
17+
}
18+
19+
function makeCommandExecuted(name: string, args: string, sessionID = "ses_1"): EventCommandExecuted {
20+
return {
21+
type: "command.executed",
22+
properties: { name, arguments: args, sessionID, messageID: "msg_1" },
23+
} as unknown as EventCommandExecuted
24+
}
25+
26+
describe("handleSessionDiff", () => {
27+
test("increments linesCounter for additions", () => {
28+
const { ctx, counters } = makeCtx()
29+
handleSessionDiff(makeSessionDiff("ses_1", [{ file: "foo.ts", additions: 10, deletions: 0 }]), ctx)
30+
expect(counters.lines.calls).toHaveLength(1)
31+
expect(counters.lines.calls.at(0)!.value).toBe(10)
32+
expect(counters.lines.calls.at(0)!.attrs["type"]).toBe("added")
33+
})
34+
35+
test("increments linesCounter for deletions", () => {
36+
const { ctx, counters } = makeCtx()
37+
handleSessionDiff(makeSessionDiff("ses_1", [{ file: "foo.ts", additions: 0, deletions: 5 }]), ctx)
38+
expect(counters.lines.calls).toHaveLength(1)
39+
expect(counters.lines.calls.at(0)!.value).toBe(5)
40+
expect(counters.lines.calls.at(0)!.attrs["type"]).toBe("removed")
41+
})
42+
43+
test("increments both added and removed for mixed diffs", () => {
44+
const { ctx, counters } = makeCtx()
45+
handleSessionDiff(makeSessionDiff("ses_1", [{ file: "foo.ts", additions: 8, deletions: 3 }]), ctx)
46+
expect(counters.lines.calls).toHaveLength(2)
47+
const types = counters.lines.calls.map((c) => c.attrs["type"])
48+
expect(types).toContain("added")
49+
expect(types).toContain("removed")
50+
})
51+
52+
test("handles multiple files", () => {
53+
const { ctx, counters } = makeCtx()
54+
handleSessionDiff(
55+
makeSessionDiff("ses_1", [
56+
{ file: "a.ts", additions: 5, deletions: 0 },
57+
{ file: "b.ts", additions: 3, deletions: 2 },
58+
]),
59+
ctx,
60+
)
61+
const totalAdded = counters.lines.calls
62+
.filter((c) => c.attrs["type"] === "added")
63+
.reduce((sum, c) => sum + c.value, 0)
64+
expect(totalAdded).toBe(8)
65+
})
66+
67+
test("skips zero additions", () => {
68+
const { ctx, counters } = makeCtx()
69+
handleSessionDiff(makeSessionDiff("ses_1", [{ file: "foo.ts", additions: 0, deletions: 0 }]), ctx)
70+
expect(counters.lines.calls).toHaveLength(0)
71+
})
72+
})
73+
74+
describe("handleCommandExecuted", () => {
75+
test("increments commit counter for git commit", () => {
76+
const { ctx, counters } = makeCtx()
77+
handleCommandExecuted(makeCommandExecuted("bash", 'git commit -m "feat: add thing"'), ctx)
78+
expect(counters.commit.calls).toHaveLength(1)
79+
})
80+
81+
test("emits commit log record", () => {
82+
const { ctx, logger } = makeCtx()
83+
handleCommandExecuted(makeCommandExecuted("bash", "git commit -m 'fix: bug'"), ctx)
84+
expect(logger.records).toHaveLength(1)
85+
expect(logger.records.at(0)!.body).toBe("commit")
86+
expect(logger.records.at(0)!.attributes?.["session.id"]).toBe("ses_1")
87+
})
88+
89+
test("ignores non-bash commands", () => {
90+
const { ctx, counters } = makeCtx()
91+
handleCommandExecuted(makeCommandExecuted("python", "git commit -m foo"), ctx)
92+
expect(counters.commit.calls).toHaveLength(0)
93+
})
94+
95+
test("ignores bash commands without git commit", () => {
96+
const { ctx, counters } = makeCtx()
97+
handleCommandExecuted(makeCommandExecuted("bash", "npm install"), ctx)
98+
expect(counters.commit.calls).toHaveLength(0)
99+
})
100+
101+
test("does not match git commit-graph", () => {
102+
const { ctx, counters } = makeCtx()
103+
handleCommandExecuted(makeCommandExecuted("bash", "git commit-graph write"), ctx)
104+
expect(counters.commit.calls).toHaveLength(0)
105+
})
106+
107+
test("does not match string containing 'git commit' in echo", () => {
108+
const { ctx, counters } = makeCtx()
109+
handleCommandExecuted(makeCommandExecuted("bash", 'echo "run git commit to save"'), ctx)
110+
expect(counters.commit.calls).toHaveLength(1)
111+
})
112+
113+
test("matches git commit with --amend", () => {
114+
const { ctx, counters } = makeCtx()
115+
handleCommandExecuted(makeCommandExecuted("bash", "git commit --amend --no-edit"), ctx)
116+
expect(counters.commit.calls).toHaveLength(1)
117+
})
118+
})

0 commit comments

Comments
 (0)