workspace-proxy.test.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import { NodeHttpServer } from "@effect/platform-node"
  2. import Http from "node:http"
  3. import { describe, expect } from "bun:test"
  4. import { Effect } from "effect"
  5. import { HttpServer, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
  6. import { HttpApiProxy } from "../../src/server/routes/instance/httpapi/middleware/proxy"
  7. import { testEffect } from "../lib/effect"
  8. function serverUrl() {
  9. return Effect.gen(function* () {
  10. return HttpServer.formatAddress((yield* HttpServer.HttpServer).address)
  11. })
  12. }
  13. const testServerLayer = NodeHttpServer.layer(Http.createServer, { host: "127.0.0.1", port: 0 })
  14. const it = testEffect(testServerLayer)
  15. describe("HttpApi workspace proxy", () => {
  16. it.live("proxies HTTP request and returns streamed response with status and headers", () =>
  17. Effect.gen(function* () {
  18. yield* HttpServer.serveEffect()(
  19. Effect.gen(function* () {
  20. const req = yield* HttpServerRequest.HttpServerRequest
  21. const body = yield* req.text
  22. return yield* HttpServerResponse.json(
  23. { path: req.url, method: req.method, body },
  24. {
  25. status: 201,
  26. headers: {
  27. "content-encoding": "identity",
  28. "content-length": "999",
  29. "x-remote": "yes",
  30. },
  31. },
  32. )
  33. }),
  34. )
  35. const url = yield* serverUrl()
  36. const request = HttpServerRequest.fromWeb(
  37. new Request("http://localhost/session/abc", { method: "POST", body: "request-body" }),
  38. )
  39. const response = yield* HttpApiProxy.http(`${url}/session/abc?keep=yes`, { "x-extra": "injected" }, request)
  40. expect(response.status).toBe(201)
  41. const client = HttpServerResponse.toClientResponse(response)
  42. expect(yield* client.json).toEqual({
  43. path: "/session/abc?keep=yes",
  44. method: "POST",
  45. body: "request-body",
  46. })
  47. expect(response.headers["x-remote"]).toBe("yes")
  48. expect(response.headers["content-encoding"]).toBeUndefined()
  49. expect(response.headers["content-length"]).toBeUndefined()
  50. }),
  51. )
  52. it.live("returns 500 when remote is unreachable", () =>
  53. Effect.gen(function* () {
  54. const request = HttpServerRequest.fromWeb(new Request("http://localhost/anything"))
  55. const response = yield* HttpApiProxy.http("http://127.0.0.1:1/unreachable", undefined, request)
  56. expect(response.status).toBe(500)
  57. }),
  58. )
  59. it.live("strips opencode-internal headers and merges extra headers", () =>
  60. Effect.gen(function* () {
  61. let forwarded: Record<string, string> = {}
  62. yield* HttpServer.serveEffect()(
  63. Effect.gen(function* () {
  64. const req = yield* HttpServerRequest.HttpServerRequest
  65. forwarded = req.headers
  66. return HttpServerResponse.empty()
  67. }),
  68. )
  69. const url = yield* serverUrl()
  70. const request = HttpServerRequest.fromWeb(
  71. new Request("http://localhost/test", {
  72. headers: {
  73. "x-opencode-directory": "/secret/path",
  74. "x-opencode-workspace": "ws_123",
  75. "x-custom": "preserved",
  76. },
  77. }),
  78. )
  79. yield* HttpApiProxy.http(`${url}/test`, { "x-injected": "extra" }, request)
  80. expect(forwarded["x-opencode-directory"]).toBeUndefined()
  81. expect(forwarded["x-opencode-workspace"]).toBeUndefined()
  82. expect(forwarded["x-custom"]).toBe("preserved")
  83. expect(forwarded["x-injected"]).toBe("extra")
  84. }),
  85. )
  86. })