Skip to content

Commit 0726839

Browse files
kitlangtonvaur94
authored andcommitted
test(httpapi): cover hono bridge middleware (anomalyco#24216)
(cherry picked from commit 97eb9fd)
1 parent d7d678f commit 0726839

2 files changed

Lines changed: 133 additions & 2 deletions

File tree

packages/opencode/src/server/routes/instance/httpapi/project.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Instance } from "@/project/instance"
1+
import * as InstanceState from "@/effect/instance-state"
22
import { Project } from "@/project"
33
import { Effect, Layer, Schema } from "effect"
44
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
@@ -52,7 +52,7 @@ export const projectHandlers = Layer.unwrap(
5252
})
5353

5454
const current = Effect.fn("ProjectHttpApi.current")(function* () {
55-
return Instance.project
55+
return (yield* InstanceState.context).project
5656
})
5757

5858
return HttpApiBuilder.group(ProjectApi, "project", (handlers) =>
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { afterEach, describe, expect, test } from "bun:test"
2+
import type { UpgradeWebSocket } from "hono/ws"
3+
import { Flag } from "../../src/flag/flag"
4+
import { Instance } from "../../src/project/instance"
5+
import { InstanceRoutes } from "../../src/server/routes/instance"
6+
import { FilePaths } from "../../src/server/routes/instance/httpapi/file"
7+
import { Log } from "../../src/util"
8+
import { resetDatabase } from "../fixture/db"
9+
import { tmpdir } from "../fixture/fixture"
10+
11+
void Log.init({ print: false })
12+
13+
const original = {
14+
OPENCODE_EXPERIMENTAL_HTTPAPI: Flag.OPENCODE_EXPERIMENTAL_HTTPAPI,
15+
OPENCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD,
16+
OPENCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME,
17+
}
18+
19+
const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket
20+
21+
function app(input?: { password?: string; username?: string }) {
22+
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
23+
Flag.OPENCODE_SERVER_PASSWORD = input?.password
24+
Flag.OPENCODE_SERVER_USERNAME = input?.username
25+
return InstanceRoutes(websocket)
26+
}
27+
28+
function authorization(username: string, password: string) {
29+
return `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`
30+
}
31+
32+
function fileUrl(input?: { directory?: string; token?: string }) {
33+
const url = new URL(`http://localhost${FilePaths.content}`)
34+
url.searchParams.set("path", "hello.txt")
35+
if (input?.directory) url.searchParams.set("directory", input.directory)
36+
if (input?.token) url.searchParams.set("auth_token", input.token)
37+
return url
38+
}
39+
40+
afterEach(async () => {
41+
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = original.OPENCODE_EXPERIMENTAL_HTTPAPI
42+
Flag.OPENCODE_SERVER_PASSWORD = original.OPENCODE_SERVER_PASSWORD
43+
Flag.OPENCODE_SERVER_USERNAME = original.OPENCODE_SERVER_USERNAME
44+
await Instance.disposeAll()
45+
await resetDatabase()
46+
})
47+
48+
describe("HttpApi Hono bridge", () => {
49+
test("allows requests when auth is disabled", async () => {
50+
await using tmp = await tmpdir({ git: true })
51+
await Bun.write(`${tmp.path}/hello.txt`, "hello")
52+
53+
const response = await app().request(fileUrl(), {
54+
headers: {
55+
"x-opencode-directory": tmp.path,
56+
},
57+
})
58+
59+
expect(response.status).toBe(200)
60+
expect(await response.json()).toMatchObject({ content: "hello" })
61+
})
62+
63+
test("provides instance context to bridged handlers", async () => {
64+
await using tmp = await tmpdir({ git: true })
65+
66+
const response = await app().request("/project/current", {
67+
headers: {
68+
"x-opencode-directory": tmp.path,
69+
},
70+
})
71+
72+
expect(response.status).toBe(200)
73+
expect(await response.json()).toMatchObject({ worktree: tmp.path })
74+
})
75+
76+
test("requires credentials when auth is enabled", async () => {
77+
await using tmp = await tmpdir({ git: true })
78+
await Bun.write(`${tmp.path}/hello.txt`, "hello")
79+
80+
const [missing, bad, good] = await Promise.all([
81+
app({ password: "secret" }).request(fileUrl(), {
82+
headers: { "x-opencode-directory": tmp.path },
83+
}),
84+
app({ password: "secret" }).request(fileUrl(), {
85+
headers: {
86+
authorization: authorization("opencode", "wrong"),
87+
"x-opencode-directory": tmp.path,
88+
},
89+
}),
90+
app({ password: "secret" }).request(fileUrl(), {
91+
headers: {
92+
authorization: authorization("opencode", "secret"),
93+
"x-opencode-directory": tmp.path,
94+
},
95+
}),
96+
])
97+
98+
expect(missing.status).toBe(401)
99+
expect(bad.status).toBe(401)
100+
expect(good.status).toBe(200)
101+
})
102+
103+
test("accepts auth_token query credentials", async () => {
104+
await using tmp = await tmpdir({ git: true })
105+
await Bun.write(`${tmp.path}/hello.txt`, "hello")
106+
107+
const response = await app({ password: "secret" }).request(fileUrl({ token: Buffer.from("opencode:secret").toString("base64") }), {
108+
headers: {
109+
"x-opencode-directory": tmp.path,
110+
},
111+
})
112+
113+
expect(response.status).toBe(200)
114+
})
115+
116+
test("selects instance from query before directory header", async () => {
117+
await using header = await tmpdir({ git: true })
118+
await using query = await tmpdir({ git: true })
119+
await Bun.write(`${header.path}/hello.txt`, "header")
120+
await Bun.write(`${query.path}/hello.txt`, "query")
121+
122+
const response = await app().request(fileUrl({ directory: query.path }), {
123+
headers: {
124+
"x-opencode-directory": header.path,
125+
},
126+
})
127+
128+
expect(response.status).toBe(200)
129+
expect(await response.json()).toMatchObject({ content: "query" })
130+
})
131+
})

0 commit comments

Comments
 (0)