diff --git a/packages/opencode/test/altimate/connections.test.ts b/packages/opencode/test/altimate/connections.test.ts index cbd01d27c..f741a8cf1 100644 --- a/packages/opencode/test/altimate/connections.test.ts +++ b/packages/opencode/test/altimate/connections.test.ts @@ -51,6 +51,36 @@ describe("ConnectionRegistry", () => { ) }) + test("cassandra gives helpful hint instead of generic unsupported error", async () => { + Registry.setConfigs({ + mydb: { type: "cassandra", host: "localhost" }, + }) + await expect(Registry.get("mydb")).rejects.toThrow("not yet supported") + await expect(Registry.get("mydb")).rejects.toThrow("cqlsh") + }) + + test("cockroachdb suggests using postgres type", async () => { + Registry.setConfigs({ + mydb: { type: "cockroachdb", host: "localhost" }, + }) + await expect(Registry.get("mydb")).rejects.toThrow("postgres") + }) + + test("timescaledb suggests using postgres type", async () => { + Registry.setConfigs({ + mydb: { type: "timescaledb", host: "localhost" }, + }) + await expect(Registry.get("mydb")).rejects.toThrow("postgres") + }) + + test("truly unknown type gives generic unsupported error with supported list", async () => { + Registry.setConfigs({ + mydb: { type: "neo4j", host: "localhost" }, + }) + await expect(Registry.get("mydb")).rejects.toThrow("Unsupported database type") + await expect(Registry.get("mydb")).rejects.toThrow("Supported:") + }) + test("getConfig returns config for known connection", () => { Registry.setConfigs({ mydb: { type: "postgres", host: "localhost" }, @@ -608,6 +638,44 @@ trino_project: fs.rmSync(tmpDir, { recursive: true }) } }) + + test("clickhouse adapter maps correctly from dbt profiles", async () => { + const fs = await import("fs") + const os = await import("os") + const path = await import("path") + + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "dbt-test-")) + const profilesPath = path.join(tmpDir, "profiles.yml") + + fs.writeFileSync( + profilesPath, + ` +ch_project: + outputs: + dev: + type: clickhouse + host: clickhouse.example.com + port: 8443 + user: default + password: secret + database: analytics + schema: default +`, + ) + + try { + const connections = await parseDbtProfiles(profilesPath) + expect(connections).toHaveLength(1) + expect(connections[0].type).toBe("clickhouse") + expect(connections[0].config.type).toBe("clickhouse") + expect(connections[0].config.host).toBe("clickhouse.example.com") + expect(connections[0].config.port).toBe(8443) + expect(connections[0].config.user).toBe("default") + expect(connections[0].config.database).toBe("analytics") + } finally { + fs.rmSync(tmpDir, { recursive: true }) + } + }) }) // --------------------------------------------------------------------------- diff --git a/packages/opencode/test/altimate/schema-finops-dbt.test.ts b/packages/opencode/test/altimate/schema-finops-dbt.test.ts index 8e6d59075..cad71b41e 100644 --- a/packages/opencode/test/altimate/schema-finops-dbt.test.ts +++ b/packages/opencode/test/altimate/schema-finops-dbt.test.ts @@ -158,6 +158,53 @@ describe("FinOps: SQL template generation", () => { const built = HistoryTemplates.buildHistoryQuery("databricks", 7, 50) expect(built?.sql).toContain("system.query.history") }) + + test("builds ClickHouse history SQL with clamped integer days and limit", () => { + const built = HistoryTemplates.buildHistoryQuery("clickhouse", 7, 100) + expect(built).not.toBeNull() + expect(built?.sql).toContain("system.query_log") + expect(built?.sql).toContain("QueryFinish") + // Days and limit should be integer-substituted, not bind params + expect(built?.binds).toEqual([]) + // Verify the clamped values are in the SQL + expect(built?.sql).toContain("today() - 7") + expect(built?.sql).toContain("LIMIT 100") + }) + + test("ClickHouse buildHistoryQuery clamps extreme days and limit values", () => { + // Days clamped to [1, 365] + const extremeDays = HistoryTemplates.buildHistoryQuery("clickhouse", 9999, 50) + expect(extremeDays?.sql).toContain("today() - 365") + + const zeroDays = HistoryTemplates.buildHistoryQuery("clickhouse", 0, 50) + // Math.floor(0) || 30 = 30 (0 is falsy), then Math.max(1, Math.min(30, 365)) = 30 + expect(zeroDays?.sql).toContain("today() - 30") + + // Limit clamped to [1, 10000] + const extremeLimit = HistoryTemplates.buildHistoryQuery("clickhouse", 7, 999999) + expect(extremeLimit?.sql).toContain("LIMIT 10000") + + const zeroLimit = HistoryTemplates.buildHistoryQuery("clickhouse", 7, 0) + // Math.floor(0) || 100 = 100 (0 is falsy), then Math.max(1, Math.min(100, 10000)) = 100 + expect(zeroLimit?.sql).toContain("LIMIT 100") + }) + + test("ClickHouse buildHistoryQuery handles NaN and float inputs safely", () => { + // NaN days defaults to 30 via || 30 fallback + const nanDays = HistoryTemplates.buildHistoryQuery("clickhouse", NaN, 50) + expect(nanDays?.sql).toContain("today() - 30") + expect(nanDays?.sql).not.toContain("NaN") + + // NaN limit defaults to 100 via || 100 fallback + const nanLimit = HistoryTemplates.buildHistoryQuery("clickhouse", 7, NaN) + expect(nanLimit?.sql).toContain("LIMIT 100") + expect(nanLimit?.sql).not.toContain("NaN") + + // Float values should be floored + const floatInputs = HistoryTemplates.buildHistoryQuery("clickhouse", 7.9, 50.5) + expect(floatInputs?.sql).toContain("today() - 7") + expect(floatInputs?.sql).toContain("LIMIT 50") + }) }) describe("warehouse-advisor", () => {