| 20 | } |
| 21 | |
| 22 | export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { |
| 23 | const cursors = new Map<string, string>() |
| 24 | let nextCursor = 0 |
| 25 | const staticRoutes: Record<string, unknown> = { |
| 26 | "/provider": config.provider, |
| 27 | "/path": { |
| 28 | state: config.directory, |
| 29 | config: config.directory, |
| 30 | worktree: config.directory, |
| 31 | directory: config.directory, |
| 32 | home: "C:/OpenCode", |
| 33 | }, |
| 34 | "/project": [config.project], |
| 35 | "/project/current": config.project, |
| 36 | "/agent": [{ name: "build", mode: "primary" }], |
| 37 | "/vcs": { branch: "main", default_branch: "main" }, |
| 38 | "/session": config.sessions, |
| 39 | } |
| 40 | |
| 41 | await page.route("**/*", async (route) => { |
| 42 | const url = new URL(route.request().url()) |
| 43 | const targetPort = process.env.PLAYWRIGHT_SERVER_PORT ?? "4096" |
| 44 | const appPort = new URL( |
| 45 | process.env.PLAYWRIGHT_BASE_URL ?? `http://127.0.0.1:${process.env.PLAYWRIGHT_PORT ?? "3000"}`, |
| 46 | ).port |
| 47 | if (url.port !== targetPort && url.port !== appPort) return route.fallback() |
| 48 | |
| 49 | const path = url.pathname |
| 50 | if (path === "/global/event" || path === "/event") return sse(route, config.events?.(), config.eventRetry) |
| 51 | if (path === "/global/health") return json(route, { healthy: true }) |
| 52 | if (path === "/permission") |
| 53 | return json(route, typeof config.permissions === "function" ? config.permissions() : (config.permissions ?? [])) |
| 54 | if (path === "/question") |
| 55 | return json(route, typeof config.questions === "function" ? config.questions() : (config.questions ?? [])) |
| 56 | if (path === "/vcs/diff" && config.vcsDiff) return json(route, config.vcsDiff) |
| 57 | if (emptyObject.has(path)) return json(route, {}) |
| 58 | if (emptyList.has(path)) return json(route, []) |
| 59 | if (path in staticRoutes) return json(route, staticRoutes[path]) |
| 60 | |
| 61 | const sessionMatch = path.match(/^\/session\/([^/]+)$/) |
| 62 | if (sessionMatch) { |
| 63 | const session = config.sessions.find((s) => s.id === sessionMatch[1]) |
| 64 | return json(route, session ?? {}) |
| 65 | } |
| 66 | |
| 67 | const todoMatch = path.match(/^\/session\/([^/]+)\/todo$/) |
| 68 | if (todoMatch) return json(route, config.todos?.(todoMatch[1]!) ?? []) |
| 69 | if (/^\/session\/[^/]+\/(children|diff)$/.test(path)) return json(route, []) |
| 70 | |
| 71 | const messagesMatch = path.match(/^\/session\/([^/]+)\/message$/) |
| 72 | if (messagesMatch) { |
| 73 | const token = url.searchParams.get("before") ?? undefined |
| 74 | const before = token ? cursors.get(token) : undefined |
| 75 | if (token && !before) return json(route, { error: "Invalid cursor" }, undefined, 400) |
| 76 | config.onMessages?.({ sessionID: messagesMatch[1], before, phase: "start" }) |
| 77 | if (config.messageDelay) await new Promise((resolve) => setTimeout(resolve, config.messageDelay)) |
| 78 | const limit = Number(url.searchParams.get("limit") ?? 80) |
| 79 | const pageData = config.pageMessages(messagesMatch[1], limit, before) |