Skip to content

Commit ff3b188

Browse files
authored
Merge pull request #3858 from cdr/jsjoeio-add-proxy-test
feat: add more tests for proxy.ts
2 parents 7e43f7d + 9137816 commit ff3b188

File tree

3 files changed

+145
-1
lines changed

3 files changed

+145
-1
lines changed

test/unit/helpers.test.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { promises as fs } from "fs"
2-
import { tmpdir, useEnv } from "../../test/utils/helpers"
2+
import { getAvailablePort, tmpdir, useEnv } from "../../test/utils/helpers"
33

44
/**
55
* This file is for testing test helpers (not core code).
@@ -39,3 +39,16 @@ describe("useEnv", () => {
3939
expect(process.env[envKey]).toEqual("test environment variable")
4040
})
4141
})
42+
43+
describe("getAvailablePort", () => {
44+
it("should return a valid port", async () => {
45+
const port = await getAvailablePort()
46+
expect(port).toBeGreaterThan(0)
47+
expect(port).toBeLessThanOrEqual(65535)
48+
})
49+
it("should return different ports for different calls", async () => {
50+
const portOne = await getAvailablePort()
51+
const portTwo = await getAvailablePort()
52+
expect(portOne).not.toEqual(portTwo)
53+
})
54+
})

test/unit/node/proxy.test.ts

+110
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import bodyParser from "body-parser"
22
import * as express from "express"
3+
import * as nodeFetch from "node-fetch"
4+
import * as http from "http"
5+
import { HttpCode } from "../../../src/common/http"
6+
import { proxy } from "../../../src/node/proxy"
37
import * as httpserver from "../../utils/httpserver"
48
import * as integration from "../../utils/integration"
9+
import { getAvailablePort } from "../../utils/helpers"
510

611
describe("proxy", () => {
712
const nhooyrDevServer = new httpserver.HttpServer()
@@ -102,4 +107,109 @@ describe("proxy", () => {
102107
expect(resp.status).toBe(200)
103108
expect(await resp.json()).toBe("coder is the best")
104109
})
110+
111+
it("should handle bad requests", async () => {
112+
e.use(bodyParser.json({ strict: false }))
113+
e.post("/wsup", (req, res) => {
114+
res.json(req.body)
115+
})
116+
codeServer = await integration.setup(["--auth=none"], "")
117+
const resp = await codeServer.fetch(proxyPath, {
118+
method: "post",
119+
body: "coder is the best",
120+
headers: {
121+
"Content-Type": "application/json",
122+
},
123+
})
124+
expect(resp.status).toBe(400)
125+
expect(resp.statusText).toMatch("Bad Request")
126+
})
127+
128+
it("should handle invalid routes", async () => {
129+
e.post("/wsup", (req, res) => {
130+
res.json(req.body)
131+
})
132+
codeServer = await integration.setup(["--auth=none"], "")
133+
const resp = await codeServer.fetch(`${proxyPath}/hello`)
134+
expect(resp.status).toBe(404)
135+
expect(resp.statusText).toMatch("Not Found")
136+
})
137+
138+
it("should handle errors", async () => {
139+
e.use(bodyParser.json({ strict: false }))
140+
e.post("/wsup", (req, res) => {
141+
throw new Error("BROKEN")
142+
})
143+
codeServer = await integration.setup(["--auth=none"], "")
144+
const resp = await codeServer.fetch(proxyPath, {
145+
method: "post",
146+
body: JSON.stringify("coder is the best"),
147+
headers: {
148+
"Content-Type": "application/json",
149+
},
150+
})
151+
expect(resp.status).toBe(500)
152+
expect(resp.statusText).toMatch("Internal Server Error")
153+
})
154+
})
155+
156+
// NOTE@jsjoeio
157+
// Both this test suite and the one above it are very similar
158+
// The main difference is this one uses http and node-fetch
159+
// and specifically tests the proxy in isolation vs. using
160+
// the httpserver abstraction we've built.
161+
//
162+
// Leaving this as a separate test suite for now because
163+
// we may consider refactoring the httpserver abstraction
164+
// in the future.
165+
//
166+
// If you're writing a test specifically for code in
167+
// src/node/proxy.ts, you should probably add it to
168+
// this test suite.
169+
describe("proxy (standalone)", () => {
170+
let URL = ""
171+
let PROXY_URL = ""
172+
let testServer: http.Server
173+
let proxyTarget: http.Server
174+
175+
beforeEach(async () => {
176+
const PORT = await getAvailablePort()
177+
const PROXY_PORT = await getAvailablePort()
178+
URL = `http://localhost:${PORT}`
179+
PROXY_URL = `http://localhost:${PROXY_PORT}`
180+
// Define server and a proxy server
181+
testServer = http.createServer((req, res) => {
182+
proxy.web(req, res, {
183+
target: PROXY_URL,
184+
})
185+
})
186+
187+
proxyTarget = http.createServer((req, res) => {
188+
res.writeHead(200, { "Content-Type": "text/plain" })
189+
res.end()
190+
})
191+
192+
// Start both servers
193+
await proxyTarget.listen(PROXY_PORT)
194+
await testServer.listen(PORT)
195+
})
196+
197+
afterEach(async () => {
198+
await testServer.close()
199+
await proxyTarget.close()
200+
})
201+
202+
it("should return a 500 when proxy target errors ", async () => {
203+
// Close the proxy target so that proxy errors
204+
await proxyTarget.close()
205+
const errorResp = await nodeFetch.default(`${URL}/error`)
206+
expect(errorResp.status).toBe(HttpCode.ServerError)
207+
expect(errorResp.statusText).toBe("Internal Server Error")
208+
})
209+
210+
it("should proxy correctly", async () => {
211+
const resp = await nodeFetch.default(`${URL}/route`)
212+
expect(resp.status).toBe(200)
213+
expect(resp.statusText).toBe("OK")
214+
})
105215
})

test/utils/helpers.ts

+21
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { promises as fs } from "fs"
22
import * as os from "os"
33
import * as path from "path"
4+
import * as net from "net"
45

56
/**
67
* Return a mock of @coder/logger.
@@ -61,3 +62,23 @@ export function useEnv(key: string): [(nextValue: string | undefined) => string
6162

6263
return [setValue, resetValue]
6364
}
65+
66+
/**
67+
* Helper function to get a random port.
68+
*
69+
* Source: https://github.com/sindresorhus/get-port/blob/main/index.js#L23-L33
70+
*/
71+
export const getAvailablePort = (options?: net.ListenOptions): Promise<number> =>
72+
new Promise((resolve, reject) => {
73+
const server = net.createServer()
74+
server.unref()
75+
server.on("error", reject)
76+
server.listen(options, () => {
77+
// NOTE@jsjoeio: not a huge fan of the type assertion
78+
// but it works for now.
79+
const { port } = server.address() as net.AddressInfo
80+
server.close(() => {
81+
resolve(port)
82+
})
83+
})
84+
})

0 commit comments

Comments
 (0)