read-only.test.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. // Tier-A smoke tests for read-only commands. Each test asserts only that the
  2. // command exits 0 and produces *some* output in the isolated harness env.
  3. //
  4. // These are not behavioral tests — they're the cheapest possible signal that
  5. // the dependency-layer wiring (config load, DB init, server boot, provider
  6. // resolution) doesn't crash for the broad class of "no inputs, no side
  7. // effects" commands. A regression in any shared layer (an Effect.fail that
  8. // propagates out of a service constructor, a renamed env var, a broken DB
  9. // migration) will fail one or more of these tests.
  10. //
  11. // If a future change should make one of these commands intentionally fail in
  12. // an empty env, update the assertion + add a note explaining the new contract.
  13. //
  14. // Speed: each test pays ~1.5s for bun startup. 7 tests serialize within this
  15. // file. See script/prebuild-test-cli.ts for an opt-in pre-built binary that
  16. // cuts per-spawn cost when this suite gets bigger.
  17. import { describe, expect } from "bun:test"
  18. import { Effect } from "effect"
  19. import { cliIt } from "../../lib/cli-process"
  20. describe("kirincode read-only commands (smoke)", () => {
  21. // `mcp list` reads MCP server config and pings each one. With the empty
  22. // KIRINCODE_CONFIG_CONTENT={} we provide, no servers should be configured
  23. // and the command should report that cleanly.
  24. cliIt.live(
  25. "mcp list: exits 0",
  26. ({ kirincode }) =>
  27. Effect.gen(function* () {
  28. const r = yield* kirincode.spawn(["mcp", "list"])
  29. opencode.expectExit(r, 0, "mcp list")
  30. }),
  31. 60_000,
  32. )
  33. // `providers list` enumerates credentials + env-resolved providers.
  34. // (Not config-injected ones — those don't appear here by design.) The
  35. // Credentials header always renders; the Environment header only renders
  36. // when at least one provider env var is set, which the isolation harness
  37. // deliberately doesn't guarantee. Assert the always-present marker so the
  38. // test passes on a clean CI runner without env-var leakage.
  39. cliIt.live(
  40. "providers list: exits 0 and prints the credentials section",
  41. ({ kirincode }) =>
  42. Effect.gen(function* () {
  43. const r = yield* kirincode.spawn(["providers", "list"])
  44. opencode.expectExit(r, 0, "providers list")
  45. expect(r.stdout).toContain("Credentials")
  46. }),
  47. 60_000,
  48. )
  49. // `models` lists models from configured providers. Our test/test-model
  50. // should appear because it's wired into the test provider config.
  51. cliIt.live(
  52. "models: exits 0 and lists the test model",
  53. ({ kirincode }) =>
  54. Effect.gen(function* () {
  55. const r = yield* kirincode.spawn(["models"])
  56. opencode.expectExit(r, 0, "models")
  57. expect(r.stdout).toContain("test/test-model")
  58. }),
  59. 60_000,
  60. )
  61. // `agent list` walks the agent config. Empty config means no agents
  62. // configured; the command should still exit 0 with a "no agents" line or
  63. // similar. We don't pin the message — just exit cleanly.
  64. cliIt.live(
  65. "agent list: exits 0",
  66. ({ kirincode }) =>
  67. Effect.gen(function* () {
  68. const r = yield* kirincode.spawn(["agent", "list"])
  69. opencode.expectExit(r, 0, "agent list")
  70. }),
  71. 60_000,
  72. )
  73. // `session list` reads the session DB. Fresh KIRINCODE_TEST_HOME means
  74. // empty DB. Exit 0 with no sessions.
  75. cliIt.live(
  76. "session list: exits 0",
  77. ({ kirincode }) =>
  78. Effect.gen(function* () {
  79. const r = yield* kirincode.spawn(["session", "list"])
  80. opencode.expectExit(r, 0, "session list")
  81. }),
  82. 60_000,
  83. )
  84. // `stats` aggregates token usage from the session DB. Empty DB → all zeros.
  85. cliIt.live(
  86. "stats: exits 0",
  87. ({ kirincode }) =>
  88. Effect.gen(function* () {
  89. const r = yield* kirincode.spawn(["stats"])
  90. opencode.expectExit(r, 0, "stats")
  91. }),
  92. 60_000,
  93. )
  94. // `db path` prints the DB file location. Under harness isolation the DB
  95. // resolves to SQLite's `:memory:` (no on-disk pollution between tests);
  96. // in production it'd be a path under KIRINCODE_TEST_HOME / XDG_DATA_HOME.
  97. // Accept either form — both prove the resolver ran without crashing.
  98. cliIt.live(
  99. "db path: exits 0 and prints a path or :memory:",
  100. ({ kirincode }) =>
  101. Effect.gen(function* () {
  102. const r = yield* kirincode.spawn(["db", "path"])
  103. opencode.expectExit(r, 0, "db path")
  104. expect(r.stdout.trim()).toMatch(/^(:memory:|[/\\].+\.(db|sqlite|sqlite3))$/i)
  105. }),
  106. 60_000,
  107. )
  108. })