mock-server.ts 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. import type { Page, Route } from "@playwright/test"
  2. const emptyList = new Set([
  3. "/skill",
  4. "/command",
  5. "/lsp",
  6. "/formatter",
  7. "/permission",
  8. "/question",
  9. "/vcs/status",
  10. "/vcs/diff",
  11. ])
  12. const emptyObject = new Set(["/global/config", "/config", "/provider/auth", "/mcp", "/session/status"])
  13. export interface MockServerConfig {
  14. provider: unknown
  15. directory: string
  16. project: unknown
  17. sessions: ({ id: string } & Record<string, unknown>)[]
  18. pageMessages: (sessionId: string, limit: number, before?: string) => { items: unknown[]; cursor?: string }
  19. }
  20. export async function mockOpenCodeServer(page: Page, config: MockServerConfig) {
  21. const staticRoutes: Record<string, unknown> = {
  22. "/provider": config.provider,
  23. "/path": {
  24. state: config.directory,
  25. config: config.directory,
  26. worktree: config.directory,
  27. directory: config.directory,
  28. home: "C:/OpenCode",
  29. },
  30. "/project": [config.project],
  31. "/project/current": config.project,
  32. "/agent": [{ name: "build", mode: "primary" }],
  33. "/vcs": { branch: "main", default_branch: "main" },
  34. "/session": config.sessions,
  35. }
  36. await page.route("**/*", async (route) => {
  37. const url = new URL(route.request().url())
  38. const targetPort = process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"
  39. if (url.port !== targetPort) return route.fallback()
  40. const path = url.pathname
  41. if (path === "/global/event" || path === "/event") return sse(route)
  42. if (emptyObject.has(path)) return json(route, {})
  43. if (emptyList.has(path)) return json(route, [])
  44. if (path in staticRoutes) return json(route, staticRoutes[path])
  45. const sessionMatch = path.match(/^\/session\/([^/]+)$/)
  46. if (sessionMatch) {
  47. const session = config.sessions.find((s) => s.id === sessionMatch[1])
  48. return json(route, session ?? {})
  49. }
  50. if (/^\/session\/[^/]+\/(children|todo|diff)$/.test(path)) return json(route, [])
  51. const messagesMatch = path.match(/^\/session\/([^/]+)\/message$/)
  52. if (messagesMatch) {
  53. const limit = Number(url.searchParams.get("limit") ?? 80)
  54. const before = url.searchParams.get("before") ?? undefined
  55. const pageData = config.pageMessages(messagesMatch[1], limit, before)
  56. return json(route, pageData.items, pageData.cursor ? { "x-next-cursor": pageData.cursor } : undefined)
  57. }
  58. return json(route, {})
  59. })
  60. }
  61. function json(route: Route, body: unknown, headers?: Record<string, string>) {
  62. return route.fulfill({
  63. status: 200,
  64. contentType: "application/json",
  65. headers: {
  66. "access-control-allow-origin": "*",
  67. "access-control-expose-headers": "x-next-cursor",
  68. ...headers,
  69. },
  70. body: JSON.stringify(body ?? null),
  71. })
  72. }
  73. function sse(route: Route) {
  74. return route.fulfill({ status: 200, contentType: "text/event-stream", body: ": ok\n\n" })
  75. }