worktree.test.ts 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. import fs from "node:fs/promises"
  2. import path from "node:path"
  3. import { $ } from "bun"
  4. import { expect } from "bun:test"
  5. import { Effect } from "effect"
  6. import { HttpServer } from "effect/unstable/http"
  7. import { tmpdir } from "../../core/test/fixture/tmpdir"
  8. import { it } from "../../core/test/lib/effect"
  9. import { ServerProcess } from "../src/process"
  10. it.live("lists, creates, and removes worktrees by project ID", () =>
  11. Effect.acquireUseRelease(
  12. Effect.promise(() => tmpdir("opencode-worktree-endpoint-")),
  13. (tmp) =>
  14. Effect.gen(function* () {
  15. const project = path.join(tmp.path, "project")
  16. const destination = path.join(tmp.path, "worktrees")
  17. yield* Effect.promise(() => fs.mkdir(project, { recursive: true }))
  18. yield* Effect.promise(() => $`git init`.cwd(project).quiet())
  19. yield* Effect.promise(() => $`git config user.email test@opencode.test`.cwd(project).quiet())
  20. yield* Effect.promise(() => $`git config user.name Test`.cwd(project).quiet())
  21. yield* Effect.promise(() => $`git commit --allow-empty -m root`.cwd(project).quiet())
  22. const server = yield* ServerProcess.start<never, never>({
  23. hostname: "127.0.0.1",
  24. port: 0,
  25. password: "secret",
  26. app: { version: "test-version" },
  27. database: { path: ":memory:" },
  28. config: { directory: path.join(tmp.path, "config") },
  29. fs: { filewatcher: false },
  30. })
  31. const base = HttpServer.formatAddress(server.address)
  32. const headers = { authorization: `Basic ${btoa("opencode:secret")}` }
  33. const location = new URL("/api/location", base)
  34. location.searchParams.set("location[directory]", project)
  35. const resolved = yield* Effect.promise(() => fetch(location, { headers }).then((response) => response.json()))
  36. if (!isRecord(resolved) || !isRecord(resolved.project) || typeof resolved.project.id !== "string")
  37. throw new Error("Expected resolved project")
  38. const url = new URL(`/api/worktree/${resolved.project.id}`, base)
  39. const initial = yield* Effect.promise(() => fetch(url, { headers }).then((response) => response.json()))
  40. expect(initial).toEqual([{ directory: project }])
  41. const created = yield* Effect.promise(() =>
  42. fetch(url, {
  43. method: "POST",
  44. headers: { ...headers, "content-type": "application/json" },
  45. body: JSON.stringify({ strategy: "git", directory: destination, name: "api" }),
  46. }).then((response) => response.json()),
  47. )
  48. expect(created).toEqual({ directory: path.join(destination, "api") })
  49. const listed = yield* Effect.promise(() => fetch(url, { headers }).then((response) => response.json()))
  50. expect(listed).toContainEqual({ directory: path.join(destination, "api"), strategy: "git" })
  51. const removed = yield* Effect.promise(() =>
  52. fetch(url, {
  53. method: "DELETE",
  54. headers: { ...headers, "content-type": "application/json" },
  55. body: JSON.stringify({ directory: path.join(destination, "api"), force: false }),
  56. }),
  57. )
  58. expect(removed.status).toBe(204)
  59. }),
  60. (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
  61. ),
  62. )
  63. function isRecord(value: unknown): value is Record<string, unknown> {
  64. return typeof value === "object" && value !== null && !Array.isArray(value)
  65. }