diff --git a/packages/opencode/test/altimate/sql-analyze-tool.test.ts b/packages/opencode/test/altimate/sql-analyze-tool.test.ts new file mode 100644 index 0000000000..21afddf46e --- /dev/null +++ b/packages/opencode/test/altimate/sql-analyze-tool.test.ts @@ -0,0 +1,172 @@ +/** + * Tests for SqlAnalyzeTool title construction and formatAnalysis output. + * + * These test the formatting logic patterns used in sql-analyze.ts. + * Since formatAnalysis is not exported, we replicate its logic here — + * same approach as tool-formatters.test.ts. This means these tests + * will not catch changes to the real function unless the test copy + * is also updated. This is an accepted tradeoff in this codebase. + */ + +import { describe, test, expect } from "bun:test" + +describe("SqlAnalyzeTool: title construction", () => { + // Replicates the title template from sql-analyze.ts execute() line 25 + function buildTitle(result: { error?: string; issue_count: number; confidence: string }) { + return `Analyze: ${result.error ? "PARSE ERROR" : `${result.issue_count} issue${result.issue_count !== 1 ? "s" : ""}`} [${result.confidence}]` + } + + test("zero issues shows '0 issues'", () => { + expect(buildTitle({ issue_count: 0, confidence: "high" })).toBe("Analyze: 0 issues [high]") + }) + + test("one issue shows singular '1 issue'", () => { + expect(buildTitle({ issue_count: 1, confidence: "high" })).toBe("Analyze: 1 issue [high]") + }) + + test("multiple issues shows plural", () => { + expect(buildTitle({ issue_count: 5, confidence: "medium" })).toBe("Analyze: 5 issues [medium]") + }) + + test("error present shows PARSE ERROR", () => { + expect(buildTitle({ error: "syntax error", issue_count: 0, confidence: "low" })).toBe( + "Analyze: PARSE ERROR [low]", + ) + }) +}) + +describe("SqlAnalyzeTool: formatAnalysis output", () => { + // Replicates formatAnalysis() from sql-analyze.ts lines 45-70 + function formatAnalysis(result: { + error?: string + issues: Array<{ + type: string + severity: string + message: string + recommendation: string + location?: string + confidence: string + }> + issue_count: number + confidence: string + confidence_factors: string[] + }): string { + if (result.error) return `Analysis failed: ${result.error}` + if (result.issues.length === 0) return "No anti-patterns or issues detected." + + const lines: string[] = [ + `Found ${result.issue_count} issue${result.issue_count !== 1 ? "s" : ""} (confidence: ${result.confidence}):`, + ] + if (result.confidence_factors.length > 0) { + lines.push(` Note: ${result.confidence_factors.join("; ")}`) + } + lines.push("") + + for (const issue of result.issues) { + const loc = issue.location ? ` \u2014 ${issue.location}` : "" + const conf = issue.confidence !== "high" ? ` [${issue.confidence} confidence]` : "" + lines.push(` [${issue.severity.toUpperCase()}] ${issue.type}${conf}`) + lines.push(` ${issue.message}${loc}`) + lines.push(` \u2192 ${issue.recommendation}`) + lines.push("") + } + + return lines.join("\n") + } + + test("error result returns failure message", () => { + const output = formatAnalysis({ + error: "parse error at line 5", + issues: [], + issue_count: 0, + confidence: "low", + confidence_factors: [], + }) + expect(output).toBe("Analysis failed: parse error at line 5") + }) + + test("zero issues returns clean message", () => { + const output = formatAnalysis({ + issues: [], + issue_count: 0, + confidence: "high", + confidence_factors: [], + }) + expect(output).toBe("No anti-patterns or issues detected.") + }) + + test("issues are formatted with severity, type, location", () => { + const output = formatAnalysis({ + issues: [ + { + type: "lint", + severity: "warning", + message: "SELECT * detected", + recommendation: "Use explicit columns", + location: "line 1", + confidence: "high", + }, + ], + issue_count: 1, + confidence: "high", + confidence_factors: ["lint"], + }) + expect(output).toContain("[WARNING] lint") + expect(output).toContain("SELECT * detected \u2014 line 1") + expect(output).toContain("\u2192 Use explicit columns") + }) + + test("non-high confidence issues show confidence tag", () => { + const output = formatAnalysis({ + issues: [ + { + type: "semantic", + severity: "info", + message: "Possible unused join", + recommendation: "Review join necessity", + confidence: "medium", + }, + ], + issue_count: 1, + confidence: "medium", + confidence_factors: ["semantics"], + }) + expect(output).toContain("[medium confidence]") + }) + + test("high confidence issues omit confidence tag", () => { + const output = formatAnalysis({ + issues: [ + { + type: "safety", + severity: "high", + message: "SQL injection risk", + recommendation: "Use parameterized queries", + confidence: "high", + }, + ], + issue_count: 1, + confidence: "high", + confidence_factors: ["safety"], + }) + expect(output).not.toContain("[high confidence]") + }) + + test("confidence factors are listed in Note line", () => { + const output = formatAnalysis({ + issues: [ + { + type: "lint", + severity: "warning", + message: "Missing LIMIT", + recommendation: "Add LIMIT clause", + confidence: "high", + }, + ], + issue_count: 1, + confidence: "high", + confidence_factors: ["lint", "semantics", "safety"], + }) + expect(output).toContain("Note: lint; semantics; safety") + }) +}) diff --git a/packages/opencode/test/command/builtin-commands.test.ts b/packages/opencode/test/command/builtin-commands.test.ts new file mode 100644 index 0000000000..9da0a8e59f --- /dev/null +++ b/packages/opencode/test/command/builtin-commands.test.ts @@ -0,0 +1,116 @@ +import { describe, test, expect } from "bun:test" +import { Command } from "../../src/command/index" +import { Instance } from "../../src/project/instance" +import { tmpdir } from "../fixture/fixture" + +async function withInstance(fn: () => Promise) { + await using tmp = await tmpdir({ git: true }) + await Instance.provide({ directory: tmp.path, fn }) +} + +describe("altimate builtin commands", () => { + describe("discover-and-add-mcps", () => { + test("is registered as a default command", async () => { + await withInstance(async () => { + const cmd = await Command.get("discover-and-add-mcps") + expect(cmd).toBeDefined() + expect(cmd.name).toBe("discover-and-add-mcps") + expect(cmd.source).toBe("command") + }) + }) + + test("has correct description", async () => { + await withInstance(async () => { + const cmd = await Command.get("discover-and-add-mcps") + expect(cmd.description).toBe("discover MCP servers from external AI tool configs and add them") + }) + }) + + test("template references MCP discovery workflow", async () => { + await withInstance(async () => { + const cmd = await Command.get("discover-and-add-mcps") + const template = await cmd.template + expect(typeof template).toBe("string") + expect(template.length).toBeGreaterThan(0) + // The template should reference MCP-related concepts + expect(template.toLowerCase()).toContain("mcp") + }) + }) + + test("is present in Command.Default constants", () => { + expect(Command.Default.DISCOVER_MCPS).toBe("discover-and-add-mcps") + }) + }) + + describe("configure-claude", () => { + test("is registered as a default command", async () => { + await withInstance(async () => { + const cmd = await Command.get("configure-claude") + expect(cmd).toBeDefined() + expect(cmd.name).toBe("configure-claude") + expect(cmd.source).toBe("command") + }) + }) + + test("has correct description", async () => { + await withInstance(async () => { + const cmd = await Command.get("configure-claude") + expect(cmd.description).toBe("configure /altimate command in Claude Code") + }) + }) + + test("is present in Command.Default constants", () => { + expect(Command.Default.CONFIGURE_CLAUDE).toBe("configure-claude") + }) + }) + + describe("configure-codex", () => { + test("is registered as a default command", async () => { + await withInstance(async () => { + const cmd = await Command.get("configure-codex") + expect(cmd).toBeDefined() + expect(cmd.name).toBe("configure-codex") + expect(cmd.source).toBe("command") + }) + }) + + test("has correct description", async () => { + await withInstance(async () => { + const cmd = await Command.get("configure-codex") + expect(cmd.description).toBe("configure altimate skill in Codex CLI") + }) + }) + + test("is present in Command.Default constants", () => { + expect(Command.Default.CONFIGURE_CODEX).toBe("configure-codex") + }) + }) +}) + +describe("Command.hints()", () => { + test("extracts numbered placeholders in order", () => { + expect(Command.hints("Do $1 then $2")).toEqual(["$1", "$2"]) + }) + + test("extracts $ARGUMENTS", () => { + expect(Command.hints("Run with $ARGUMENTS")).toEqual(["$ARGUMENTS"]) + }) + + test("extracts both numbered and $ARGUMENTS", () => { + expect(Command.hints("Do $1 with $ARGUMENTS")).toEqual(["$1", "$ARGUMENTS"]) + }) + + test("deduplicates repeated placeholders", () => { + expect(Command.hints("$1 and $1 again")).toEqual(["$1"]) + }) + + test("returns empty array for no placeholders", () => { + expect(Command.hints("plain text")).toEqual([]) + }) + + test("sorts numbered placeholders lexicographically", () => { + // Note: uses .sort() with no comparator, so $10 sorts before $2. + // For single-digit placeholders, lexicographic order matches numeric. + expect(Command.hints("$3 then $1 then $2")).toEqual(["$1", "$2", "$3"]) + }) +})