Skip to content
Closed
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
157 changes: 157 additions & 0 deletions packages/opencode/test/cli/error-format.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { describe, test, expect } from "bun:test"
import { FormatError, FormatUnknownError } from "../../src/cli/error"

describe("FormatError: known error types", () => {
test("MCP.Failed returns helpful message", () => {
const err = { name: "MCPFailed", data: { name: "my-server" } }
const result = FormatError(err)
expect(result).toContain("my-server")
expect(result).toContain("failed")
})

test("Provider.ModelNotFoundError with suggestions", () => {
const err = {
name: "ProviderModelNotFoundError",
data: { providerID: "openai", modelID: "gpt-5", suggestions: ["gpt-4", "gpt-4o"] },
}
const result = FormatError(err)
expect(result).toContain("gpt-5")
expect(result).toContain("Did you mean")
expect(result).toContain("gpt-4")
})

test("Provider.ModelNotFoundError without suggestions", () => {
const err = {
name: "ProviderModelNotFoundError",
data: { providerID: "openai", modelID: "gpt-5", suggestions: [] },
}
const result = FormatError(err)
expect(result).toContain("gpt-5")
expect(result).not.toContain("Did you mean")
})

test("Provider.InitError returns provider name", () => {
const err = { name: "ProviderInitError", data: { providerID: "anthropic" } }
const result = FormatError(err)
expect(result).toContain("anthropic")
})

test("Config.JsonError with message", () => {
const err = { name: "ConfigJsonError", data: { path: "/home/user/.config/altimate.json", message: "Unexpected token" } }
const result = FormatError(err)
expect(result).toContain("altimate.json")
expect(result).toContain("Unexpected token")
})

test("Config.JsonError without message", () => {
const err = { name: "ConfigJsonError", data: { path: "/path/to/config.json" } }
const result = FormatError(err)
expect(result).toContain("config.json")
expect(result).toContain("not valid JSON")
})

test("Config.ConfigDirectoryTypoError", () => {
const err = {
name: "ConfigDirectoryTypoError",
data: { dir: ".openCode", path: "/project/.openCode", suggestion: ".opencode" },
}
const result = FormatError(err)
expect(result).toContain(".openCode")
expect(result).toContain(".opencode")
expect(result).toContain("typo")
})

test("ConfigMarkdown.FrontmatterError", () => {
const err = {
name: "ConfigFrontmatterError",
data: { path: "CLAUDE.md", message: "CLAUDE.md: Failed to parse YAML frontmatter: invalid key" },
}
const result = FormatError(err)
expect(result).toContain("Failed to parse")
})

test("Config.InvalidError with issues", () => {
const err = {
name: "ConfigInvalidError",
data: {
path: "provider.model",
message: "Invalid model",
issues: [{ message: "must be string", path: ["provider", "model"] }],
},
}
const result = FormatError(err)
expect(result).toContain("provider.model")
expect(result).toContain("must be string")
})

test("Config.InvalidError without path shows generic header", () => {
const err = {
name: "ConfigInvalidError",
data: { path: "config", issues: [] },
}
const result = FormatError(err)
expect(result).toContain("Configuration is invalid")
// "config" path should not appear as a location qualifier
expect(result).not.toContain("at config")
})

test("UI.CancelledError returns empty string", () => {
const err = { name: "UICancelledError" }
const result = FormatError(err)
expect(result).toBe("")
})

test("unknown error returns undefined", () => {
const err = new Error("random error")
expect(FormatError(err)).toBeUndefined()
})

test("null input returns undefined", () => {
expect(FormatError(null)).toBeUndefined()
})
})

