import { describe, expect, it } from "bun:test"; import express from "express"; import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { attachTrustedAgentContext } from "../../src/context/localContext.js"; import { registerChatInteractionRoutes } from "../../src/routes/chatInteractionRoutes.js"; import { bindSessionCredential, credentialRefreshCoordinator, releaseSessionCredential } from "../../src/product/credential-refresh.js"; import { SessionMetadataStore } from "../../src/sessions/metadataStore.js"; import { SessionUiStateStore } from "../../src/sessions/uiStateStore.js"; import { toActorKey, toProjectKey } from "../../src/utils/fileStore.js"; import type { ActiveRun } from "../../src/routes/chatUiState.js"; import type { OpencodeRuntimeAdapter } from "../../src/runtime/opencode.js"; describe("credential refresh HTTP route", () => { it("binds fresh headers only after validating the session owner, project and pending request", async () => { const directory = await mkdtemp(join(tmpdir(), "credential-route-")); const originalFetch = globalThis.fetch; const metadata = new SessionMetadataStore(join(directory, "metadata")); await metadata.initialize(); const projectId = "10000000-0000-4000-8000-000000000001"; await metadata.ensure({ sessionId: "credential-route-session", actorKey: toActorKey("product:owner"), projectKey: toProjectKey(`product:${projectId}`), projectId, userId: "owner" }); globalThis.fetch = (async (_url, init) => { const headers = new Headers(init?.headers); return Response.json({ user_id: headers.get("Authorization") === "Bearer TEST_OWNER_NEW" ? "owner" : "other", project_id: headers.get("X-Project-Id"), network: "supply", token_expires_at: new Date(Date.now() + 300000).toISOString() }); }) as typeof fetch; const app = express(); app.use(express.json()); const router = express.Router(); router.use((req, res, next) => { void attachTrustedAgentContext(req).then(() => next()).catch(() => res.sendStatus(401)); }); const activeRuns = new Map([["credential-route-session", { clientSessionId: "credential-route-session", controller: new AbortController(), messages: [], pendingPermissions: new Map(), pendingQuestions: new Map(), subscribers: new Set(), status: "running" }]]); registerChatInteractionRoutes(router, { activeRuns, runtime: {} as OpencodeRuntimeAdapter, sessionMetadataStore: metadata, sessionUiStateStore: new SessionUiStateStore(join(directory, "ui")) }); app.use(router); const server = app.listen(0, "127.0.0.1"); await new Promise((resolve) => server.once("listening", resolve)); const base = `http://127.0.0.1:${(server.address() as { port: number }).port}`; bindSessionCredential("credential-route-session", { authorization: "Bearer TEST_OLD", projectId }); const unsubscribe = credentialRefreshCoordinator.subscribe("credential-route-session", () => {}); const pending = credentialRefreshCoordinator.request("credential-route-session", "access_token_rejected"); const requestId = credentialRefreshCoordinator.getPendingRequestId("credential-route-session")!; const post = (authorization: string, id = requestId, project = projectId) => originalFetch(`${base}/session/credential-route-session/credentials`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: authorization, "X-Project-Id": project }, body: JSON.stringify({ request_id: id }) }); try { expect((await post("Bearer TEST_OTHER")).status).toBe(404); expect((await post("Bearer TEST_OWNER_NEW", requestId, "other-project")).status).toBe(404); expect((await post("Bearer TEST_OWNER_NEW", "stale-request")).status).toBe(409); expect((await post("Bearer TEST_OWNER_NEW")).status).toBe(202); expect(await pending).toMatchObject({ authorization: "Bearer TEST_OWNER_NEW", projectId }); } finally { unsubscribe(); releaseSessionCredential("credential-route-session"); await pending.catch(() => undefined); globalThis.fetch = originalFetch; await new Promise((resolve) => server.close(() => resolve())); await rm(directory, { force: true, recursive: true }); } }); });