From dd420ba7c9c2bdccd3af770ca349392d5d2d9578 Mon Sep 17 00:00:00 2001 From: seven Date: Sun, 22 Dec 2024 00:40:34 +0800 Subject: [PATCH 1/5] feat: setup test for koa application Signed-off-by: seven --- package-lock.json | 632 ++++++++++++++++++ package.json | 4 + src/index.ts | 3 +- src/sendRequest.ts | 13 +- src/types.ts | 3 +- .../{index.test.ts => index-express.test.ts} | 0 tests/index-koa.test.ts | 176 +++++ 7 files changed, 827 insertions(+), 4 deletions(-) rename tests/{index.test.ts => index-express.test.ts} (100%) create mode 100644 tests/index-koa.test.ts diff --git a/package-lock.json b/package-lock.json index 083e579..860ccc8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,9 +14,12 @@ }, "devDependencies": { "@eslint/js": "^9.12.0", + "@koa/router": "^13.1.0", "@types/debug": "^4.1.12", "@types/express": "^5.0.0", "@types/jest": "^29.5.13", + "@types/koa": "^2.15.0", + "@types/koa__router": "^12.0.4", "@types/node": "^22.7.4", "@typescript-eslint/eslint-plugin": "^8.8.0", "@typescript-eslint/parser": "^8.8.0", @@ -28,6 +31,7 @@ "globals": "^15.10.0", "husky": "^9.1.6", "jest": "^29.7.0", + "koa": "^2.15.3", "prettier": "^3.3.3", "ts-jest": "^29.2.5", "ts-node": "^10.9.2", @@ -1385,6 +1389,26 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@koa/router": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@koa/router/-/router-13.1.0.tgz", + "integrity": "sha512-mNVu1nvkpSd8Q8gMebGbCkDWJ51ODetrFvLKYusej+V0ByD4btqHYnPIzTBLXnQMVUlm/oxVwqmWBY3zQfZilw==", + "dev": true, + "dependencies": { + "http-errors": "^2.0.0", + "koa-compose": "^4.1.0", + "path-to-regexp": "^6.3.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@koa/router/node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1480,6 +1504,15 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, + "node_modules/@types/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1540,6 +1573,24 @@ "@types/node": "*" } }, + "node_modules/@types/content-disposition": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.8.tgz", + "integrity": "sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==", + "dev": true + }, + "node_modules/@types/cookies": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.0.tgz", + "integrity": "sha512-40Zk8qR147RABiQ7NQnBzWzDcjKzNrntB5BAmeGCb2p/MIyOE+4BVvc17wumsUqUw00bJYqoXFHYygQnEFh4/Q==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/express": "*", + "@types/keygrip": "*", + "@types/node": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -1582,6 +1633,12 @@ "@types/node": "*" } }, + "node_modules/@types/http-assert": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.6.tgz", + "integrity": "sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==", + "dev": true + }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -1622,6 +1679,46 @@ "pretty-format": "^29.0.0" } }, + "node_modules/@types/keygrip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz", + "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==", + "dev": true + }, + "node_modules/@types/koa": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.15.0.tgz", + "integrity": "sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==", + "dev": true, + "dependencies": { + "@types/accepts": "*", + "@types/content-disposition": "*", + "@types/cookies": "*", + "@types/http-assert": "*", + "@types/http-errors": "*", + "@types/keygrip": "*", + "@types/koa-compose": "*", + "@types/node": "*" + } + }, + "node_modules/@types/koa__router": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/@types/koa__router/-/koa__router-12.0.4.tgz", + "integrity": "sha512-Y7YBbSmfXZpa/m5UGGzb7XadJIRBRnwNY9cdAojZGp65Cpe5MAP3mOZE7e3bImt8dfKS4UFcR16SLH8L/z7PBw==", + "dev": true, + "dependencies": { + "@types/koa": "*" + } + }, + "node_modules/@types/koa-compose": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.8.tgz", + "integrity": "sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==", + "dev": true, + "dependencies": { + "@types/koa": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -2333,6 +2430,19 @@ "node": ">= 0.8" } }, + "node_modules/cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "dev": true, + "dependencies": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -2516,6 +2626,19 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "dev": true }, + "node_modules/cookies": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", + "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==", + "dev": true, + "dependencies": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -2603,6 +2726,12 @@ } } }, + "node_modules/deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==", + "dev": true + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2635,6 +2764,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -3592,6 +3727,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", @@ -3610,6 +3760,53 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/http-assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", + "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", + "dev": true, + "dependencies": { + "deep-equal": "~1.0.1", + "http-errors": "~1.8.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-assert/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-assert/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-assert/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -3782,6 +3979,21 @@ "node": ">=6" } }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -4769,6 +4981,18 @@ "node": ">=6" } }, + "node_modules/keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "dev": true, + "dependencies": { + "tsscmp": "1.0.6" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4787,6 +5011,102 @@ "node": ">=6" } }, + "node_modules/koa": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.15.3.tgz", + "integrity": "sha512-j/8tY9j5t+GVMLeioLaxweJiKUayFhlGqNTzf2ZGwL0ZCQijd2RLHK0SLW5Tsko8YyyqCZC2cojIb0/s62qTAg==", + "dev": true, + "dependencies": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.9.0", + "debug": "^4.3.2", + "delegates": "^1.0.0", + "depd": "^2.0.0", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^2.0.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + }, + "engines": { + "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" + } + }, + "node_modules/koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", + "dev": true + }, + "node_modules/koa-convert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-2.0.0.tgz", + "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==", + "dev": true, + "dependencies": { + "co": "^4.6.0", + "koa-compose": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/koa/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/koa/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/http-errors/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -5082,6 +5402,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==", + "dev": true + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -6107,6 +6433,15 @@ "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "dev": true, + "engines": { + "node": ">=0.6.x" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -6376,6 +6711,15 @@ "node": ">=12" } }, + "node_modules/ylru": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.4.0.tgz", + "integrity": "sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -7436,6 +7780,25 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "@koa/router": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@koa/router/-/router-13.1.0.tgz", + "integrity": "sha512-mNVu1nvkpSd8Q8gMebGbCkDWJ51ODetrFvLKYusej+V0ByD4btqHYnPIzTBLXnQMVUlm/oxVwqmWBY3zQfZilw==", + "dev": true, + "requires": { + "http-errors": "^2.0.0", + "koa-compose": "^4.1.0", + "path-to-regexp": "^6.3.0" + }, + "dependencies": { + "path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true + } + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -7516,6 +7879,15 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, + "@types/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -7576,6 +7948,24 @@ "@types/node": "*" } }, + "@types/content-disposition": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.8.tgz", + "integrity": "sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==", + "dev": true + }, + "@types/cookies": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.0.tgz", + "integrity": "sha512-40Zk8qR147RABiQ7NQnBzWzDcjKzNrntB5BAmeGCb2p/MIyOE+4BVvc17wumsUqUw00bJYqoXFHYygQnEFh4/Q==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/express": "*", + "@types/keygrip": "*", + "@types/node": "*" + } + }, "@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -7618,6 +8008,12 @@ "@types/node": "*" } }, + "@types/http-assert": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.6.tgz", + "integrity": "sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==", + "dev": true + }, "@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -7658,6 +8054,46 @@ "pretty-format": "^29.0.0" } }, + "@types/keygrip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz", + "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==", + "dev": true + }, + "@types/koa": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.15.0.tgz", + "integrity": "sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==", + "dev": true, + "requires": { + "@types/accepts": "*", + "@types/content-disposition": "*", + "@types/cookies": "*", + "@types/http-assert": "*", + "@types/http-errors": "*", + "@types/keygrip": "*", + "@types/koa-compose": "*", + "@types/node": "*" + } + }, + "@types/koa__router": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/@types/koa__router/-/koa__router-12.0.4.tgz", + "integrity": "sha512-Y7YBbSmfXZpa/m5UGGzb7XadJIRBRnwNY9cdAojZGp65Cpe5MAP3mOZE7e3bImt8dfKS4UFcR16SLH8L/z7PBw==", + "dev": true, + "requires": { + "@types/koa": "*" + } + }, + "@types/koa-compose": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.8.tgz", + "integrity": "sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==", + "dev": true, + "requires": { + "@types/koa": "*" + } + }, "@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -8177,6 +8613,16 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, + "cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "dev": true, + "requires": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + } + }, "call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -8303,6 +8749,16 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "dev": true }, + "cookies": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", + "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==", + "dev": true, + "requires": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + } + }, "create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -8362,6 +8818,12 @@ "dev": true, "requires": {} }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==", + "dev": true + }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -8385,6 +8847,12 @@ "gopd": "^1.0.1" } }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -9090,6 +9558,15 @@ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "dev": true }, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.3" + } + }, "hasown": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", @@ -9105,6 +9582,43 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "http-assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", + "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", + "dev": true, + "requires": { + "deep-equal": "~1.0.1", + "http-errors": "~1.8.0" + }, + "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true + }, + "http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true + } + } + }, "http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -9223,6 +9737,15 @@ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true }, + "is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -9968,6 +10491,15 @@ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, + "keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "dev": true, + "requires": { + "tsscmp": "1.0.6" + } + }, "keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -9983,6 +10515,88 @@ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true }, + "koa": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.15.3.tgz", + "integrity": "sha512-j/8tY9j5t+GVMLeioLaxweJiKUayFhlGqNTzf2ZGwL0ZCQijd2RLHK0SLW5Tsko8YyyqCZC2cojIb0/s62qTAg==", + "dev": true, + "requires": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.9.0", + "debug": "^4.3.2", + "delegates": "^1.0.0", + "depd": "^2.0.0", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^2.0.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + }, + "dependencies": { + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true + }, + "http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true + } + } + }, + "koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", + "dev": true + }, + "koa-convert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-2.0.0.tgz", + "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==", + "dev": true, + "requires": { + "co": "^4.6.0", + "koa-compose": "^4.1.0" + } + }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -10203,6 +10817,12 @@ "mimic-fn": "^2.1.0" } }, + "only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==", + "dev": true + }, "optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -10894,6 +11514,12 @@ "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, + "tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "dev": true + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -11073,6 +11699,12 @@ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true }, + "ylru": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.4.0.tgz", + "integrity": "sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==", + "dev": true + }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 9b5822d..996cd21 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,9 @@ "@eslint/js": "^9.12.0", "@types/debug": "^4.1.12", "@types/express": "^5.0.0", + "@types/koa__router": "^12.0.4", "@types/jest": "^29.5.13", + "@types/koa": "^2.15.0", "@types/node": "^22.7.4", "@typescript-eslint/eslint-plugin": "^8.8.0", "@typescript-eslint/parser": "^8.8.0", @@ -65,6 +67,8 @@ "globals": "^15.10.0", "husky": "^9.1.6", "jest": "^29.7.0", + "koa": "^2.15.3", + "@koa/router": "^13.1.0", "prettier": "^3.3.3", "ts-jest": "^29.2.5", "ts-node": "^10.9.2", diff --git a/src/index.ts b/src/index.ts index 94143d0..3754f18 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,12 @@ import { Express } from 'express'; +import Application from 'koa'; import { ServerlessAdapter } from './types'; import sendRequest from './sendRequest'; import { IncomingHttpHeaders } from 'http'; import { constructFrameworkContext } from './context'; import { buildResponse, waitForStreamComplete } from './transport'; -const serverlessAdapter: ServerlessAdapter = (app: Express) => { +const serverlessAdapter: ServerlessAdapter = (app: Express | Application) => { return async (event, context) => { const { request, response } = constructFrameworkContext(event, context); diff --git a/src/sendRequest.ts b/src/sendRequest.ts index b2c09f1..76e89f4 100644 --- a/src/sendRequest.ts +++ b/src/sendRequest.ts @@ -1,8 +1,17 @@ import { Express } from 'express'; +import Application from 'koa'; import { IncomingMessage, ServerResponse } from 'node:http'; -const sendRequest = async (app: Express, request: IncomingMessage, response: ServerResponse) => { - app(request, response); +const sendRequest = async ( + app: Express | Application, + request: IncomingMessage, + response: ServerResponse, +) => { + if (app instanceof Application) { + app.createContext(request, response); + } else { + app(request, response); + } }; export default sendRequest; diff --git a/src/types.ts b/src/types.ts index 646553e..6ad618e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,5 @@ import { Express } from 'express'; +import Application from 'koa'; import { IncomingHttpHeaders } from 'http'; type AliyunApiGatewayEvent = { @@ -52,7 +53,7 @@ type AliyunApiGatewayContext = { export type Event = AliyunApiGatewayEvent; export type Context = AliyunApiGatewayContext; -export type ServerlessAdapter = (app: Express) => ( +export type ServerlessAdapter = (app: Express | Application) => ( event: Event, context: Context, ) => Promise<{ diff --git a/tests/index.test.ts b/tests/index-express.test.ts similarity index 100% rename from tests/index.test.ts rename to tests/index-express.test.ts diff --git a/tests/index-koa.test.ts b/tests/index-koa.test.ts new file mode 100644 index 0000000..dd319ee --- /dev/null +++ b/tests/index-koa.test.ts @@ -0,0 +1,176 @@ +import Koa from 'koa'; +import Router from '@koa/router'; +import serverlessAdapter from '../src'; +import { defaultContext, defaultEvent } from './fixtures/fcContext'; + +describe('koa', () => { + let app: Koa; + let router: Router; + beforeEach(() => { + app = new Koa(); + router = new Router(); + }); + + it('basic middleware should set statusCode and default body', async () => { + router.get('/api/test', (ctx) => { + ctx.status = 200; + ctx.body = 'Hello, world!'; + }); + app.use(router.routes()); + + const response = await serverlessAdapter(app)(defaultEvent, defaultContext); + + expect(response.statusCode).toEqual(418); + expect(response.body).toEqual(`I'm a teapot`); + }); + + it('basic middleware should get text body', async () => { + router.get('/api/test', (ctx) => { + ctx.status = 200; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + ctx.body = ctx.request.body; + }); + app.use(router.routes()); + + const response = await serverlessAdapter(app)( + { + ...defaultEvent, + httpMethod: 'GET', + body: 'hello, world', + headers: { + 'Content-Type': 'text/plain', + 'Content-Length': '12', + }, + }, + defaultContext, + ); + + expect(response.statusCode).toEqual(200); + expect(response.body).toEqual('hello, world'); + }); + + it('basic middleware should get json body', async () => { + router.get('/api/test', (ctx) => { + ctx.status = 200; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + ctx.body = ctx.request.body.hello; + }); + + const response = await serverlessAdapter(app)( + { + ...defaultEvent, + httpMethod: 'GET', + body: JSON.stringify({ + hello: 'world', + }), + headers: { + 'Content-Type': 'application/json', + }, + }, + defaultContext, + ); + + expect(response.statusCode).toEqual(200); + expect(response.body).toEqual('world'); + }); + + it('basic middleware should get undefined body', async () => { + router.get('/api/test', (ctx) => { + ctx.status = 200; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + ctx.body = ctx.request.body.hello; + }); + + const response = await serverlessAdapter(app)( + { + ...defaultEvent, + httpMethod: 'GET', + body: undefined, + headers: { + 'Content-Type': 'application/json', + }, + }, + defaultContext, + ); + + expect(response.statusCode).toEqual(200); + expect(response.body).toBeDefined(); + }); + + it('basic middleware should get query params', async () => { + router.get('/api/test', (ctx) => { + ctx.status = 200; + ctx.body = ctx.request.query.foo; + }); + + const response = await serverlessAdapter(app)( + { + ...defaultEvent, + httpMethod: 'GET', + path: '/', + queryParameters: { + foo: 'bar', + }, + }, + defaultContext, + ); + expect(response.statusCode).toEqual(200); + expect(response.body).toEqual('bar'); + }); + + it('should match verbs', async () => { + router.get('/*', (ctx) => { + ctx.status = 200; + ctx.body = 'foo'; + }); + router.put('/*', (ctx) => { + ctx.status = 201; + ctx.body = 'bar'; + }); + app.use(router.routes()); + + const response = await serverlessAdapter(app)( + { ...defaultEvent, httpMethod: 'PUT' }, + defaultContext, + ); + + expect(response.statusCode).toEqual(201); + expect(response.body).toEqual('bar'); + }); + + // it('should serve files', async () => { + // app.use(express.static('tests/fixtures')); + // + // const response = await serverlessAdapter(app)( + // { + // ...defaultEvent, + // httpMethod: 'GET', + // path: '/file.txt', + // }, + // defaultContext, + // ); + // expect(response.statusCode).toEqual(200); + // expect(response.body).toEqual('this is a test\n'); + // }); + // + // it('destroy weird', async () => { + // app.use((req: Request, res: Response) => { + // // this was causing a .destroy is not a function error + // res.send('test'); + // res.json({ test: 'test' }); + // }); + // + // const response = await serverlessAdapter(app)( + // { + // ...defaultEvent, + // httpMethod: 'GET', + // }, + // defaultContext, + // ); + // expect(response.statusCode).toEqual(200); + // expect(response.body).toEqual('test'); + // }); +}); From 9d2afb8fdec9b5b64420753bb6c2a3fe9fb02682 Mon Sep 17 00:00:00 2001 From: seven Date: Sun, 22 Dec 2024 03:02:13 +0800 Subject: [PATCH 2/5] fix: koa tests [wip] Signed-off-by: seven --- package-lock.json | 262 +++++++++++++++++++++++++++++++++++++++ package.json | 5 +- src/context.ts | 5 +- src/framework.ts | 25 ++++ src/index.ts | 9 +- src/serverlessRequest.ts | 28 ++--- src/transport.ts | 8 +- tests/index-koa.test.ts | 21 ++-- 8 files changed, 318 insertions(+), 45 deletions(-) create mode 100644 src/framework.ts diff --git a/package-lock.json b/package-lock.json index 860ccc8..52eae40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "husky": "^9.1.6", "jest": "^29.7.0", "koa": "^2.15.3", + "koa-body": "^6.0.1", "prettier": "^3.3.3", "ts-jest": "^29.2.5", "ts-node": "^10.9.2", @@ -840,6 +841,12 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@hapi/bourne": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz", + "integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==", + "dev": true + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -1564,6 +1571,16 @@ "@types/node": "*" } }, + "node_modules/@types/co-body": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/co-body/-/co-body-6.1.3.tgz", + "integrity": "sha512-UhuhrQ5hclX6UJctv5m4Rfp52AfG9o9+d9/HwjxhVB5NjXxr5t9oKgJxN8xRHgr35oo8meUEHUPFWiKg6y71aA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -1624,6 +1641,15 @@ "@types/send": "*" } }, + "node_modules/@types/formidable": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-2.0.6.tgz", + "integrity": "sha512-L4HcrA05IgQyNYJj6kItuIkXrInJvsXTPC5B1i64FggWKKqSL+4hgt7asiSNva75AoLQjq29oPxFfU4GAQ6Z2w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -2131,6 +2157,12 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -2554,6 +2586,49 @@ "node": ">= 0.12.0" } }, + "node_modules/co-body": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/co-body/-/co-body-6.2.0.tgz", + "integrity": "sha512-Kbpv2Yd1NdL1V/V4cwLVxraHDV6K8ayohr2rmH0J87Er8+zJjcTa6dAn9QMPC9CRgU8+aNajKbSf1TzDB1yKPA==", + "dev": true, + "dependencies": { + "@hapi/bourne": "^3.0.0", + "inflation": "^2.0.0", + "qs": "^6.5.2", + "raw-body": "^2.3.3", + "type-is": "^1.6.16" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/co-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/co-body/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/collect-v8-coverage": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", @@ -2797,6 +2872,16 @@ "node": ">=8" } }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -3509,6 +3594,21 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/formidable": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", + "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", + "dev": true, + "dependencies": { + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -3754,6 +3854,15 @@ "node": ">= 0.4" } }, + "node_modules/hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -3910,6 +4019,15 @@ "node": ">=0.8.19" } }, + "node_modules/inflation": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.1.0.tgz", + "integrity": "sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -5045,6 +5163,20 @@ "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" } }, + "node_modules/koa-body": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/koa-body/-/koa-body-6.0.1.tgz", + "integrity": "sha512-M8ZvMD8r+kPHy28aWP9VxL7kY8oPWA+C7ZgCljrCMeaU7uX6wsIQgDHskyrAr9sw+jqnIXyv4Mlxri5R4InIJg==", + "dev": true, + "dependencies": { + "@types/co-body": "^6.1.0", + "@types/formidable": "^2.0.5", + "@types/koa": "^2.13.5", + "co-body": "^6.1.0", + "formidable": "^2.0.1", + "zod": "^3.19.1" + } + }, "node_modules/koa-compose": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", @@ -6740,6 +6872,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } }, "dependencies": { @@ -7355,6 +7496,12 @@ "integrity": "sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==", "dev": true }, + "@hapi/bourne": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz", + "integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==", + "dev": true + }, "@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -7939,6 +8086,16 @@ "@types/node": "*" } }, + "@types/co-body": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/co-body/-/co-body-6.1.3.tgz", + "integrity": "sha512-UhuhrQ5hclX6UJctv5m4Rfp52AfG9o9+d9/HwjxhVB5NjXxr5t9oKgJxN8xRHgr35oo8meUEHUPFWiKg6y71aA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*" + } + }, "@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -7999,6 +8156,15 @@ "@types/send": "*" } }, + "@types/formidable": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-2.0.6.tgz", + "integrity": "sha512-L4HcrA05IgQyNYJj6kItuIkXrInJvsXTPC5B1i64FggWKKqSL+4hgt7asiSNva75AoLQjq29oPxFfU4GAQ6Z2w==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -8380,6 +8546,12 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, "async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -8689,6 +8861,42 @@ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true }, + "co-body": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/co-body/-/co-body-6.2.0.tgz", + "integrity": "sha512-Kbpv2Yd1NdL1V/V4cwLVxraHDV6K8ayohr2rmH0J87Er8+zJjcTa6dAn9QMPC9CRgU8+aNajKbSf1TzDB1yKPA==", + "dev": true, + "requires": { + "@hapi/bourne": "^3.0.0", + "inflation": "^2.0.0", + "qs": "^6.5.2", + "raw-body": "^2.3.3", + "type-is": "^1.6.16" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + } + } + }, "collect-v8-coverage": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", @@ -8870,6 +9078,16 @@ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true }, + "dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -9413,6 +9631,18 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "formidable": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", + "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", + "dev": true, + "requires": { + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -9576,6 +9806,12 @@ "function-bind": "^1.1.2" } }, + "hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "dev": true + }, "html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -9683,6 +9919,12 @@ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true }, + "inflation": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.1.0.tgz", + "integrity": "sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -10581,6 +10823,20 @@ } } }, + "koa-body": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/koa-body/-/koa-body-6.0.1.tgz", + "integrity": "sha512-M8ZvMD8r+kPHy28aWP9VxL7kY8oPWA+C7ZgCljrCMeaU7uX6wsIQgDHskyrAr9sw+jqnIXyv4Mlxri5R4InIJg==", + "dev": true, + "requires": { + "@types/co-body": "^6.1.0", + "@types/formidable": "^2.0.5", + "@types/koa": "^2.13.5", + "co-body": "^6.1.0", + "formidable": "^2.0.1", + "zod": "^3.19.1" + } + }, "koa-compose": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", @@ -11716,6 +11972,12 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true + }, + "zod": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "dev": true } } } diff --git a/package.json b/package.json index 996cd21..a543c73 100644 --- a/package.json +++ b/package.json @@ -51,11 +51,12 @@ }, "devDependencies": { "@eslint/js": "^9.12.0", + "@koa/router": "^13.1.0", "@types/debug": "^4.1.12", "@types/express": "^5.0.0", - "@types/koa__router": "^12.0.4", "@types/jest": "^29.5.13", "@types/koa": "^2.15.0", + "@types/koa__router": "^12.0.4", "@types/node": "^22.7.4", "@typescript-eslint/eslint-plugin": "^8.8.0", "@typescript-eslint/parser": "^8.8.0", @@ -68,7 +69,7 @@ "husky": "^9.1.6", "jest": "^29.7.0", "koa": "^2.15.3", - "@koa/router": "^13.1.0", + "koa-body": "^6.0.1", "prettier": "^3.3.3", "ts-jest": "^29.2.5", "ts-node": "^10.9.2", diff --git a/src/context.ts b/src/context.ts index f095b39..4fb50a4 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,7 +1,6 @@ import { Context, Event } from './types'; import ServerlessRequest from './serverlessRequest'; import url from 'node:url'; -import ServerlessResponse from './serverlessResponse'; import { debug } from './common'; // const requestRemoteAddress = (event) => { @@ -16,6 +15,7 @@ export const constructFrameworkContext = (event: Event, context: Context) => { const request = new ServerlessRequest({ method: event.httpMethod, headers: event.headers, + path: event.path, body: event.body !== undefined && event.body !== null ? Buffer.from(event.body, event.isBase64Encoded ? 'base64' : 'utf8') @@ -27,7 +27,6 @@ export const constructFrameworkContext = (event: Event, context: Context) => { }), isBase64Encoded: event.isBase64Encoded, }); - const response = new ServerlessResponse(request); - return { request, response }; + return { request }; }; diff --git a/src/framework.ts b/src/framework.ts new file mode 100644 index 0000000..2fbbbb4 --- /dev/null +++ b/src/framework.ts @@ -0,0 +1,25 @@ +import { Express } from 'express'; +import Application from 'koa'; +import ServerlessResponse from './serverlessResponse'; +import ServerlessRequest from './serverlessRequest'; + +// eslint-disable-next-line +const callableFn = (callback: (req: any, res: any) => Promise) => { + return async (request: ServerlessRequest) => { + const response = new ServerlessResponse(request); + + callback(request, response); + + return response; + }; +}; + +export const constructFramework = (app: Express | Application) => { + if (app instanceof Application) { + return callableFn(app.callback()); + } else if (typeof app === 'function') { + return callableFn(app); + } else { + throw new Error(`Unsupported framework ${app}`); + } +}; diff --git a/src/index.ts b/src/index.ts index 3754f18..8f7faee 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,19 +1,18 @@ import { Express } from 'express'; import Application from 'koa'; import { ServerlessAdapter } from './types'; -import sendRequest from './sendRequest'; import { IncomingHttpHeaders } from 'http'; import { constructFrameworkContext } from './context'; import { buildResponse, waitForStreamComplete } from './transport'; +import { constructFramework } from './framework'; const serverlessAdapter: ServerlessAdapter = (app: Express | Application) => { + const serverlessFramework = constructFramework(app); return async (event, context) => { - const { request, response } = constructFrameworkContext(event, context); + const { request } = constructFrameworkContext(event, context); try { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - await sendRequest(app, request, response); + const response = await serverlessFramework(request); await waitForStreamComplete(response); return buildResponse({ request, response }); } catch (err) { diff --git a/src/serverlessRequest.ts b/src/serverlessRequest.ts index fd3f7a9..bb49b16 100644 --- a/src/serverlessRequest.ts +++ b/src/serverlessRequest.ts @@ -6,6 +6,7 @@ const HTTPS_PORT = 443; interface ServerlessRequestOptions { method: string; url: string; + path: string; headers: { [key: string]: string | number }; body: Buffer | string | undefined; remoteAddress: string; @@ -21,46 +22,39 @@ export default class ServerlessRequest extends IncomingMessage { isBase64Encoded: boolean; - constructor({ - method, - url, - headers, - body, - remoteAddress, - isBase64Encoded, - }: ServerlessRequestOptions) { + constructor(request: ServerlessRequestOptions) { super({ encrypted: true, readable: false, - remoteAddress, + remoteAddress: request.remoteAddress, address: () => ({ port: HTTPS_PORT }), end: NO_OP, destroy: NO_OP, + path: request.path, } as unknown as Socket); const combinedHeaders = Object.fromEntries( Object.entries({ - ...headers, - 'content-length': Buffer.byteLength(body ?? '').toString(), + ...request.headers, + 'content-length': Buffer.byteLength(request.body ?? '').toString(), }).map(([key, value]) => [key.toLowerCase(), value]), ); Object.assign(this, { + ...request, complete: true, httpVersion: '1.1', httpVersionMajor: '1', httpVersionMinor: '1', - method, - url, headers: combinedHeaders, }); - this.body = body; - this.ip = remoteAddress; - this.isBase64Encoded = isBase64Encoded; + this.body = request.body; + this.ip = request.remoteAddress; + this.isBase64Encoded = request.isBase64Encoded; this._read = () => { - this.push(body); + this.push(request.body); this.push(null); }; } diff --git a/src/transport.ts b/src/transport.ts index e08148d..83d1d46 100644 --- a/src/transport.ts +++ b/src/transport.ts @@ -8,10 +8,6 @@ export const waitForStreamComplete = (stream: Writable): Promise => { } return new Promise((resolve, reject) => { - stream.once('error', complete); - stream.once('end', complete); - stream.once('finish', complete); - let isComplete = false; function complete(err?: Error) { @@ -31,6 +27,10 @@ export const waitForStreamComplete = (stream: Writable): Promise => { resolve(stream); } } + + stream.once('error', complete); + stream.once('end', complete); + stream.once('finish', complete); }); }; diff --git a/tests/index-koa.test.ts b/tests/index-koa.test.ts index dd319ee..0dc8748 100644 --- a/tests/index-koa.test.ts +++ b/tests/index-koa.test.ts @@ -1,5 +1,6 @@ import Koa from 'koa'; import Router from '@koa/router'; +import koaBody from 'koa-body'; import serverlessAdapter from '../src'; import { defaultContext, defaultEvent } from './fixtures/fcContext'; @@ -8,27 +9,26 @@ describe('koa', () => { let router: Router; beforeEach(() => { app = new Koa(); + app.use(koaBody()); router = new Router(); }); it('basic middleware should set statusCode and default body', async () => { router.get('/api/test', (ctx) => { - ctx.status = 200; - ctx.body = 'Hello, world!'; + ctx.status = 418; + ctx.body = 'Hello, world koa!'; }); app.use(router.routes()); const response = await serverlessAdapter(app)(defaultEvent, defaultContext); expect(response.statusCode).toEqual(418); - expect(response.body).toEqual(`I'm a teapot`); + expect(response.body).toEqual('Hello, world koa!'); }); it('basic middleware should get text body', async () => { router.get('/api/test', (ctx) => { ctx.status = 200; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error ctx.body = ctx.request.body; }); app.use(router.routes()); @@ -53,8 +53,6 @@ describe('koa', () => { it('basic middleware should get json body', async () => { router.get('/api/test', (ctx) => { ctx.status = 200; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error ctx.body = ctx.request.body.hello; }); @@ -62,12 +60,8 @@ describe('koa', () => { { ...defaultEvent, httpMethod: 'GET', - body: JSON.stringify({ - hello: 'world', - }), - headers: { - 'Content-Type': 'application/json', - }, + body: JSON.stringify({ hello: 'world' }), + headers: { 'Content-Type': 'application/json' }, }, defaultContext, ); @@ -80,7 +74,6 @@ describe('koa', () => { router.get('/api/test', (ctx) => { ctx.status = 200; // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error ctx.body = ctx.request.body.hello; }); From 5f39279a68164000416fc28b4b537676892ec0ae Mon Sep 17 00:00:00 2001 From: seven Date: Sun, 22 Dec 2024 17:38:23 +0800 Subject: [PATCH 3/5] fix: koa tests - sanitizeHeaders Signed-off-by: seven --- src/context.ts | 22 ++++++++++++++---- src/serverlessResponse.ts | 9 ++----- src/transport.ts | 49 +++++++++++++++++++++++++++++++++++++-- tests/index-koa.test.ts | 6 +++-- 4 files changed, 71 insertions(+), 15 deletions(-) diff --git a/src/context.ts b/src/context.ts index 4fb50a4..12bc9be 100644 --- a/src/context.ts +++ b/src/context.ts @@ -10,16 +10,30 @@ import { debug } from './common'; // return event.requestContext.identity.sourceIp; // }; +const requestBody = (event: Event) => { + if (event.body === undefined || event.body === null) { + return undefined; + } + const type = typeof event.body; + + if (Buffer.isBuffer(event.body)) { + return event.body; + } else if (type === 'string') { + return Buffer.from(event.body as string, event.isBase64Encoded ? 'base64' : 'utf8'); + } else if (type === 'object') { + return Buffer.from(JSON.stringify(event.body)); + } + + throw new Error(`Unexpected event.body type: ${typeof event.body}`); +}; + export const constructFrameworkContext = (event: Event, context: Context) => { debug(`constructFrameworkContext: ${JSON.stringify({ event, context })}`); const request = new ServerlessRequest({ method: event.httpMethod, headers: event.headers, path: event.path, - body: - event.body !== undefined && event.body !== null - ? Buffer.from(event.body, event.isBase64Encoded ? 'base64' : 'utf8') - : undefined, + body: requestBody(event), remoteAddress: '', url: url.format({ pathname: event.path, diff --git a/src/serverlessResponse.ts b/src/serverlessResponse.ts index 38b1b29..00f3383 100644 --- a/src/serverlessResponse.ts +++ b/src/serverlessResponse.ts @@ -1,8 +1,7 @@ import { IncomingHttpHeaders, ServerResponse } from 'http'; -import { IncomingMessage } from 'node:http'; -import ServerlessRequest from './serverlessRequest'; import { Socket } from 'node:net'; import { debug } from './common'; +import ServerlessRequest from './serverlessRequest'; const headerEnd = '\r\n\r\n'; @@ -37,12 +36,8 @@ export default class ServerlessResponse extends ServerResponse { [BODY]: Buffer[]; [HEADERS]: IncomingHttpHeaders; - static from(res: IncomingMessage): ServerlessResponse { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error + static from(res: ServerlessRequest): ServerlessResponse { const response = new ServerlessResponse(res); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error const { statusCode = 0, headers, body } = res; response.statusCode = statusCode; response[HEADERS] = headers; diff --git a/src/transport.ts b/src/transport.ts index 83d1d46..5347f42 100644 --- a/src/transport.ts +++ b/src/transport.ts @@ -1,7 +1,11 @@ import { Writable } from 'stream'; +import { IncomingHttpHeaders } from 'http'; import ServerlessRequest from './serverlessRequest'; import ServerlessResponse from './serverlessResponse'; +type MultiValueHeaders = { + [key: string]: string[]; +}; export const waitForStreamComplete = (stream: Writable): Promise => { if (stream.writableFinished || stream.writableEnded) { return Promise.resolve(stream); @@ -34,6 +38,29 @@ export const waitForStreamComplete = (stream: Writable): Promise => { }); }; +const sanitizeHeaders = (headers: IncomingHttpHeaders) => { + return Object.keys(headers).reduce( + (memo, key) => { + const value = headers[key]; + + if (Array.isArray(value)) { + memo.multiValueHeaders[key] = value; + if (key.toLowerCase() !== 'set-cookie') { + memo.headers[key] = value.join(', '); + } + } else { + memo.headers[key] = value == null ? '' : value.toString(); + } + + return memo; + }, + { + headers: {} as IncomingHttpHeaders, + multiValueHeaders: {} as MultiValueHeaders, + }, + ); +}; + export const buildResponse = ({ request, response, @@ -41,10 +68,28 @@ export const buildResponse = ({ request: ServerlessRequest; response: ServerlessResponse; }) => { + const { headers, multiValueHeaders } = sanitizeHeaders(ServerlessResponse.headers(response)); + + let body = ServerlessResponse.body(response).toString( + request.isBase64Encoded ? 'base64' : 'utf8', + ); + if (headers['transfer-encoding'] === 'chunked' || response.chunkedEncoding) { + const raw = ServerlessResponse.body(response).toString().split('\r\n'); + const parsed = []; + for (let i = 0; i < raw.length; i += 2) { + const size = parseInt(raw[i], 16); + const value = raw[i + 1]; + if (value) { + parsed.push(value.substring(0, size)); + } + } + body = parsed.join(''); + } return { statusCode: response.statusCode, - body: ServerlessResponse.body(response).toString(request.isBase64Encoded ? 'base64' : 'utf8'), - headers: response.headers, + body, + headers, + multiValueHeaders, isBase64Encoded: request.isBase64Encoded, }; }; diff --git a/tests/index-koa.test.ts b/tests/index-koa.test.ts index 0dc8748..925700f 100644 --- a/tests/index-koa.test.ts +++ b/tests/index-koa.test.ts @@ -55,6 +55,7 @@ describe('koa', () => { ctx.status = 200; ctx.body = ctx.request.body.hello; }); + app.use(router.routes()); const response = await serverlessAdapter(app)( { @@ -76,6 +77,7 @@ describe('koa', () => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment ctx.body = ctx.request.body.hello; }); + app.use(router.routes()); const response = await serverlessAdapter(app)( { @@ -89,7 +91,7 @@ describe('koa', () => { defaultContext, ); - expect(response.statusCode).toEqual(200); + expect(response.statusCode).toEqual(204); expect(response.body).toBeDefined(); }); @@ -98,12 +100,12 @@ describe('koa', () => { ctx.status = 200; ctx.body = ctx.request.query.foo; }); + app.use(router.routes()); const response = await serverlessAdapter(app)( { ...defaultEvent, httpMethod: 'GET', - path: '/', queryParameters: { foo: 'bar', }, From b53e6ae6bb5f2dc00a0e94666b40126635073d01 Mon Sep 17 00:00:00 2001 From: seven Date: Mon, 23 Dec 2024 00:07:33 +0800 Subject: [PATCH 4/5] fix: koa tests - ok Signed-off-by: seven --- package-lock.json | 275 +++++++++++++++++++++++++++++++++++++++ package.json | 2 + src/context.ts | 23 +++- src/serverlessRequest.ts | 1 + tests/index-koa.test.ts | 70 ++++------ 5 files changed, 326 insertions(+), 45 deletions(-) diff --git a/package-lock.json b/package-lock.json index 52eae40..7595b70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@types/jest": "^29.5.13", "@types/koa": "^2.15.0", "@types/koa__router": "^12.0.4", + "@types/koa-static": "^4.0.4", "@types/node": "^22.7.4", "@typescript-eslint/eslint-plugin": "^8.8.0", "@typescript-eslint/parser": "^8.8.0", @@ -33,6 +34,7 @@ "jest": "^29.7.0", "koa": "^2.15.3", "koa-body": "^6.0.1", + "koa-static": "^5.0.0", "prettier": "^3.3.3", "ts-jest": "^29.2.5", "ts-node": "^10.9.2", @@ -1745,6 +1747,25 @@ "@types/koa": "*" } }, + "node_modules/@types/koa-send": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/koa-send/-/koa-send-4.1.6.tgz", + "integrity": "sha512-vgnNGoOJkx7FrF0Jl6rbK1f8bBecqAchKpXtKuXzqIEdXTDO6dsSTjr+eZ5m7ltSjH4K/E7auNJEQCAd0McUPA==", + "dev": true, + "dependencies": { + "@types/koa": "*" + } + }, + "node_modules/@types/koa-static": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/koa-static/-/koa-static-4.0.4.tgz", + "integrity": "sha512-j1AUzzl7eJYEk9g01hNTlhmipFh8RFbOQmaMNLvLcNNAkPw0bdTs3XTa3V045XFlrWN0QYnblbDJv2RzawTn6A==", + "dev": true, + "dependencies": { + "@types/koa": "*", + "@types/koa-send": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -5196,6 +5217,76 @@ "node": ">= 10" } }, + "node_modules/koa-send": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/koa-send/-/koa-send-5.0.1.tgz", + "integrity": "sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "http-errors": "^1.7.3", + "resolve-path": "^1.4.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/koa-send/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa-send/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa-send/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa-static": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/koa-static/-/koa-static-5.0.0.tgz", + "integrity": "sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==", + "dev": true, + "dependencies": { + "debug": "^3.1.0", + "koa-send": "^5.0.0" + }, + "engines": { + "node": ">= 7.6.0" + } + }, + "node_modules/koa-static/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/koa/node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -5998,6 +6089,64 @@ "node": ">=4" } }, + "node_modules/resolve-path": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz", + "integrity": "sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w==", + "dev": true, + "dependencies": { + "http-errors": "~1.6.2", + "path-is-absolute": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/resolve-path/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/resolve-path/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/resolve-path/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/resolve-path/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/resolve-path/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/resolve.exports": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", @@ -8260,6 +8409,25 @@ "@types/koa": "*" } }, + "@types/koa-send": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/koa-send/-/koa-send-4.1.6.tgz", + "integrity": "sha512-vgnNGoOJkx7FrF0Jl6rbK1f8bBecqAchKpXtKuXzqIEdXTDO6dsSTjr+eZ5m7ltSjH4K/E7auNJEQCAd0McUPA==", + "dev": true, + "requires": { + "@types/koa": "*" + } + }, + "@types/koa-static": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/koa-static/-/koa-static-4.0.4.tgz", + "integrity": "sha512-j1AUzzl7eJYEk9g01hNTlhmipFh8RFbOQmaMNLvLcNNAkPw0bdTs3XTa3V045XFlrWN0QYnblbDJv2RzawTn6A==", + "dev": true, + "requires": { + "@types/koa": "*", + "@types/koa-send": "*" + } + }, "@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -10853,6 +11021,65 @@ "koa-compose": "^4.1.0" } }, + "koa-send": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/koa-send/-/koa-send-5.0.1.tgz", + "integrity": "sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "http-errors": "^1.7.3", + "resolve-path": "^1.4.0" + }, + "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true + }, + "http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true + } + } + }, + "koa-static": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/koa-static/-/koa-static-5.0.0.tgz", + "integrity": "sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==", + "dev": true, + "requires": { + "debug": "^3.1.0", + "koa-send": "^5.0.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -11390,6 +11617,54 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, + "resolve-path": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz", + "integrity": "sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w==", + "dev": true, + "requires": { + "http-errors": "~1.6.2", + "path-is-absolute": "1.0.1" + }, + "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true + } + } + }, "resolve.exports": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", diff --git a/package.json b/package.json index a543c73..f6523d7 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@types/express": "^5.0.0", "@types/jest": "^29.5.13", "@types/koa": "^2.15.0", + "@types/koa-static": "^4.0.4", "@types/koa__router": "^12.0.4", "@types/node": "^22.7.4", "@typescript-eslint/eslint-plugin": "^8.8.0", @@ -70,6 +71,7 @@ "jest": "^29.7.0", "koa": "^2.15.3", "koa-body": "^6.0.1", + "koa-static": "^5.0.0", "prettier": "^3.3.3", "ts-jest": "^29.2.5", "ts-node": "^10.9.2", diff --git a/src/context.ts b/src/context.ts index 12bc9be..a1fdaca 100644 --- a/src/context.ts +++ b/src/context.ts @@ -27,13 +27,32 @@ const requestBody = (event: Event) => { throw new Error(`Unexpected event.body type: ${typeof event.body}`); }; +const requestHeaders = (event: Event) => { + const initialHeader = {} as Record; + + // if (event.multiValueHeaders) { + // Object.keys(event.multiValueHeaders).reduce((headers, key) => { + // headers[key.toLowerCase()] = event.multiValueHeaders[key].join(', '); + // return headers; + // }, initialHeader); + // } + + return Object.keys(event.headers).reduce((headers, key) => { + headers[key.toLowerCase()] = event.headers[key]; + return headers; + }, initialHeader); +}; + export const constructFrameworkContext = (event: Event, context: Context) => { debug(`constructFrameworkContext: ${JSON.stringify({ event, context })}`); + const body = requestBody(event); + const headers = requestHeaders(event); + const request = new ServerlessRequest({ method: event.httpMethod, - headers: event.headers, path: event.path, - body: requestBody(event), + headers, + body, remoteAddress: '', url: url.format({ pathname: event.path, diff --git a/src/serverlessRequest.ts b/src/serverlessRequest.ts index bb49b16..32ff9f5 100644 --- a/src/serverlessRequest.ts +++ b/src/serverlessRequest.ts @@ -31,6 +31,7 @@ export default class ServerlessRequest extends IncomingMessage { end: NO_OP, destroy: NO_OP, path: request.path, + headers: request.headers, } as unknown as Socket); const combinedHeaders = Object.fromEntries( diff --git a/tests/index-koa.test.ts b/tests/index-koa.test.ts index 925700f..cea77f7 100644 --- a/tests/index-koa.test.ts +++ b/tests/index-koa.test.ts @@ -1,6 +1,7 @@ import Koa from 'koa'; import Router from '@koa/router'; import koaBody from 'koa-body'; +import serve from 'koa-static'; import serverlessAdapter from '../src'; import { defaultContext, defaultEvent } from './fixtures/fcContext'; @@ -9,7 +10,7 @@ describe('koa', () => { let router: Router; beforeEach(() => { app = new Koa(); - app.use(koaBody()); + app.use(koaBody({ text: true, json: true, multipart: true, urlencoded: true })); router = new Router(); }); @@ -26,8 +27,8 @@ describe('koa', () => { expect(response.body).toEqual('Hello, world koa!'); }); - it('basic middleware should get text body', async () => { - router.get('/api/test', (ctx) => { + it('basic middleware should parse text body', async () => { + router.post('/api/test', (ctx) => { ctx.status = 200; ctx.body = ctx.request.body; }); @@ -36,7 +37,7 @@ describe('koa', () => { const response = await serverlessAdapter(app)( { ...defaultEvent, - httpMethod: 'GET', + httpMethod: 'POST', body: 'hello, world', headers: { 'Content-Type': 'text/plain', @@ -50,8 +51,8 @@ describe('koa', () => { expect(response.body).toEqual('hello, world'); }); - it('basic middleware should get json body', async () => { - router.get('/api/test', (ctx) => { + it('basic middleware should parse json body', async () => { + router.post('/api/test', (ctx) => { ctx.status = 200; ctx.body = ctx.request.body.hello; }); @@ -60,7 +61,7 @@ describe('koa', () => { const response = await serverlessAdapter(app)( { ...defaultEvent, - httpMethod: 'GET', + httpMethod: 'POST', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' }, }, @@ -72,7 +73,7 @@ describe('koa', () => { }); it('basic middleware should get undefined body', async () => { - router.get('/api/test', (ctx) => { + router.post('/api/test', (ctx) => { ctx.status = 200; // eslint-disable-next-line @typescript-eslint/ban-ts-comment ctx.body = ctx.request.body.hello; @@ -82,7 +83,7 @@ describe('koa', () => { const response = await serverlessAdapter(app)( { ...defaultEvent, - httpMethod: 'GET', + httpMethod: 'POST', body: undefined, headers: { 'Content-Type': 'application/json', @@ -117,11 +118,11 @@ describe('koa', () => { }); it('should match verbs', async () => { - router.get('/*', (ctx) => { + router.get('/api/test', (ctx) => { ctx.status = 200; ctx.body = 'foo'; }); - router.put('/*', (ctx) => { + router.put('/api/test', (ctx) => { ctx.status = 201; ctx.body = 'bar'; }); @@ -136,36 +137,19 @@ describe('koa', () => { expect(response.body).toEqual('bar'); }); - // it('should serve files', async () => { - // app.use(express.static('tests/fixtures')); - // - // const response = await serverlessAdapter(app)( - // { - // ...defaultEvent, - // httpMethod: 'GET', - // path: '/file.txt', - // }, - // defaultContext, - // ); - // expect(response.statusCode).toEqual(200); - // expect(response.body).toEqual('this is a test\n'); - // }); - // - // it('destroy weird', async () => { - // app.use((req: Request, res: Response) => { - // // this was causing a .destroy is not a function error - // res.send('test'); - // res.json({ test: 'test' }); - // }); - // - // const response = await serverlessAdapter(app)( - // { - // ...defaultEvent, - // httpMethod: 'GET', - // }, - // defaultContext, - // ); - // expect(response.statusCode).toEqual(200); - // expect(response.body).toEqual('test'); - // }); + it('should serve files', async () => { + app.use(serve('tests/fixtures')); + + const response = await serverlessAdapter(app)( + { + ...defaultEvent, + httpMethod: 'GET', + path: '/file.txt', + }, + defaultContext, + ); + + expect(response.statusCode).toEqual(200); + expect(response.body).toEqual('this is a test\n'); + }); }); From bf80d98ae6e2fcf759a59baf7d83ae8cb2e16166 Mon Sep 17 00:00:00 2001 From: seven Date: Mon, 23 Dec 2024 00:11:35 +0800 Subject: [PATCH 5/5] refactor: delete duplications Signed-off-by: seven --- src/sendRequest.ts | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 src/sendRequest.ts diff --git a/src/sendRequest.ts b/src/sendRequest.ts deleted file mode 100644 index 76e89f4..0000000 --- a/src/sendRequest.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Express } from 'express'; -import Application from 'koa'; -import { IncomingMessage, ServerResponse } from 'node:http'; - -const sendRequest = async ( - app: Express | Application, - request: IncomingMessage, - response: ServerResponse, -) => { - if (app instanceof Application) { - app.createContext(request, response); - } else { - app(request, response); - } -}; - -export default sendRequest;