Bladeren bron

test(server): regression reproducers for #25698 (#25714)

Kit Langton 3 maanden geleden
bovenliggende
commit
6e9f10ad3f

+ 40 - 0
packages/opencode/test/server/httpapi-listen.test.ts

@@ -257,6 +257,46 @@ describe("HttpApi Server.listen", () => {
     }
   })
 
+  // Regression for #25698 (Ope): the app's SDK call to
+  // `client.pty.connectToken({ ptyID })` originally omitted `directory`, so
+  // the server resolved the PTY in its own cwd context — where the project
+  // PTY isn't registered — and returned 404. The fix is to always pass
+  // `directory` from the app side; this test locks in two contracts:
+  //   1. Mint without directory cannot find a PTY registered in another dir.
+  //   2. Mint with the project directory succeeds; the resulting ticket
+  //      consumes cleanly when the WS upgrade carries the same directory.
+  testPty("PTY connect token requires matching directory across mint and connect", async () => {
+    await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } })
+    const listener = await startListener()
+    try {
+      const info = await createCat(listener, tmp.path)
+
+      // Mint without directory — server uses its own cwd, can't find the PTY.
+      const ambiguous = await fetch(new URL(PtyPaths.connectToken.replace(":ptyID", info.id), listener.url), {
+        method: "POST",
+        headers: { authorization: authorization(), "x-opencode-ticket": "1" },
+      })
+      expect(ambiguous.status).toBe(404)
+
+      // Mint with the project directory — succeeds, ticket binds to that scope.
+      const scoped = await fetch(
+        new URL(`${PtyPaths.connectToken.replace(":ptyID", info.id)}?directory=${encodeURIComponent(tmp.path)}`, listener.url),
+        {
+          method: "POST",
+          headers: { authorization: authorization(), "x-opencode-ticket": "1" },
+        },
+      )
+      expect(scoped.status).toBe(200)
+      const mint = (await scoped.json()) as { ticket: string }
+
+      // Same directory on the WS upgrade → consume succeeds.
+      const ws = await openSocket(socketURL(listener, info.id, tmp.path, mint.ticket))
+      ws.close(1000)
+    } finally {
+      await stop(listener, "timed out cleaning up directory-scope listener").catch(() => undefined)
+    }
+  })
+
   testPty("keeps PTY websocket tickets optional when server auth is disabled", async () => {
     await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } })
     const listener = await startNoAuthListener()

+ 65 - 0
packages/opencode/test/server/httpapi-ui.test.ts

@@ -184,6 +184,52 @@ describe("HttpApi UI fallback", () => {
     expect(await response.text()).toBe("console.log('ok')")
   })
 
+  // Regression for #25698 (Ope): upstream `transfer-encoding: chunked` was
+  // forwarded through the proxy while the proxy itself re-frames the body,
+  // causing browsers to fail with `ERR_INVALID_CHUNKED_ENCODING`.
+  test("strips upstream transfer-encoding header from proxied assets", async () => {
+    Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
+    Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true
+
+    const response = await Effect.runPromise(
+      Effect.gen(function* () {
+        const fs = yield* AppFileSystem.Service
+        const client = yield* HttpClient.HttpClient
+        return yield* serveUIEffect(HttpServerRequest.fromWeb(new Request("http://localhost/")), {
+          fs,
+          client,
+        })
+      }).pipe(
+        Effect.provide(
+          Layer.mergeAll(
+            AppFileSystem.defaultLayer,
+            Layer.succeed(
+              HttpClient.HttpClient,
+              HttpClient.make((request) =>
+                Effect.succeed(
+                  HttpClientResponse.fromWeb(
+                    request,
+                    new Response("<html>opencode</html>", {
+                      headers: {
+                        "transfer-encoding": "chunked",
+                        "content-type": "text/html",
+                      },
+                    }),
+                  ),
+                ),
+              ),
+            ),
+          ),
+        ),
+        Effect.map(HttpServerResponse.toWeb),
+      ),
+    )
+
+    expect(response.status).toBe(200)
+    expect(response.headers.get("transfer-encoding")).toBeNull()
+    expect(await response.text()).toBe("<html>opencode</html>")
+  })
+
   test("serves embedded UI assets when Bun can read them but access reports missing", async () => {
     Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
     let readPath: string | undefined
@@ -257,6 +303,25 @@ describe("HttpApi UI fallback", () => {
     expect(response.status).toBe(200)
   })
 
+  // Regression for #25698 (Ope): the browser fetches the PWA manifest and
+  // its icons via flows that don't carry app-managed credentials (the
+  // `<link rel="manifest">` request is not under page-auth control), so the
+  // server returning 401 breaks PWA install. These specific public assets
+  // should bypass auth.
+  test("serves the PWA manifest without auth even when a server password is set", async () => {
+    Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
+    Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true
+
+    for (const path of ["/site.webmanifest", "/web-app-manifest-192x192.png", "/web-app-manifest-512x512.png"]) {
+      const response = await uiApp({
+        password: "secret",
+        username: "opencode",
+        client: httpClient(new Response("ok")),
+      }).request(path)
+      expect(response.status).not.toBe(401)
+    }
+  })
+
   test("allows web UI preflight without auth", async () => {
     Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true