From 09fb7555aecebc2047cd68efafe0f54dc17b6108 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sat, 21 Dec 2024 15:16:52 +0800 Subject: [PATCH] feat: support cjs and esm both by tshy (#1) BREAKING CHANGE: drop Node.js < 18.19.0 support part of https://github.com/eggjs/egg/issues/3644 https://github.com/eggjs/egg/issues/5257 ## Summary by CodeRabbit - **New Features** - Introduced a new `TestAgent` class for enhanced HTTP and HTTP/2 requests. - Added a new `Request` class to facilitate server requests. - Implemented a new `AssertError` class for improved error handling. - Created new GitHub Actions workflows for CI and package publishing. - **Documentation** - Updated the `README.md` to reflect rebranding to `@eggjs/supertest` and revised installation instructions. - **Bug Fixes** - Enhanced error handling and type safety in tests. - **Chores** - Removed outdated configuration files and unnecessary dependencies. - Updated TypeScript configuration for stricter type checking. --- .editorconfig | 13 - .eslintrc | 26 +- .github/workflows/node.js.yml | 35 -- .github/workflows/nodejs.yml | 16 + .github/workflows/pkg.pr.new.yml | 23 + .github/workflows/release.yml | 13 + .gitignore | 4 + .npmignore | 8 - LICENSE | 1 + README.md | 59 +- ci/remove-deps-4-old-node.js | 22 - index.js | 65 --- lib/agent.js | 89 --- package.json | 101 ++-- src/agent.ts | 93 +++ src/error/AssertError.ts | 12 + src/index.ts | 39 ++ src/request.ts | 59 ++ lib/test.js => src/test.ts | 232 ++++---- src/types.ts | 17 + test/.eslintrc | 10 - test/{supertest.js => supertest.test.ts} | 704 +++++++++++------------ test/{throwError.js => throwError.ts} | 6 +- tsconfig.json | 10 + 24 files changed, 834 insertions(+), 823 deletions(-) delete mode 100644 .editorconfig delete mode 100644 .github/workflows/node.js.yml create mode 100644 .github/workflows/nodejs.yml create mode 100644 .github/workflows/pkg.pr.new.yml create mode 100644 .github/workflows/release.yml delete mode 100644 .npmignore delete mode 100644 ci/remove-deps-4-old-node.js delete mode 100644 index.js delete mode 100644 lib/agent.js create mode 100644 src/agent.ts create mode 100644 src/error/AssertError.ts create mode 100644 src/index.ts create mode 100644 src/request.ts rename lib/test.js => src/test.ts (55%) create mode 100644 src/types.ts delete mode 100644 test/.eslintrc rename test/{supertest.js => supertest.test.ts} (62%) rename test/{throwError.js => throwError.ts} (71%) create mode 100644 tsconfig.json diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 5cd5590f..00000000 --- a/.editorconfig +++ /dev/null @@ -1,13 +0,0 @@ -root = true - -[*] -charset = utf-8 -insert_final_newline = true -end_of_line = lf -trim_trailing_whitespace = true -indent_style = space -indent_size = 2 - -[*.{js,json}] -indent_size = 2 -indent_style = space diff --git a/.eslintrc b/.eslintrc index 6492a517..9bcdb468 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,24 +1,6 @@ { - "extends": "airbnb-base/legacy", - "env": { - "node": true, - "mocha": true - }, - "parserOptions": { - "ecmaVersion": 6 - }, - "rules": { - // disabled - disagree with airbnb - "func-names": [0], - "space-before-function-paren": [0], - "consistent-return": [0], - - // Disabled but may want to refactor code eventually - "no-use-before-define": [2, "nofunc"], - "no-underscore-dangle": [0], - - // IMHO, more sensible overrides to existing airbnb error definitions - "max-len": [2, 100, 4, {"ignoreComments": true, "ignoreUrls": true}], - "no-unused-expressions": [2, { "allowShortCircuit": true, "allowTernary": true }] - } + "extends": [ + "eslint-config-egg/typescript", + "eslint-config-egg/lib/rules/enforce-node-prefix" + ] } diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml deleted file mode 100644 index 183dea0f..00000000 --- a/.github/workflows/node.js.yml +++ /dev/null @@ -1,35 +0,0 @@ -# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions - -name: Node.js CI - -on: [push, pull_request] - -jobs: - build: - - runs-on: ubuntu-latest - - strategy: - matrix: - include: - - node-version: 14.x - - node-version: 16.x - - node-version: 18.x - - steps: - - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - path: ~/.npm - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }} - - name: Install Dependencies On Node ${{ matrix.node-version }} - run: yarn install - - run: npm test - - name: Coverage On Node ${{ matrix.node-version }} - run: - npm run coverage - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml new file mode 100644 index 00000000..9ed9cf2b --- /dev/null +++ b/.github/workflows/nodejs.yml @@ -0,0 +1,16 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + Job: + name: Node.js + uses: node-modules/github-actions/.github/workflows/node-test.yml@master + with: + version: '18.19.0, 18, 20, 22, 23' + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/pkg.pr.new.yml b/.github/workflows/pkg.pr.new.yml new file mode 100644 index 00000000..bac3facc --- /dev/null +++ b/.github/workflows/pkg.pr.new.yml @@ -0,0 +1,23 @@ +name: Publish Any Commit +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - run: corepack enable + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install dependencies + run: npm install + + - name: Build + run: npm run prepublishOnly --if-present + + - run: npx pkg-pr-new publish diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..a2bf04a7 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,13 @@ +name: Release + +on: + push: + branches: [ master ] + +jobs: + release: + name: Node.js + uses: eggjs/github-actions/.github/workflows/node-release.yml@master + secrets: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + GIT_TOKEN: ${{ secrets.GIT_TOKEN }} diff --git a/.gitignore b/.gitignore index c6c5ab13..1dbbee9e 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,7 @@ coverage # Files # ################### *.log +.tshy* +.eslintcache +dist +coverage diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 1291ee7a..00000000 --- a/.npmignore +++ /dev/null @@ -1,8 +0,0 @@ -.editorconfig -.eslintrc -.travis.yml -.idea -.vscode -.nyc_output -test -coverage diff --git a/LICENSE b/LICENSE index a7693b07..bf1000be 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ (The MIT License) Copyright (c) 2014 TJ Holowaychuk +Copyright (c) 2024-present eggjs and the contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md index 960b0f26..4b0d3cae 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,26 @@ -# [SuperTest](https://ladjs.github.io/superagent/) +# @eggjs/supertest +[![NPM version][npm-image]][npm-url] [![code coverage][coverage-badge]][coverage] -[![Build Status][travis-badge]][travis] -[![Dependencies][dependencies-badge]][dependencies] -[![PRs Welcome][prs-badge]][prs] +[![Node.js CI](https://github.com/eggjs/supertest/actions/workflows/nodejs.yml/badge.svg)](https://github.com/eggjs/supertest/actions/workflows/nodejs.yml) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com) [![MIT License][license-badge]][license] +[![npm download][download-image]][download-url] +[![Node.js Version](https://img.shields.io/node/v/@eggjs/mock.svg?style=flat)](https://nodejs.org/en/download/) + +[npm-image]: https://img.shields.io/npm/v/@eggjs/supertest.svg?style=flat-square +[npm-url]: https://npmjs.org/package/@eggjs/supertest +[coverage-badge]: https://img.shields.io/codecov/c/github/eggjs/supertest.svg +[coverage]: https://codecov.io/gh/eggjs/supertest +[license-badge]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square +[license]: https://github.com/eggjs/supertest/blob/master/LICENSE +[download-image]: https://img.shields.io/npm/dm/@eggjs/supertest.svg?style=flat-square +[download-url]: https://npmjs.org/package/@eggjs/supertest > HTTP assertions made easy via [superagent](http://github.com/ladjs/superagent). Maintained for [Forward Email](https://github.com/forwardemail) and [Lad](https://github.com/ladjs). +> Forked for TypeScript friendly + +Document see [SuperTest](https://ladjs.github.io/superagent/) ## About @@ -18,7 +32,7 @@ HTTP, while still allowing you to drop down to the [lower-level API](https://lad Install SuperTest as an npm module and save it to your package.json file as a development dependency: ```bash -npm install supertest --save-dev +npm install @eggjs/supertest --save-dev ``` Once installed it can now be referenced by simply calling ```require('supertest');``` @@ -33,7 +47,7 @@ SuperTest works with any test framework, here is an example without using any test framework at all: ```js -const request = require('supertest'); +const { request } = require('@eggjs/supertest'); const express = require('express'); const app = express(); @@ -55,7 +69,7 @@ request(app) To enable http2 protocol, simply append an options to `request` or `request.agent`: ```js -const request = require('supertest'); +const { request } = require('@eggjs/supertest'); const express = require('express'); const app = express(); @@ -207,13 +221,13 @@ the same host you may simply re-assign the request variable with the initialization app or url, a new `Test` is created per `request.VERB()` call. ```js -request = request('http://localhost:5555'); +t = request('http://localhost:5555'); -request.get('/').expect(200, function(err){ +t.get('/').expect(200, function(err){ console.log(err); }); -request.get('/').expect('heya', function(err){ +t.get('/').expect('heya', function(err){ console.log(err); }); ``` @@ -221,7 +235,7 @@ request.get('/').expect('heya', function(err){ Here's an example with mocha that shows how to persist a request and its cookies: ```js -const request = require('supertest'); +const { agent } = require('@eggjs/supertest'); const should = require('should'); const express = require('express'); const cookieParser = require('cookie-parser'); @@ -240,16 +254,16 @@ describe('request.agent(app)', function() { else res.send(':(') }); - const agent = request.agent(app); + const testAgent = agent(app); it('should save cookies', function(done) { - agent + testAgent .get('/') .expect('set-cookie', 'cookie=hey; Path=/', done); }); it('should send cookies', function(done) { - agent + testAgent .get('/return') .expect('hey', done); }); @@ -324,15 +338,10 @@ Inspired by [api-easy](https://github.com/flatiron/api-easy) minus vows coupling ## License -MIT +[MIT](LICENSE) -[coverage-badge]: https://img.shields.io/codecov/c/github/ladjs/supertest.svg -[coverage]: https://codecov.io/gh/ladjs/supertest -[travis-badge]: https://travis-ci.org/ladjs/supertest.svg?branch=master -[travis]: https://travis-ci.org/ladjs/supertest -[dependencies-badge]: https://david-dm.org/ladjs/supertest/status.svg -[dependencies]: https://david-dm.org/ladjs/supertest -[prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square -[prs]: http://makeapullrequest.com -[license-badge]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square -[license]: https://github.com/ladjs/supertest/blob/master/LICENSE +## Contributors + +[![Contributors](https://contrib.rocks/image?repo=eggjs/supertest)](https://github.com/eggjs/supertest/graphs/contributors) + +Made with [contributors-img](https://contrib.rocks). diff --git a/ci/remove-deps-4-old-node.js b/ci/remove-deps-4-old-node.js deleted file mode 100644 index ca1368b1..00000000 --- a/ci/remove-deps-4-old-node.js +++ /dev/null @@ -1,22 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const package = require('../package.json'); - -const UNSUPPORT_DEPS_4_OLD = { - 'eslint': undefined, - 'mocha': '6.x' -}; - -const deps = Object.keys(UNSUPPORT_DEPS_4_OLD); -for (const item in package.devDependencies) { - if (deps.includes(item)) { - package.devDependencies[item] = UNSUPPORT_DEPS_4_OLD[item]; - } -} - -delete package.scripts.lint; - -fs.writeFileSync( - path.join(__dirname, '../package.json'), - JSON.stringify(package, null, 2) -); diff --git a/index.js b/index.js deleted file mode 100644 index 8db8e432..00000000 --- a/index.js +++ /dev/null @@ -1,65 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ -const methods = require('methods'); -const http = require('http'); -let http2; -try { - http2 = require('http2'); // eslint-disable-line global-require -} catch (_) { - // eslint-disable-line no-empty -} -const Test = require('./lib/test.js'); -const agent = require('./lib/agent.js'); - -/** - * Test against the given `app`, - * returning a new `Test`. - * - * @param {Function|Server|String} app - * @return {Test} - * @api public - */ -module.exports = function(app, options = {}) { - const obj = {}; - - if (typeof app === 'function') { - if (options.http2) { - if (!http2) { - throw new Error( - 'supertest: this version of Node.js does not support http2' - ); - } - app = http2.createServer(app); // eslint-disable-line no-param-reassign - } else { - app = http.createServer(app); // eslint-disable-line no-param-reassign - } - } - - methods.forEach(function(method) { - obj[method] = function(url) { - var test = new Test(app, method, url); - if (options.http2) { - test.http2(); - } - return test; - }; - }); - - // Support previous use of del - obj.del = obj.delete; - - return obj; -}; - -/** - * Expose `Test` - */ -module.exports.Test = Test; - -/** - * Expose the agent function - */ -module.exports.agent = agent; diff --git a/lib/agent.js b/lib/agent.js deleted file mode 100644 index 6e4e7d88..00000000 --- a/lib/agent.js +++ /dev/null @@ -1,89 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -const { agent: Agent } = require('superagent'); -const methods = require('methods'); -const http = require('http'); -let http2; -try { - http2 = require('http2'); // eslint-disable-line global-require -} catch (_) { - // eslint-disable-line no-empty -} -const Test = require('./test.js'); - -/** - * Initialize a new `TestAgent`. - * - * @param {Function|Server} app - * @param {Object} options - * @api public - */ - -function TestAgent(app, options = {}) { - if (!(this instanceof TestAgent)) return new TestAgent(app, options); - - const agent = new Agent(options); - Object.assign(this, agent); - - this._options = options; - - if (typeof app === 'function') { - if (options.http2) { - if (!http2) { - throw new Error( - 'supertest: this version of Node.js does not support http2' - ); - } - app = http2.createServer(app); // eslint-disable-line no-param-reassign - } else { - app = http.createServer(app); // eslint-disable-line no-param-reassign - } - } - this.app = app; -} - -/** - * Inherits from `Agent.prototype`. - */ - -Object.setPrototypeOf(TestAgent.prototype, Agent.prototype); - -// set a host name -TestAgent.prototype.host = function(host) { - this._host = host; - return this; -}; - -// override HTTP verb methods -methods.forEach(function(method) { - TestAgent.prototype[method] = function(url, fn) { // eslint-disable-line no-unused-vars - const req = new Test(this.app, method.toUpperCase(), url); - if (this._options.http2) { - req.http2(); - } - - if (this._host) { - req.set('host', this._host); - } - - req.on('response', this._saveCookies.bind(this)); - req.on('redirect', this._saveCookies.bind(this)); - req.on('redirect', this._attachCookies.bind(this, req)); - this._setDefaults(req); - this._attachCookies(req); - - return req; - }; -}); - -TestAgent.prototype.del = TestAgent.prototype.delete; - -/** - * Expose `Agent`. - */ - -module.exports = TestAgent; diff --git a/package.json b/package.json index d4c6387a..b179caaa 100644 --- a/package.json +++ b/package.json @@ -1,33 +1,11 @@ { - "name": "supertest", + "name": "@eggjs/supertest", "description": "SuperAgent driven library for testing HTTP servers", "version": "7.0.0", - "author": "TJ Holowaychuk", - "contributors": [], - "dependencies": { - "methods": "^1.1.2", - "superagent": "^9.0.1" + "publishConfig": { + "access": "public" }, - "devDependencies": { - "body-parser": "^1.20.2", - "cookie-parser": "^1.4.6", - "eslint": "^8.32.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-plugin-import": "^2.27.5", - "express": "^4.18.2", - "mocha": "^10.2.0", - "nock": "^13.3.0", - "nyc": "^15.1.0", - "proxyquire": "^2.1.3", - "should": "^13.2.3" - }, - "engines": { - "node": ">=14.18.0" - }, - "files": [ - "index.js", - "lib" - ], + "author": "TJ Holowaychuk", "keywords": [ "bdd", "http", @@ -38,16 +16,71 @@ "testing" ], "license": "MIT", - "main": "index.js", "repository": { "type": "git", - "url": "https://github.com/ladjs/supertest.git" + "url": "https://github.com/eggjs/supertest.git" + }, + "engines": { + "node": ">= 18.19.0" + }, + "dependencies": { + "superagent": "^9.0.1" + }, + "devDependencies": { + "@arethetypeswrong/cli": "^0.17.1", + "@eggjs/tsconfig": "1", + "@types/body-parser": "^1.19.5", + "@types/cookie-parser": "^1.4.8", + "@types/express": "^5.0.0", + "@types/mocha": "10", + "@types/node": "22", + "@types/superagent": "^8.1.9", + "body-parser": "^1.20.3", + "cookie-parser": "^1.4.6", + "egg-bin": "6", + "eslint": "8", + "eslint-config-egg": "14", + "express": "^4.18.2", + "nock": "^13.3.0", + "nyc": "^15.1.0", + "should": "^13.2.3", + "tshy": "3", + "tshy-after": "1", + "typescript": "5" }, "scripts": { - "coverage": "nyc report --reporter=text-lcov > coverage.lcov", - "lint": "eslint lib/**/*.js test/**/*.js index.js", - "lint:fix": "eslint --fix lib/**/*.js test/**/*.js index.js", - "pretest": "npm run lint --if-present", - "test": "nyc --reporter=html --reporter=text mocha --exit --require should --reporter spec --check-leaks" - } + "lint": "eslint --cache src test --ext .ts", + "pretest": "npm run lint -- --fix && npm run prepublishOnly", + "test": "egg-bin test", + "preci": "npm run lint && npm run prepublishOnly && attw --pack", + "ci": "egg-bin cov", + "prepublishOnly": "tshy && tshy-after" + }, + "type": "module", + "tshy": { + "exports": { + ".": "./src/index.ts", + "./package.json": "./package.json" + } + }, + "exports": { + ".": { + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/commonjs/index.d.ts", + "default": "./dist/commonjs/index.js" + } + }, + "./package.json": "./package.json" + }, + "files": [ + "dist", + "src" + ], + "types": "./dist/commonjs/index.d.ts", + "main": "./dist/commonjs/index.js", + "module": "./dist/esm/index.js" } diff --git a/src/agent.ts b/src/agent.ts new file mode 100644 index 00000000..2363228c --- /dev/null +++ b/src/agent.ts @@ -0,0 +1,93 @@ +import http from 'node:http'; +import http2 from 'node:http2'; +import type { Server } from 'node:net'; +import { agent as Agent } from 'superagent'; +import { Test } from './test.js'; +import type { AgentOptions, H1RequestListener, H2RequestListener, App } from './types.js'; + +/** + * Initialize a new `TestAgent`. + * + * @param {Function|Server} app + * @param {Object} options + */ + +export class TestAgent extends Agent { + app: Server | string; + _host: string; + #http2 = false; + + constructor(appOrListener: App, options: AgentOptions = {}) { + super(options); + if (typeof appOrListener === 'function') { + if (options.http2) { + this.#http2 = true; + this.app = http2.createServer(appOrListener as H2RequestListener); // eslint-disable-line no-param-reassign + } else { + this.app = http.createServer(appOrListener as H1RequestListener); // eslint-disable-line no-param-reassign + } + } else { + this.app = appOrListener; + } + } + + // set a host name + host(host: string) { + this._host = host; + return this; + } + + // TestAgent.prototype.del = TestAgent.prototype.delete; + + protected _testRequest(method: string, url: string) { + const req = new Test(this.app, method.toUpperCase(), url); + if (this.#http2) { + req.http2(); + } + + if (this._host) { + req.set('host', this._host); + } + + const that = this as any; + // access not internal methods + req.on('response', that._saveCookies.bind(this)); + req.on('redirect', that._saveCookies.bind(this)); + req.on('redirect', that._attachCookies.bind(this, req)); + that._setDefaults(req); + that._attachCookies(req); + + return req; + } + delete(url: string) { + return this._testRequest('delete', url); + } + del(url: string) { + return this._testRequest('delete', url); + } + get(url: string) { + return this._testRequest('get', url); + } + head(url: string) { + return this._testRequest('head', url); + } + put(url: string) { + return this._testRequest('put', url); + } + post(url: string) { + return this._testRequest('post', url); + } + patch(url: string) { + return this._testRequest('patch', url); + } + options(url: string) { + return this._testRequest('options', url); + } +} + +// allow keep use by `agent()` +export const proxyAgent = new Proxy(TestAgent, { + apply(target, _, argumentsList) { + return new target(argumentsList[0], argumentsList[1]); + }, +}); diff --git a/src/error/AssertError.ts b/src/error/AssertError.ts new file mode 100644 index 00000000..e82a55be --- /dev/null +++ b/src/error/AssertError.ts @@ -0,0 +1,12 @@ +export class AssertError extends Error { + expected: any; + actual: any; + + constructor(message: string, expected: any, actual: any, options?: ErrorOptions) { + super(message, options); + this.name = this.constructor.name; + this.expected = expected; + this.actual = actual; + Error.captureStackTrace(this, this.constructor); + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..ce1cd0ca --- /dev/null +++ b/src/index.ts @@ -0,0 +1,39 @@ +import { Request, RequestOptions } from './request.js'; +import { TestAgent, proxyAgent } from './agent.js'; +import type { App, AgentOptions } from './types.js'; + +/** + * Test against the given `app`, + * returning a new `Test`. + */ +export function request(app: App, options: RequestOptions = {}) { + return new Request(app, options); +} + +export { + Request, RequestOptions, + TestAgent, + // import { agent } from '@eggjs/supertest'; + // agent() + proxyAgent as agent, +}; + +export * from './test.js'; + +// import request from '@eggjs/supertest'; +// request() +export default new Proxy(request, { + apply(target, _, argumentsList) { + return target(argumentsList[0], argumentsList[1]); + }, + get(target, property, receiver) { + // import request from '@eggjs/supertest'; + // request.agent() + if (property === 'agent') { + return proxyAgent; + } + return Reflect.get(target, property, receiver); + }, +}) as unknown as ((app: App, options?: RequestOptions) => Request) & { + agent: (app: App, options?: AgentOptions) => TestAgent; +}; diff --git a/src/request.ts b/src/request.ts new file mode 100644 index 00000000..538b6ee6 --- /dev/null +++ b/src/request.ts @@ -0,0 +1,59 @@ +import http from 'node:http'; +import http2 from 'node:http2'; +import type { Server } from 'node:net'; +import type { H1RequestListener, H2RequestListener, App } from './types.js'; +import { Test } from './test.js'; + +export interface RequestOptions { + http2?: boolean; +} + +export class Request { + app: string | Server; + #http2 = false; + + constructor(appOrListener: App, options: RequestOptions = {}) { + if (typeof appOrListener === 'function') { + if (options.http2) { + this.#http2 = true; + this.app = http2.createServer(appOrListener as H2RequestListener); // eslint-disable-line no-param-reassign + } else { + this.app = http.createServer(appOrListener as H1RequestListener); // eslint-disable-line no-param-reassign + } + } else { + this.app = appOrListener; + } + } + + protected _testRequest(method: string, url: string) { + const req = new Test(this.app, method.toUpperCase(), url); + if (this.#http2) { + req.http2(); + } + return req; + } + delete(url: string) { + return this._testRequest('delete', url); + } + del(url: string) { + return this._testRequest('delete', url); + } + get(url: string) { + return this._testRequest('get', url); + } + head(url: string) { + return this._testRequest('head', url); + } + put(url: string) { + return this._testRequest('put', url); + } + post(url: string) { + return this._testRequest('post', url); + } + patch(url: string) { + return this._testRequest('patch', url); + } + options(url: string) { + return this._testRequest('options', url); + } +} diff --git a/lib/test.js b/src/test.ts similarity index 55% rename from lib/test.js rename to src/test.ts index 3b3b255b..00869e08 100644 --- a/lib/test.js +++ b/src/test.ts @@ -1,34 +1,36 @@ -'use strict'; - -/** - * Module dependencies. - */ - -const { inspect } = require('util'); -const { STATUS_CODES } = require('http'); -const { Server } = require('tls'); -const { deepStrictEqual } = require('assert'); -const { Request } = require('superagent'); +import { inspect } from 'node:util'; +import { STATUS_CODES } from 'node:http'; +import { Server as HttpsServer } from 'node:tls'; +import type { Server, AddressInfo } from 'node:net'; +import { deepStrictEqual } from 'node:assert'; +import { Request, type Response } from 'superagent'; +import { AssertError } from './error/AssertError.js'; + +export type TestApplication = Server | string; + +export type AssertFunction = (res: Response) => AssertError | void; +export type CallbackFunction = (err: AssertError | Error | null, res: Response) => void; +export type ResponseError = Error & { syscall?: string; code?: string; status?: number }; +export interface ExpectHeader { + name: string; + value: string | number | RegExp; +} -/** @typedef {import('superagent').Response} Response */ +export class Test extends Request { + app: TestApplication; + _server: Server; + _asserts: AssertFunction[] = []; -class Test extends Request { /** * Initialize a new `Test` with the given `app`, * request `method` and `path`. - * - * @param {Server} app - * @param {String} method - * @param {String} path - * @api public */ - constructor (app, method, path) { + constructor(app: TestApplication, method: string, path: string) { super(method.toUpperCase(), path); this.redirects(0); this.buffer(); this.app = app; - this._asserts = []; this.url = typeof app === 'string' ? app + path : this.serverAddress(app, path); @@ -37,23 +39,23 @@ class Test extends Request { /** * Returns a URL, extracted from a server. * - * @param {Server} app - * @param {String} path - * @returns {String} URL address - * @api private + * @return {String} URL address + * @private */ - serverAddress(app, path) { + protected serverAddress(app: Server, path: string): string { const addr = app.address(); - - if (!addr) this._server = app.listen(0); - const port = app.address().port; - const protocol = app instanceof Server ? 'https' : 'http'; - return protocol + '://127.0.0.1:' + port + path; + if (!addr) { + this._server = app.listen(0); + } + const port = (app.address() as AddressInfo).port; + const protocol = (app instanceof HttpsServer || this._server instanceof HttpsServer) ? 'https' : 'http'; + return `${protocol}://127.0.0.1:${port}${path}`; } /** * Expectations: * + * ```js * .expect(200) * .expect(200, fn) * .expect(200, body) @@ -64,24 +66,34 @@ class Test extends Request { * .expect('Content-Type', 'application/json', fn) * .expect(fn) * .expect([200, 404]) + * ``` * - * @return {Test} - * @api public + * @return {Test} The current Test instance for chaining. */ - expect(a, b, c) { + expect(a: number | string | RegExp | object | AssertFunction, b?: string | number | RegExp | CallbackFunction, c?: CallbackFunction): Test { // callback if (typeof a === 'function') { - this._asserts.push(wrapAssertFn(a)); + // .expect(fn) + this._asserts.push(wrapAssertFn(a as AssertFunction)); return this; } - if (typeof b === 'function') this.end(b); - if (typeof c === 'function') this.end(c); + if (typeof b === 'function') { + // .expect('Some body', fn) + this.end(b); + } + if (typeof c === 'function') { + // .expect('Content-Type', 'application/json', fn) + this.end(c); + } // status if (typeof a === 'number') { this._asserts.push(wrapAssertFn(this._assertStatus.bind(this, a))); // body if (typeof b !== 'function' && arguments.length > 1) { + // .expect(200, 'body') + // .expect(200, null) + // .expect(200, 9999999) this._asserts.push(wrapAssertFn(this._assertBody.bind(this, b))); } return this; @@ -89,17 +101,23 @@ class Test extends Request { // multiple statuses if (Array.isArray(a) && a.length > 0 && a.every(val => typeof val === 'number')) { + // .expect([200, 300]) this._asserts.push(wrapAssertFn(this._assertStatusArray.bind(this, a))); return this; } // header field if (typeof b === 'string' || typeof b === 'number' || b instanceof RegExp) { - this._asserts.push(wrapAssertFn(this._assertHeader.bind(this, { name: '' + a, value: b }))); + // .expect('Content-Type', 'application/json') + // .expect('Content-Type', /json/) + this._asserts.push(wrapAssertFn(this._assertHeader.bind(this, { name: String(a), value: b }))); return this; } // body + // .expect('body') + // .expect(['json array body', { key: 'val' }]) + // .expect(/foo/) this._asserts.push(wrapAssertFn(this._assertBody.bind(this, a))); return this; @@ -108,11 +126,8 @@ class Test extends Request { /** * Defer invoking superagent's `.end()` until * the server is listening. - * - * @param {Function} fn - * @api public */ - end(fn) { + end(fn: CallbackFunction) { const server = this._server; super.end((err, res) => { @@ -120,7 +135,9 @@ class Test extends Request { this.assert(err, res, fn); }; - if (server && server._handle) return server.close(localAssert); + if (server && '_handle' in server && server._handle) { + return server.close(localAssert); + } localAssert(); }); @@ -130,29 +147,26 @@ class Test extends Request { /** * Perform assertions and invoke `fn(err, res)`. - * - * @param {?Error} resError - * @param {Response} res - * @param {Function} fn - * @api private */ - assert(resError, res, fn) { - let errorObj; + assert(resError: ResponseError | null, res: Response, fn: CallbackFunction) { + let errorObj: Error | undefined; // check for unexpected network errors or server not running/reachable errors // when there is no response and superagent sends back a System Error // do not check further for other asserts, if any, in such case // https://nodejs.org/api/errors.html#errors_common_system_errors - const sysErrors = { + const sysErrors: Record = { ECONNREFUSED: 'Connection refused', ECONNRESET: 'Connection reset by peer', EPIPE: 'Broken pipe', - ETIMEDOUT: 'Operation timed out' + ETIMEDOUT: 'Operation timed out', }; if (!res && resError) { - if (resError instanceof Error && resError.syscall === 'connect' - && Object.getOwnPropertyNames(sysErrors).indexOf(resError.code) >= 0) { + if (resError instanceof Error + && resError.syscall === 'connect' + && resError.code + && sysErrors[resError.code]) { errorObj = new Error(resError.code + ': ' + sysErrors[resError.code]); } else { errorObj = resError; @@ -174,13 +188,8 @@ class Test extends Request { /** * Perform assertions on a response body and return an Error upon failure. - * - * @param {Mixed} body - * @param {Response} res - * @return {?Error} - * @api private - */// eslint-disable-next-line class-methods-use-this - _assertBody(body, res) { + */ + _assertBody(body: RegExp | string | number | object | null | undefined, res: Response) { const isRegexp = body instanceof RegExp; // parsed @@ -190,7 +199,7 @@ class Test extends Request { } catch (err) { const a = inspect(body); const b = inspect(res.body); - return error('expected ' + a + ' response body, got ' + b, body, res.body); + return new AssertError('expected ' + a + ' response body, got ' + b, body, res.body, { cause: err }); } } else if (body !== res.text) { // string @@ -200,28 +209,25 @@ class Test extends Request { // regexp if (isRegexp) { if (!body.test(res.text)) { - return error('expected body ' + b + ' to match ' + body, body, res.body); + return new AssertError('expected body ' + b + ' to match ' + body, body, res.body); } } else { - return error('expected ' + a + ' response body, got ' + b, body, res.body); + return new AssertError('expected ' + a + ' response body, got ' + b, body, res.body); } } } /** * Perform assertions on a response header and return an Error upon failure. - * - * @param {Object} header - * @param {Response} res - * @return {?Error} - * @api private - */// eslint-disable-next-line class-methods-use-this - _assertHeader(header, res) { + */ + _assertHeader(header: ExpectHeader, res: Response) { const field = header.name; const actual = res.header[field.toLowerCase()]; const fieldExpected = header.value; - if (typeof actual === 'undefined') return new Error('expected "' + field + '" header field'); + if (typeof actual === 'undefined') { + return new AssertError('expected "' + field + '" header field', header, actual); + } // This check handles header values that may be a String or single element Array if ((Array.isArray(actual) && actual.toString() === fieldExpected) || fieldExpected === actual) { @@ -229,64 +235,56 @@ class Test extends Request { } if (fieldExpected instanceof RegExp) { if (!fieldExpected.test(actual)) { - return new Error('expected "' + field + '" matching ' - + fieldExpected + ', got "' + actual + '"'); + return new AssertError('expected "' + field + '" matching ' + + fieldExpected + ', got "' + actual + '"', header, actual); } } else { - return new Error('expected "' + field + '" of "' + fieldExpected + '", got "' + actual + '"'); + return new AssertError('expected "' + field + '" of "' + fieldExpected + '", got "' + actual + '"', + header, actual, + ); } } /** * Perform assertions on the response status and return an Error upon failure. - * - * @param {Number} status - * @param {Response} res - * @return {?Error} - * @api private - */// eslint-disable-next-line class-methods-use-this - _assertStatus(status, res) { + */ + _assertStatus(status: number, res: Response) { if (res.status !== status) { const a = STATUS_CODES[status]; const b = STATUS_CODES[res.status]; - return new Error('expected ' + status + ' "' + a + '", got ' + res.status + ' "' + b + '"'); + return new AssertError('expected ' + status + ' "' + a + '", got ' + res.status + ' "' + b + '"', + status, res.status, + ); } } /** * Perform assertions on the response status and return an Error upon failure. - * - * @param {Array} statusArray - * @param {Response} res - * @return {?Error} - * @api private - */// eslint-disable-next-line class-methods-use-this - _assertStatusArray(statusArray, res) { + */ + _assertStatusArray(statusArray: number[], res: Response) { if (!statusArray.includes(res.status)) { const b = STATUS_CODES[res.status]; const expectedList = statusArray.join(', '); - return new Error( - 'expected one of "' + expectedList + '", got ' + res.status + ' "' + b + '"' + return new AssertError( + 'expected one of "' + expectedList + '", got ' + res.status + ' "' + b + '"', + statusArray, res.status, ); } } /** * Performs an assertion by calling a function and return an Error upon failure. - * - * @param {Function} fn - * @param {Response} res - * @return {?Error} - * @api private - */// eslint-disable-next-line class-methods-use-this - _assertFunction(fn, res) { + */ + _assertFunction(fn: AssertFunction, res: Response) { let err; try { err = fn(res); } catch (e) { err = e; } - if (err instanceof Error) return err; + if (err instanceof Error) { + return err; + } } } @@ -295,23 +293,23 @@ class Test extends Request { * The wrapper function edit the stack trace of any assertion error, prepending a more useful stack to it. * * @param {Function} assertFn - * @returns {Function} wrapped assert function + * @return {Function} wrapped assert function */ -function wrapAssertFn(assertFn) { - const savedStack = new Error().stack.split('\n').slice(3); +function wrapAssertFn(assertFn: AssertFunction) { + const savedStack = new Error().stack!.split('\n').slice(3); - return function(res) { + return (res: Response) => { let badStack; let err; try { err = assertFn(res); - } catch (e) { + } catch (e: any) { err = e; } if (err instanceof Error && err.stack) { badStack = err.stack.replace(err.message, '').split('\n').slice(1); - err.stack = [err.toString()] + err.stack = [ err.toString() ] .concat(savedStack) .concat('----') .concat(badStack) @@ -320,27 +318,3 @@ function wrapAssertFn(assertFn) { return err; }; } - -/** - * Return an `Error` with `msg` and results properties. - * - * @param {String} msg - * @param {Mixed} expected - * @param {Mixed} actual - * @return {Error} - * @api private - */ - -function error(msg, expected, actual) { - const err = new Error(msg); - err.expected = expected; - err.actual = actual; - err.showDiff = true; - return err; -} - -/** - * Expose `Test`. - */ - -module.exports = Test; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 00000000..d99b3919 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,17 @@ +import type { RequestListener } from 'node:http'; +import type { Http2ServerRequest, Http2ServerResponse } from 'node:http2'; +import type { Server } from 'node:net'; +import type { AgentOptions as SAgentOptions } from 'superagent'; + +export type H2RequestListener = (request: Http2ServerRequest, response: Http2ServerResponse) => void; +export type H1RequestListener = RequestListener; + +export type App = + | Server + | H1RequestListener + | H2RequestListener + | string; + +export interface AgentOptions extends SAgentOptions { + http2?: boolean; +} diff --git a/test/.eslintrc b/test/.eslintrc deleted file mode 100644 index 3844a33d..00000000 --- a/test/.eslintrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "rules": { - // errors - disabled for chai test support - "no-unused-expressions": [0], - // allow function args for superagent - "no-unused-vars": [2, {"args": "none"}], - // allow updates to response for certain tests - "no-param-reassign": [2, {"props": false}] - } -} diff --git a/test/supertest.js b/test/supertest.test.ts similarity index 62% rename from test/supertest.js rename to test/supertest.test.ts index 9eebf312..55702079 100644 --- a/test/supertest.js +++ b/test/supertest.test.ts @@ -1,60 +1,59 @@ -'use strict'; - -const https = require('https'); -let http2; -try { - http2 = require('http2'); // eslint-disable-line global-require -} catch (_) { - // eslint-disable-line no-empty -} -const fs = require('fs'); -const path = require('path'); -const should = require('should'); -const express = require('express'); -const bodyParser = require('body-parser'); -const cookieParser = require('cookie-parser'); -const nock = require('nock'); -const request = require('../index.js'); -const throwError = require('./throwError'); +import { strict as assert } from 'node:assert'; +import https from 'node:https'; +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { AddressInfo } from 'node:net'; +import should from 'should'; +import express, { Express } from 'express'; +import bodyParser from 'body-parser'; +import cookieParser from 'cookie-parser'; +import nock from 'nock'; +import request, { Test } from '../src/index.js'; +import { throwError } from './throwError.js'; process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; -function shouldIncludeStackWithThisFile(err) { - err.stack.should.match(/test\/supertest.js:/); - err.stack.should.startWith(err.name + ':'); +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +function shouldIncludeStackWithThisFile(err: Error) { + // console.error(err.stack); + err.stack!.should.match(/test\/supertest\.test\.ts:/); + err.stack!.should.startWith(err.name + ':'); } -describe('request(url)', function () { - it('should be supported', function (done) { +describe('request(url)', function() { + it('should be supported', function(done) { const app = express(); - let server; - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('hello'); }); - server = app.listen(function () { - const url = 'http://localhost:' + server.address().port; + const server = app.listen(function() { + const url = 'http://localhost:' + (server.address() as AddressInfo).port; request(url) .get('/') .expect('hello', done); }); }); - describe('.end(cb)', function () { - it('should set `this` to the test object when calling cb', function (done) { + describe('.end(cb)', function() { + it('should set `this` to the test object when calling cb', function(done) { const app = express(); - let server; - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('hello'); }); - server = app.listen(function () { - const url = 'http://localhost:' + server.address().port; + const server = app.listen(function() { + const url = 'http://localhost:' + (server.address() as AddressInfo).port; const test = request(url).get('/'); - test.end(function (err, res) { - this.should.eql(test); + test.end(function(this: Test, err, res) { + assert.equal(this, test); + assert.equal(err, null); + assert.equal(res.text, 'hello'); done(); }); }); @@ -62,35 +61,36 @@ describe('request(url)', function () { }); }); -describe('request(app)', function () { - it('should fire up the app on an ephemeral port', function (done) { +describe('request(app)', function() { + it('should fire up the app on an ephemeral port', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('hey'); }); request(app) .get('/') - .end(function (err, res) { + .end(function(err, res) { + assert.equal(err, null); res.status.should.equal(200); res.text.should.equal('hey'); done(); }); }); - it('should work with an active server', function (done) { + it('should work with an active server', function(done) { const app = express(); - let server; - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('hey'); }); - server = app.listen(function () { + const server = app.listen(function() { request(server) .get('/') - .end(function (err, res) { + .end(function(err, res) { + assert.equal(err, null); res.status.should.equal(200); res.text.should.equal('hey'); done(); @@ -98,19 +98,19 @@ describe('request(app)', function () { }); }); - it('should work with remote server', function (done) { + it('should work with remote server', function(done) { const app = express(); - let server; - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('hey'); }); - server = app.listen(function () { - const url = 'http://localhost:' + server.address().port; + const server = app.listen(function() { + const url = 'http://localhost:' + (server.address() as AddressInfo).port; request(url) .get('/') - .end(function (err, res) { + .end(function(err, res) { + assert.equal(err, null); res.status.should.equal(200); res.text.should.equal('hey'); done(); @@ -118,21 +118,21 @@ describe('request(app)', function () { }); }); - it('should work with a https server', function (done) { + it('should work with a https server', function(done) { const app = express(); const fixtures = path.join(__dirname, 'fixtures'); const server = https.createServer({ key: fs.readFileSync(path.join(fixtures, 'test_key.pem')), - cert: fs.readFileSync(path.join(fixtures, 'test_cert.pem')) + cert: fs.readFileSync(path.join(fixtures, 'test_cert.pem')), }, app); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('hey'); }); request(server) .get('/') - .end(function (err, res) { + .end(function(err, res) { if (err) return done(err); res.status.should.equal(200); res.text.should.equal('hey'); @@ -140,12 +140,12 @@ describe('request(app)', function () { }); }); - it('should work with .send() etc', function (done) { + it('should work with .send() etc', function(done) { const app = express(); app.use(bodyParser.json()); - app.post('/', function (req, res) { + app.post('/', function(req, res) { res.send(req.body.name); }); @@ -155,10 +155,10 @@ describe('request(app)', function () { .expect('john', done); }); - it('should work when unbuffered', function (done) { + it('should work when unbuffered', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.end('Hello'); }); @@ -167,10 +167,10 @@ describe('request(app)', function () { .expect('Hello', done); }); - it('should default redirects to 0', function (done) { + it('should default redirects to 0', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.redirect('/login'); }); @@ -179,21 +179,22 @@ describe('request(app)', function () { .expect(302, done); }); - it('should handle redirects', function (done) { + it('should handle redirects', function(done) { const app = express(); - app.get('/login', function (req, res) { + app.get('/login', function(_req, res) { res.end('Login'); }); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.redirect('/login'); }); request(app) .get('/') .redirects(1) - .end(function (err, res) { + .end(function(err, res) { + assert.equal(err, null); should.exist(res); res.status.should.be.equal(200); res.text.should.be.equal('Login'); @@ -201,75 +202,73 @@ describe('request(app)', function () { }); }); - it('should handle socket errors', function (done) { + it('should handle socket errors', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.destroy(); }); request(app) .get('/') - .end(function (err) { + .end(function(err) { should.exist(err); done(); }); }); - describe('.end(fn)', function () { - it('should close server', function (done) { + describe('.end(fn)', function() { + it('should close server', function(done) { const app = express(); - let test; - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('supertest FTW!'); }); - test = request(app) + const test = request(app) .get('/') - .end(function () { + .end(function() { }); - test._server.on('close', function () { + test._server.on('close', function() { done(); }); }); - it('should wait for server to close before invoking fn', function (done) { + it('should wait for server to close before invoking fn', function(done) { const app = express(); let closed = false; - let test; - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('supertest FTW!'); }); - test = request(app) + const test = request(app) .get('/') - .end(function () { + .end(function() { closed.should.be.true; done(); }); - test._server.on('close', function () { + test._server.on('close', function() { closed = true; }); }); - it('should support nested requests', function (done) { + it('should support nested requests', function(done) { const app = express(); const test = request(app); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('supertest FTW!'); }); test .get('/') - .end(function () { + .end(function() { test .get('/') - .end(function (err, res) { + .end(function(err, res) { (err === null).should.be.true; res.status.should.equal(200); res.text.should.equal('supertest FTW!'); @@ -278,19 +277,19 @@ describe('request(app)', function () { }); }); - it('should include the response in the error callback', function (done) { + it('should include the response in the error callback', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('whatever'); }); request(app) .get('/') - .expect(function () { + .expect(function() { throw new Error('Some error'); }) - .end(function (err, res) { + .end(function(err, res) { should.exist(err); should.exist(res); // Duck-typing response, just in case. @@ -299,60 +298,61 @@ describe('request(app)', function () { }); }); - it('should set `this` to the test object when calling the error callback', function (done) { + it('should set `this` to the test object when calling the error callback', function(done) { const app = express(); - let test; - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('whatever'); }); - test = request(app).get('/'); - test.expect(function () { + const test = request(app).get('/'); + test.expect(function() { throw new Error('Some error'); - }).end(function (err, res) { + }).end(function(this: Test, err, res) { should.exist(err); - this.should.eql(test); + assert.equal(err!.message, 'Some error'); + assert.equal(this, test); + assert.equal(res.text, 'whatever'); done(); }); }); - it('should handle an undefined Response', function (done) { + it('should handle an undefined Response', function(done) { const app = express(); - let server; - app.get('/', function (req, res) { - setTimeout(function () { + app.get('/', function(_req, res) { + setTimeout(function() { res.end(); }, 20); }); - server = app.listen(function () { - const url = 'http://localhost:' + server.address().port; + const server = app.listen(function() { + const url = 'http://localhost:' + (server.address() as AddressInfo).port; request(url) .get('/') .timeout(1) - .expect(200, function (err) { + .expect(200, function(err) { + assert(err instanceof Error); err.should.be.an.instanceof(Error); return done(); }); }); }); - it('should handle error returned when server goes down', function (done) { + it('should handle error returned when server goes down', function(done) { const app = express(); - let server; - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.end(); }); - server = app.listen(function () { - const url = 'http://localhost:' + server.address().port; + const server = app.listen(function() { + const url = 'http://localhost:' + (server.address() as AddressInfo).port; server.close(); request(url) .get('/') - .expect(200, function (err) { + .expect(200, function(err) { + assert(err instanceof Error); err.should.be.an.instanceof(Error); return done(); }); @@ -360,18 +360,19 @@ describe('request(app)', function () { }); }); - describe('.expect(status[, fn])', function () { - it('should assert the response status', function (done) { + describe('.expect(status[, fn])', function() { + it('should assert the response status', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('hey'); }); request(app) .get('/') .expect(404) - .end(function (err, res) { + .end(function(err) { + assert(err instanceof Error); err.message.should.equal('expected 404 "Not Found", got 200 "OK"'); shouldIncludeStackWithThisFile(err); done(); @@ -379,25 +380,27 @@ describe('request(app)', function () { }); }); - describe('.expect(status)', function () { - it('should handle connection error', function (done) { - const req = request.agent('http://localhost:1234'); + describe('.expect(status)', function() { + it('should handle connection error', function(done) { + const req = request.agent('http://127.0.0.1:1234'); req .get('/') .expect(200) - .end(function (err, res) { + .end(function(err) { + assert(err instanceof Error); + // console.error(err); err.message.should.equal('ECONNREFUSED: Connection refused'); done(); }); }); }); - describe('.expect(status)', function () { - it('should assert only status', function (done) { + describe('.expect(status)', function() { + it('should assert only status', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('hey'); }); @@ -408,31 +411,32 @@ describe('request(app)', function () { }); }); - describe('.expect(statusArray)', function () { - it('should assert only status', function (done) { + describe('.expect(statusArray)', function() { + it('should assert only status', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('hey'); }); request(app) .get('/') - .expect([200, 404]) + .expect([ 200, 404 ]) .end(done); }); - it('should reject if status is not in valid statuses array', function (done) { + it('should reject if status is not in valid statuses array', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('hey'); }); request(app) .get('/') - .expect([500, 404]) - .end(function (err, res) { + .expect([ 500, 404 ]) + .end(function(err) { + assert(err instanceof Error); err.message.should.equal('expected one of "500, 404", got 200 "OK"'); shouldIncludeStackWithThisFile(err); done(); @@ -440,11 +444,11 @@ describe('request(app)', function () { }); }); - describe('.expect(status, body[, fn])', function () { - it('should assert the response body and status', function (done) { + describe('.expect(status, body[, fn])', function() { + it('should assert the response body and status', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('foo'); }); @@ -453,18 +457,19 @@ describe('request(app)', function () { .expect(200, 'foo', done); }); - describe('when the body argument is an empty string', function () { - it('should not quietly pass on failure', function (done) { + describe('when the body argument is an empty string', function() { + it('should not quietly pass on failure', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('foo'); }); request(app) .get('/') .expect(200, '') - .end(function (err, res) { + .end(function(err) { + assert(err instanceof Error); err.message.should.equal('expected \'\' response body, got \'foo\''); shouldIncludeStackWithThisFile(err); done(); @@ -473,32 +478,33 @@ describe('request(app)', function () { }); }); - describe('.expect(body[, fn])', function () { - it('should assert the response body', function (done) { + describe('.expect(body[, fn])', function() { + it('should assert the response body', function(done) { const app = express(); app.set('json spaces', 0); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send({ foo: 'bar' }); }); request(app) .get('/') .expect('hey') - .end(function (err, res) { + .end(function(err) { + assert(err instanceof Error); err.message.should.equal('expected \'hey\' response body, got \'{"foo":"bar"}\''); shouldIncludeStackWithThisFile(err); done(); }); }); - it('should assert the status before the body', function (done) { + it('should assert the status before the body', function(done) { const app = express(); app.set('json spaces', 0); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.status(500).send({ message: 'something went wrong' }); }); @@ -506,19 +512,20 @@ describe('request(app)', function () { .get('/') .expect(200) .expect('hey') - .end(function (err, res) { + .end(function(err) { + assert(err instanceof Error); err.message.should.equal('expected 200 "OK", got 500 "Internal Server Error"'); shouldIncludeStackWithThisFile(err); done(); }); }); - it('should assert the response text', function (done) { + it('should assert the response text', function(done) { const app = express(); app.set('json spaces', 0); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send({ foo: 'bar' }); }); @@ -527,19 +534,20 @@ describe('request(app)', function () { .expect('{"foo":"bar"}', done); }); - it('should assert the parsed response body', function (done) { + it('should assert the parsed response body', function(done) { const app = express(); app.set('json spaces', 0); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send({ foo: 'bar' }); }); request(app) .get('/') .expect({ foo: 'baz' }) - .end(function (err, res) { + .end(function(err) { + assert(err instanceof Error); err.message.should.equal('expected { foo: \'baz\' } response body, got { foo: \'bar\' }'); shouldIncludeStackWithThisFile(err); @@ -550,9 +558,9 @@ describe('request(app)', function () { }); }); - it('should test response object types', function (done) { + it('should test response object types', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.status(200).json({ stringValue: 'foo', numberValue: 3 }); }); @@ -561,9 +569,9 @@ describe('request(app)', function () { .expect({ stringValue: 'foo', numberValue: 3 }, done); }); - it('should deep test response object types', function (done) { + it('should deep test response object types', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.status(200) .json({ stringValue: 'foo', numberValue: 3, nestedObject: { innerString: '5' } }); }); @@ -571,7 +579,8 @@ describe('request(app)', function () { request(app) .get('/') .expect({ stringValue: 'foo', numberValue: 3, nestedObject: { innerString: 5 } }) - .end(function (err, res) { + .end(function(err) { + assert(err instanceof Error); err.message.replace(/[^a-zA-Z]/g, '').should.equal('expected {\n stringValue: \'foo\',\n numberValue: 3,\n nestedObject: { innerString: 5 }\n} response body, got {\n stringValue: \'foo\',\n numberValue: 3,\n nestedObject: { innerString: \'5\' }\n}'.replace(/[^a-zA-Z]/g, '')); // eslint-disable-line max-len shouldIncludeStackWithThisFile(err); @@ -582,20 +591,20 @@ describe('request(app)', function () { }); }); - it('should support parsed response arrays', function (done) { + it('should support parsed response arrays', function(done) { const app = express(); - app.get('/', function (req, res) { - res.status(200).json(['a', { id: 1 }]); + app.get('/', function(_req, res) { + res.status(200).json([ 'a', { id: 1 }]); }); request(app) .get('/') - .expect(['a', { id: 1 }], done); + .expect([ 'a', { id: 1 }], done); }); - it('should support empty array responses', function (done) { + it('should support empty array responses', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.status(200).json([]); }); @@ -604,27 +613,28 @@ describe('request(app)', function () { .expect([], done); }); - it('should support regular expressions', function (done) { + it('should support regular expressions', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('foobar'); }); request(app) .get('/') .expect(/^bar/) - .end(function (err, res) { + .end(function(err) { + assert(err instanceof Error); err.message.should.equal('expected body \'foobar\' to match /^bar/'); shouldIncludeStackWithThisFile(err); done(); }); }); - it('should assert response body multiple times', function (done) { + it('should assert response body multiple times', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('hey tj'); }); @@ -633,17 +643,18 @@ describe('request(app)', function () { .expect(/tj/) .expect('hey') .expect('hey tj') - .end(function (err, res) { + .end(function(err) { + assert(err instanceof Error); err.message.should.equal("expected 'hey' response body, got 'hey tj'"); shouldIncludeStackWithThisFile(err); done(); }); }); - it('should assert response body multiple times with no exception', function (done) { + it('should assert response body multiple times with no exception', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('hey tj'); }); @@ -655,35 +666,37 @@ describe('request(app)', function () { }); }); - describe('.expect(field, value[, fn])', function () { - it('should assert the header field presence', function (done) { + describe('.expect(field, value[, fn])', function() { + it('should assert the header field presence', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send({ foo: 'bar' }); }); request(app) .get('/') .expect('Content-Foo', 'bar') - .end(function (err, res) { + .end(function(err) { + assert(err instanceof Error); err.message.should.equal('expected "Content-Foo" header field'); shouldIncludeStackWithThisFile(err); done(); }); }); - it('should assert the header field value', function (done) { + it('should assert the header field value', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send({ foo: 'bar' }); }); request(app) .get('/') .expect('Content-Type', 'text/html') - .end(function (err, res) { + .end(function(err) { + assert(err instanceof Error); err.message.should.equal('expected "Content-Type" of "text/html", ' + 'got "application/json; charset=utf-8"'); shouldIncludeStackWithThisFile(err); @@ -691,10 +704,10 @@ describe('request(app)', function () { }); }); - it('should assert multiple fields', function (done) { + it('should assert multiple fields', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('hey'); }); @@ -705,17 +718,18 @@ describe('request(app)', function () { .end(done); }); - it('should support regular expressions', function (done) { + it('should support regular expressions', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('hey'); }); request(app) .get('/') .expect('Content-Type', /^application/) - .end(function (err) { + .end(function(err) { + assert(err instanceof Error); err.message.should.equal('expected "Content-Type" matching /^application/, ' + 'got "text/html; charset=utf-8"'); shouldIncludeStackWithThisFile(err); @@ -723,42 +737,44 @@ describe('request(app)', function () { }); }); - it('should support numbers', function (done) { + it('should support numbers', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('hey'); }); request(app) .get('/') .expect('Content-Length', 4) - .end(function (err) { + .end(function(err) { + assert(err instanceof Error); err.message.should.equal('expected "Content-Length" of "4", got "3"'); shouldIncludeStackWithThisFile(err); done(); }); }); - describe('handling arbitrary expect functions', function () { - let app; - let get; + describe('handling arbitrary expect functions', function() { + let app: Express; + let get: Test; - before(function () { + before(function() { app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('hey'); }); }); - beforeEach(function () { + beforeEach(function() { get = request(app).get('/'); }); - it('reports errors', function (done) { + it('reports errors', function(done) { get .expect(throwError('failed')) - .end(function (err) { + .end(function(err) { + assert(err instanceof Error); err.message.should.equal('failed'); shouldIncludeStackWithThisFile(err); done(); @@ -768,15 +784,16 @@ describe('request(app)', function () { // this scenario should never happen after https://github.com/ladjs/supertest/pull/767 // meant for test coverage for lib/test.js#287 // https://github.com/ladjs/supertest/blob/e064b5ae71e1dfa3e1a74745fda527ac542e1878/lib/test.js#L287 - it('_assertFunction should catch and return error', function (done) { + it('_assertFunction should catch and return error', function(done) { const error = new Error('failed'); const returnedError = get // private api - ._assertFunction(function (res) { + ._assertFunction(function() { throw error; - }); + }, {} as any); get - .end(function () { + .end(function() { + assert(returnedError instanceof Error); returnedError.should.equal(error); returnedError.message.should.equal('failed'); shouldIncludeStackWithThisFile(returnedError); @@ -786,22 +803,23 @@ describe('request(app)', function () { it( 'ensures truthy non-errors returned from asserts are not promoted to errors', - function (done) { + function(done) { get - .expect(function (res) { + .expect(function() { return 'some descriptive error'; }) - .end(function (err) { + .end(function(err) { should.not.exist(err); done(); }); - } + }, ); - it('ensures truthy errors returned from asserts are throw to end', function (done) { + it('ensures truthy errors returned from asserts are throw to end', function(done) { get .expect(throwError('some descriptive error')) - .end(function (err) { + .end(function(err) { + assert(err instanceof Error); err.message.should.equal('some descriptive error'); shouldIncludeStackWithThisFile(err); (err instanceof Error).should.be.true; @@ -809,18 +827,18 @@ describe('request(app)', function () { }); }); - it("doesn't create false negatives", function (done) { + it("doesn't create false negatives", function(done) { get - .expect(function (res) { + .expect(function() { }) .end(done); }); - it("doesn't create false negatives on non error objects", function (done) { + it("doesn't create false negatives on non error objects", function(done) { const handler = { - get: function(target, prop, receiver) { + get() { throw Error('Should not be called for non Error objects'); - } + }, }; const proxy = new Proxy({}, handler); // eslint-disable-line no-undef get @@ -828,20 +846,20 @@ describe('request(app)', function () { .end(done); }); - it('handles multiple asserts', function (done) { - const calls = []; + it('handles multiple asserts', function(done) { + const calls: number[] = []; get - .expect(function (res) { + .expect(function() { calls[0] = 1; }) - .expect(function (res) { + .expect(function() { calls[1] = 1; }) - .expect(function (res) { + .expect(function() { calls[2] = 1; }) - .end(function () { - const callCount = [0, 1, 2].reduce(function (count, i) { + .end(function() { + const callCount = [ 0, 1, 2 ].reduce(function(count, i) { return count + calls[i]; }, 0); callCount.should.equal(3, "didn't see all assertions run"); @@ -849,34 +867,35 @@ describe('request(app)', function () { }); }); - it('plays well with normal assertions - no false positives', function (done) { + it('plays well with normal assertions - no false positives', function(done) { get - .expect(function (res) { + .expect(function() { }) .expect('Content-Type', /json/) - .end(function (err) { + .end(function(err) { + assert(err instanceof Error); err.message.should.match(/Content-Type/); shouldIncludeStackWithThisFile(err); done(); }); }); - it('plays well with normal assertions - no false negatives', function (done) { + it('plays well with normal assertions - no false negatives', function(done) { get - .expect(function (res) { + .expect(function() { }) .expect('Content-Type', /html/) - .expect(function (res) { + .expect(function() { }) .expect('Content-Type', /text/) .end(done); }); }); - describe('handling multiple assertions per field', function () { - it('should work', function (done) { + describe('handling multiple assertions per field', function() { + it('should work', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('hey'); }); @@ -887,9 +906,9 @@ describe('request(app)', function () { .end(done); }); - it('should return an error if the first one fails', function (done) { + it('should return an error if the first one fails', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('hey'); }); @@ -897,7 +916,8 @@ describe('request(app)', function () { .get('/') .expect('Content-Type', /bloop/) .expect('Content-Type', /html/) - .end(function (err) { + .end(function(err) { + assert(err instanceof Error); err.message.should.equal('expected "Content-Type" matching /bloop/, ' + 'got "text/html; charset=utf-8"'); shouldIncludeStackWithThisFile(err); @@ -905,9 +925,9 @@ describe('request(app)', function () { }); }); - it('should return an error if a middle one fails', function (done) { + it('should return an error if a middle one fails', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('hey'); }); @@ -916,7 +936,8 @@ describe('request(app)', function () { .expect('Content-Type', /text/) .expect('Content-Type', /bloop/) .expect('Content-Type', /html/) - .end(function (err) { + .end(function(err) { + assert(err instanceof Error); err.message.should.equal('expected "Content-Type" matching /bloop/, ' + 'got "text/html; charset=utf-8"'); shouldIncludeStackWithThisFile(err); @@ -924,9 +945,9 @@ describe('request(app)', function () { }); }); - it('should return an error if the last one fails', function (done) { + it('should return an error if the last one fails', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.send('hey'); }); @@ -935,7 +956,8 @@ describe('request(app)', function () { .expect('Content-Type', /text/) .expect('Content-Type', /html/) .expect('Content-Type', /bloop/) - .end(function (err) { + .end(function(err) { + assert(err instanceof Error); err.message.should.equal('expected "Content-Type" matching /bloop/, ' + 'got "text/html; charset=utf-8"'); shouldIncludeStackWithThisFile(err); @@ -946,60 +968,60 @@ describe('request(app)', function () { }); }); -describe('request.agent(app)', function () { +describe('request.agent(app)', function() { const app = express(); const agent = request.agent(app) .set('header', 'hey'); app.use(cookieParser()); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.cookie('cookie', 'hey'); res.send(); }); - app.get('/return_cookies', function (req, res) { + app.get('/return_cookies', function(req, res) { if (req.cookies.cookie) res.send(req.cookies.cookie); else res.send(':('); }); - app.get('/return_headers', function (req, res) { + app.get('/return_headers', function(req, res) { if (req.get('header')) res.send(req.get('header')); else res.send(':('); }); - it('should save cookies', function (done) { + it('should save cookies', function(done) { agent .get('/') .expect('set-cookie', 'cookie=hey; Path=/', done); }); - it('should send cookies', function (done) { + it('should send cookies', function(done) { agent .get('/return_cookies') .expect('hey', done); }); - it('should send global agent headers', function (done) { + it('should send global agent headers', function(done) { agent .get('/return_headers') .expect('hey', done); }); }); -describe('agent.host(host)', function () { - it('should set request hostname', function (done) { +describe('agent.host(host)', function() { + it('should set request hostname', function(done) { const app = express(); const agent = request.agent(app); - app.get('/', function (req, res) { + app.get('/', function(req, res) { res.send({ hostname: req.hostname }); }); agent .host('something.test') .get('/') - .end(function (err, res) { + .end(function(err, res) { if (err) return done(err); res.body.hostname.should.equal('something.test'); done(); @@ -1007,10 +1029,10 @@ describe('agent.host(host)', function () { }); }); -describe('. works as expected', function () { - it('.delete should work', function (done) { +describe('. works as expected', function() { + it('.delete should work', function(done) { const app = express(); - app.delete('/', function (req, res) { + app.delete('/', function(_req, res) { res.sendStatus(200); }); @@ -1018,9 +1040,9 @@ describe('. works as expected', function () { .delete('/') .expect(200, done); }); - it('.del should work', function (done) { + it('.del should work', function(done) { const app = express(); - app.delete('/', function (req, res) { + app.delete('/', function(_req, res) { res.sendStatus(200); }); @@ -1028,9 +1050,9 @@ describe('. works as expected', function () { .del('/') .expect(200, done); }); - it('.get should work', function (done) { + it('.get should work', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.sendStatus(200); }); @@ -1038,9 +1060,9 @@ describe('. works as expected', function () { .get('/') .expect(200, done); }); - it('.post should work', function (done) { + it('.post should work', function(done) { const app = express(); - app.post('/', function (req, res) { + app.post('/', function(_req, res) { res.sendStatus(200); }); @@ -1048,9 +1070,9 @@ describe('. works as expected', function () { .post('/') .expect(200, done); }); - it('.put should work', function (done) { + it('.put should work', function(done) { const app = express(); - app.put('/', function (req, res) { + app.put('/', function(_req, res) { res.sendStatus(200); }); @@ -1058,9 +1080,9 @@ describe('. works as expected', function () { .put('/') .expect(200, done); }); - it('.head should work', function (done) { + it('.head should work', function(done) { const app = express(); - app.head('/', function (req, res) { + app.head('/', function(_req, res) { res.statusCode = 200; res.set('Content-Encoding', 'gzip'); res.set('Content-Length', '1024'); @@ -1071,7 +1093,7 @@ describe('. works as expected', function () { request(app) .head('/') .set('accept-encoding', 'gzip, deflate') - .end(function (err, res) { + .end(function(err, res) { if (err) return done(err); res.should.have.property('statusCode', 200); res.headers.should.have.property('content-length', '1024'); @@ -1080,13 +1102,13 @@ describe('. works as expected', function () { }); }); -describe('assert ordering by call order', function () { - it('should assert the body before status', function (done) { +describe('assert ordering by call order', function() { + it('should assert the body before status', function(done) { const app = express(); app.set('json spaces', 0); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.status(500).json({ message: 'something went wrong' }); }); @@ -1094,7 +1116,8 @@ describe('assert ordering by call order', function () { .get('/') .expect('hey') .expect(200) - .end(function (err, res) { + .end(function(err) { + assert(err instanceof Error); err.message.should.equal('expected \'hey\' response body, ' + 'got \'{"message":"something went wrong"}\''); shouldIncludeStackWithThisFile(err); @@ -1102,12 +1125,12 @@ describe('assert ordering by call order', function () { }); }); - it('should assert the status before body', function (done) { + it('should assert the status before body', function(done) { const app = express(); app.set('json spaces', 0); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.status(500).json({ message: 'something went wrong' }); }); @@ -1115,19 +1138,20 @@ describe('assert ordering by call order', function () { .get('/') .expect(200) .expect('hey') - .end(function (err, res) { + .end(function(err) { + assert(err instanceof Error); err.message.should.equal('expected 200 "OK", got 500 "Internal Server Error"'); shouldIncludeStackWithThisFile(err); done(); }); }); - it('should assert the fields before body and status', function (done) { + it('should assert the fields before body and status', function(done) { const app = express(); app.set('json spaces', 0); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.status(200).json({ hello: 'world' }); }); @@ -1135,7 +1159,8 @@ describe('assert ordering by call order', function () { .get('/') .expect('content-type', /html/) .expect('hello') - .end(function (err, res) { + .end(function(err) { + assert(err instanceof Error); err.message.should.equal('expected "content-type" matching /html/, ' + 'got "application/json; charset=utf-8"'); shouldIncludeStackWithThisFile(err); @@ -1143,23 +1168,23 @@ describe('assert ordering by call order', function () { }); }); - it('should call the expect function in order', function (done) { + it('should call the expect function in order', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.status(200).json({}); }); request(app) .get('/') - .expect(function (res) { + .expect(function(res) { res.body.first = 1; }) - .expect(function (res) { + .expect(function(res) { (res.body.first === 1).should.be.true; res.body.second = 2; }) - .end(function (err, res) { + .end(function(err, res) { if (err) return done(err); (res.body.first === 1).should.be.true; (res.body.second === 2).should.be.true; @@ -1167,29 +1192,29 @@ describe('assert ordering by call order', function () { }); }); - it('should call expect(fn) and expect(status, fn) in order', function (done) { + it('should call expect(fn) and expect(status, fn) in order', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.status(200).json({}); }); request(app) .get('/') - .expect(function (res) { + .expect(function(res) { res.body.first = 1; }) - .expect(200, function (err, res) { + .expect(200, function(err, res) { (err === null).should.be.true; (res.body.first === 1).should.be.true; done(); }); }); - it('should call expect(fn) and expect(header,value) in order', function (done) { + it('should call expect(fn) and expect(header,value) in order', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res .set('X-Some-Header', 'Some value') .send(); @@ -1198,29 +1223,29 @@ describe('assert ordering by call order', function () { request(app) .get('/') .expect('X-Some-Header', 'Some value') - .expect(function (res) { + .expect(function(res) { res.headers['x-some-header'] = ''; }) .expect('X-Some-Header', '') .end(done); }); - it('should call expect(fn) and expect(body) in order', function (done) { + it('should call expect(fn) and expect(body) in order', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.json({ somebody: 'some body value' }); }); request(app) .get('/') .expect(/some body value/) - .expect(function (res) { + .expect(function(res) { res.body.somebody = 'nobody'; }) .expect(/some body value/) // res.text should not be modified. .expect({ somebody: 'nobody' }) - .expect(function (res) { + .expect(function(res) { res.text = 'gone'; }) .expect('gone') @@ -1230,75 +1255,79 @@ describe('assert ordering by call order', function () { }); }); -describe('request.get(url).query(vals) works as expected', function () { - it('normal single query string value works', function (done) { +describe('request.get(url).query(vals) works as expected', function() { + it('normal single query string value works', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(req, res) { res.status(200).send(req.query.val); }); request(app) .get('/') .query({ val: 'Test1' }) - .expect(200, function (err, res) { + .expect(200, function(err, res) { + assert.equal(err, null); res.text.should.be.equal('Test1'); done(); }); }); - it('array query string value works', function (done) { + it('array query string value works', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(req, res) { res.status(200).send(Array.isArray(req.query.val)); }); request(app) .get('/') - .query({ 'val[]': ['Test1', 'Test2'] }) - .expect(200, function (err, res) { + .query({ 'val[]': [ 'Test1', 'Test2' ] }) + .expect(200, function(err, res: any) { + assert.equal(err, null); res.req.path.should.be.equal('/?val%5B%5D=Test1&val%5B%5D=Test2'); res.text.should.be.equal('true'); done(); }); }); - it('array query string value work even with single value', function (done) { + it('array query string value work even with single value', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(req, res) { res.status(200).send(Array.isArray(req.query.val)); }); request(app) .get('/') - .query({ 'val[]': ['Test1'] }) - .expect(200, function (err, res) { + .query({ 'val[]': [ 'Test1' ] }) + .expect(200, function(err, res: any) { + assert.equal(err, null); res.req.path.should.be.equal('/?val%5B%5D=Test1'); res.text.should.be.equal('true'); done(); }); }); - it('object query string value works', function (done) { + it('object query string value works', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(req: any, res) { res.status(200).send(req.query.val.test); }); request(app) .get('/') .query({ val: { test: 'Test1' } }) - .expect(200, function (err, res) { + .expect(200, function(err, res) { + assert.equal(err, null); res.text.should.be.equal('Test1'); done(); }); }); - it('handles unknown errors (err without res)', function (done) { + it('handles unknown errors (err without res)', function(done) { const app = express(); nock.disableNetConnect(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.status(200).send('OK'); }); @@ -1308,12 +1337,12 @@ describe('request.get(url).query(vals) works as expected', function () { // errors being obscured by the response assertions // https://github.com/ladjs/supertest/issues/352 .expect(200) - .end(function (err, res) { + .end(function(err, res) { should.exist(err); should.not.exist(res); - err.should.be.an.instanceof(Error); - err.message.should.match(/Nock: Disallowed net connect/); - shouldIncludeStackWithThisFile(err); + err!.should.be.an.instanceof(Error); + err!.message.should.match(/Nock: Disallowed net connect/); + shouldIncludeStackWithThisFile(err!); done(); }); @@ -1324,103 +1353,44 @@ describe('request.get(url).query(vals) works as expected', function () { // there shouldn't be any res if there is an err // meant for test coverage for lib/test.js#169 // https://github.com/ladjs/supertest/blob/5543d674cf9aa4547927ba6010d31d9474950dec/lib/test.js#L169 - it('handles unknown errors (err with res)', function (done) { + it('handles unknown errors (err with res)', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.status(200).send('OK'); }); const resError = new Error(); - resError.status = 400; + (resError as any).status = 400; const serverRes = { status: 200 }; request(app) .get('/') // private api - .assert(resError, serverRes, function (err, res) { + .assert(resError, serverRes as any, function(this: Test, err, res) { should.exist(err); should.exist(res); - err.should.equal(resError); + err!.should.equal(resError); res.should.equal(serverRes); // close the server explicitly (as we are not using expect/end/then) this.end(done); }); }); - it('should assert using promises', function (done) { + it('should assert using promises', function(done) { const app = express(); - app.get('/', function (req, res) { + app.get('/', function(_req, res) { res.status(400).send({ promise: true }); }); request(app) .get('/') .expect(400) - .then((res) => { + .then(res => { res.body.promise.should.be.equal(true); done(); }); }); }); - -const describeHttp2 = (http2) ? describe : describe.skip; -describeHttp2('http2', function() { - // eslint-disable-next-line global-require - const proxyquire = require('proxyquire'); - - const tests = [ - { - title: 'request(app)', - api: request, - mockApi: proxyquire('../index.js', { http2: null }) - }, - { - title: 'request.agent(app)', - api: request.agent, - mockApi: proxyquire('../lib/agent.js', { http2: null }) - } - ]; - - tests.forEach(({ title, api, mockApi }) => { - describe(title, function () { - const app = function(req, res) { - res.end('hey'); - }; - - it('should fire up the app on an ephemeral port', function (done) { - api(app, { http2: true }) - .get('/') - .end(function (err, res) { - res.status.should.equal(200); - res.text.should.equal('hey'); - done(); - }); - }); - - it('should work with an active server', function (done) { - const server = http2.createServer(app); - - server.listen(function () { - api(server) - .get('/') - .http2() - .end(function (err, res) { - res.status.should.equal(200); - res.text.should.equal('hey'); - // close the external server explictly - server.close(done); - }); - }); - }); - - it('should throw error if http2 is not supported', function() { - (function() { - mockApi(app, { http2: true }); - }).should.throw('supertest: this version of Node.js does not support http2'); - }); - }); - }); -}); diff --git a/test/throwError.js b/test/throwError.ts similarity index 71% rename from test/throwError.js rename to test/throwError.ts index ce7ee526..b39d34d2 100644 --- a/test/throwError.js +++ b/test/throwError.ts @@ -1,10 +1,8 @@ -'use strict'; - /** * This method needs to reside in its own module in order to properly test stack trace handling. */ -module.exports = function throwError(message) { +export function throwError(message: string) { return function() { throw new Error(message); }; -}; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..ff41b734 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@eggjs/tsconfig", + "compilerOptions": { + "strict": true, + "noImplicitAny": true, + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext" + } +}