config.test.ts 3.5 KB

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