Skip to content

Commit ebdf382

Browse files
committed
test(httpapi): cover workspace target routing
1 parent 62c73c7 commit ebdf382

1 file changed

Lines changed: 113 additions & 1 deletion

File tree

packages/opencode/test/server/httpapi-workspace.test.ts

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { afterEach, describe, expect, test } from "bun:test"
1+
import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"
22
import { mkdir } from "node:fs/promises"
33
import path from "node:path"
44
import { Effect } from "effect"
@@ -13,6 +13,7 @@ import { Server } from "../../src/server/server"
1313
import { resetDatabase } from "../fixture/db"
1414
import { tmpdir } from "../fixture/fixture"
1515
import { Instance } from "../../src/project/instance"
16+
import { InstancePaths } from "../../src/server/routes/instance/httpapi/groups/instance"
1617

1718
void Log.init({ print: false })
1819

@@ -54,7 +55,41 @@ function localAdaptor(directory: string): WorkspaceAdaptor {
5455
}
5556
}
5657

58+
function remoteAdaptor(directory: string, url: string): WorkspaceAdaptor {
59+
return {
60+
name: "Remote Test",
61+
description: "Create a remote test workspace",
62+
configure(info) {
63+
return {
64+
...info,
65+
name: "remote-test",
66+
directory,
67+
}
68+
},
69+
async create() {
70+
await mkdir(directory, { recursive: true })
71+
},
72+
async remove() {},
73+
target() {
74+
return {
75+
type: "remote" as const,
76+
url,
77+
}
78+
},
79+
}
80+
}
81+
82+
function eventStreamResponse() {
83+
return new Response(new ReadableStream({ start() {} }), {
84+
status: 200,
85+
headers: {
86+
"content-type": "text/event-stream",
87+
},
88+
})
89+
}
90+
5791
afterEach(async () => {
92+
mock.restore()
5893
Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = originalWorkspaces
5994
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = originalHttpApi
6095
await Instance.disposeAll()
@@ -125,4 +160,81 @@ describe("workspace HttpApi", () => {
125160
expect(listed.status).toBe(200)
126161
expect(await listed.json()).toEqual([])
127162
})
163+
164+
test("routes local workspace requests through the workspace target directory", async () => {
165+
Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = true
166+
await using tmp = await tmpdir({ git: true })
167+
const workspaceDir = path.join(tmp.path, ".workspace-local")
168+
const workspace = await Instance.provide({
169+
directory: tmp.path,
170+
fn: async () => {
171+
registerAdaptor(Instance.project.id, "local-target", localAdaptor(workspaceDir))
172+
return Workspace.create({
173+
type: "local-target",
174+
branch: null,
175+
extra: null,
176+
projectID: Instance.project.id,
177+
})
178+
},
179+
})
180+
181+
const url = new URL(`http://localhost${InstancePaths.path}`)
182+
url.searchParams.set("workspace", workspace.id)
183+
184+
try {
185+
const response = await request(url.toString(), tmp.path)
186+
187+
expect(response.status).toBe(200)
188+
expect(await response.json()).toMatchObject({ directory: workspaceDir })
189+
} finally {
190+
await Workspace.remove(workspace.id)
191+
}
192+
})
193+
194+
test("proxies remote workspace HTTP requests", async () => {
195+
Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = true
196+
await using tmp = await tmpdir({ git: true })
197+
const proxied: string[] = []
198+
const rawFetch = globalThis.fetch
199+
spyOn(globalThis, "fetch").mockImplementation(
200+
Object.assign(
201+
async (input: URL | RequestInfo, init?: BunFetchRequestInit | RequestInit) => {
202+
const url = new URL(typeof input === "string" || input instanceof URL ? input : input.url)
203+
if (url.pathname === "/base/global/event") return eventStreamResponse()
204+
if (url.pathname === "/base/sync/history") return Response.json([])
205+
proxied.push(url.toString())
206+
return Response.json({ proxied: true, path: url.pathname, workspace: url.searchParams.get("workspace") })
207+
},
208+
{
209+
preconnect: rawFetch.preconnect?.bind(rawFetch),
210+
},
211+
) as typeof globalThis.fetch,
212+
)
213+
214+
const workspace = await Instance.provide({
215+
directory: tmp.path,
216+
fn: async () => {
217+
registerAdaptor(Instance.project.id, "remote-target", remoteAdaptor(path.join(tmp.path, ".remote"), "https://remote.test/base"))
218+
return Workspace.create({
219+
type: "remote-target",
220+
branch: null,
221+
extra: null,
222+
projectID: Instance.project.id,
223+
})
224+
},
225+
})
226+
227+
const url = new URL(`http://localhost${InstancePaths.path}`)
228+
url.searchParams.set("workspace", workspace.id)
229+
230+
try {
231+
const response = await request(url.toString(), tmp.path)
232+
233+
expect(response.status).toBe(200)
234+
expect(await response.json()).toEqual({ proxied: true, path: "/base/path", workspace: null })
235+
expect(proxied).toEqual(["https://remote.test/base/path"])
236+
} finally {
237+
await Workspace.remove(workspace.id)
238+
}
239+
})
128240
})

0 commit comments

Comments
 (0)