config.test.ts 3.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import { describe, expect, test } from "bun:test"
  2. import { Schema } from "effect"
  3. import { Config } from "../src/config.js"
  4. import { ConfigAgent } from "../src/config/agent.js"
  5. import { ConfigMCP } from "../src/config/mcp.js"
  6. import { ConfigProvider } from "../src/config/provider.js"
  7. import { Mcp } from "../src/mcp.js"
  8. import { AbsolutePath } from "../src/schema.js"
  9. import { WebSearch } from "../src/websearch.js"
  10. describe("Config.Entry", () => {
  11. test("accepts disabled, fixed, and random web search selection", () => {
  12. const decode = Schema.decodeUnknownSync(Config.Info)
  13. expect(decode({ websearch: false }).websearch).toBe(false)
  14. expect(decode({ websearch: { provider: "exa" } }).websearch).toEqual({ provider: WebSearch.ID.make("exa") })
  15. expect(decode({ websearch: { provider: "random" } }).websearch).toEqual({ provider: "random" })
  16. })
  17. test("round-trips every configuration entry type", () => {
  18. const entries = [
  19. new Config.Document({
  20. type: "document",
  21. path: AbsolutePath.make("/project/opencode.json"),
  22. info: new Config.Info({
  23. permissions: [
  24. { action: "shell", resource: "*", effect: "ask" },
  25. { action: "shell", resource: "git status", effect: "allow" },
  26. ],
  27. }),
  28. }),
  29. new Config.Document({ type: "document", info: new Config.Info({ shell: "/bin/zsh" }) }),
  30. new Config.Directory({ type: "directory", path: AbsolutePath.make("/project/.opencode") }),
  31. new Config.AgentsDirectory({ type: "agents", path: AbsolutePath.make("/project/.agents") }),
  32. new Config.ClaudeDirectory({ type: "claude", path: AbsolutePath.make("/project/.claude") }),
  33. ]
  34. const encoded = Schema.encodeSync(Schema.Array(Config.Entry))(entries)
  35. const decoded = Schema.decodeUnknownSync(Schema.Array(Config.Entry))(encoded)
  36. expect(decoded).toEqual(entries)
  37. expect(decoded[0]).toBeInstanceOf(Config.Document)
  38. expect(decoded[1]).not.toHaveProperty("path")
  39. expect(decoded.map((entry) => entry.type)).toEqual(["document", "document", "directory", "agents", "claude"])
  40. expect(decoded[0]?.type === "document" ? decoded[0].info.permissions : undefined).toEqual([
  41. { action: "shell", resource: "*", effect: "ask" },
  42. { action: "shell", resource: "git status", effect: "allow" },
  43. ])
  44. })
  45. test("has a stable public identifier", () => {
  46. expect(Config.Entry.ast.annotations?.identifier).toBe("Config.Entry")
  47. })
  48. test("omits undefined optional properties while encoding", () => {
  49. const entry = new Config.Document({
  50. type: "document",
  51. path: undefined,
  52. info: new Config.Info({
  53. default_agent: undefined,
  54. agents: { reviewer: new ConfigAgent.Info({ description: undefined }) },
  55. mcp: new ConfigMCP.Info({
  56. timeout: undefined,
  57. servers: {
  58. docs: new Mcp.RemoteConfig({
  59. type: "remote",
  60. url: "https://example.com/mcp",
  61. headers: undefined,
  62. oauth: new Mcp.OAuthConfig({ client_id: undefined }),
  63. }),
  64. },
  65. }),
  66. providers: { custom: new ConfigProvider.Info({ headers: undefined }) },
  67. }),
  68. })
  69. const encoded = Schema.encodeSync(Config.Entry)(entry)
  70. if (encoded.type !== "document") throw new Error("Expected a config document")
  71. expect(encoded).not.toHaveProperty("path")
  72. expect(encoded.info).not.toHaveProperty("default_agent")
  73. expect(encoded.info.agents?.reviewer).not.toHaveProperty("description")
  74. expect(encoded.info.mcp).not.toHaveProperty("timeout")
  75. const docs = encoded.info.mcp?.servers?.docs
  76. if (docs?.type !== "remote" || docs.oauth === false) throw new Error("Expected a remote MCP server")
  77. expect(docs).not.toHaveProperty("headers")
  78. expect(docs.oauth).not.toHaveProperty("client_id")
  79. expect(encoded.info.providers?.custom).not.toHaveProperty("headers")
  80. })
  81. })