import { describe, expect } from "bun:test" import { Effect, Layer } from "effect" import { HttpRouter } from "effect/unstable/http" import { Flag } from "@opencode-ai/core/flag/flag" import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server" import { ExperimentalPaths } from "../../src/server/routes/instance/httpapi/groups/experimental" import { WorkspacePaths } from "../../src/server/routes/instance/httpapi/groups/workspace" import { withTimeout } from "../../src/util/timeout" import { resetDatabase } from "../fixture/db" import { TestInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" const stateLayer = Layer.effectDiscard( Effect.gen(function* () { const original = { OPENCODE_EXPERIMENTAL_WORKSPACES: Flag.OPENCODE_EXPERIMENTAL_WORKSPACES, } Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = true yield* Effect.addFinalizer(() => Effect.promise(async () => { Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = original.OPENCODE_EXPERIMENTAL_WORKSPACES await resetDatabase() }), ) }), ) const it = testEffect(stateLayer) type TestServer = ReturnType function serverScoped() { return Effect.acquireRelease( Effect.sync(() => HttpRouter.toWebHandler(ExperimentalHttpApiServer.routes, { disableLogger: true })), (server) => Effect.promise(() => server.dispose()).pipe(Effect.ignore), ) } function request(server: TestServer, input: string, init?: RequestInit) { return Effect.promise(() => server.handler(new Request(new URL(input, "http://localhost"), init), ExperimentalHttpApiServer.context), ) } function withRequestTimeout(effect: Effect.Effect, label: string, ms = 5_000) { return Effect.promise(() => withTimeout(Effect.runPromise(effect), ms, label)) } function setProjectStartCommand(input: { server: TestServer; directory: string; command: string }) { return Effect.gen(function* () { const current = yield* request(input.server, `/project/current?directory=${encodeURIComponent(input.directory)}`) expect(current.status).toBe(200) const project = (yield* Effect.promise(() => current.json())) as { id: string } const updated = yield* request( input.server, `/project/${project.id}?directory=${encodeURIComponent(input.directory)}`, { method: "PATCH", headers: { "content-type": "application/json" }, body: JSON.stringify({ commands: { start: input.command } }), }, ) expect(updated.status).toBe(200) }) } describe("worktree endpoint reproduction", () => { it.instance( "direct HttpApi worktree create returns without waiting for boot", () => Effect.gen(function* () { const test = yield* TestInstance const server = yield* serverScoped() const response = yield* withRequestTimeout( request(server, `${ExperimentalPaths.worktree}?directory=${encodeURIComponent(test.directory)}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({}), }), "direct worktree create", ) expect(response.status).toBe(200) expect(yield* Effect.promise(() => response.json())).toMatchObject({ directory: expect.any(String) }) }), { git: true }, ) it.instance( "workspace worktree create does not hang", () => Effect.gen(function* () { const test = yield* TestInstance const server = yield* serverScoped() const response = yield* withRequestTimeout( request(server, `${WorkspacePaths.list}?directory=${encodeURIComponent(test.directory)}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ type: "worktree", branch: null }), }), "workspace worktree create", 8_000, ) expect(response.status).toBe(200) expect(yield* Effect.promise(() => response.json())).toMatchObject({ type: "worktree", directory: expect.any(String), }) }), { git: true }, ) it.instance( "workspace worktree create returns without waiting for project start command", () => Effect.gen(function* () { const test = yield* TestInstance const server = yield* serverScoped() yield* setProjectStartCommand({ server, directory: test.directory, command: 'bun -e "setTimeout(() => {}, 2000)"', }) const started = Date.now() const response = yield* withRequestTimeout( request(server, `${WorkspacePaths.list}?directory=${encodeURIComponent(test.directory)}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ type: "worktree", branch: null }), }), "workspace worktree create with project start command", 6_000, ) expect(response.status).toBe(200) expect(Date.now() - started).toBeLessThan(1_500) }), { git: true }, ) })