summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-04-24 18:03:51 -0400
committerGitHub <[email protected]>2026-04-24 18:03:51 -0400
commit97eb9fdee839020e18a1cf80bf4c5873e8138633 (patch)
tree886cbd7bd43c618cd8e386f254334d7b825577f4 /packages
parent5a04de231ee455bb112afc7b19af5c8e4144c6a2 (diff)
downloadopencode-97eb9fdee839020e18a1cf80bf4c5873e8138633.tar.gz
opencode-97eb9fdee839020e18a1cf80bf4c5873e8138633.zip
test(httpapi): cover hono bridge middleware (#24216)
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/server/routes/instance/httpapi/project.ts4
-rw-r--r--packages/opencode/test/server/httpapi-bridge.test.ts131
2 files changed, 133 insertions, 2 deletions
diff --git a/packages/opencode/src/server/routes/instance/httpapi/project.ts b/packages/opencode/src/server/routes/instance/httpapi/project.ts
index 10cf25118..6d3143df8 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/project.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/project.ts
@@ -1,4 +1,4 @@
-import { Instance } from "@/project/instance"
+import * as InstanceState from "@/effect/instance-state"
import { Project } from "@/project"
import { Effect, Layer, Schema } from "effect"
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
@@ -54,7 +54,7 @@ export const projectHandlers = Layer.unwrap(
})
const current = Effect.fn("ProjectHttpApi.current")(function* () {
- return Instance.project
+ return (yield* InstanceState.context).project
})
return HttpApiBuilder.group(ProjectApi, "project", (handlers) =>
diff --git a/packages/opencode/test/server/httpapi-bridge.test.ts b/packages/opencode/test/server/httpapi-bridge.test.ts
new file mode 100644
index 000000000..29f85b881
--- /dev/null
+++ b/packages/opencode/test/server/httpapi-bridge.test.ts
@@ -0,0 +1,131 @@
+import { afterEach, describe, expect, test } from "bun:test"
+import type { UpgradeWebSocket } from "hono/ws"
+import { Flag } from "../../src/flag/flag"
+import { Instance } from "../../src/project/instance"
+import { InstanceRoutes } from "../../src/server/routes/instance"
+import { FilePaths } from "../../src/server/routes/instance/httpapi/file"
+import { Log } from "../../src/util"
+import { resetDatabase } from "../fixture/db"
+import { tmpdir } from "../fixture/fixture"
+
+void Log.init({ print: false })
+
+const original = {
+ OPENCODE_EXPERIMENTAL_HTTPAPI: Flag.OPENCODE_EXPERIMENTAL_HTTPAPI,
+ OPENCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD,
+ OPENCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME,
+}
+
+const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket
+
+function app(input?: { password?: string; username?: string }) {
+ Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
+ Flag.OPENCODE_SERVER_PASSWORD = input?.password
+ Flag.OPENCODE_SERVER_USERNAME = input?.username
+ return InstanceRoutes(websocket)
+}
+
+function authorization(username: string, password: string) {
+ return `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`
+}
+
+function fileUrl(input?: { directory?: string; token?: string }) {
+ const url = new URL(`http://localhost${FilePaths.content}`)
+ url.searchParams.set("path", "hello.txt")
+ if (input?.directory) url.searchParams.set("directory", input.directory)
+ if (input?.token) url.searchParams.set("auth_token", input.token)
+ return url
+}
+
+afterEach(async () => {
+ Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = original.OPENCODE_EXPERIMENTAL_HTTPAPI
+ Flag.OPENCODE_SERVER_PASSWORD = original.OPENCODE_SERVER_PASSWORD
+ Flag.OPENCODE_SERVER_USERNAME = original.OPENCODE_SERVER_USERNAME
+ await Instance.disposeAll()
+ await resetDatabase()
+})
+
+describe("HttpApi Hono bridge", () => {
+ test("allows requests when auth is disabled", async () => {
+ await using tmp = await tmpdir({ git: true })
+ await Bun.write(`${tmp.path}/hello.txt`, "hello")
+
+ const response = await app().request(fileUrl(), {
+ headers: {
+ "x-opencode-directory": tmp.path,
+ },
+ })
+
+ expect(response.status).toBe(200)
+ expect(await response.json()).toMatchObject({ content: "hello" })
+ })
+
+ test("provides instance context to bridged handlers", async () => {
+ await using tmp = await tmpdir({ git: true })
+
+ const response = await app().request("/project/current", {
+ headers: {
+ "x-opencode-directory": tmp.path,
+ },
+ })
+
+ expect(response.status).toBe(200)
+ expect(await response.json()).toMatchObject({ worktree: tmp.path })
+ })
+
+ test("requires credentials when auth is enabled", async () => {
+ await using tmp = await tmpdir({ git: true })
+ await Bun.write(`${tmp.path}/hello.txt`, "hello")
+
+ const [missing, bad, good] = await Promise.all([
+ app({ password: "secret" }).request(fileUrl(), {
+ headers: { "x-opencode-directory": tmp.path },
+ }),
+ app({ password: "secret" }).request(fileUrl(), {
+ headers: {
+ authorization: authorization("opencode", "wrong"),
+ "x-opencode-directory": tmp.path,
+ },
+ }),
+ app({ password: "secret" }).request(fileUrl(), {
+ headers: {
+ authorization: authorization("opencode", "secret"),
+ "x-opencode-directory": tmp.path,
+ },
+ }),
+ ])
+
+ expect(missing.status).toBe(401)
+ expect(bad.status).toBe(401)
+ expect(good.status).toBe(200)
+ })
+
+ test("accepts auth_token query credentials", async () => {
+ await using tmp = await tmpdir({ git: true })
+ await Bun.write(`${tmp.path}/hello.txt`, "hello")
+
+ const response = await app({ password: "secret" }).request(fileUrl({ token: Buffer.from("opencode:secret").toString("base64") }), {
+ headers: {
+ "x-opencode-directory": tmp.path,
+ },
+ })
+
+ expect(response.status).toBe(200)
+ })
+
+ test("selects instance from query before directory header", async () => {
+ await using header = await tmpdir({ git: true })
+ await using query = await tmpdir({ git: true })
+ await Bun.write(`${header.path}/hello.txt`, "header")
+ await Bun.write(`${query.path}/hello.txt`, "query")
+
+ const response = await app().request(fileUrl({ directory: query.path }), {
+ headers: {
+ "x-opencode-directory": header.path,
+ },
+ })
+
+ expect(response.status).toBe(200)
+ expect(await response.json()).toMatchObject({ content: "query" })
+ })
+})