Skip to content

Commit b0aacad

Browse files
committed
test(server): regression reproducers for #25698
Adds three regression tests that lock in the contracts behind Ope's cleanup PR #25698: 1. httpapi-ui.test.ts: 'strips upstream transfer-encoding header from proxied assets' — fails on dev today (transfer-encoding leaks through the UI proxy and triggers ERR_INVALID_CHUNKED_ENCODING in browsers). 2. httpapi-ui.test.ts: 'serves the PWA manifest without auth even when a server password is set' — fails on dev today (auth middleware rejects /site.webmanifest and the web-app-manifest icons with 401, breaking PWA install). 3. httpapi-listen.test.ts: 'PTY connect token requires matching directory across mint and connect' — passes on dev (the server already enforces strict directory scope match), but documents the contract Ope's app fix relies on. Without a directory query, the server resolves the PTY in its own cwd and returns 404 — the symptom the app hit when calling client.pty.connectToken({ ptyID }) without directory. On dev: 2 fail, 1 passes (3rd is contract-only). With #25698 applied: 3 pass.
1 parent 1251a87 commit b0aacad

2 files changed

Lines changed: 105 additions & 0 deletions

File tree

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,46 @@ describe("HttpApi Server.listen", () => {
257257
}
258258
})
259259

260+
// Regression for #25698 (Ope): the app's SDK call to
261+
// `client.pty.connectToken({ ptyID })` originally omitted `directory`, so
262+
// the server resolved the PTY in its own cwd context — where the project
263+
// PTY isn't registered — and returned 404. The fix is to always pass
264+
// `directory` from the app side; this test locks in two contracts:
265+
// 1. Mint without directory cannot find a PTY registered in another dir.
266+
// 2. Mint with the project directory succeeds; the resulting ticket
267+
// consumes cleanly when the WS upgrade carries the same directory.
268+
testPty("PTY connect token requires matching directory across mint and connect", async () => {
269+
await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } })
270+
const listener = await startListener()
271+
try {
272+
const info = await createCat(listener, tmp.path)
273+
274+
// Mint without directory — server uses its own cwd, can't find the PTY.
275+
const ambiguous = await fetch(new URL(PtyPaths.connectToken.replace(":ptyID", info.id), listener.url), {
276+
method: "POST",
277+
headers: { authorization: authorization(), "x-opencode-ticket": "1" },
278+
})
279+
expect(ambiguous.status).toBe(404)
280+
281+
// Mint with the project directory — succeeds, ticket binds to that scope.
282+
const scoped = await fetch(
283+
new URL(`${PtyPaths.connectToken.replace(":ptyID", info.id)}?directory=${encodeURIComponent(tmp.path)}`, listener.url),
284+
{
285+
method: "POST",
286+
headers: { authorization: authorization(), "x-opencode-ticket": "1" },
287+
},
288+
)
289+
expect(scoped.status).toBe(200)
290+
const mint = (await scoped.json()) as { ticket: string }
291+
292+
// Same directory on the WS upgrade → consume succeeds.
293+
const ws = await openSocket(socketURL(listener, info.id, tmp.path, mint.ticket))
294+
ws.close(1000)
295+
} finally {
296+
await stop(listener, "timed out cleaning up directory-scope listener").catch(() => undefined)
297+
}
298+
})
299+
260300
testPty("keeps PTY websocket tickets optional when server auth is disabled", async () => {
261301
await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } })
262302
const listener = await startNoAuthListener()

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

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,52 @@ describe("HttpApi UI fallback", () => {
184184
expect(await response.text()).toBe("console.log('ok')")
185185
})
186186

187+
// Regression for #25698 (Ope): upstream `transfer-encoding: chunked` was
188+
// forwarded through the proxy while the proxy itself re-frames the body,
189+
// causing browsers to fail with `ERR_INVALID_CHUNKED_ENCODING`.
190+
test("strips upstream transfer-encoding header from proxied assets", async () => {
191+
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
192+
Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true
193+
194+
const response = await Effect.runPromise(
195+
Effect.gen(function* () {
196+
const fs = yield* AppFileSystem.Service
197+
const client = yield* HttpClient.HttpClient
198+
return yield* serveUIEffect(HttpServerRequest.fromWeb(new Request("http://localhost/")), {
199+
fs,
200+
client,
201+
})
202+
}).pipe(
203+
Effect.provide(
204+
Layer.mergeAll(
205+
AppFileSystem.defaultLayer,
206+
Layer.succeed(
207+
HttpClient.HttpClient,
208+
HttpClient.make((request) =>
209+
Effect.succeed(
210+
HttpClientResponse.fromWeb(
211+
request,
212+
new Response("<html>opencode</html>", {
213+
headers: {
214+
"transfer-encoding": "chunked",
215+
"content-type": "text/html",
216+
},
217+
}),
218+
),
219+
),
220+
),
221+
),
222+
),
223+
),
224+
Effect.map(HttpServerResponse.toWeb),
225+
),
226+
)
227+
228+
expect(response.status).toBe(200)
229+
expect(response.headers.get("transfer-encoding")).toBeNull()
230+
expect(await response.text()).toBe("<html>opencode</html>")
231+
})
232+
187233
test("serves embedded UI assets when Bun can read them but access reports missing", async () => {
188234
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
189235
let readPath: string | undefined
@@ -257,6 +303,25 @@ describe("HttpApi UI fallback", () => {
257303
expect(response.status).toBe(200)
258304
})
259305

306+
// Regression for #25698 (Ope): the browser fetches the PWA manifest and
307+
// its icons via flows that don't carry app-managed credentials (the
308+
// `<link rel="manifest">` request is not under page-auth control), so the
309+
// server returning 401 breaks PWA install. These specific public assets
310+
// should bypass auth.
311+
test("serves the PWA manifest without auth even when a server password is set", async () => {
312+
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
313+
Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true
314+
315+
for (const path of ["/site.webmanifest", "/web-app-manifest-192x192.png", "/web-app-manifest-512x512.png"]) {
316+
const response = await uiApp({
317+
password: "secret",
318+
username: "opencode",
319+
client: httpClient(new Response("ok")),
320+
}).request(path)
321+
expect(response.status).not.toBe(401)
322+
}
323+
})
324+
260325
test("allows web UI preflight without auth", async () => {
261326
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
262327

0 commit comments

Comments
 (0)