thread.test.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"
  2. import fs from "fs/promises"
  3. import path from "path"
  4. import { tmpdir } from "../../fixture/fixture"
  5. import * as App from "../../../src/cli/cmd/tui/app"
  6. import { Rpc } from "../../../src/util"
  7. import { UI } from "../../../src/cli/ui"
  8. import * as Timeout from "../../../src/util/timeout"
  9. import * as Network from "../../../src/cli/network"
  10. import * as Win32 from "../../../src/cli/cmd/tui/win32"
  11. import { TuiConfig } from "../../../src/cli/cmd/tui/config/tui"
  12. const stop = new Error("stop")
  13. const seen = {
  14. tui: [] as string[],
  15. }
  16. function setup() {
  17. // Intentionally avoid mock.module() here: Bun keeps module overrides in cache
  18. // and mock.restore() does not reset mock.module values. If this switches back
  19. // to module mocks, later suites can see mocked @/config/tui and fail (e.g.
  20. // plugin-loader tests expecting real TuiConfig.waitForDependencies). See:
  21. // https://github.com/oven-sh/bun/issues/7823 and #12823.
  22. spyOn(App, "tui").mockImplementation(async (input) => {
  23. if (input.directory) seen.tui.push(input.directory)
  24. throw stop
  25. })
  26. spyOn(Rpc, "client").mockImplementation(() => ({
  27. call: async () => ({ url: "http://127.0.0.1" }) as never,
  28. on: () => () => {},
  29. }))
  30. spyOn(UI, "error").mockImplementation(() => {})
  31. spyOn(Timeout, "withTimeout").mockImplementation((input) => input)
  32. spyOn(Network, "resolveNetworkOptions").mockResolvedValue({
  33. mdns: false,
  34. port: 0,
  35. hostname: "127.0.0.1",
  36. mdnsDomain: "opencode.local",
  37. cors: [],
  38. })
  39. spyOn(Win32, "win32DisableProcessedInput").mockImplementation(() => {})
  40. spyOn(Win32, "win32InstallCtrlCGuard").mockReturnValue(undefined)
  41. }
  42. describe("tui thread", () => {
  43. afterEach(() => {
  44. mock.restore()
  45. })
  46. async function call(project?: string) {
  47. const { TuiThreadCommand } = await import("../../../src/cli/cmd/tui/thread")
  48. const args: Parameters<NonNullable<typeof TuiThreadCommand.handler>>[0] = {
  49. _: [],
  50. $0: "kilo", // kilocode_change
  51. project,
  52. prompt: "hi",
  53. model: undefined,
  54. agent: undefined,
  55. session: undefined,
  56. continue: false,
  57. fork: false,
  58. "cloud-fork": undefined, // kilocode_change
  59. cloudFork: undefined, // kilocode_change
  60. port: 0,
  61. hostname: "127.0.0.1",
  62. mdns: false,
  63. "mdns-domain": "kilo.local", // kilocode_change
  64. mdnsDomain: "kilo.local", // kilocode_change
  65. cors: [],
  66. }
  67. return TuiThreadCommand.handler(args)
  68. }
  69. async function check(project?: string) {
  70. setup()
  71. await using tmp = await tmpdir({ git: true })
  72. const cwd = process.cwd()
  73. const pwd = process.env.PWD
  74. const worker = globalThis.Worker
  75. const tty = Object.getOwnPropertyDescriptor(process.stdin, "isTTY")
  76. const link = path.join(path.dirname(tmp.path), path.basename(tmp.path) + "-link")
  77. const type = process.platform === "win32" ? "junction" : "dir"
  78. seen.tui.length = 0
  79. await fs.symlink(tmp.path, link, type)
  80. Object.defineProperty(process.stdin, "isTTY", {
  81. configurable: true,
  82. value: true,
  83. })
  84. globalThis.Worker = class extends EventTarget {
  85. onerror = null
  86. onmessage = null
  87. onmessageerror = null
  88. postMessage() {}
  89. terminate() {}
  90. } as unknown as typeof Worker
  91. try {
  92. process.chdir(tmp.path)
  93. process.env.PWD = link
  94. await expect(call(project)).rejects.toBe(stop)
  95. expect(seen.tui[0]).toBe(tmp.path)
  96. } finally {
  97. process.chdir(cwd)
  98. if (pwd === undefined) delete process.env.PWD
  99. else process.env.PWD = pwd
  100. if (tty) Object.defineProperty(process.stdin, "isTTY", tty)
  101. else delete (process.stdin as { isTTY?: boolean }).isTTY
  102. globalThis.Worker = worker
  103. await fs.rm(link, { recursive: true, force: true }).catch(() => undefined)
  104. }
  105. }
  106. test("uses the real cwd when PWD points at a symlink", async () => {
  107. await check()
  108. })
  109. test("uses the real cwd after resolving a relative project from PWD", async () => {
  110. await check(".")
  111. })
  112. })