├── src ├── __tests__ │ ├── avatar.png │ └── vercel-node-server.test.ts └── index.ts ├── .vscode └── settings.json ├── .gitignore ├── jest.config.js ├── .travis.yml ├── wallaby.js ├── tsconfig.json ├── LICENSE ├── package.json └── README.md /src/__tests__/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/vercel-node-server/HEAD/src/__tests__/avatar.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "typescript.tsdk": "node_modules/typescript/lib" 4 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .rts2_cache_cjs 5 | .rts2_cache_esm 6 | .rts2_cache_umd 7 | .rts2_cache_system 8 | dist 9 | test/temp.png 10 | coverage -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | 3 | const path = require('path'); 4 | 5 | module.exports = { 6 | preset: 'ts-jest', 7 | testEnvironment: 'node', 8 | }; 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | yarn: true 5 | directories: 6 | - node_modules 7 | node_js: 8 | - "node" 9 | script: 10 | # Unfortunately flow falls over when a dep exists in peer deps and others. :( 11 | # @see https://github.com/flowtype/flow-typed/issues/528 12 | #- yarn run flow:defs 13 | - yarn run test 14 | after_success: 15 | # Deploy code coverage report to codecov.io 16 | - yarn run test:coverage:deploy 17 | -------------------------------------------------------------------------------- /wallaby.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | 3 | process.env.NODE_ENV = 'test'; 4 | 5 | module.exports = wallaby => ({ 6 | files: [ 7 | 'src/**/*.ts', 8 | 'jest.config.js', 9 | 'test/avatar.png', 10 | 'test/profile.jpg', 11 | ], 12 | tests: ['test/**/*.test.ts'], 13 | testFramework: 'jest', 14 | env: { 15 | type: 'node', 16 | runner: 'node', 17 | }, 18 | // setup: function(wallaby) { 19 | // const jestConfig = require('./backend/jest.config'); 20 | // wallaby.testFramework.configure(jestConfig); 21 | // }, 22 | setup: function(wallaby) { 23 | const jestConfig = require('./jest.config'); 24 | wallaby.testFramework.configure(jestConfig); 25 | }, 26 | workers: { 27 | initial: 1, 28 | regular: 1, 29 | }, 30 | }); 31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "types", "src/test"], 3 | "compilerOptions": { 4 | "target": "es5", 5 | "module": "esnext", 6 | "lib": ["dom", "dom.iterable", "esnext"], 7 | "importHelpers": true, 8 | "declaration": true, 9 | "sourceMap": true, 10 | "rootDir": "./src", 11 | "strict": true, 12 | "noImplicitAny": true, 13 | "strictNullChecks": true, 14 | "strictFunctionTypes": true, 15 | "strictPropertyInitialization": true, 16 | "noImplicitThis": true, 17 | "alwaysStrict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noImplicitReturns": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "moduleResolution": "node", 23 | "baseUrl": "./", 24 | "paths": { 25 | "*": ["src/*", "node_modules/*"] 26 | }, 27 | "jsx": "react", 28 | "esModuleInterop": true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Sean Matheson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vercel-node-server", 3 | "version": "2.2.1", 4 | "description": "Create a server for your Vercel Node lambdas in order to test them", 5 | "license": "MIT", 6 | "author": "Sean Matheson", 7 | "main": "dist/index.js", 8 | "module": "dist/vercel-node-server.esm.js", 9 | "typings": "dist/index.d.ts", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/ctrlplusb/vercel-node-server.git" 13 | }, 14 | "files": [ 15 | "dist" 16 | ], 17 | "scripts": { 18 | "prepublish": "npm run build", 19 | "start": "tsdx watch", 20 | "build": "tsdx build", 21 | "test": "tsdx test", 22 | "lint": "tsdx lint", 23 | "test:coverage": "npm run test -- --coverage", 24 | "test:coverage:deploy": "npm run test:coverage && codecov" 25 | }, 26 | "peerDependencies": {}, 27 | "husky": { 28 | "hooks": { 29 | "pre-commit": "tsdx lint" 30 | } 31 | }, 32 | "prettier": { 33 | "printWidth": 80, 34 | "semi": true, 35 | "singleQuote": true, 36 | "trailingComma": "es5" 37 | }, 38 | "devDependencies": { 39 | "@types/content-type": "^1.1.3", 40 | "@types/cookie": "^0.4.0", 41 | "@types/jest": "^25.2.3", 42 | "@types/micro": "^7.3.3", 43 | "@types/test-listen": "^1.1.0", 44 | "axios": "^0.19.2", 45 | "codecov": "^3.7.0", 46 | "form-data": "^3.0.0", 47 | "husky": "^4.2.5", 48 | "jest": "^26.0.1", 49 | "test-listen": "^1.1.0", 50 | "ts-jest": "^26.1.0", 51 | "tsdx": "^0.13.2", 52 | "tslib": "^1.13.0", 53 | "typescript": "^3.9.5" 54 | }, 55 | "dependencies": { 56 | "@vercel/node": "^1.6.1", 57 | "clone-response": "^1.0.2", 58 | "content-type": "^1.0.4", 59 | "cookie": "^0.4.1", 60 | "micro": "^9.3.4", 61 | "querystring": "^0.2.0" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vercel-node-server 2 | 3 | An unofficial package allowing you to create Node [`http.Server`](https://nodejs.org/api/http.html#http_class_http_server) instances of your [Vercel](https://vercel.com/) [Node](https://vercel.com/docs/runtimes#official-runtimes) lambdas. 4 | 5 | Enables you to write unit/integration tests for your lambdas, or to perform manual testing against a local server instance. 6 | 7 | [![npm](https://img.shields.io/npm/v/vercel-node-server.svg?style=flat-square)](http://npm.im/vercel-node-server) 8 | [![MIT License](https://img.shields.io/npm/l/vercel-node-server.svg?style=flat-square)](http://opensource.org/licenses/MIT) 9 | [![Travis](https://img.shields.io/travis/ctrlplusb/vercel-node-server.svg?style=flat-square)](https://travis-ci.org/ctrlplusb/vercel-node-server) 10 | [![Codecov](https://img.shields.io/codecov/c/github/ctrlplusb/vercel-node-server.svg?style=flat-square)](https://codecov.io/github/ctrlplusb/vercel-node-server) 11 | 12 | ## Installation 13 | 14 | ```bash 15 | npm install vercel-node-server 16 | ``` 17 | 18 | ## Supported API 19 | 20 | This package has taken the code from the official [`@vercel/node`](https://vercel.com/docs/runtimes#official-runtimes) builder in order to ensure maximum API compatibility. As far as I am aware we have 100% API coverage. 21 | 22 | ## Unit testing your lambdas 23 | 24 | In the below example we will make use of a local server in order to perform an integration test against our lambda. 25 | 26 | We will be making use of the [`test-listen`](https://github.com/zeit/test-listen) package, which accepts a [`http.Server`](https://nodejs.org/api/http.html#http_class_http_server) instance and will return a unique URL based on an available port. 27 | 28 | We will also make use of [`axios`](https://github.com/axios/axios) in order to make the request against our lambda. 29 | 30 | ```javascript 31 | import { createServer } from 'vercel-node-server'; 32 | import listen from 'test-listen'; 33 | import axios from 'axios'; 34 | import helloLambda from './api/hello'; 35 | 36 | let server; 37 | let url; 38 | 39 | beforeAll(async () => { 40 | server = createServer(routeUnderTest); 41 | url = await listen(server); 42 | }); 43 | 44 | afterAll(() => { 45 | server.close(); 46 | }); 47 | 48 | it('should return the expected response', async () => { 49 | const response = await axios.get(url, { params: { name: 'Pearl' } }); 50 | expect(response.data).toBe('Hello Pearl'); 51 | }); 52 | ``` 53 | 54 | ## Running a local server 55 | 56 | Given the following lambda. 57 | 58 | ```javascript 59 | const helloLambda = (req, res) => { 60 | res.send(`Hello ${req.query.name}`); 61 | }; 62 | ``` 63 | 64 | You can create a Node [`http.Server`](https://nodejs.org/api/http.html#http_class_http_server) instance like so. 65 | 66 | ```javascript 67 | import { createServer } from 'vercel-node-server'; 68 | import helloLambda from './api/hello'; 69 | 70 | const server = createServer(helloLambda); 71 | 72 | // start listening on port 8000 73 | server.listen(8000); 74 | ``` 75 | 76 | Then you can then make requests against it. 77 | 78 | ```bash 79 | > curl http://localhost:8000?name=Pearl 80 | Hello Pearl% 81 | ``` 82 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | NowRequestCookies, 3 | NowRequestQuery, 4 | NowRequestBody, 5 | NowRequest, 6 | NowResponse, 7 | } from '@vercel/node'; 8 | import { IncomingMessage, ServerResponse, Server, RequestListener } from 'http'; 9 | import micro, { buffer, send } from 'micro'; 10 | // @ts-expect-error 11 | import cloneResponse from 'clone-response'; 12 | 13 | export class ApiError extends Error { 14 | readonly statusCode: number; 15 | 16 | constructor(statusCode: number, message: string) { 17 | super(message); 18 | this.statusCode = statusCode; 19 | } 20 | } 21 | 22 | function getBodyParser(req: NowRequest, body: Buffer | string) { 23 | return function parseBody(): NowRequestBody { 24 | if (!req.headers['content-type']) { 25 | return undefined; 26 | } 27 | // eslint-disable-next-line @typescript-eslint/no-var-requires 28 | const { parse: parseContentType } = require('content-type'); 29 | const { type } = parseContentType(req.headers['content-type']); 30 | 31 | if (type === 'application/json') { 32 | try { 33 | const str = body.toString(); 34 | return str ? JSON.parse(str) : {}; 35 | } catch (error) { 36 | throw new ApiError(400, 'Invalid JSON'); 37 | } 38 | } 39 | 40 | if (type === 'application/octet-stream') { 41 | return body; 42 | } 43 | 44 | if (type === 'application/x-www-form-urlencoded') { 45 | // eslint-disable-next-line @typescript-eslint/no-var-requires 46 | const { parse: parseQS } = require('querystring'); 47 | // note: querystring.parse does not produce an iterable object 48 | // https://nodejs.org/api/querystring.html#querystring_querystring_parse_str_sep_eq_options 49 | return parseQS(body.toString()); 50 | } 51 | 52 | if (type === 'text/plain') { 53 | return body.toString(); 54 | } 55 | 56 | return undefined; 57 | }; 58 | } 59 | 60 | function getQueryParser({ url = '/' }: NowRequest) { 61 | return function parseQuery(): NowRequestQuery { 62 | // eslint-disable-next-line @typescript-eslint/no-var-requires 63 | const { parse: parseURL } = require('url'); 64 | return parseURL(url, true).query; 65 | }; 66 | } 67 | 68 | function getCookieParser(req: NowRequest) { 69 | return function parseCookie(): NowRequestCookies { 70 | const header: undefined | string | string[] = req.headers.cookie; 71 | 72 | if (!header) { 73 | return {}; 74 | } 75 | 76 | // eslint-disable-next-line @typescript-eslint/no-var-requires 77 | const { parse } = require('cookie'); 78 | return parse(Array.isArray(header) ? header.join(';') : header); 79 | }; 80 | } 81 | 82 | function setLazyProp(req: NowRequest, prop: string, getter: () => T) { 83 | const opts = { configurable: true, enumerable: true }; 84 | const optsReset = { ...opts, writable: true }; 85 | 86 | Object.defineProperty(req, prop, { 87 | ...opts, 88 | get: () => { 89 | const value = getter(); 90 | // we set the property on the object to avoid recalculating it 91 | Object.defineProperty(req, prop, { ...optsReset, value }); 92 | return value; 93 | }, 94 | set: value => { 95 | Object.defineProperty(req, prop, { ...optsReset, value }); 96 | }, 97 | }); 98 | } 99 | 100 | export const enhanceRequest = async (req: NowRequest): Promise => { 101 | // We clone the request, so that we can read the incoming stream but then 102 | // still allow subsequent consumers to do the same 103 | const reqClone = cloneResponse(req); 104 | const newReq = cloneResponse(req); 105 | const body = await buffer(reqClone); 106 | 107 | setLazyProp(newReq, 'cookies', getCookieParser(newReq)); 108 | setLazyProp(newReq, 'query', getQueryParser(newReq)); 109 | if (body != null) { 110 | setLazyProp(newReq, 'body', getBodyParser(newReq, body)); 111 | } 112 | 113 | return newReq; 114 | }; 115 | 116 | export const enhanceResponse = (res: ServerResponse): NowResponse => { 117 | let _status: number; 118 | const nowRes = Object.assign(res, { 119 | status: (status: number) => { 120 | _status = status; 121 | return nowRes; 122 | }, 123 | json: (jsonBody: any) => { 124 | send(nowRes, _status || 200, jsonBody); 125 | return nowRes; 126 | }, 127 | send: (body: string | object | Buffer) => { 128 | send(nowRes, _status || 200, body); 129 | return nowRes; 130 | }, 131 | text: (body: string) => { 132 | send(nowRes, _status || 200, body); 133 | return nowRes; 134 | }, 135 | }); 136 | return nowRes; 137 | }; 138 | 139 | export interface Config { 140 | disableHelpers?: boolean; 141 | } 142 | 143 | interface DefaultConfig { 144 | disableHelpers: false; 145 | } 146 | 147 | type NowRoute = (req: NowRequest, res: NowResponse) => void; 148 | 149 | export const createServer = ( 150 | route: C extends undefined 151 | ? NowRoute 152 | : C['disableHelpers'] extends true 153 | ? RequestListener 154 | : NowRoute, 155 | config?: C 156 | ) => { 157 | if (config != null && config.disableHelpers) { 158 | // @ts-expect-error 159 | return new Server(route); 160 | } else { 161 | return micro(async (req: IncomingMessage, res: ServerResponse) => { 162 | // @ts-expect-error 163 | const nowReq = await enhanceRequest(req); 164 | const nowRes = enhanceResponse(res); 165 | return await route(nowReq, nowRes); 166 | }); 167 | } 168 | }; 169 | -------------------------------------------------------------------------------- /src/__tests__/vercel-node-server.test.ts: -------------------------------------------------------------------------------- 1 | import { NowRequest, NowResponse } from '@vercel/node'; 2 | import { Server, IncomingMessage, ServerResponse } from 'http'; 3 | import axios from 'axios'; 4 | import fs from 'fs'; 5 | import path from 'path'; 6 | import listen from 'test-listen'; 7 | import { createServer } from '../'; 8 | 9 | let server: Server; 10 | let url: string; 11 | let route: (req: NowRequest, res: NowResponse) => any; 12 | 13 | beforeAll(async () => { 14 | server = createServer((req, res) => { 15 | if (route) { 16 | return route(req, res); 17 | } 18 | }); 19 | url = await listen(server); 20 | }); 21 | 22 | afterAll(() => { 23 | server.close(); 24 | }); 25 | 26 | describe('responses', () => { 27 | it('text', async () => { 28 | // ARRANGE 29 | route = (_, res) => { 30 | // @ts-ignore: The official documentation claims this API exists 31 | res.text('Hello world'); 32 | }; 33 | 34 | // ACT 35 | const actual = await axios.get(url); 36 | 37 | // ASSERT 38 | expect(actual.data).toBe('Hello world'); 39 | }); 40 | 41 | it('send(string)', async () => { 42 | // ARRANGE 43 | route = (_, res) => { 44 | res.send('Hello world'); 45 | }; 46 | 47 | // ACT 48 | const actual = await axios.get(url); 49 | 50 | // ASSERT 51 | expect(actual.data).toBe('Hello world'); 52 | }); 53 | 54 | it('send(json)', async () => { 55 | // ARRANGE 56 | route = (_, res) => { 57 | res.send({ msg: 'Hello world' }); 58 | }; 59 | 60 | // ACT 61 | const actual = await axios.get(url); 62 | 63 | // ASSERT 64 | expect(actual.data).toEqual({ msg: 'Hello world' }); 65 | }); 66 | 67 | it('status', async () => { 68 | // ARRANGE 69 | route = (_, res) => { 70 | res.status(202).send(undefined); 71 | }; 72 | 73 | // ACT 74 | const actual = await axios.get(url); 75 | 76 | // ASSERT 77 | expect(actual.status).toEqual(202); 78 | }); 79 | 80 | it('json(obj)', async () => { 81 | // ARRANGE 82 | route = (_, res) => { 83 | res.json({ 84 | msg: 'Hello world', 85 | }); 86 | }; 87 | 88 | // ACT 89 | const actual = await axios.get(url); 90 | 91 | // ASSERT 92 | expect(actual.data).toEqual({ 93 | msg: 'Hello world', 94 | }); 95 | }); 96 | 97 | it('async', async () => { 98 | // ARRANGE 99 | route = async (_, res) => { 100 | await new Promise(resolve => setTimeout(resolve, 1)); 101 | res.send('Hello world'); 102 | }; 103 | 104 | // ACT 105 | const actual = await axios.get(url); 106 | 107 | // ASSERT 108 | expect(actual.data).toBe('Hello world'); 109 | }); 110 | }); 111 | 112 | it('body - json', async () => { 113 | // ARRANGE 114 | let requestBody: any; 115 | route = (req, res) => { 116 | requestBody = req.body; 117 | res.send('ok'); 118 | }; 119 | 120 | // ACT 121 | await axios.post( 122 | url, 123 | { 124 | msg: 'Hello world', 125 | }, 126 | { 127 | headers: { 128 | 'Content-Type': 'application/json', 129 | }, 130 | } 131 | ); 132 | 133 | // ASSERT 134 | expect(requestBody).toEqual({ 135 | msg: 'Hello world', 136 | }); 137 | }); 138 | 139 | describe('request handling', () => { 140 | it('body - invalid json', async () => { 141 | // ARRANGE 142 | route = req => { 143 | // @ts-expect-error 144 | const foo = req.body; 145 | }; 146 | 147 | expect.assertions(2); 148 | 149 | // ACT 150 | try { 151 | await axios.post( 152 | url, 153 | `{ 154 | msg: 'Hello world', 155 | `, 156 | { 157 | headers: { 158 | 'Content-Type': 'application/json', 159 | }, 160 | } 161 | ); 162 | } catch (err) { 163 | expect(err.response.status).toBe(400); 164 | expect(err.response.data).toBe('Invalid JSON'); 165 | } 166 | }); 167 | 168 | const formUrlEncoded = (x: object) => 169 | Object.keys(x).reduce( 170 | // @ts-ignore 171 | (p, c) => p + `&${c}=${encodeURIComponent(x[c])}`, 172 | '' 173 | ); 174 | 175 | it('body - form data', async () => { 176 | // ARRANGE 177 | let requestBody: any; 178 | route = (req, res) => { 179 | requestBody = req.body; 180 | res.send('ok'); 181 | }; 182 | 183 | // ACT 184 | await axios.post( 185 | url, 186 | formUrlEncoded({ 187 | msg: 'Hello world', 188 | }), 189 | { 190 | headers: { 191 | 'Content-Type': 'application/x-www-form-urlencoded', 192 | }, 193 | } 194 | ); 195 | 196 | // ASSERT 197 | expect(requestBody).toEqual({ 198 | msg: 'Hello world', 199 | }); 200 | }); 201 | 202 | it('body - text', async () => { 203 | // ARRANGE 204 | let requestBody: any; 205 | route = (req, res) => { 206 | requestBody = req.body; 207 | res.send('ok'); 208 | }; 209 | 210 | // ACT 211 | await axios.post(url, 'Hello world', { 212 | headers: { 213 | 'Content-Type': 'text/plain', 214 | }, 215 | }); 216 | 217 | // ASSERT 218 | expect(requestBody).toEqual('Hello world'); 219 | }); 220 | 221 | it('body - unsupported', async () => { 222 | // ARRANGE 223 | let requestBody: any; 224 | route = (req, res) => { 225 | requestBody = req.body; 226 | res.send('ok'); 227 | }; 228 | 229 | // ACT 230 | await axios.post(url, 'Hello world', { 231 | headers: { 232 | 'Content-Type': 'foo/bar', 233 | }, 234 | }); 235 | 236 | // ASSERT 237 | expect(requestBody).toEqual(undefined); 238 | }); 239 | 240 | it('body - file', async () => { 241 | // ARRANGE 242 | let requestBody: any; 243 | route = (req, res) => { 244 | requestBody = req.body; 245 | res.send('ok'); 246 | }; 247 | const sourceFilePath = path.join(__dirname, 'avatar.png'); 248 | 249 | // ACT 250 | await axios({ 251 | method: 'POST', 252 | url, 253 | data: fs.createReadStream(sourceFilePath), 254 | headers: { 255 | 'Content-Type': 'application/octet-stream', 256 | }, 257 | }); 258 | 259 | // ASSERT 260 | const expected = fs.readFileSync(path.join(__dirname, 'avatar.png')); 261 | expect(expected.equals(requestBody)).toBe(true); 262 | }); 263 | 264 | it('cookies', async () => { 265 | // ARRANGE 266 | let requestCookies: any; 267 | route = (req, res) => { 268 | requestCookies = req.cookies; 269 | res.send('ok'); 270 | }; 271 | 272 | // ACT 273 | await axios.get(url, { 274 | headers: { 275 | Cookie: 'one=item1; two=item2;', 276 | }, 277 | }); 278 | 279 | // ASSERT 280 | expect(requestCookies).toEqual({ 281 | one: 'item1', 282 | two: 'item2', 283 | }); 284 | }); 285 | 286 | it('query', async () => { 287 | // ARRANGE 288 | let requestQuery: any; 289 | route = (req, res) => { 290 | requestQuery = req.query; 291 | res.send('ok'); 292 | }; 293 | 294 | // ACT 295 | await axios.get(`${url}?one=item1&two=item2`); 296 | 297 | // ASSERT 298 | expect(requestQuery).toEqual({ 299 | one: 'item1', 300 | two: 'item2', 301 | }); 302 | }); 303 | }); 304 | 305 | describe('disableHelpers', () => { 306 | let simpleServer: Server; 307 | let simpleServerUrl: string; 308 | let route: (req: IncomingMessage, res: ServerResponse) => void; 309 | 310 | beforeAll(async () => { 311 | simpleServer = createServer( 312 | (req, res) => { 313 | if (route) { 314 | return route(req, res); 315 | } 316 | }, 317 | { disableHelpers: true } 318 | ); 319 | simpleServerUrl = await listen(simpleServer); 320 | }); 321 | 322 | afterAll(() => { 323 | simpleServer.close(); 324 | }); 325 | 326 | it('does not apply the vercel helpers', async () => { 327 | // ARRANGE 328 | let actualRequest: any; 329 | let actualResponse: any; 330 | route = (req, res) => { 331 | actualRequest = req; 332 | actualResponse = res; 333 | res.end(); 334 | }; 335 | 336 | // ACT 337 | await axios.get(`${simpleServerUrl}?one=item1&two=item2`, { 338 | headers: { 339 | Cookie: 'foo=bar', 340 | }, 341 | }); 342 | 343 | // ASSERT 344 | expect(actualRequest.query).toBeUndefined(); 345 | expect(actualRequest.body).toBeUndefined(); 346 | expect(actualRequest.cookies).toBeUndefined(); 347 | expect(actualResponse.status).toBeUndefined(); 348 | expect(actualResponse.json).toBeUndefined(); 349 | expect(actualResponse.send).toBeUndefined(); 350 | }); 351 | }); 352 | --------------------------------------------------------------------------------