describe("FormatUnknownError", () => {
test("Error with stack returns stack", () => {
const err = new Error("boom")
const result = FormatUnknownError(err)
expect(result).toContain("boom")
expect(result).toContain("Error")
})

test("Error without stack returns name + message", () => {
const err = new Error("boom")
err.stack = undefined
const result = FormatUnknownError(err)
expect(result).toBe("Error: boom")
})

test("plain object is JSON stringified", () => {
const result = FormatUnknownError({ code: 42, msg: "fail" })
expect(result).toContain('"code": 42')
expect(result).toContain('"msg": "fail"')
})

test("circular object returns fallback message", () => {
const obj: any = { a: 1 }
obj.self = obj
const result = FormatUnknownError(obj)
expect(result).toBe("Unexpected error (unserializable)")
})

test("string input returns itself", () => {
expect(FormatUnknownError("something went wrong")).toBe("something went wrong")
})

test("number input returns string representation", () => {
expect(FormatUnknownError(404)).toBe("404")
})

test("undefined returns string 'undefined'", () => {
expect(FormatUnknownError(undefined)).toBe("undefined")
})

test("null returns string 'null'", () => {
expect(FormatUnknownError(null)).toBe("null")
})
})
75 changes: 75 additions & 0 deletions packages/opencode/test/config/markdown.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,78 @@ describe("ConfigMarkdown: frontmatter has weird model id", async () => {
expect(result.content.trim()).toBe("Strictly follow da rules")
})
})

describe("ConfigMarkdown.fallbackSanitization", () => {
test("converts unquoted value with colon to block scalar that gray-matter can parse", async () => {
const input = `---\nurl: https://example.com:8080\n---\nContent`
const result = ConfigMarkdown.fallbackSanitization(input)
expect(result).toContain("url: |-")
expect(result).toContain(" https://example.com:8080")
// Verify the sanitized output is actually parseable
const matter = await import("gray-matter")
const parsed = matter.default(result)
expect(parsed.data.url).toBe("https://example.com:8080")
})

test("preserves already double-quoted values", () => {
const input = `---\nurl: "https://example.com:8080"\n---\nContent`
const result = ConfigMarkdown.fallbackSanitization(input)
expect(result).toContain('url: "https://example.com:8080"')
})

test("preserves already single-quoted values", () => {
const input = `---\nurl: 'https://example.com:8080'\n---\nContent`
const result = ConfigMarkdown.fallbackSanitization(input)
expect(result).toContain("url: 'https://example.com:8080'")
})

test("preserves empty values", () => {
const input = `---\nempty:\n---\nContent`
const result = ConfigMarkdown.fallbackSanitization(input)
expect(result).toContain("empty:")
})

test("preserves YAML comments", () => {
const input = `---\n# This is a comment\nkey: value\n---\nContent`
const result = ConfigMarkdown.fallbackSanitization(input)
expect(result).toContain("# This is a comment")
})

test("preserves indented continuation lines", () => {
const input = `---\nlist:\n - item1\n - item2\n---\nContent`
const result = ConfigMarkdown.fallbackSanitization(input)
expect(result).toContain(" - item1")
expect(result).toContain(" - item2")
})

test("returns input unchanged when no frontmatter present", () => {
const input = "Just plain content"
expect(ConfigMarkdown.fallbackSanitization(input)).toBe(input)
})

test("handles multiple values with colons", async () => {
const input = `---\nurl: http://a:1\nmodel: org/repo:tag\n---\nContent`
const result = ConfigMarkdown.fallbackSanitization(input)
expect(result).toContain("url: |-")
expect(result).toContain("model: |-")
// Verify both values parse correctly
const matter = await import("gray-matter")
const parsed = matter.default(result)
expect(parsed.data.url).toBe("http://a:1")
expect(parsed.data.model).toBe("org/repo:tag")
})

test("values without colons are left unchanged", () => {
const input = `---\nname: John\nage: 30\n---\nContent`
const result = ConfigMarkdown.fallbackSanitization(input)
expect(result).toContain("name: John")
expect(result).toContain("age: 30")
})

test("content after frontmatter is not modified", () => {
const input = `---\nkey: val:ue\n---\nurl: https://example.com:8080/path`
const result = ConfigMarkdown.fallbackSanitization(input)
// Body content should be preserved verbatim
expect(result).toContain("url: https://example.com:8080/path")
})
})
Loading