Skip to content

Commit f59ef40

Browse files
anandgupta42claude
andauthored
test: clickhouse — finops query history, dbt profiles, and registry error hints (#624)
Cover ClickHouse paths added in PR #574 that had zero test coverage: - buildHistoryQuery ClickHouse branch: SQL template correctness, integer clamping for days/limit (prevents NaN/float injection into string-interpolated SQL), and boundary values - dbt profiles.yml ClickHouse adapter mapping (prevents silent connection skip) - Registry known-unsupported DB hints (cassandra, cockroachdb, timescaledb) and generic unsupported error message Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> https://claude.ai/code/session_01CB7m2CjEFJbJia3ZJKZpHN Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1a9c6fe commit f59ef40

File tree

2 files changed

+115
-0
lines changed

2 files changed

+115
-0
lines changed

packages/opencode/test/altimate/connections.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,36 @@ describe("ConnectionRegistry", () => {
5151
)
5252
})
5353

54+
test("cassandra gives helpful hint instead of generic unsupported error", async () => {
55+
Registry.setConfigs({
56+
mydb: { type: "cassandra", host: "localhost" },
57+
})
58+
await expect(Registry.get("mydb")).rejects.toThrow("not yet supported")
59+
await expect(Registry.get("mydb")).rejects.toThrow("cqlsh")
60+
})
61+
62+
test("cockroachdb suggests using postgres type", async () => {
63+
Registry.setConfigs({
64+
mydb: { type: "cockroachdb", host: "localhost" },
65+
})
66+
await expect(Registry.get("mydb")).rejects.toThrow("postgres")
67+
})
68+
69+
test("timescaledb suggests using postgres type", async () => {
70+
Registry.setConfigs({
71+
mydb: { type: "timescaledb", host: "localhost" },
72+
})
73+
await expect(Registry.get("mydb")).rejects.toThrow("postgres")
74+
})
75+
76+
test("truly unknown type gives generic unsupported error with supported list", async () => {
77+
Registry.setConfigs({
78+
mydb: { type: "neo4j", host: "localhost" },
79+
})
80+
await expect(Registry.get("mydb")).rejects.toThrow("Unsupported database type")
81+
await expect(Registry.get("mydb")).rejects.toThrow("Supported:")
82+
})
83+
5484
test("getConfig returns config for known connection", () => {
5585
Registry.setConfigs({
5686
mydb: { type: "postgres", host: "localhost" },
@@ -608,6 +638,44 @@ trino_project:
608638
fs.rmSync(tmpDir, { recursive: true })
609639
}
610640
})
641+
642+
test("clickhouse adapter maps correctly from dbt profiles", async () => {
643+
const fs = await import("fs")
644+
const os = await import("os")
645+
const path = await import("path")
646+
647+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "dbt-test-"))
648+
const profilesPath = path.join(tmpDir, "profiles.yml")
649+
650+
fs.writeFileSync(
651+
profilesPath,
652+
`
653+
ch_project:
654+
outputs:
655+
dev:
656+
type: clickhouse
657+
host: clickhouse.example.com
658+
port: 8443
659+
user: default
660+
password: secret
661+
database: analytics
662+
schema: default
663+
`,
664+
)
665+
666+
try {
667+
const connections = await parseDbtProfiles(profilesPath)
668+
expect(connections).toHaveLength(1)
669+
expect(connections[0].type).toBe("clickhouse")
670+
expect(connections[0].config.type).toBe("clickhouse")
671+
expect(connections[0].config.host).toBe("clickhouse.example.com")
672+
expect(connections[0].config.port).toBe(8443)
673+
expect(connections[0].config.user).toBe("default")
674+
expect(connections[0].config.database).toBe("analytics")
675+
} finally {
676+
fs.rmSync(tmpDir, { recursive: true })
677+
}
678+
})
611679
})
612680

613681
// ---------------------------------------------------------------------------

packages/opencode/test/altimate/schema-finops-dbt.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,53 @@ describe("FinOps: SQL template generation", () => {
158158
const built = HistoryTemplates.buildHistoryQuery("databricks", 7, 50)
159159
expect(built?.sql).toContain("system.query.history")
160160
})
161+
162+
test("builds ClickHouse history SQL with clamped integer days and limit", () => {
163+
const built = HistoryTemplates.buildHistoryQuery("clickhouse", 7, 100)
164+
expect(built).not.toBeNull()
165+
expect(built?.sql).toContain("system.query_log")
166+
expect(built?.sql).toContain("QueryFinish")
167+
// Days and limit should be integer-substituted, not bind params
168+
expect(built?.binds).toEqual([])
169+
// Verify the clamped values are in the SQL
170+
expect(built?.sql).toContain("today() - 7")
171+
expect(built?.sql).toContain("LIMIT 100")
172+
})
173+
174+
test("ClickHouse buildHistoryQuery clamps extreme days and limit values", () => {
175+
// Days clamped to [1, 365]
176+
const extremeDays = HistoryTemplates.buildHistoryQuery("clickhouse", 9999, 50)
177+
expect(extremeDays?.sql).toContain("today() - 365")
178+
179+
const zeroDays = HistoryTemplates.buildHistoryQuery("clickhouse", 0, 50)
180+
// Math.floor(0) || 30 = 30 (0 is falsy), then Math.max(1, Math.min(30, 365)) = 30
181+
expect(zeroDays?.sql).toContain("today() - 30")
182+
183+
// Limit clamped to [1, 10000]
184+
const extremeLimit = HistoryTemplates.buildHistoryQuery("clickhouse", 7, 999999)
185+
expect(extremeLimit?.sql).toContain("LIMIT 10000")
186+
187+
const zeroLimit = HistoryTemplates.buildHistoryQuery("clickhouse", 7, 0)
188+
// Math.floor(0) || 100 = 100 (0 is falsy), then Math.max(1, Math.min(100, 10000)) = 100
189+
expect(zeroLimit?.sql).toContain("LIMIT 100")
190+
})
191+
192+
test("ClickHouse buildHistoryQuery handles NaN and float inputs safely", () => {
193+
// NaN days defaults to 30 via || 30 fallback
194+
const nanDays = HistoryTemplates.buildHistoryQuery("clickhouse", NaN, 50)
195+
expect(nanDays?.sql).toContain("today() - 30")
196+
expect(nanDays?.sql).not.toContain("NaN")
197+
198+
// NaN limit defaults to 100 via || 100 fallback
199+
const nanLimit = HistoryTemplates.buildHistoryQuery("clickhouse", 7, NaN)
200+
expect(nanLimit?.sql).toContain("LIMIT 100")
201+
expect(nanLimit?.sql).not.toContain("NaN")
202+
203+
// Float values should be floored
204+
const floatInputs = HistoryTemplates.buildHistoryQuery("clickhouse", 7.9, 50.5)
205+
expect(floatInputs?.sql).toContain("today() - 7")
206+
expect(floatInputs?.sql).toContain("LIMIT 50")
207+
})
161208
})
162209

163210
describe("warehouse-advisor", () => {

0 commit comments

Comments
 (0)