acrylblends 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +6 -1
- package/dist/index.js +84 -5
- package/dist/index.js.map +1 -1
- package/docs/framework/LICENSE +23 -0
- package/docs/framework/README.md +63 -0
- package/docs/framework/UPSTREAM.md +3 -0
- package/docs/framework/api-summary.md +138 -0
- package/docs/framework/concepts.md +105 -0
- package/docs/framework/context.md +129 -0
- package/docs/framework/getting-started.md +146 -0
- package/docs/framework/helpers.md +118 -0
- package/docs/framework/middleware.md +154 -0
- package/docs/framework/migration-from-0-3.md +108 -0
- package/docs/framework/migration-from-dsh-webserver.md +103 -0
- package/docs/framework/node-server.md +75 -0
- package/docs/framework/plugin-lifecycle.md +111 -0
- package/docs/framework/request.md +106 -0
- package/docs/framework/routing.md +188 -0
- package/docs/framework/surfaces.md +71 -0
- package/docs/framework/upstream.json +9 -0
- package/docs/framework/validation.md +135 -0
- package/package.json +4 -2
- package/plugins/acryl-inspect/plugin.json +2 -1
- package/plugins/acryl-inspect/src/report.ts +21 -13
- package/plugins/acryl-inspect/src/service.ts +9 -7
- package/plugins/acryl-inspect/tests/report.spec.ts +13 -8
- package/plugins/acryl-inspect/tests/service.spec.ts +11 -10
- package/plugins/acryl-inspect/upstream.json +2 -2
- package/plugins/acryl-node-server/LICENSE +26 -0
- package/plugins/acryl-node-server/plugin.json +2 -1
- package/plugins/acryl-node-server/src/adapter/conninfo.ts +23 -0
- package/plugins/acryl-node-server/src/adapter/early-hints.ts +55 -0
- package/plugins/acryl-node-server/src/adapter/error.ts +11 -0
- package/plugins/acryl-node-server/src/adapter/headers.ts +249 -0
- package/plugins/acryl-node-server/src/adapter/index.ts +6 -0
- package/plugins/acryl-node-server/src/adapter/listener.ts +540 -0
- package/plugins/acryl-node-server/src/adapter/request.ts +797 -0
- package/plugins/acryl-node-server/src/adapter/response.ts +186 -0
- package/plugins/acryl-node-server/src/adapter/serve-static.ts +238 -0
- package/plugins/acryl-node-server/src/adapter/server.ts +36 -0
- package/plugins/acryl-node-server/src/adapter/types.ts +82 -0
- package/plugins/acryl-node-server/src/adapter/url.ts +42 -0
- package/plugins/acryl-node-server/src/adapter/utils/response/constants.ts +1 -0
- package/plugins/acryl-node-server/src/adapter/utils/response.ts +4 -0
- package/plugins/acryl-node-server/src/adapter/utils/stream.ts +88 -0
- package/plugins/acryl-node-server/src/adapter/utils.ts +96 -0
- package/plugins/acryl-node-server/src/adapter/websocket-types.ts +45 -0
- package/plugins/acryl-node-server/src/adapter/websocket.ts +375 -0
- package/plugins/acryl-node-server/src/index.ts +19 -6
- package/plugins/acryl-node-server/tests/adapter/app.ts +76 -0
- package/plugins/acryl-node-server/tests/adapter/assets/.static/plain.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/favicon.ico +0 -0
- package/plugins/acryl-node-server/tests/adapter/assets/secret.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/acryl.html +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/admin/secret.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/data.json +5 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/extensionless +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/foo..bar.txt +0 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/index.html +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/plain.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-context-path/plain.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin.br +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.br +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.zst +1 -0
- package/plugins/acryl-node-server/tests/adapter/conninfo.test.ts +34 -0
- package/plugins/acryl-node-server/tests/adapter/content-length.test.ts +248 -0
- package/plugins/acryl-node-server/tests/adapter/early-hints.test.ts +457 -0
- package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-cert.pem +23 -0
- package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-key.pem +27 -0
- package/plugins/acryl-node-server/tests/adapter/headers.test.ts +252 -0
- package/plugins/acryl-node-server/tests/adapter/helpers/request.ts +190 -0
- package/plugins/acryl-node-server/tests/adapter/listener.test.ts +727 -0
- package/plugins/acryl-node-server/tests/adapter/request-client-disconnect.test.ts +189 -0
- package/plugins/acryl-node-server/tests/adapter/request.test.ts +1230 -0
- package/plugins/acryl-node-server/tests/adapter/response.test.ts +285 -0
- package/plugins/acryl-node-server/tests/adapter/serve-static.test.ts +849 -0
- package/plugins/acryl-node-server/tests/adapter/server-socket.test.ts +790 -0
- package/plugins/acryl-node-server/tests/adapter/server.test.ts +1302 -0
- package/plugins/acryl-node-server/tests/adapter/setup.ts +16 -0
- package/plugins/acryl-node-server/tests/adapter/url.test.ts +76 -0
- package/plugins/acryl-node-server/tests/adapter/utils/response.test.ts +23 -0
- package/plugins/acryl-node-server/tests/adapter/utils/stream.test.ts +75 -0
- package/plugins/acryl-node-server/tests/adapter/utils.test.ts +235 -0
- package/plugins/acryl-node-server/tests/adapter/websocket.test.ts +260 -0
- package/plugins/acryl-node-server/tests/server.spec.ts +44 -8
- package/plugins/acryl-node-server/upstream.json +3 -3
- package/plugins/acryl-router/LICENSE +26 -0
- package/plugins/acryl-router/plugin.json +2 -1
- package/plugins/acryl-router/src/client.ts +2 -0
- package/plugins/acryl-router/src/connection.ts +42 -0
- package/plugins/acryl-router/src/core/app-base.ts +544 -0
- package/plugins/acryl-router/src/core/app.test.ts +3833 -0
- package/plugins/acryl-router/src/core/app.ts +34 -0
- package/plugins/acryl-router/src/core/client/client.test.ts +2313 -0
- package/plugins/acryl-router/src/core/client/client.ts +249 -0
- package/plugins/acryl-router/src/core/client/fetch-result-please.ts +95 -0
- package/plugins/acryl-router/src/core/client/index.ts +17 -0
- package/plugins/acryl-router/src/core/client/types.test.ts +149 -0
- package/plugins/acryl-router/src/core/client/types.ts +393 -0
- package/plugins/acryl-router/src/core/client/utils.test.ts +405 -0
- package/plugins/acryl-router/src/core/client/utils.ts +117 -0
- package/plugins/acryl-router/src/core/compose.test.ts +751 -0
- package/plugins/acryl-router/src/core/compose.ts +73 -0
- package/plugins/acryl-router/src/core/context.test.ts +629 -0
- package/plugins/acryl-router/src/core/context.ts +781 -0
- package/plugins/acryl-router/src/core/helper/accepts/accepts.test.ts +344 -0
- package/plugins/acryl-router/src/core/helper/accepts/accepts.ts +96 -0
- package/plugins/acryl-router/src/core/helper/accepts/index.ts +6 -0
- package/plugins/acryl-router/src/core/helper/adapter/index.test.ts +55 -0
- package/plugins/acryl-router/src/core/helper/adapter/index.ts +90 -0
- package/plugins/acryl-router/src/core/helper/conninfo/index.ts +6 -0
- package/plugins/acryl-router/src/core/helper/conninfo/types.ts +45 -0
- package/plugins/acryl-router/src/core/helper/cookie/index.test.ts +510 -0
- package/plugins/acryl-router/src/core/helper/cookie/index.ts +146 -0
- package/plugins/acryl-router/src/core/helper/factory/index.test.ts +434 -0
- package/plugins/acryl-router/src/core/helper/factory/index.ts +375 -0
- package/plugins/acryl-router/src/core/helper/html/index.test.ts +78 -0
- package/plugins/acryl-router/src/core/helper/html/index.ts +57 -0
- package/plugins/acryl-router/src/core/helper/route/index.test.ts +259 -0
- package/plugins/acryl-router/src/core/helper/route/index.ts +144 -0
- package/plugins/acryl-router/src/core/helper/streaming/index.ts +9 -0
- package/plugins/acryl-router/src/core/helper/streaming/sse.ts +101 -0
- package/plugins/acryl-router/src/core/helper/streaming/stream.test.ts +128 -0
- package/plugins/acryl-router/src/core/helper/streaming/stream.ts +45 -0
- package/plugins/acryl-router/src/core/helper/streaming/text.test.ts +34 -0
- package/plugins/acryl-router/src/core/helper/streaming/text.ts +15 -0
- package/plugins/acryl-router/src/core/helper/streaming/utils.ts +11 -0
- package/plugins/acryl-router/src/core/helper/testing/index.test.ts +42 -0
- package/plugins/acryl-router/src/core/helper/testing/index.ts +28 -0
- package/plugins/acryl-router/src/core/helper/websocket/index.test.ts +124 -0
- package/plugins/acryl-router/src/core/helper/websocket/index.ts +140 -0
- package/plugins/acryl-router/src/core/http-exception.test.ts +45 -0
- package/plugins/acryl-router/src/core/http-exception.ts +77 -0
- package/plugins/acryl-router/src/core/index.ts +53 -0
- package/plugins/acryl-router/src/core/middleware/basic-auth/index.test.ts +434 -0
- package/plugins/acryl-router/src/core/middleware/basic-auth/index.ts +152 -0
- package/plugins/acryl-router/src/core/middleware/bearer-auth/index.test.ts +1018 -0
- package/plugins/acryl-router/src/core/middleware/bearer-auth/index.ts +220 -0
- package/plugins/acryl-router/src/core/middleware/body-limit/index.test.ts +273 -0
- package/plugins/acryl-router/src/core/middleware/body-limit/index.ts +110 -0
- package/plugins/acryl-router/src/core/middleware/combine/index.test.ts +355 -0
- package/plugins/acryl-router/src/core/middleware/combine/index.ts +171 -0
- package/plugins/acryl-router/src/core/middleware/compress/index.test.ts +535 -0
- package/plugins/acryl-router/src/core/middleware/compress/index.ts +142 -0
- package/plugins/acryl-router/src/core/middleware/context-storage/index.test.ts +43 -0
- package/plugins/acryl-router/src/core/middleware/context-storage/index.ts +58 -0
- package/plugins/acryl-router/src/core/middleware/cors/index.test.ts +532 -0
- package/plugins/acryl-router/src/core/middleware/cors/index.ts +163 -0
- package/plugins/acryl-router/src/core/middleware/csrf/index.test.ts +534 -0
- package/plugins/acryl-router/src/core/middleware/csrf/index.ts +150 -0
- package/plugins/acryl-router/src/core/middleware/etag/digest.test.ts +21 -0
- package/plugins/acryl-router/src/core/middleware/etag/digest.ts +95 -0
- package/plugins/acryl-router/src/core/middleware/etag/index.test.ts +503 -0
- package/plugins/acryl-router/src/core/middleware/etag/index.ts +135 -0
- package/plugins/acryl-router/src/core/middleware/ip-restriction/index.test.ts +217 -0
- package/plugins/acryl-router/src/core/middleware/ip-restriction/index.ts +278 -0
- package/plugins/acryl-router/src/core/middleware/jwk/index.test.ts +1135 -0
- package/plugins/acryl-router/src/core/middleware/jwk/index.ts +1 -0
- package/plugins/acryl-router/src/core/middleware/jwk/jwk.ts +195 -0
- package/plugins/acryl-router/src/core/middleware/jwk/keys.test.json +48 -0
- package/plugins/acryl-router/src/core/middleware/jwt/index.test.ts +802 -0
- package/plugins/acryl-router/src/core/middleware/jwt/index.ts +9 -0
- package/plugins/acryl-router/src/core/middleware/jwt/jwt.ts +189 -0
- package/plugins/acryl-router/src/core/middleware/language/index.test.ts +552 -0
- package/plugins/acryl-router/src/core/middleware/language/index.ts +12 -0
- package/plugins/acryl-router/src/core/middleware/language/language.ts +314 -0
- package/plugins/acryl-router/src/core/middleware/logger/index.test.ts +204 -0
- package/plugins/acryl-router/src/core/middleware/logger/index.ts +95 -0
- package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.test.ts +292 -0
- package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.ts +141 -0
- package/plugins/acryl-router/src/core/middleware/method-override/index.test.ts +221 -0
- package/plugins/acryl-router/src/core/middleware/method-override/index.ts +147 -0
- package/plugins/acryl-router/src/core/middleware/powered-by/index.test.ts +37 -0
- package/plugins/acryl-router/src/core/middleware/powered-by/index.ts +35 -0
- package/plugins/acryl-router/src/core/middleware/pretty-json/index.test.ts +218 -0
- package/plugins/acryl-router/src/core/middleware/pretty-json/index.ts +67 -0
- package/plugins/acryl-router/src/core/middleware/request-id/index.test.ts +168 -0
- package/plugins/acryl-router/src/core/middleware/request-id/index.ts +7 -0
- package/plugins/acryl-router/src/core/middleware/request-id/request-id.ts +59 -0
- package/plugins/acryl-router/src/core/middleware/secure-headers/index.test.ts +671 -0
- package/plugins/acryl-router/src/core/middleware/secure-headers/index.ts +8 -0
- package/plugins/acryl-router/src/core/middleware/secure-headers/permissions-policy.ts +101 -0
- package/plugins/acryl-router/src/core/middleware/secure-headers/secure-headers.ts +340 -0
- package/plugins/acryl-router/src/core/middleware/serve-static/index.test.ts +450 -0
- package/plugins/acryl-router/src/core/middleware/serve-static/index.ts +135 -0
- package/plugins/acryl-router/src/core/middleware/serve-static/path.test.ts +50 -0
- package/plugins/acryl-router/src/core/middleware/serve-static/path.ts +25 -0
- package/plugins/acryl-router/src/core/middleware/timeout/index.test.ts +70 -0
- package/plugins/acryl-router/src/core/middleware/timeout/index.ts +58 -0
- package/plugins/acryl-router/src/core/middleware/timing/index.test.ts +310 -0
- package/plugins/acryl-router/src/core/middleware/timing/index.ts +8 -0
- package/plugins/acryl-router/src/core/middleware/timing/timing.ts +256 -0
- package/plugins/acryl-router/src/core/middleware/trailing-slash/index.test.ts +381 -0
- package/plugins/acryl-router/src/core/middleware/trailing-slash/index.ts +156 -0
- package/plugins/acryl-router/src/core/request/constants.ts +1 -0
- package/plugins/acryl-router/src/core/request.test.ts +751 -0
- package/plugins/acryl-router/src/core/request.ts +502 -0
- package/plugins/acryl-router/src/core/router/common.case.test.ts +1132 -0
- package/plugins/acryl-router/src/core/router/linear-router/index.ts +6 -0
- package/plugins/acryl-router/src/core/router/linear-router/router.test.ts +80 -0
- package/plugins/acryl-router/src/core/router/linear-router/router.ts +152 -0
- package/plugins/acryl-router/src/core/router/pattern-router/index.ts +6 -0
- package/plugins/acryl-router/src/core/router/pattern-router/router.test.ts +46 -0
- package/plugins/acryl-router/src/core/router/pattern-router/router.ts +54 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/index.ts +7 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/matcher.ts +33 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/node.ts +170 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.test.ts +154 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.ts +165 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/router.test.ts +265 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/router.ts +174 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/trie.ts +84 -0
- package/plugins/acryl-router/src/core/router/smart-router/index.ts +6 -0
- package/plugins/acryl-router/src/core/router/smart-router/router.test.ts +13 -0
- package/plugins/acryl-router/src/core/router/smart-router/router.ts +70 -0
- package/plugins/acryl-router/src/core/router/trie-router/index.ts +6 -0
- package/plugins/acryl-router/src/core/router/trie-router/node.test.ts +896 -0
- package/plugins/acryl-router/src/core/router/trie-router/node.ts +221 -0
- package/plugins/acryl-router/src/core/router/trie-router/router.test.ts +8 -0
- package/plugins/acryl-router/src/core/router/trie-router/router.ts +18 -0
- package/plugins/acryl-router/src/core/router/utils.ts +1 -0
- package/plugins/acryl-router/src/core/router.ts +103 -0
- package/plugins/acryl-router/src/core/types.test.ts +4134 -0
- package/plugins/acryl-router/src/core/types.ts +2782 -0
- package/plugins/acryl-router/src/core/utils/accept.test.ts +299 -0
- package/plugins/acryl-router/src/core/utils/accept.ts +268 -0
- package/plugins/acryl-router/src/core/utils/basic-auth.test.ts +56 -0
- package/plugins/acryl-router/src/core/utils/basic-auth.ts +26 -0
- package/plugins/acryl-router/src/core/utils/body.test.ts +496 -0
- package/plugins/acryl-router/src/core/utils/body.ts +263 -0
- package/plugins/acryl-router/src/core/utils/buffer.test.ts +147 -0
- package/plugins/acryl-router/src/core/utils/buffer.ts +117 -0
- package/plugins/acryl-router/src/core/utils/color.test.ts +23 -0
- package/plugins/acryl-router/src/core/utils/color.ts +56 -0
- package/plugins/acryl-router/src/core/utils/compress.ts +10 -0
- package/plugins/acryl-router/src/core/utils/concurrent.test.ts +69 -0
- package/plugins/acryl-router/src/core/utils/concurrent.ts +55 -0
- package/plugins/acryl-router/src/core/utils/constants.ts +4 -0
- package/plugins/acryl-router/src/core/utils/cookie.test.ts +460 -0
- package/plugins/acryl-router/src/core/utils/cookie.ts +281 -0
- package/plugins/acryl-router/src/core/utils/crypto.test.ts +43 -0
- package/plugins/acryl-router/src/core/utils/crypto.ts +58 -0
- package/plugins/acryl-router/src/core/utils/encode.test.ts +61 -0
- package/plugins/acryl-router/src/core/utils/encode.ts +34 -0
- package/plugins/acryl-router/src/core/utils/filepath.test.ts +78 -0
- package/plugins/acryl-router/src/core/utils/filepath.ts +60 -0
- package/plugins/acryl-router/src/core/utils/handler.ts +15 -0
- package/plugins/acryl-router/src/core/utils/headers.ts +355 -0
- package/plugins/acryl-router/src/core/utils/html.test.ts +16 -0
- package/plugins/acryl-router/src/core/utils/html.ts +182 -0
- package/plugins/acryl-router/src/core/utils/http-status.ts +72 -0
- package/plugins/acryl-router/src/core/utils/ipaddr.test.ts +149 -0
- package/plugins/acryl-router/src/core/utils/ipaddr.ts +374 -0
- package/plugins/acryl-router/src/core/utils/jwt/index.ts +7 -0
- package/plugins/acryl-router/src/core/utils/jwt/jwa.test.ts +86 -0
- package/plugins/acryl-router/src/core/utils/jwt/jwa.ts +38 -0
- package/plugins/acryl-router/src/core/utils/jwt/jws.ts +235 -0
- package/plugins/acryl-router/src/core/utils/jwt/jwt.test.ts +1788 -0
- package/plugins/acryl-router/src/core/utils/jwt/jwt.ts +292 -0
- package/plugins/acryl-router/src/core/utils/jwt/types.ts +157 -0
- package/plugins/acryl-router/src/core/utils/jwt/utf8.ts +7 -0
- package/plugins/acryl-router/src/core/utils/mime.test.ts +64 -0
- package/plugins/acryl-router/src/core/utils/mime.ts +94 -0
- package/plugins/acryl-router/src/core/utils/stream.test.ts +212 -0
- package/plugins/acryl-router/src/core/utils/stream.ts +107 -0
- package/plugins/acryl-router/src/core/utils/types.test.ts +295 -0
- package/plugins/acryl-router/src/core/utils/types.ts +123 -0
- package/plugins/acryl-router/src/core/utils/url.test.ts +384 -0
- package/plugins/acryl-router/src/core/utils/url.ts +337 -0
- package/plugins/acryl-router/src/core/validator/index.ts +8 -0
- package/plugins/acryl-router/src/core/validator/utils.test.ts +82 -0
- package/plugins/acryl-router/src/core/validator/utils.ts +69 -0
- package/plugins/acryl-router/src/core/validator/validator.test.ts +1488 -0
- package/plugins/acryl-router/src/core/validator/validator.ts +185 -0
- package/plugins/acryl-router/src/helpers/index.ts +13 -3
- package/plugins/acryl-router/src/helpers/schema-validator.ts +35 -0
- package/plugins/acryl-router/src/index.ts +6 -6
- package/plugins/acryl-router/src/middleware/index.ts +25 -6
- package/plugins/acryl-router/src/middleware/loopback-only.ts +53 -14
- package/plugins/acryl-router/src/service.ts +262 -64
- package/plugins/acryl-router/tests/docs-examples.spec.ts +152 -0
- package/plugins/acryl-router/tests/live-install.spec.ts +2 -2
- package/plugins/acryl-router/tests/loader.spec.ts +3 -2
- package/plugins/acryl-router/tests/loopback-only.spec.ts +80 -0
- package/plugins/acryl-router/tests/service.spec.ts +281 -66
- package/plugins/acryl-router/tests/shadow-state.spec.ts +79 -0
- package/plugins/acryl-router/upstream.json +2 -2
- package/plugins/acryl-router-http/plugin.json +2 -1
- package/plugins/acryl-router-http/src/bridge.ts +3 -1
- package/plugins/acryl-router-http/tests/adapter.spec.ts +16 -12
- package/plugins/acryl-router-http/tests/surfaces.spec.ts +12 -10
- package/plugins/acryl-router-http/upstream.json +3 -3
- package/plugins/acryl-state/NOTICE +30 -0
- package/plugins/acryl-state/plugin.json +1 -1
- package/plugins/acryl-state/src/cell.ts +1 -1
- package/plugins/acryl-state/src/service.ts +33 -15
- package/plugins/acryl-state/upstream.json +6 -4
- package/plugins/builder/script.ts +4 -7
- package/plugins/extensions/index.ts +6 -5
- package/plugins/extensions/resolve.ts +28 -3
- package/plugins/openai/prompt.ts +9 -7
- package/plugins/tsconfig.json +5 -4
- package/shared/AGENTS.core.md +54 -0
- package/templates/agent/AGENTS.md +6 -0
- package/templates/agent/README.md +2 -2
- package/templates/agent/blend.yaml +1 -1
- package/templates/agent/src/routes.ts +37 -27
- package/templates/agent/test/model.test.ts +2 -1
- package/templates/agent/tsconfig.json +8 -5
- package/templates/blank/AGENTS.md +5 -0
- package/templates/blank/README.md +1 -1
- package/templates/blank/src/routes.ts +6 -4
- package/templates/blank/tsconfig.json +2 -4
- package/templates/todo/AGENTS.md +5 -0
- package/templates/todo/src/page.ts +1 -1
- package/templates/todo/src/todo.ts +58 -29
- package/templates/todo/tsconfig.json +2 -4
- package/templates/todo/upstream.json +2 -2
- package/plugins/acryl-router/src/compose.ts +0 -21
- package/plugins/acryl-router/src/context.ts +0 -59
- package/plugins/acryl-router/src/helpers/read-body.ts +0 -59
- package/plugins/acryl-router/src/helpers/sse.ts +0 -83
- package/plugins/acryl-router/src/helpers/validate.ts +0 -44
- package/plugins/acryl-router/src/http-error.ts +0 -21
- package/plugins/acryl-router/src/middleware/body-limit.ts +0 -25
- package/plugins/acryl-router/src/middleware/cors.ts +0 -46
- package/plugins/acryl-router/src/middleware/logger.ts +0 -21
- package/plugins/acryl-router/src/middleware/request-id.ts +0 -24
- package/plugins/acryl-router/src/middleware/secure-headers.ts +0 -23
- package/plugins/acryl-router/src/pattern.ts +0 -89
- package/plugins/acryl-router/src/registrar.ts +0 -90
- package/plugins/acryl-router/src/table.ts +0 -156
- package/plugins/acryl-router/src/types.ts +0 -73
- package/plugins/acryl-router/src/util.ts +0 -23
- package/plugins/acryl-router/tests/api.spec.ts +0 -223
- package/plugins/acryl-router/tests/conformance.ts +0 -170
- package/plugins/acryl-router/tests/helpers.spec.ts +0 -172
- package/plugins/acryl-router/tests/middleware.spec.ts +0 -183
- package/plugins/acryl-router/tests/table.spec.ts +0 -47
|
@@ -0,0 +1,3833 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
+
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
3
|
+
import { expectTypeOf } from 'vitest'
|
|
4
|
+
import { hc } from './client/index.ts'
|
|
5
|
+
import type { Context, ExecutionContext } from './context.ts'
|
|
6
|
+
import { App } from './app.ts'
|
|
7
|
+
import { HTTPException } from './http-exception.ts'
|
|
8
|
+
import { logger } from './middleware/logger/index.ts'
|
|
9
|
+
import { poweredBy } from './middleware/powered-by/index.ts'
|
|
10
|
+
import { RegExpRouter } from './router/reg-exp-router/index.ts'
|
|
11
|
+
import { SmartRouter } from './router/smart-router/index.ts'
|
|
12
|
+
import { TrieRouter } from './router/trie-router/index.ts'
|
|
13
|
+
import type { Handler, MiddlewareHandler, Next } from './types.ts'
|
|
14
|
+
import type { Equal, Expect } from './utils/types.ts'
|
|
15
|
+
import { getPath } from './utils/url.ts'
|
|
16
|
+
|
|
17
|
+
// https://stackoverflow.com/a/65666402
|
|
18
|
+
function throwExpression(errorMessage: string): never {
|
|
19
|
+
throw new Error(errorMessage)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type Env = {
|
|
23
|
+
Bindings: {
|
|
24
|
+
_: string
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const createResponseProxy = (response: Response) => {
|
|
29
|
+
return new Proxy(response, {
|
|
30
|
+
get(target, prop, receiver) {
|
|
31
|
+
const value = target[prop as keyof Response]
|
|
32
|
+
if (typeof value === 'function') {
|
|
33
|
+
return Object.defineProperties(
|
|
34
|
+
function (...args: unknown[]) {
|
|
35
|
+
// @ts-expect-error: `this` context is intentionally dynamic for proxy method binding
|
|
36
|
+
return Reflect.apply(value, this === receiver ? target : this, args)
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: { value: value.name },
|
|
40
|
+
length: { value: value.length },
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
return value
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe('GET Request', () => {
|
|
50
|
+
describe('without middleware', () => {
|
|
51
|
+
// In other words, this is a test for cases that do not use `compose()`
|
|
52
|
+
|
|
53
|
+
const app = new App<Env>()
|
|
54
|
+
|
|
55
|
+
app.get('/hello', async () => {
|
|
56
|
+
return new Response('hello', {
|
|
57
|
+
status: 200,
|
|
58
|
+
statusText: 'App is OK',
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
app.get('/hello-with-shortcuts', (c) => {
|
|
63
|
+
c.header('X-Custom', 'This is App')
|
|
64
|
+
c.status(201)
|
|
65
|
+
return c.html('<h1>App!!!</h1>')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
app.get('/hello-env', (c) => {
|
|
69
|
+
return c.json(c.env)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
app.get('/proxy-object', () => createResponseProxy(new Response('proxy')))
|
|
73
|
+
|
|
74
|
+
app.get('/async-proxy-object', async () => createResponseProxy(new Response('proxy')))
|
|
75
|
+
|
|
76
|
+
it('GET http://localhost/hello is ok', async () => {
|
|
77
|
+
const res = await app.request('http://localhost/hello')
|
|
78
|
+
expect(res).not.toBeNull()
|
|
79
|
+
expect(res.status).toBe(200)
|
|
80
|
+
expect(res.statusText).toBe('App is OK')
|
|
81
|
+
expect(await res.text()).toBe('hello')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('GET httphello is ng', async () => {
|
|
85
|
+
const res = await app.request('httphello')
|
|
86
|
+
expect(res.status).toBe(404)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('GET /hello is ok', async () => {
|
|
90
|
+
const res = await app.request('/hello')
|
|
91
|
+
expect(res).not.toBeNull()
|
|
92
|
+
expect(res.status).toBe(200)
|
|
93
|
+
expect(res.statusText).toBe('App is OK')
|
|
94
|
+
expect(await res.text()).toBe('hello')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('GET hello is ok', async () => {
|
|
98
|
+
const res = await app.request('hello')
|
|
99
|
+
expect(res).not.toBeNull()
|
|
100
|
+
expect(res.status).toBe(200)
|
|
101
|
+
expect(res.statusText).toBe('App is OK')
|
|
102
|
+
expect(await res.text()).toBe('hello')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('GET /hello-with-shortcuts is ok', async () => {
|
|
106
|
+
const res = await app.request('http://localhost/hello-with-shortcuts')
|
|
107
|
+
expect(res).not.toBeNull()
|
|
108
|
+
expect(res.status).toBe(201)
|
|
109
|
+
expect(res.headers.get('X-Custom')).toBe('This is App')
|
|
110
|
+
expect(res.headers.get('Content-Type')).toMatch(/text\/html/)
|
|
111
|
+
expect(await res.text()).toBe('<h1>App!!!</h1>')
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('GET / is not found', async () => {
|
|
115
|
+
const res = await app.request('http://localhost/')
|
|
116
|
+
expect(res).not.toBeNull()
|
|
117
|
+
expect(res.status).toBe(404)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('GET /hello-env is ok', async () => {
|
|
121
|
+
const res = await app.request('/hello-env', undefined, { HELLO: 'world' })
|
|
122
|
+
expect(res.status).toBe(200)
|
|
123
|
+
expect(await res.json()).toEqual({ HELLO: 'world' })
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('GET /proxy-object is ok', async () => {
|
|
127
|
+
const res = await app.request('/proxy-object')
|
|
128
|
+
expect(res).not.toBeNull()
|
|
129
|
+
expect(res.status).toBe(200)
|
|
130
|
+
expect(await res.text()).toBe('proxy')
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('GET /async-proxy-object is ok', async () => {
|
|
134
|
+
const res = await app.request('/proxy-object')
|
|
135
|
+
expect(res).not.toBeNull()
|
|
136
|
+
expect(res.status).toBe(200)
|
|
137
|
+
expect(await res.text()).toBe('proxy')
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
describe('with middleware', () => {
|
|
142
|
+
// when using `compose()`
|
|
143
|
+
|
|
144
|
+
const app = new App<Env>()
|
|
145
|
+
|
|
146
|
+
app.use('*', async (ctx, next) => {
|
|
147
|
+
await next()
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
app.get('/hello', async () => {
|
|
151
|
+
return new Response('hello', {
|
|
152
|
+
status: 200,
|
|
153
|
+
statusText: 'App is OK',
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
app.get('/hello-with-shortcuts', (c) => {
|
|
158
|
+
c.header('X-Custom', 'This is App')
|
|
159
|
+
c.status(201)
|
|
160
|
+
return c.html('<h1>App!!!</h1>')
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
app.get('/hello-env', (c) => {
|
|
164
|
+
return c.json(c.env)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
app.get('/proxy-object', () => createResponseProxy(new Response('proxy')))
|
|
168
|
+
|
|
169
|
+
app.get('/async-proxy-object', async () => createResponseProxy(new Response('proxy')))
|
|
170
|
+
|
|
171
|
+
it('GET http://localhost/hello is ok', async () => {
|
|
172
|
+
const res = await app.request('http://localhost/hello')
|
|
173
|
+
expect(res).not.toBeNull()
|
|
174
|
+
expect(res.status).toBe(200)
|
|
175
|
+
expect(res.statusText).toBe('App is OK')
|
|
176
|
+
expect(await res.text()).toBe('hello')
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('GET httphello is ng', async () => {
|
|
180
|
+
const res = await app.request('httphello')
|
|
181
|
+
expect(res.status).toBe(404)
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it('GET /hello is ok', async () => {
|
|
185
|
+
const res = await app.request('/hello')
|
|
186
|
+
expect(res).not.toBeNull()
|
|
187
|
+
expect(res.status).toBe(200)
|
|
188
|
+
expect(res.statusText).toBe('App is OK')
|
|
189
|
+
expect(await res.text()).toBe('hello')
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it('GET hello is ok', async () => {
|
|
193
|
+
const res = await app.request('hello')
|
|
194
|
+
expect(res).not.toBeNull()
|
|
195
|
+
expect(res.status).toBe(200)
|
|
196
|
+
expect(res.statusText).toBe('App is OK')
|
|
197
|
+
expect(await res.text()).toBe('hello')
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it('GET /hello-with-shortcuts is ok', async () => {
|
|
201
|
+
const res = await app.request('http://localhost/hello-with-shortcuts')
|
|
202
|
+
expect(res).not.toBeNull()
|
|
203
|
+
expect(res.status).toBe(201)
|
|
204
|
+
expect(res.headers.get('X-Custom')).toBe('This is App')
|
|
205
|
+
expect(res.headers.get('Content-Type')).toMatch(/text\/html/)
|
|
206
|
+
expect(await res.text()).toBe('<h1>App!!!</h1>')
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('GET / is not found', async () => {
|
|
210
|
+
const res = await app.request('http://localhost/')
|
|
211
|
+
expect(res).not.toBeNull()
|
|
212
|
+
expect(res.status).toBe(404)
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it('GET /hello-env is ok', async () => {
|
|
216
|
+
const res = await app.request('/hello-env', undefined, { HELLO: 'world' })
|
|
217
|
+
expect(res.status).toBe(200)
|
|
218
|
+
expect(await res.json()).toEqual({ HELLO: 'world' })
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('GET /proxy-object is ok', async () => {
|
|
222
|
+
const res = await app.request('/proxy-object')
|
|
223
|
+
expect(res).not.toBeNull()
|
|
224
|
+
expect(res.status).toBe(200)
|
|
225
|
+
expect(await res.text()).toBe('proxy')
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('GET /async-proxy-object is ok', async () => {
|
|
229
|
+
const res = await app.request('/proxy-object')
|
|
230
|
+
expect(res).not.toBeNull()
|
|
231
|
+
expect(res.status).toBe(200)
|
|
232
|
+
expect(await res.text()).toBe('proxy')
|
|
233
|
+
})
|
|
234
|
+
})
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
describe('Register handlers without a path', () => {
|
|
238
|
+
describe('No basePath', () => {
|
|
239
|
+
const app = new App()
|
|
240
|
+
|
|
241
|
+
app.get((c) => {
|
|
242
|
+
return c.text('Hello')
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
it('GET http://localhost/ is ok', async () => {
|
|
246
|
+
const res = await app.request('/')
|
|
247
|
+
expect(res.status).toBe(200)
|
|
248
|
+
expect(await res.text()).toBe('Hello')
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('GET http://localhost/anything is not found', async () => {
|
|
252
|
+
const res = await app.request('/anything')
|
|
253
|
+
expect(res.status).toBe(404)
|
|
254
|
+
})
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
describe('With specifying basePath', () => {
|
|
258
|
+
const app = new App().basePath('/about')
|
|
259
|
+
|
|
260
|
+
app.get((c) => {
|
|
261
|
+
return c.text('About')
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
it('GET http://localhost/about is ok', async () => {
|
|
265
|
+
const res = await app.request('/about')
|
|
266
|
+
expect(res.status).toBe(200)
|
|
267
|
+
expect(await res.text()).toBe('About')
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it('GET http://localhost/ is not found', async () => {
|
|
271
|
+
const res = await app.request('/')
|
|
272
|
+
expect(res.status).toBe(404)
|
|
273
|
+
})
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
describe('With chaining', () => {
|
|
277
|
+
const app = new App()
|
|
278
|
+
|
|
279
|
+
app.post('/books').get((c) => {
|
|
280
|
+
return c.text('Books')
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
it('GET http://localhost/books is ok', async () => {
|
|
284
|
+
const res = await app.request('/books')
|
|
285
|
+
expect(res.status).toBe(200)
|
|
286
|
+
expect(await res.text()).toBe('Books')
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
it('GET http://localhost/ is not found', async () => {
|
|
290
|
+
const res = await app.request('/')
|
|
291
|
+
expect(res.status).toBe(404)
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
describe('Options', () => {
|
|
297
|
+
describe('router option', () => {
|
|
298
|
+
it('Should be SmartRouter', () => {
|
|
299
|
+
const app = new App()
|
|
300
|
+
expect(app.router instanceof SmartRouter).toBe(true)
|
|
301
|
+
})
|
|
302
|
+
it('Should be RegExpRouter', () => {
|
|
303
|
+
const app = new App({
|
|
304
|
+
router: new RegExpRouter(),
|
|
305
|
+
})
|
|
306
|
+
expect(app.router instanceof RegExpRouter).toBe(true)
|
|
307
|
+
})
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
describe('strict parameter', () => {
|
|
311
|
+
describe('strict is true with not slash', () => {
|
|
312
|
+
const app = new App()
|
|
313
|
+
|
|
314
|
+
app.get('/hello', (c) => {
|
|
315
|
+
return c.text('/hello')
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
it('/hello/ is not found', async () => {
|
|
319
|
+
let res = await app.request('http://localhost/hello')
|
|
320
|
+
expect(res).not.toBeNull()
|
|
321
|
+
expect(res.status).toBe(200)
|
|
322
|
+
res = await app.request('http://localhost/hello/')
|
|
323
|
+
expect(res).not.toBeNull()
|
|
324
|
+
expect(res.status).toBe(404)
|
|
325
|
+
})
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
describe('strict is true with slash', () => {
|
|
329
|
+
const app = new App()
|
|
330
|
+
|
|
331
|
+
app.get('/hello/', (c) => {
|
|
332
|
+
return c.text('/hello/')
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
it('/hello is not found', async () => {
|
|
336
|
+
let res = await app.request('http://localhost/hello/')
|
|
337
|
+
expect(res).not.toBeNull()
|
|
338
|
+
expect(res.status).toBe(200)
|
|
339
|
+
res = await app.request('http://localhost/hello')
|
|
340
|
+
expect(res).not.toBeNull()
|
|
341
|
+
expect(res.status).toBe(404)
|
|
342
|
+
})
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
describe('strict is false', () => {
|
|
346
|
+
const app = new App({ strict: false })
|
|
347
|
+
|
|
348
|
+
app.get('/hello', (c) => {
|
|
349
|
+
return c.text('/hello')
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
it('/hello and /hello/ are treated as the same', async () => {
|
|
353
|
+
let res = await app.request('http://localhost/hello')
|
|
354
|
+
expect(res).not.toBeNull()
|
|
355
|
+
expect(res.status).toBe(200)
|
|
356
|
+
res = await app.request('http://localhost/hello/')
|
|
357
|
+
expect(res).not.toBeNull()
|
|
358
|
+
expect(res.status).toBe(200)
|
|
359
|
+
})
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
describe('strict is false with `getPath` option', () => {
|
|
363
|
+
const app = new App({
|
|
364
|
+
strict: false,
|
|
365
|
+
// @ts-expect-error `getPath` is not used when `strict` is false
|
|
366
|
+
getPath: getPath,
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
app.get('/hello', (c) => {
|
|
370
|
+
return c.text('/hello')
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
it('/hello and /hello/ are treated as the same', async () => {
|
|
374
|
+
let res = await app.request('http://localhost/hello')
|
|
375
|
+
expect(res).not.toBeNull()
|
|
376
|
+
expect(res.status).toBe(200)
|
|
377
|
+
res = await app.request('http://localhost/hello/')
|
|
378
|
+
expect(res).not.toBeNull()
|
|
379
|
+
expect(res.status).toBe(200)
|
|
380
|
+
})
|
|
381
|
+
})
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
it('Should not modify the options passed to it', () => {
|
|
385
|
+
const options = { strict: true }
|
|
386
|
+
const clone = structuredClone(options)
|
|
387
|
+
const app = new App(clone)
|
|
388
|
+
expect(clone).toEqual(options)
|
|
389
|
+
})
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
describe('Destruct functions in context', () => {
|
|
393
|
+
it('Should return 200 response - text', async () => {
|
|
394
|
+
const app = new App()
|
|
395
|
+
app.get('/text', ({ text }) => text('foo'))
|
|
396
|
+
const res = await app.request('http://localhost/text')
|
|
397
|
+
expect(res.status).toBe(200)
|
|
398
|
+
})
|
|
399
|
+
it('Should return 200 response - json', async () => {
|
|
400
|
+
const app = new App()
|
|
401
|
+
app.get('/json', ({ json }) => json({ foo: 'bar' }))
|
|
402
|
+
const res = await app.request('http://localhost/json')
|
|
403
|
+
expect(res.status).toBe(200)
|
|
404
|
+
})
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
describe('Routing', () => {
|
|
408
|
+
it('Return it self', async () => {
|
|
409
|
+
const app = new App()
|
|
410
|
+
|
|
411
|
+
const app2 = app.get('/', () => new Response('get /'))
|
|
412
|
+
expect(app2).not.toBeUndefined()
|
|
413
|
+
app2.delete('/', () => new Response('delete /'))
|
|
414
|
+
|
|
415
|
+
let res = await app2.request('http://localhost/', { method: 'GET' })
|
|
416
|
+
expect(res).not.toBeNull()
|
|
417
|
+
expect(res.status).toBe(200)
|
|
418
|
+
expect(await res.text()).toBe('get /')
|
|
419
|
+
|
|
420
|
+
res = await app2.request('http://localhost/', { method: 'DELETE' })
|
|
421
|
+
expect(res).not.toBeNull()
|
|
422
|
+
expect(res.status).toBe(200)
|
|
423
|
+
expect(await res.text()).toBe('delete /')
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
it('Nested route', async () => {
|
|
427
|
+
const app = new App()
|
|
428
|
+
|
|
429
|
+
const book = app.basePath('/book')
|
|
430
|
+
book.get('/', (c) => c.text('get /book'))
|
|
431
|
+
book.get('/:id', (c) => {
|
|
432
|
+
return c.text('get /book/' + c.req.param('id'))
|
|
433
|
+
})
|
|
434
|
+
book.post('/', (c) => c.text('post /book'))
|
|
435
|
+
|
|
436
|
+
const user = app.basePath('/user')
|
|
437
|
+
user.get('/login', (c) => c.text('get /user/login'))
|
|
438
|
+
user.post('/register', (c) => c.text('post /user/register'))
|
|
439
|
+
|
|
440
|
+
const appForEachUser = user.basePath(':id')
|
|
441
|
+
appForEachUser.get('/profile', (c) => c.text('get /user/' + c.req.param('id') + '/profile'))
|
|
442
|
+
|
|
443
|
+
app.get('/add-path-after-route-call', (c) => c.text('get /add-path-after-route-call'))
|
|
444
|
+
|
|
445
|
+
let res = await app.request('http://localhost/book', { method: 'GET' })
|
|
446
|
+
expect(res.status).toBe(200)
|
|
447
|
+
expect(await res.text()).toBe('get /book')
|
|
448
|
+
|
|
449
|
+
res = await app.request('http://localhost/book/123', { method: 'GET' })
|
|
450
|
+
expect(res.status).toBe(200)
|
|
451
|
+
expect(await res.text()).toBe('get /book/123')
|
|
452
|
+
|
|
453
|
+
res = await app.request('http://localhost/book', { method: 'POST' })
|
|
454
|
+
expect(res.status).toBe(200)
|
|
455
|
+
expect(await res.text()).toBe('post /book')
|
|
456
|
+
|
|
457
|
+
res = await app.request('http://localhost/book/', { method: 'GET' })
|
|
458
|
+
expect(res.status).toBe(404)
|
|
459
|
+
|
|
460
|
+
res = await app.request('http://localhost/user/login', { method: 'GET' })
|
|
461
|
+
expect(res.status).toBe(200)
|
|
462
|
+
expect(await res.text()).toBe('get /user/login')
|
|
463
|
+
|
|
464
|
+
res = await app.request('http://localhost/user/register', { method: 'POST' })
|
|
465
|
+
expect(res.status).toBe(200)
|
|
466
|
+
expect(await res.text()).toBe('post /user/register')
|
|
467
|
+
|
|
468
|
+
res = await app.request('http://localhost/user/123/profile', { method: 'GET' })
|
|
469
|
+
expect(res.status).toBe(200)
|
|
470
|
+
expect(await res.text()).toBe('get /user/123/profile')
|
|
471
|
+
|
|
472
|
+
res = await app.request('http://localhost/add-path-after-route-call', { method: 'GET' })
|
|
473
|
+
expect(res.status).toBe(200)
|
|
474
|
+
expect(await res.text()).toBe('get /add-path-after-route-call')
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
it('Should match a suffix wildcard after falling back to TrieRouter', async () => {
|
|
478
|
+
const app = new App()
|
|
479
|
+
const sub = new App()
|
|
480
|
+
sub.post('/items', (c) => c.text('items'))
|
|
481
|
+
sub.post('/:slug', (c) => c.text('slug'))
|
|
482
|
+
app.route('/api', sub)
|
|
483
|
+
app.get('/assets*', (c) => c.text('asset'))
|
|
484
|
+
|
|
485
|
+
const res = await app.request('http://localhost/assets/app.js')
|
|
486
|
+
expect(res.status).toBe(200)
|
|
487
|
+
expect(await res.text()).toBe('asset')
|
|
488
|
+
expect(app.router.name).toBe('SmartRouter + TrieRouter')
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
it('Nested route - subApp with basePath', async () => {
|
|
492
|
+
const app = new App()
|
|
493
|
+
const book = new App().basePath('/book')
|
|
494
|
+
book.get('/', (c) => c.text('get /book'))
|
|
495
|
+
app.route('/api', book)
|
|
496
|
+
|
|
497
|
+
const res = await app.request('http://localhost/api/book', { method: 'GET' })
|
|
498
|
+
expect(res.status).toBe(200)
|
|
499
|
+
expect(await res.text()).toBe('get /book')
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
describe('Nested route - basePath of the mounted routes', () => {
|
|
503
|
+
it('Should set basePath to the mount path', () => {
|
|
504
|
+
const app = new App()
|
|
505
|
+
const sub = new App()
|
|
506
|
+
sub.get('/posts/:id', (c) => c.text('post'))
|
|
507
|
+
app.route('/:sub', sub)
|
|
508
|
+
|
|
509
|
+
expect(app.routes).toEqual([
|
|
510
|
+
{
|
|
511
|
+
basePath: '/:sub',
|
|
512
|
+
method: 'GET',
|
|
513
|
+
path: '/:sub/posts/:id',
|
|
514
|
+
handler: expect.any(Function),
|
|
515
|
+
},
|
|
516
|
+
])
|
|
517
|
+
})
|
|
518
|
+
|
|
519
|
+
it('Should accumulate basePath through nested route() calls', () => {
|
|
520
|
+
const app = new App()
|
|
521
|
+
const sub1 = new App()
|
|
522
|
+
const sub2 = new App()
|
|
523
|
+
sub2.get('/posts/:id', (c) => c.text('post'))
|
|
524
|
+
sub1.route('/:sub2', sub2)
|
|
525
|
+
app.route('/:sub1', sub1)
|
|
526
|
+
|
|
527
|
+
expect(app.routes).toEqual([
|
|
528
|
+
{
|
|
529
|
+
basePath: '/:sub1/:sub2',
|
|
530
|
+
method: 'GET',
|
|
531
|
+
path: '/:sub1/:sub2/posts/:id',
|
|
532
|
+
handler: expect.any(Function),
|
|
533
|
+
},
|
|
534
|
+
])
|
|
535
|
+
})
|
|
536
|
+
|
|
537
|
+
it('Should merge the basePath of a subApp created with basePath()', () => {
|
|
538
|
+
const app = new App()
|
|
539
|
+
const sub = new App().basePath('/book')
|
|
540
|
+
sub.get('/:id', (c) => c.text('book'))
|
|
541
|
+
app.route('/api', sub)
|
|
542
|
+
|
|
543
|
+
expect(app.routes).toEqual([
|
|
544
|
+
{
|
|
545
|
+
basePath: '/api/book',
|
|
546
|
+
method: 'GET',
|
|
547
|
+
path: '/api/book/:id',
|
|
548
|
+
handler: expect.any(Function),
|
|
549
|
+
},
|
|
550
|
+
])
|
|
551
|
+
})
|
|
552
|
+
})
|
|
553
|
+
|
|
554
|
+
it('Multiple route', async () => {
|
|
555
|
+
const app = new App()
|
|
556
|
+
|
|
557
|
+
const book = new App()
|
|
558
|
+
book.get('/hello', (c) => c.text('get /book/hello'))
|
|
559
|
+
|
|
560
|
+
const user = new App()
|
|
561
|
+
user.get('/hello', (c) => c.text('get /user/hello'))
|
|
562
|
+
|
|
563
|
+
app.route('/book', book).route('/user', user)
|
|
564
|
+
|
|
565
|
+
let res = await app.request('http://localhost/book/hello', { method: 'GET' })
|
|
566
|
+
expect(res.status).toBe(200)
|
|
567
|
+
expect(await res.text()).toBe('get /book/hello')
|
|
568
|
+
|
|
569
|
+
res = await app.request('http://localhost/user/hello', { method: 'GET' })
|
|
570
|
+
expect(res.status).toBe(200)
|
|
571
|
+
expect(await res.text()).toBe('get /user/hello')
|
|
572
|
+
})
|
|
573
|
+
|
|
574
|
+
describe('Nested route with middleware', () => {
|
|
575
|
+
const api = new App()
|
|
576
|
+
const api2 = api.use('*', async (_c, next) => await next())
|
|
577
|
+
|
|
578
|
+
it('Should mount routes with no type errors', () => {
|
|
579
|
+
const app = new App().route('/api', api2)
|
|
580
|
+
})
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
describe('Grouped route', () => {
|
|
584
|
+
let one: App, two: App, three: App
|
|
585
|
+
|
|
586
|
+
beforeEach(() => {
|
|
587
|
+
one = new App()
|
|
588
|
+
two = new App()
|
|
589
|
+
three = new App()
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
it('only works with correct order', async () => {
|
|
593
|
+
three.get('/hi', (c) => c.text('hi'))
|
|
594
|
+
two.route('/three', three)
|
|
595
|
+
one.route('/two', two)
|
|
596
|
+
|
|
597
|
+
const { status } = await one.request('http://localhost/two/three/hi', { method: 'GET' })
|
|
598
|
+
expect(status).toBe(200)
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
it('fails with incorrect order 1', async () => {
|
|
602
|
+
three.get('/hi', (c) => c.text('hi'))
|
|
603
|
+
one.route('/two', two)
|
|
604
|
+
two.route('/three', three)
|
|
605
|
+
|
|
606
|
+
const { status } = await one.request('http://localhost/two/three/hi', { method: 'GET' })
|
|
607
|
+
expect(status).toBe(404)
|
|
608
|
+
})
|
|
609
|
+
|
|
610
|
+
it('fails with incorrect order 2', async () => {
|
|
611
|
+
two.route('/three', three)
|
|
612
|
+
three.get('/hi', (c) => c.text('hi'))
|
|
613
|
+
one.route('/two', two)
|
|
614
|
+
|
|
615
|
+
const { status } = await one.request('http://localhost/two/three/hi', { method: 'GET' })
|
|
616
|
+
expect(status).toBe(404)
|
|
617
|
+
})
|
|
618
|
+
|
|
619
|
+
it('fails with incorrect order 3', async () => {
|
|
620
|
+
two.route('/three', three)
|
|
621
|
+
one.route('/two', two)
|
|
622
|
+
three.get('/hi', (c) => c.text('hi'))
|
|
623
|
+
|
|
624
|
+
const { status } = await one.request('http://localhost/two/three/hi', { method: 'GET' })
|
|
625
|
+
expect(status).toBe(404)
|
|
626
|
+
})
|
|
627
|
+
|
|
628
|
+
it('fails with incorrect order 4', async () => {
|
|
629
|
+
one.route('/two', two)
|
|
630
|
+
three.get('/hi', (c) => c.text('hi'))
|
|
631
|
+
two.route('/three', three)
|
|
632
|
+
|
|
633
|
+
const { status } = await one.request('http://localhost/two/three/hi', { method: 'GET' })
|
|
634
|
+
expect(status).toBe(404)
|
|
635
|
+
})
|
|
636
|
+
|
|
637
|
+
it('fails with incorrect order 5', async () => {
|
|
638
|
+
one.route('/two', two)
|
|
639
|
+
two.route('/three', three)
|
|
640
|
+
three.get('/hi', (c) => c.text('hi'))
|
|
641
|
+
|
|
642
|
+
const { status } = await one.request('http://localhost/two/three/hi', { method: 'GET' })
|
|
643
|
+
expect(status).toBe(404)
|
|
644
|
+
})
|
|
645
|
+
})
|
|
646
|
+
|
|
647
|
+
it('routing with hostname', async () => {
|
|
648
|
+
const app = new App({
|
|
649
|
+
getPath: (req) => req.url.replace(/^https?:\/(.+?)$/, '$1'),
|
|
650
|
+
})
|
|
651
|
+
|
|
652
|
+
const sub = new App()
|
|
653
|
+
sub.get('/', (c) => c.text('hello sub'))
|
|
654
|
+
sub.get('/foo', (c) => c.text('hello sub foo'))
|
|
655
|
+
|
|
656
|
+
app.get('/www1.example.com/hello', () => new Response('hello www1'))
|
|
657
|
+
app.get('/www2.example.com/hello', () => new Response('hello www2'))
|
|
658
|
+
|
|
659
|
+
app.get('/www1.example.com/', (c) => c.text('hello www1 root'))
|
|
660
|
+
app.route('/www1.example.com/sub', sub)
|
|
661
|
+
|
|
662
|
+
let res = await app.request('http://www1.example.com/hello')
|
|
663
|
+
expect(res).not.toBeNull()
|
|
664
|
+
expect(res.status).toBe(200)
|
|
665
|
+
expect(await res.text()).toBe('hello www1')
|
|
666
|
+
|
|
667
|
+
res = await app.request('http://www2.example.com/hello')
|
|
668
|
+
expect(res).not.toBeNull()
|
|
669
|
+
expect(res.status).toBe(200)
|
|
670
|
+
expect(await res.text()).toBe('hello www2')
|
|
671
|
+
|
|
672
|
+
res = await app.request('http://www1.example.com/')
|
|
673
|
+
expect(res).not.toBeNull()
|
|
674
|
+
expect(res.status).toBe(200)
|
|
675
|
+
expect(await res.text()).toBe('hello www1 root')
|
|
676
|
+
|
|
677
|
+
res = await app.request('http://www1.example.com/sub')
|
|
678
|
+
expect(res).not.toBeNull()
|
|
679
|
+
expect(res.status).toBe(200)
|
|
680
|
+
expect(await res.text()).toBe('hello sub')
|
|
681
|
+
|
|
682
|
+
res = await app.request('http://www1.example.com/sub/foo')
|
|
683
|
+
expect(res).not.toBeNull()
|
|
684
|
+
expect(res.status).toBe(200)
|
|
685
|
+
expect(await res.text()).toBe('hello sub foo')
|
|
686
|
+
})
|
|
687
|
+
|
|
688
|
+
it('routing with request header', async () => {
|
|
689
|
+
const app = new App({
|
|
690
|
+
getPath: (req) =>
|
|
691
|
+
'/' + req.headers.get('host') + req.url.replace(/^https?:\/\/[^/]+(\/[^?]*)/, '$1'),
|
|
692
|
+
})
|
|
693
|
+
|
|
694
|
+
const sub = new App()
|
|
695
|
+
sub.get('/', (c) => c.text('hello sub'))
|
|
696
|
+
sub.get('/foo', (c) => c.text('hello sub foo'))
|
|
697
|
+
|
|
698
|
+
app.get('/www1.example.com/hello', () => new Response('hello www1'))
|
|
699
|
+
app.get('/www2.example.com/hello', () => new Response('hello www2'))
|
|
700
|
+
|
|
701
|
+
app.get('/www1.example.com/', (c) => c.text('hello www1 root'))
|
|
702
|
+
app.route('/www1.example.com/sub', sub)
|
|
703
|
+
|
|
704
|
+
let res = await app.request('http://www1.example.com/hello', {
|
|
705
|
+
headers: {
|
|
706
|
+
host: 'www1.example.com',
|
|
707
|
+
},
|
|
708
|
+
})
|
|
709
|
+
expect(res).not.toBeNull()
|
|
710
|
+
expect(res.status).toBe(200)
|
|
711
|
+
expect(await res.text()).toBe('hello www1')
|
|
712
|
+
|
|
713
|
+
res = await app.request('http://www2.example.com/hello', {
|
|
714
|
+
headers: {
|
|
715
|
+
host: 'www2.example.com',
|
|
716
|
+
},
|
|
717
|
+
})
|
|
718
|
+
expect(res).not.toBeNull()
|
|
719
|
+
expect(res.status).toBe(200)
|
|
720
|
+
expect(await res.text()).toBe('hello www2')
|
|
721
|
+
|
|
722
|
+
res = await app.request('http://www1.example.com/', {
|
|
723
|
+
headers: {
|
|
724
|
+
host: 'www1.example.com',
|
|
725
|
+
},
|
|
726
|
+
})
|
|
727
|
+
expect(res).not.toBeNull()
|
|
728
|
+
expect(res.status).toBe(200)
|
|
729
|
+
expect(await res.text()).toBe('hello www1 root')
|
|
730
|
+
|
|
731
|
+
res = await app.request('http://www1.example.com/sub', {
|
|
732
|
+
headers: {
|
|
733
|
+
host: 'www1.example.com',
|
|
734
|
+
},
|
|
735
|
+
})
|
|
736
|
+
expect(res).not.toBeNull()
|
|
737
|
+
expect(res.status).toBe(200)
|
|
738
|
+
expect(await res.text()).toBe('hello sub')
|
|
739
|
+
|
|
740
|
+
res = await app.request('http://www1.example.com/sub/foo', {
|
|
741
|
+
headers: {
|
|
742
|
+
host: 'www1.example.com',
|
|
743
|
+
},
|
|
744
|
+
})
|
|
745
|
+
expect(res).not.toBeNull()
|
|
746
|
+
expect(res.status).toBe(200)
|
|
747
|
+
expect(await res.text()).toBe('hello sub foo')
|
|
748
|
+
expect(res.status).toBe(200)
|
|
749
|
+
})
|
|
750
|
+
|
|
751
|
+
describe('routing with the bindings value', () => {
|
|
752
|
+
const app = new App<{ Bindings: { host: string } }>({
|
|
753
|
+
getPath: (req, options) => {
|
|
754
|
+
const url = new URL(req.url)
|
|
755
|
+
const host = options?.env?.host
|
|
756
|
+
const prefix = url.host === host ? '/FOO' : ''
|
|
757
|
+
return url.pathname === '/' ? prefix : `${prefix}${url.pathname}`
|
|
758
|
+
},
|
|
759
|
+
})
|
|
760
|
+
|
|
761
|
+
app.get('/about', (c) => c.text('About root'))
|
|
762
|
+
app.get('/FOO/about', (c) => c.text('About FOO'))
|
|
763
|
+
|
|
764
|
+
it('Should return 200 without specifying a hostname', async () => {
|
|
765
|
+
const res = await app.request('/about')
|
|
766
|
+
expect(res.status).toBe(200)
|
|
767
|
+
expect(await res.text()).toBe('About root')
|
|
768
|
+
})
|
|
769
|
+
|
|
770
|
+
it('Should return 200 with specifying the hostname in env', async () => {
|
|
771
|
+
const req = new Request('http://foo.localhost/about')
|
|
772
|
+
const res = await app.fetch(req, { host: 'foo.localhost' })
|
|
773
|
+
expect(res.status).toBe(200)
|
|
774
|
+
expect(await res.text()).toBe('About FOO')
|
|
775
|
+
})
|
|
776
|
+
})
|
|
777
|
+
|
|
778
|
+
describe('Chained route', () => {
|
|
779
|
+
const app = new App()
|
|
780
|
+
|
|
781
|
+
app
|
|
782
|
+
.get('/chained/:abc', (c) => {
|
|
783
|
+
const abc = c.req.param('abc')
|
|
784
|
+
return c.text(`GET for ${abc}`)
|
|
785
|
+
})
|
|
786
|
+
.post((c) => {
|
|
787
|
+
const abc = c.req.param('abc')
|
|
788
|
+
return c.text(`POST for ${abc}`)
|
|
789
|
+
})
|
|
790
|
+
it('Should return 200 response from GET request', async () => {
|
|
791
|
+
const res = await app.request('http://localhost/chained/abc', { method: 'GET' })
|
|
792
|
+
expect(res.status).toBe(200)
|
|
793
|
+
expect(await res.text()).toBe('GET for abc')
|
|
794
|
+
})
|
|
795
|
+
it('Should return 200 response from POST request', async () => {
|
|
796
|
+
const res = await app.request('http://localhost/chained/abc', { method: 'POST' })
|
|
797
|
+
expect(res.status).toBe(200)
|
|
798
|
+
expect(await res.text()).toBe('POST for abc')
|
|
799
|
+
})
|
|
800
|
+
it('Should return 404 response from PUT request', async () => {
|
|
801
|
+
const res = await app.request('http://localhost/chained/abc', { method: 'PUT' })
|
|
802
|
+
expect(res.status).toBe(404)
|
|
803
|
+
})
|
|
804
|
+
})
|
|
805
|
+
|
|
806
|
+
describe('Encoded path', () => {
|
|
807
|
+
let app: App
|
|
808
|
+
beforeEach(() => {
|
|
809
|
+
app = new App()
|
|
810
|
+
})
|
|
811
|
+
|
|
812
|
+
it('should decode path parameter', async () => {
|
|
813
|
+
app.get('/users/:id', (c) => c.text(`id is ${c.req.param('id')}`))
|
|
814
|
+
|
|
815
|
+
const res = await app.request('http://localhost/users/%C3%A7awa%20y%C3%AE%3F')
|
|
816
|
+
expect(res.status).toBe(200)
|
|
817
|
+
expect(await res.text()).toBe('id is çawa yî?')
|
|
818
|
+
})
|
|
819
|
+
|
|
820
|
+
it('should decode "/"', async () => {
|
|
821
|
+
app.get('/users/:id', (c) => c.text(`id is ${c.req.param('id')}`))
|
|
822
|
+
|
|
823
|
+
const res = await app.request('http://localhost/users/acryl%2Fposts') // %2F is '/'
|
|
824
|
+
expect(res.status).toBe(200)
|
|
825
|
+
expect(await res.text()).toBe('id is acryl/posts')
|
|
826
|
+
})
|
|
827
|
+
|
|
828
|
+
it('should decode alphabets', async () => {
|
|
829
|
+
app.get('/users/static', (c) => c.text('static'))
|
|
830
|
+
|
|
831
|
+
const res = await app.request('http://localhost/users/%73tatic') // %73 is 's'
|
|
832
|
+
expect(res.status).toBe(200)
|
|
833
|
+
expect(await res.text()).toBe('static')
|
|
834
|
+
})
|
|
835
|
+
|
|
836
|
+
it('should decode alphabets with invalid UTF-8 sequence', async () => {
|
|
837
|
+
app.get('/static/:path', (c) => {
|
|
838
|
+
return c.text(`by c.req.param: ${c.req.param('path')}`)
|
|
839
|
+
})
|
|
840
|
+
|
|
841
|
+
const res = await app.request('http://localhost/%73tatic/%A4%A2') // %73 is 's', %A4%A2 is invalid UTF-8 sequence
|
|
842
|
+
expect(res.status).toBe(200)
|
|
843
|
+
expect(await res.text()).toBe('by c.req.param: %A4%A2')
|
|
844
|
+
})
|
|
845
|
+
|
|
846
|
+
it('should decode alphabets with invalid percent encoding', async () => {
|
|
847
|
+
app.get('/static/:path', (c) => {
|
|
848
|
+
return c.text(`by c.req.param: ${c.req.param('path')}`)
|
|
849
|
+
})
|
|
850
|
+
|
|
851
|
+
const res = await app.request('http://localhost/%73tatic/%a') // %73 is 's', %a is invalid percent encoding
|
|
852
|
+
expect(res.status).toBe(200)
|
|
853
|
+
expect(await res.text()).toBe('by c.req.param: %a')
|
|
854
|
+
})
|
|
855
|
+
|
|
856
|
+
it('should not double decode', async () => {
|
|
857
|
+
app.get('/users/:id', (c) => c.text(`posts of ${c.req.param('id')}`))
|
|
858
|
+
|
|
859
|
+
const res = await app.request('http://localhost/users/%2525') // %25 is '%'
|
|
860
|
+
expect(res.status).toBe(200)
|
|
861
|
+
expect(await res.text()).toBe('posts of %25')
|
|
862
|
+
})
|
|
863
|
+
})
|
|
864
|
+
})
|
|
865
|
+
|
|
866
|
+
describe('param and query', () => {
|
|
867
|
+
const apps: Record<string, App> = {}
|
|
868
|
+
apps['get by name'] = (() => {
|
|
869
|
+
const app = new App()
|
|
870
|
+
|
|
871
|
+
app.get('/entry/:id', (c) => {
|
|
872
|
+
const id = c.req.param('id')
|
|
873
|
+
return c.text(`id is ${id}`)
|
|
874
|
+
})
|
|
875
|
+
|
|
876
|
+
app.get('/date/:date{[0-9]+}', (c) => {
|
|
877
|
+
const date = c.req.param('date')
|
|
878
|
+
return c.text(`date is ${date}`)
|
|
879
|
+
})
|
|
880
|
+
|
|
881
|
+
app.get('/search', (c) => {
|
|
882
|
+
const name = c.req.query('name')
|
|
883
|
+
return c.text(`name is ${name}`)
|
|
884
|
+
})
|
|
885
|
+
|
|
886
|
+
app.get('/multiple-values', (c) => {
|
|
887
|
+
const queries = c.req.queries('q') ?? throwExpression('missing query values')
|
|
888
|
+
const limit = c.req.queries('limit') ?? throwExpression('missing query values')
|
|
889
|
+
return c.text(`q is ${queries[0]} and ${queries[1]}, limit is ${limit[0]}`)
|
|
890
|
+
})
|
|
891
|
+
|
|
892
|
+
app.get('/add-header', (c) => {
|
|
893
|
+
const bar = c.req.header('X-Foo')
|
|
894
|
+
return c.text(`foo is ${bar}`)
|
|
895
|
+
})
|
|
896
|
+
|
|
897
|
+
return app
|
|
898
|
+
})()
|
|
899
|
+
|
|
900
|
+
apps['get all as an object'] = (() => {
|
|
901
|
+
const app = new App()
|
|
902
|
+
|
|
903
|
+
app.get('/entry/:id', (c) => {
|
|
904
|
+
const { id } = c.req.param()
|
|
905
|
+
return c.text(`id is ${id}`)
|
|
906
|
+
})
|
|
907
|
+
|
|
908
|
+
app.get('/date/:date{[0-9]+}', (c) => {
|
|
909
|
+
const { date } = c.req.param()
|
|
910
|
+
return c.text(`date is ${date}`)
|
|
911
|
+
})
|
|
912
|
+
|
|
913
|
+
app.get('/search', (c) => {
|
|
914
|
+
const { name } = c.req.query()
|
|
915
|
+
return c.text(`name is ${name}`)
|
|
916
|
+
})
|
|
917
|
+
|
|
918
|
+
app.get('/multiple-values', (c) => {
|
|
919
|
+
const { q, limit } = c.req.queries()
|
|
920
|
+
return c.text(`q is ${q[0]} and ${q[1]}, limit is ${limit[0]}`)
|
|
921
|
+
})
|
|
922
|
+
|
|
923
|
+
app.get('/add-header', (c) => {
|
|
924
|
+
const { 'x-foo': bar } = c.req.header()
|
|
925
|
+
return c.text(`foo is ${bar}`)
|
|
926
|
+
})
|
|
927
|
+
|
|
928
|
+
return app
|
|
929
|
+
})()
|
|
930
|
+
|
|
931
|
+
describe.each(Object.keys(apps))('%s', (name) => {
|
|
932
|
+
const app = apps[name]
|
|
933
|
+
|
|
934
|
+
it('param of /entry/:id is found', async () => {
|
|
935
|
+
const res = await app.request('http://localhost/entry/123')
|
|
936
|
+
expect(res.status).toBe(200)
|
|
937
|
+
expect(await res.text()).toBe('id is 123')
|
|
938
|
+
})
|
|
939
|
+
|
|
940
|
+
it('param of /entry/:id is found, even for Array object method names', async () => {
|
|
941
|
+
const res = await app.request('http://localhost/entry/key')
|
|
942
|
+
expect(res.status).toBe(200)
|
|
943
|
+
expect(await res.text()).toBe('id is key')
|
|
944
|
+
})
|
|
945
|
+
|
|
946
|
+
it('param of /entry/:id is decoded', async () => {
|
|
947
|
+
const res = await app.request('http://localhost/entry/%C3%A7awa%20y%C3%AE%3F')
|
|
948
|
+
expect(res.status).toBe(200)
|
|
949
|
+
expect(await res.text()).toBe('id is çawa yî?')
|
|
950
|
+
})
|
|
951
|
+
|
|
952
|
+
it('param of /date/:date is found', async () => {
|
|
953
|
+
const res = await app.request('http://localhost/date/0401')
|
|
954
|
+
expect(res.status).toBe(200)
|
|
955
|
+
expect(await res.text()).toBe('date is 0401')
|
|
956
|
+
})
|
|
957
|
+
|
|
958
|
+
it('query of /search?name=sam is found', async () => {
|
|
959
|
+
const res = await app.request('http://localhost/search?name=sam')
|
|
960
|
+
expect(res.status).toBe(200)
|
|
961
|
+
expect(await res.text()).toBe('name is sam')
|
|
962
|
+
})
|
|
963
|
+
|
|
964
|
+
it('query of /search?name=sam&name=tom is found', async () => {
|
|
965
|
+
const res = await app.request('http://localhost/search?name=sam&name=tom')
|
|
966
|
+
expect(res.status).toBe(200)
|
|
967
|
+
expect(await res.text()).toBe('name is sam')
|
|
968
|
+
})
|
|
969
|
+
|
|
970
|
+
it('query of /multiple-values?q=foo&q=bar&limit=10 is found', async () => {
|
|
971
|
+
const res = await app.request('http://localhost/multiple-values?q=foo&q=bar&limit=10')
|
|
972
|
+
expect(res.status).toBe(200)
|
|
973
|
+
expect(await res.text()).toBe('q is foo and bar, limit is 10')
|
|
974
|
+
})
|
|
975
|
+
|
|
976
|
+
it('/add-header header - X-Foo is Bar', async () => {
|
|
977
|
+
const req = new Request('http://localhost/add-header')
|
|
978
|
+
req.headers.append('X-Foo', 'Bar')
|
|
979
|
+
const res = await app.request(req)
|
|
980
|
+
expect(res.status).toBe(200)
|
|
981
|
+
expect(await res.text()).toBe('foo is Bar')
|
|
982
|
+
})
|
|
983
|
+
})
|
|
984
|
+
|
|
985
|
+
describe('param with undefined', () => {
|
|
986
|
+
const app = new App()
|
|
987
|
+
app.get('/foo/:foo', (c) => {
|
|
988
|
+
const bar = c.req.param('bar')
|
|
989
|
+
return c.json({ foo: bar })
|
|
990
|
+
})
|
|
991
|
+
it('param of /foo/foo should return undefined not "undefined"', async () => {
|
|
992
|
+
const res = await app.request('http://localhost/foo/foo')
|
|
993
|
+
expect(res.status).toBe(200)
|
|
994
|
+
expect(await res.json()).toEqual({ foo: undefined })
|
|
995
|
+
})
|
|
996
|
+
})
|
|
997
|
+
})
|
|
998
|
+
|
|
999
|
+
describe('c.req.path', () => {
|
|
1000
|
+
const app = new App()
|
|
1001
|
+
app.get('/', (c) => c.text(c.req.path))
|
|
1002
|
+
app.get('/search', (c) => c.text(c.req.path))
|
|
1003
|
+
|
|
1004
|
+
it('Should get the path `/` correctly', async () => {
|
|
1005
|
+
const res = await app.request('/')
|
|
1006
|
+
expect(res.status).toBe(200)
|
|
1007
|
+
expect(await res.text()).toBe('/')
|
|
1008
|
+
})
|
|
1009
|
+
|
|
1010
|
+
it('Should get the path `/search` correctly with a query', async () => {
|
|
1011
|
+
const res = await app.request('/search?query=acryl')
|
|
1012
|
+
expect(res.status).toBe(200)
|
|
1013
|
+
expect(await res.text()).toBe('/search')
|
|
1014
|
+
})
|
|
1015
|
+
})
|
|
1016
|
+
|
|
1017
|
+
describe('Header', () => {
|
|
1018
|
+
const app = new App()
|
|
1019
|
+
|
|
1020
|
+
app.get('/text', (c) => {
|
|
1021
|
+
return c.text('Hello')
|
|
1022
|
+
})
|
|
1023
|
+
|
|
1024
|
+
app.get('/text-with-custom-header', (c) => {
|
|
1025
|
+
c.header('X-Custom', 'Message')
|
|
1026
|
+
return c.text('Hello')
|
|
1027
|
+
})
|
|
1028
|
+
|
|
1029
|
+
it('Should return correct headers - /text', async () => {
|
|
1030
|
+
const res = await app.request('/text')
|
|
1031
|
+
expect(res.status).toBe(200)
|
|
1032
|
+
expect(res.headers.get('content-type')).toMatch(/^text\/plain/)
|
|
1033
|
+
expect(await res.text()).toBe('Hello')
|
|
1034
|
+
})
|
|
1035
|
+
|
|
1036
|
+
it('Should return correct headers - /text-with-custom-header', async () => {
|
|
1037
|
+
const res = await app.request('/text-with-custom-header')
|
|
1038
|
+
expect(res.status).toBe(200)
|
|
1039
|
+
expect(res.headers.get('x-custom')).toBe('Message')
|
|
1040
|
+
expect(res.headers.get('content-type')).toMatch(/^text\/plain/)
|
|
1041
|
+
expect(await res.text()).toBe('Hello')
|
|
1042
|
+
})
|
|
1043
|
+
})
|
|
1044
|
+
|
|
1045
|
+
describe('Middleware', () => {
|
|
1046
|
+
describe('Basic', () => {
|
|
1047
|
+
const app = new App()
|
|
1048
|
+
|
|
1049
|
+
// Custom Logger
|
|
1050
|
+
app.use('*', async (c, next) => {
|
|
1051
|
+
console.log(`${c.req.method} : ${c.req.url}`)
|
|
1052
|
+
await next()
|
|
1053
|
+
})
|
|
1054
|
+
|
|
1055
|
+
// Append Custom Header
|
|
1056
|
+
app.use('*', async (c, next) => {
|
|
1057
|
+
await next()
|
|
1058
|
+
c.res.headers.append('x-custom', 'root')
|
|
1059
|
+
})
|
|
1060
|
+
|
|
1061
|
+
app.use('/hello', async (c, next) => {
|
|
1062
|
+
await next()
|
|
1063
|
+
c.res.headers.append('x-message', 'custom-header')
|
|
1064
|
+
})
|
|
1065
|
+
|
|
1066
|
+
app.use('/hello/*', async (c, next) => {
|
|
1067
|
+
await next()
|
|
1068
|
+
c.res.headers.append('x-message-2', 'custom-header-2')
|
|
1069
|
+
})
|
|
1070
|
+
|
|
1071
|
+
app.get('/hello', (c) => {
|
|
1072
|
+
return c.text('hello')
|
|
1073
|
+
})
|
|
1074
|
+
|
|
1075
|
+
app.use('/json/*', async (c, next) => {
|
|
1076
|
+
c.res.headers.append('foo', 'bar')
|
|
1077
|
+
await next()
|
|
1078
|
+
})
|
|
1079
|
+
|
|
1080
|
+
app.get('/json', (c) => {
|
|
1081
|
+
// With a raw response
|
|
1082
|
+
return new Response(
|
|
1083
|
+
JSON.stringify({
|
|
1084
|
+
message: 'hello',
|
|
1085
|
+
}),
|
|
1086
|
+
{
|
|
1087
|
+
headers: {
|
|
1088
|
+
'content-type': 'application/json',
|
|
1089
|
+
},
|
|
1090
|
+
}
|
|
1091
|
+
)
|
|
1092
|
+
})
|
|
1093
|
+
|
|
1094
|
+
app.get('/hello/:message', (c) => {
|
|
1095
|
+
const message = c.req.param('message')
|
|
1096
|
+
return c.text(`${message}`)
|
|
1097
|
+
})
|
|
1098
|
+
|
|
1099
|
+
app.get('/error', () => {
|
|
1100
|
+
throw new Error('Error!')
|
|
1101
|
+
})
|
|
1102
|
+
|
|
1103
|
+
app.notFound((c) => {
|
|
1104
|
+
return c.text('Not Found Foo', 404)
|
|
1105
|
+
})
|
|
1106
|
+
|
|
1107
|
+
it('logging and custom header', async () => {
|
|
1108
|
+
const res = await app.request('http://localhost/hello')
|
|
1109
|
+
expect(res.status).toBe(200)
|
|
1110
|
+
expect(await res.text()).toBe('hello')
|
|
1111
|
+
expect(res.headers.get('x-custom')).toBe('root')
|
|
1112
|
+
expect(res.headers.get('x-message')).toBe('custom-header')
|
|
1113
|
+
expect(res.headers.get('x-message-2')).toBe('custom-header-2')
|
|
1114
|
+
})
|
|
1115
|
+
|
|
1116
|
+
it('logging and custom header with named param', async () => {
|
|
1117
|
+
const res = await app.request('http://localhost/hello/message')
|
|
1118
|
+
expect(res.status).toBe(200)
|
|
1119
|
+
expect(await res.text()).toBe('message')
|
|
1120
|
+
expect(res.headers.get('x-custom')).toBe('root')
|
|
1121
|
+
expect(res.headers.get('x-message-2')).toBe('custom-header-2')
|
|
1122
|
+
})
|
|
1123
|
+
|
|
1124
|
+
it('should return correct the content-type header', async () => {
|
|
1125
|
+
const res = await app.request('http://localhost/json')
|
|
1126
|
+
expect(res.status).toBe(200)
|
|
1127
|
+
expect(res.headers.get('content-type')).toMatch(/^application\/json/)
|
|
1128
|
+
})
|
|
1129
|
+
|
|
1130
|
+
it('not found', async () => {
|
|
1131
|
+
const res = await app.request('http://localhost/foo')
|
|
1132
|
+
expect(res.status).toBe(404)
|
|
1133
|
+
expect(await res.text()).toBe('Not Found Foo')
|
|
1134
|
+
})
|
|
1135
|
+
|
|
1136
|
+
it('internal server error', async () => {
|
|
1137
|
+
const res = await app.request('http://localhost/error')
|
|
1138
|
+
expect(res.status).toBe(500)
|
|
1139
|
+
console.log(await res.text())
|
|
1140
|
+
})
|
|
1141
|
+
})
|
|
1142
|
+
|
|
1143
|
+
describe('Chained route', () => {
|
|
1144
|
+
const app = new App()
|
|
1145
|
+
app
|
|
1146
|
+
.use('/chained/*', async (c, next) => {
|
|
1147
|
+
c.req.raw.headers.append('x-before', 'abc')
|
|
1148
|
+
await next()
|
|
1149
|
+
})
|
|
1150
|
+
.use(async (c, next) => {
|
|
1151
|
+
await next()
|
|
1152
|
+
c.header(
|
|
1153
|
+
'x-after',
|
|
1154
|
+
c.req.header('x-before') ?? throwExpression('missing `x-before` header')
|
|
1155
|
+
)
|
|
1156
|
+
})
|
|
1157
|
+
.get('/chained/abc', (c) => {
|
|
1158
|
+
return c.text('GET chained')
|
|
1159
|
+
})
|
|
1160
|
+
it('GET /chained/abc', async () => {
|
|
1161
|
+
const res = await app.request('http://localhost/chained/abc')
|
|
1162
|
+
expect(res.status).toBe(200)
|
|
1163
|
+
expect(await res.text()).toBe('GET chained')
|
|
1164
|
+
expect(res.headers.get('x-after')).toBe('abc')
|
|
1165
|
+
})
|
|
1166
|
+
})
|
|
1167
|
+
|
|
1168
|
+
describe('Multiple handler', () => {
|
|
1169
|
+
const app = new App()
|
|
1170
|
+
app
|
|
1171
|
+
.use(
|
|
1172
|
+
'/multiple/*',
|
|
1173
|
+
async (c, next) => {
|
|
1174
|
+
c.req.raw.headers.append('x-before', 'abc')
|
|
1175
|
+
await next()
|
|
1176
|
+
},
|
|
1177
|
+
async (c, next) => {
|
|
1178
|
+
await next()
|
|
1179
|
+
c.header(
|
|
1180
|
+
'x-after',
|
|
1181
|
+
c.req.header('x-before') ?? throwExpression('missing `x-before` header')
|
|
1182
|
+
)
|
|
1183
|
+
}
|
|
1184
|
+
)
|
|
1185
|
+
.get('/multiple/abc', (c) => {
|
|
1186
|
+
return c.text('GET multiple')
|
|
1187
|
+
})
|
|
1188
|
+
it('GET /multiple/abc', async () => {
|
|
1189
|
+
const res = await app.request('http://localhost/multiple/abc')
|
|
1190
|
+
expect(res.status).toBe(200)
|
|
1191
|
+
expect(await res.text()).toBe('GET multiple')
|
|
1192
|
+
expect(res.headers.get('x-after')).toBe('abc')
|
|
1193
|
+
})
|
|
1194
|
+
})
|
|
1195
|
+
|
|
1196
|
+
describe('Overwrite the response from middleware after next()', () => {
|
|
1197
|
+
const app = new App()
|
|
1198
|
+
|
|
1199
|
+
app.use('/normal', async (c, next) => {
|
|
1200
|
+
await next()
|
|
1201
|
+
c.res = new Response('Middleware')
|
|
1202
|
+
})
|
|
1203
|
+
|
|
1204
|
+
app.use('/overwrite', async (c, next) => {
|
|
1205
|
+
await next()
|
|
1206
|
+
c.res = undefined
|
|
1207
|
+
c.res = new Response('Middleware')
|
|
1208
|
+
})
|
|
1209
|
+
|
|
1210
|
+
app.get('*', (c) => {
|
|
1211
|
+
c.header('x-custom', 'foo')
|
|
1212
|
+
return c.text('Handler')
|
|
1213
|
+
})
|
|
1214
|
+
|
|
1215
|
+
it('Should have the custom header', async () => {
|
|
1216
|
+
const res = await app.request('/normal')
|
|
1217
|
+
expect(res.headers.get('x-custom')).toBe('foo')
|
|
1218
|
+
})
|
|
1219
|
+
|
|
1220
|
+
it('Should not have the custom header', async () => {
|
|
1221
|
+
const res = await app.request('/overwrite')
|
|
1222
|
+
expect(res.headers.get('x-custom')).toBe(null)
|
|
1223
|
+
})
|
|
1224
|
+
})
|
|
1225
|
+
})
|
|
1226
|
+
|
|
1227
|
+
describe('Builtin Middleware', () => {
|
|
1228
|
+
const app = new App()
|
|
1229
|
+
app.use('/abc', poweredBy())
|
|
1230
|
+
app.use('/def', async (c, next) => {
|
|
1231
|
+
const middleware = poweredBy()
|
|
1232
|
+
await middleware(c, next)
|
|
1233
|
+
})
|
|
1234
|
+
app.get('/abc', () => new Response())
|
|
1235
|
+
app.get('/def', () => new Response())
|
|
1236
|
+
|
|
1237
|
+
it('"powered-by" middleware', async () => {
|
|
1238
|
+
const res = await app.request('http://localhost/abc')
|
|
1239
|
+
expect(res.headers.get('x-powered-by')).toBe('ACRYL')
|
|
1240
|
+
})
|
|
1241
|
+
|
|
1242
|
+
it('"powered-by" middleware in a handler', async () => {
|
|
1243
|
+
const res = await app.request('http://localhost/def')
|
|
1244
|
+
expect(res.headers.get('x-powered-by')).toBe('ACRYL')
|
|
1245
|
+
})
|
|
1246
|
+
})
|
|
1247
|
+
|
|
1248
|
+
describe('Middleware with app.HTTP_METHOD', () => {
|
|
1249
|
+
describe('Basic', () => {
|
|
1250
|
+
const app = new App()
|
|
1251
|
+
|
|
1252
|
+
app.all('*', async (c, next) => {
|
|
1253
|
+
c.header('x-before-dispatch', 'foo')
|
|
1254
|
+
await next()
|
|
1255
|
+
c.header('x-custom-message', 'hello')
|
|
1256
|
+
})
|
|
1257
|
+
|
|
1258
|
+
const customHeader = async (c: Context, next: Next) => {
|
|
1259
|
+
c.req.raw.headers.append('x-custom-foo', 'bar')
|
|
1260
|
+
await next()
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
const customHeader2 = async (c: Context, next: Next) => {
|
|
1264
|
+
await next()
|
|
1265
|
+
c.header('x-custom-foo-2', 'bar-2')
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
app
|
|
1269
|
+
.get('/abc', customHeader, (c) => {
|
|
1270
|
+
const foo = c.req.header('x-custom-foo') || ''
|
|
1271
|
+
return c.text(foo)
|
|
1272
|
+
})
|
|
1273
|
+
.post(customHeader2, (c) => {
|
|
1274
|
+
return c.text('POST /abc')
|
|
1275
|
+
})
|
|
1276
|
+
|
|
1277
|
+
it('GET /abc', async () => {
|
|
1278
|
+
const res = await app.request('http://localhost/abc')
|
|
1279
|
+
expect(res.status).toBe(200)
|
|
1280
|
+
expect(res.headers.get('x-custom-message')).toBe('hello')
|
|
1281
|
+
expect(res.headers.get('x-before-dispatch')).toBe('foo')
|
|
1282
|
+
expect(await res.text()).toBe('bar')
|
|
1283
|
+
})
|
|
1284
|
+
it('POST /abc', async () => {
|
|
1285
|
+
const res = await app.request('http://localhost/abc', { method: 'POST' })
|
|
1286
|
+
expect(res.status).toBe(200)
|
|
1287
|
+
expect(await res.text()).toBe('POST /abc')
|
|
1288
|
+
expect(res.headers.get('x-custom-foo-2')).toBe('bar-2')
|
|
1289
|
+
})
|
|
1290
|
+
})
|
|
1291
|
+
|
|
1292
|
+
describe('With builtin middleware', () => {
|
|
1293
|
+
const app = new App()
|
|
1294
|
+
app.get('/abc', poweredBy(), (c) => {
|
|
1295
|
+
return c.text('GET /abc')
|
|
1296
|
+
})
|
|
1297
|
+
it('GET /abc', async () => {
|
|
1298
|
+
const res = await app.request('http://localhost/abc')
|
|
1299
|
+
expect(res.status).toBe(200)
|
|
1300
|
+
expect(await res.text()).toBe('GET /abc')
|
|
1301
|
+
expect(res.headers.get('x-powered-by')).toBe('ACRYL')
|
|
1302
|
+
})
|
|
1303
|
+
})
|
|
1304
|
+
})
|
|
1305
|
+
|
|
1306
|
+
describe('Not Found', () => {
|
|
1307
|
+
const app = new App()
|
|
1308
|
+
|
|
1309
|
+
app.notFound((c) => {
|
|
1310
|
+
return c.text('Custom 404 Not Found', 404)
|
|
1311
|
+
})
|
|
1312
|
+
|
|
1313
|
+
app.get('/hello', (c) => {
|
|
1314
|
+
return c.text('hello')
|
|
1315
|
+
})
|
|
1316
|
+
|
|
1317
|
+
app.get('/notfound', (c) => {
|
|
1318
|
+
return c.notFound()
|
|
1319
|
+
})
|
|
1320
|
+
|
|
1321
|
+
it('Custom 404 Not Found', async () => {
|
|
1322
|
+
let res = await app.request('http://localhost/hello')
|
|
1323
|
+
expect(res.status).toBe(200)
|
|
1324
|
+
res = await app.request('http://localhost/notfound')
|
|
1325
|
+
expect(res.status).toBe(404)
|
|
1326
|
+
res = await app.request('http://localhost/foo')
|
|
1327
|
+
expect(res.status).toBe(404)
|
|
1328
|
+
expect(await res.text()).toBe('Custom 404 Not Found')
|
|
1329
|
+
})
|
|
1330
|
+
|
|
1331
|
+
describe('Not Found with a middleware', () => {
|
|
1332
|
+
const app = new App()
|
|
1333
|
+
|
|
1334
|
+
app.get('/', (c) => c.text('hello'))
|
|
1335
|
+
app.use('*', async (c, next) => {
|
|
1336
|
+
await next()
|
|
1337
|
+
c.res = new Response((await c.res.text()) + ' + Middleware', c.res)
|
|
1338
|
+
})
|
|
1339
|
+
|
|
1340
|
+
it('Custom 404 Not Found', async () => {
|
|
1341
|
+
let res = await app.request('http://localhost/')
|
|
1342
|
+
expect(res.status).toBe(200)
|
|
1343
|
+
expect(await res.text()).toBe('hello')
|
|
1344
|
+
res = await app.request('http://localhost/foo')
|
|
1345
|
+
expect(res.status).toBe(404)
|
|
1346
|
+
expect(await res.text()).toBe('404 Not Found + Middleware')
|
|
1347
|
+
})
|
|
1348
|
+
})
|
|
1349
|
+
|
|
1350
|
+
describe('Not Found with some middleware', () => {
|
|
1351
|
+
const app = new App()
|
|
1352
|
+
|
|
1353
|
+
app.get('/', (c) => c.text('hello'))
|
|
1354
|
+
app.use('*', async (c, next) => {
|
|
1355
|
+
await next()
|
|
1356
|
+
c.res = new Response((await c.res.text()) + ' + Middleware 1', c.res)
|
|
1357
|
+
})
|
|
1358
|
+
app.use('*', async (c, next) => {
|
|
1359
|
+
await next()
|
|
1360
|
+
c.res = new Response((await c.res.text()) + ' + Middleware 2', c.res)
|
|
1361
|
+
})
|
|
1362
|
+
|
|
1363
|
+
it('Custom 404 Not Found', async () => {
|
|
1364
|
+
let res = await app.request('http://localhost/')
|
|
1365
|
+
expect(res.status).toBe(200)
|
|
1366
|
+
expect(await res.text()).toBe('hello')
|
|
1367
|
+
res = await app.request('http://localhost/foo')
|
|
1368
|
+
expect(res.status).toBe(404)
|
|
1369
|
+
expect(await res.text()).toBe('404 Not Found + Middleware 2 + Middleware 1')
|
|
1370
|
+
})
|
|
1371
|
+
})
|
|
1372
|
+
|
|
1373
|
+
describe('No response from a handler', () => {
|
|
1374
|
+
const app = new App()
|
|
1375
|
+
|
|
1376
|
+
app.get('/', (c) => c.text('hello'))
|
|
1377
|
+
app.get('/not-found', async (c) => undefined)
|
|
1378
|
+
|
|
1379
|
+
it('Custom 404 Not Found', async () => {
|
|
1380
|
+
let res = await app.request('http://localhost/')
|
|
1381
|
+
expect(res.status).toBe(200)
|
|
1382
|
+
expect(await res.text()).toBe('hello')
|
|
1383
|
+
res = await app.request('http://localhost/not-found')
|
|
1384
|
+
expect(res.status).toBe(404)
|
|
1385
|
+
expect(await res.text()).toBe('404 Not Found')
|
|
1386
|
+
})
|
|
1387
|
+
})
|
|
1388
|
+
|
|
1389
|
+
describe('Custom 404 Not Found with a middleware like Compress Middleware', () => {
|
|
1390
|
+
const app = new App()
|
|
1391
|
+
|
|
1392
|
+
// Custom Middleware which creates a new Response object after `next()`.
|
|
1393
|
+
app.use('*', async (c, next) => {
|
|
1394
|
+
await next()
|
|
1395
|
+
c.res = new Response(await c.res.text(), c.res)
|
|
1396
|
+
})
|
|
1397
|
+
|
|
1398
|
+
app.notFound((c) => {
|
|
1399
|
+
return c.text('Custom NotFound', 404)
|
|
1400
|
+
})
|
|
1401
|
+
|
|
1402
|
+
it('Custom 404 Not Found', async () => {
|
|
1403
|
+
const res = await app.request('http://localhost/')
|
|
1404
|
+
expect(res.status).toBe(404)
|
|
1405
|
+
expect(await res.text()).toBe('Custom NotFound')
|
|
1406
|
+
})
|
|
1407
|
+
})
|
|
1408
|
+
})
|
|
1409
|
+
|
|
1410
|
+
describe('Redirect', () => {
|
|
1411
|
+
const app = new App()
|
|
1412
|
+
app.get('/redirect', (c) => {
|
|
1413
|
+
return c.redirect('/')
|
|
1414
|
+
})
|
|
1415
|
+
|
|
1416
|
+
it('Absolute URL', async () => {
|
|
1417
|
+
const res = await app.request('https://example.com/redirect')
|
|
1418
|
+
expect(res.status).toBe(302)
|
|
1419
|
+
expect(res.headers.get('Location')).toBe('/')
|
|
1420
|
+
})
|
|
1421
|
+
})
|
|
1422
|
+
|
|
1423
|
+
describe('Error handle', () => {
|
|
1424
|
+
describe('Basic', () => {
|
|
1425
|
+
const app = new App()
|
|
1426
|
+
|
|
1427
|
+
app.get('/error', () => {
|
|
1428
|
+
throw new Error('This is Error')
|
|
1429
|
+
})
|
|
1430
|
+
|
|
1431
|
+
app.get('/error-string', () => {
|
|
1432
|
+
throw 'This is Error'
|
|
1433
|
+
})
|
|
1434
|
+
|
|
1435
|
+
app.use('/error-middleware', async () => {
|
|
1436
|
+
throw new Error('This is Middleware Error')
|
|
1437
|
+
})
|
|
1438
|
+
|
|
1439
|
+
app.onError((err, c) => {
|
|
1440
|
+
c.header('x-debug', err.message)
|
|
1441
|
+
return c.text('Custom Error Message', 500)
|
|
1442
|
+
})
|
|
1443
|
+
|
|
1444
|
+
it('Should throw Error if a non-Error object is thrown in a handler', async () => {
|
|
1445
|
+
expect(() => app.request('/error-string')).toThrowError()
|
|
1446
|
+
})
|
|
1447
|
+
|
|
1448
|
+
it('Custom Error Message', async () => {
|
|
1449
|
+
let res = await app.request('https://example.com/error')
|
|
1450
|
+
expect(res.status).toBe(500)
|
|
1451
|
+
expect(await res.text()).toBe('Custom Error Message')
|
|
1452
|
+
expect(res.headers.get('x-debug')).toBe('This is Error')
|
|
1453
|
+
|
|
1454
|
+
res = await app.request('https://example.com/error-middleware')
|
|
1455
|
+
expect(res.status).toBe(500)
|
|
1456
|
+
expect(await res.text()).toBe('Custom Error Message')
|
|
1457
|
+
expect(res.headers.get('x-debug')).toBe('This is Middleware Error')
|
|
1458
|
+
})
|
|
1459
|
+
})
|
|
1460
|
+
|
|
1461
|
+
describe('Async custom handler', () => {
|
|
1462
|
+
const app = new App()
|
|
1463
|
+
|
|
1464
|
+
app.get('/error', () => {
|
|
1465
|
+
throw new Error('This is Error')
|
|
1466
|
+
})
|
|
1467
|
+
|
|
1468
|
+
app.use('/error-middleware', async () => {
|
|
1469
|
+
throw new Error('This is Middleware Error')
|
|
1470
|
+
})
|
|
1471
|
+
|
|
1472
|
+
app.onError(async (err, c) => {
|
|
1473
|
+
const promise = new Promise((resolve) =>
|
|
1474
|
+
setTimeout(() => {
|
|
1475
|
+
resolve('Promised')
|
|
1476
|
+
}, 1)
|
|
1477
|
+
)
|
|
1478
|
+
const message = (await promise) as string
|
|
1479
|
+
c.header('x-debug', err.message)
|
|
1480
|
+
return c.text(`Custom Error Message with ${message}`, 500)
|
|
1481
|
+
})
|
|
1482
|
+
|
|
1483
|
+
it('Custom Error Message', async () => {
|
|
1484
|
+
let res = await app.request('https://example.com/error')
|
|
1485
|
+
expect(res.status).toBe(500)
|
|
1486
|
+
expect(await res.text()).toBe('Custom Error Message with Promised')
|
|
1487
|
+
expect(res.headers.get('x-debug')).toBe('This is Error')
|
|
1488
|
+
|
|
1489
|
+
res = await app.request('https://example.com/error-middleware')
|
|
1490
|
+
expect(res.status).toBe(500)
|
|
1491
|
+
expect(await res.text()).toBe('Custom Error Message with Promised')
|
|
1492
|
+
expect(res.headers.get('x-debug')).toBe('This is Middleware Error')
|
|
1493
|
+
})
|
|
1494
|
+
})
|
|
1495
|
+
|
|
1496
|
+
describe('Handle HTTPException', () => {
|
|
1497
|
+
const app = new App()
|
|
1498
|
+
|
|
1499
|
+
app.get('/exception', () => {
|
|
1500
|
+
throw new HTTPException(401, {
|
|
1501
|
+
message: 'Unauthorized',
|
|
1502
|
+
})
|
|
1503
|
+
})
|
|
1504
|
+
|
|
1505
|
+
it('Should return 401 response', async () => {
|
|
1506
|
+
const res = await app.request('http://localhost/exception')
|
|
1507
|
+
expect(res.status).toBe(401)
|
|
1508
|
+
expect(await res.text()).toBe('Unauthorized')
|
|
1509
|
+
})
|
|
1510
|
+
|
|
1511
|
+
const app2 = new App()
|
|
1512
|
+
|
|
1513
|
+
app2.get('/exception', () => {
|
|
1514
|
+
throw new HTTPException(401)
|
|
1515
|
+
})
|
|
1516
|
+
|
|
1517
|
+
app2.onError((err, c) => {
|
|
1518
|
+
if (err instanceof HTTPException && err.status === 401) {
|
|
1519
|
+
return c.text('Custom Error Message', 401)
|
|
1520
|
+
}
|
|
1521
|
+
return c.text('Internal Server Error', 500)
|
|
1522
|
+
})
|
|
1523
|
+
|
|
1524
|
+
it('Should return 401 response with a custom message', async () => {
|
|
1525
|
+
const res = await app2.request('http://localhost/exception')
|
|
1526
|
+
expect(res.status).toBe(401)
|
|
1527
|
+
expect(await res.text()).toBe('Custom Error Message')
|
|
1528
|
+
})
|
|
1529
|
+
})
|
|
1530
|
+
|
|
1531
|
+
describe('Handle HTTPException like object', () => {
|
|
1532
|
+
const app = new App()
|
|
1533
|
+
|
|
1534
|
+
class CustomError extends Error {
|
|
1535
|
+
getResponse() {
|
|
1536
|
+
return new Response('Custom Error', { status: 400 })
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
app.get('/exception', () => {
|
|
1541
|
+
throw new CustomError()
|
|
1542
|
+
})
|
|
1543
|
+
|
|
1544
|
+
it('Should return 401 response', async () => {
|
|
1545
|
+
const res = await app.request('http://localhost/exception')
|
|
1546
|
+
expect(res.status).toBe(400)
|
|
1547
|
+
expect(await res.text()).toBe('Custom Error')
|
|
1548
|
+
})
|
|
1549
|
+
})
|
|
1550
|
+
|
|
1551
|
+
describe('HTTPException with finally block', () => {
|
|
1552
|
+
const app = new App()
|
|
1553
|
+
app.use(async (c) => {
|
|
1554
|
+
try {
|
|
1555
|
+
throw new Error()
|
|
1556
|
+
} catch (cause) {
|
|
1557
|
+
throw new HTTPException(302, {
|
|
1558
|
+
cause,
|
|
1559
|
+
res: c.redirect('/?error=invalid_request', 302),
|
|
1560
|
+
})
|
|
1561
|
+
} finally {
|
|
1562
|
+
c.header('x-custom', 'custom message')
|
|
1563
|
+
}
|
|
1564
|
+
})
|
|
1565
|
+
it('Should have the custom header', async () => {
|
|
1566
|
+
const res = await app.request('http://localhost/')
|
|
1567
|
+
expect(res.status).toBe(302)
|
|
1568
|
+
expect(res.headers.get('x-custom')).toBe('custom message')
|
|
1569
|
+
})
|
|
1570
|
+
})
|
|
1571
|
+
})
|
|
1572
|
+
|
|
1573
|
+
describe('Error handling in middleware', () => {
|
|
1574
|
+
const app = new App()
|
|
1575
|
+
|
|
1576
|
+
app.get('/handle-error-in-middleware', async (c, next) => {
|
|
1577
|
+
await next()
|
|
1578
|
+
if (c.error) {
|
|
1579
|
+
const message = c.error.message
|
|
1580
|
+
c.res = c.text(`Handle the error in middleware, original message is ${message}`, 500)
|
|
1581
|
+
}
|
|
1582
|
+
})
|
|
1583
|
+
|
|
1584
|
+
app.get('/handle-error-in-middleware-async', async (c, next) => {
|
|
1585
|
+
await next()
|
|
1586
|
+
if (c.error) {
|
|
1587
|
+
const message = c.error.message
|
|
1588
|
+
c.res = c.text(
|
|
1589
|
+
`Handle the error in middleware with async, original message is ${message}`,
|
|
1590
|
+
500
|
|
1591
|
+
)
|
|
1592
|
+
}
|
|
1593
|
+
})
|
|
1594
|
+
|
|
1595
|
+
app.get('/handle-error-in-middleware', () => {
|
|
1596
|
+
throw new Error('Error message')
|
|
1597
|
+
})
|
|
1598
|
+
|
|
1599
|
+
app.get('/handle-error-in-middleware-async', async () => {
|
|
1600
|
+
throw new Error('Error message')
|
|
1601
|
+
})
|
|
1602
|
+
|
|
1603
|
+
it('Should handle the error in middleware', async () => {
|
|
1604
|
+
const res = await app.request('https://example.com/handle-error-in-middleware')
|
|
1605
|
+
expect(res.status).toBe(500)
|
|
1606
|
+
expect(await res.text()).toBe(
|
|
1607
|
+
'Handle the error in middleware, original message is Error message'
|
|
1608
|
+
)
|
|
1609
|
+
})
|
|
1610
|
+
|
|
1611
|
+
it('Should handle the error in middleware - async', async () => {
|
|
1612
|
+
const res = await app.request('https://example.com/handle-error-in-middleware-async')
|
|
1613
|
+
expect(res.status).toBe(500)
|
|
1614
|
+
expect(await res.text()).toBe(
|
|
1615
|
+
'Handle the error in middleware with async, original message is Error message'
|
|
1616
|
+
)
|
|
1617
|
+
})
|
|
1618
|
+
|
|
1619
|
+
describe('Default route app.use', () => {
|
|
1620
|
+
const app = new App()
|
|
1621
|
+
app
|
|
1622
|
+
.use(async (c, next) => {
|
|
1623
|
+
c.header('x-default-use', 'abc')
|
|
1624
|
+
await next()
|
|
1625
|
+
})
|
|
1626
|
+
.get('/multiple/abc', (c) => {
|
|
1627
|
+
return c.text('GET multiple')
|
|
1628
|
+
})
|
|
1629
|
+
it('GET /multiple/abc', async () => {
|
|
1630
|
+
const res = await app.request('http://localhost/multiple/abc')
|
|
1631
|
+
expect(res.status).toBe(200)
|
|
1632
|
+
expect(await res.text()).toBe('GET multiple')
|
|
1633
|
+
expect(res.headers.get('x-default-use')).toBe('abc')
|
|
1634
|
+
})
|
|
1635
|
+
})
|
|
1636
|
+
|
|
1637
|
+
describe('Error in `notFound()`', () => {
|
|
1638
|
+
const app = new App()
|
|
1639
|
+
|
|
1640
|
+
app.use('*', async () => {})
|
|
1641
|
+
|
|
1642
|
+
app.notFound(() => {
|
|
1643
|
+
throw new Error('Error in Not Found')
|
|
1644
|
+
})
|
|
1645
|
+
|
|
1646
|
+
app.onError((err, c) => {
|
|
1647
|
+
return c.text(err.message, 400)
|
|
1648
|
+
})
|
|
1649
|
+
|
|
1650
|
+
it('Should handle the error thrown in `notFound()``', async () => {
|
|
1651
|
+
const res = await app.request('http://localhost/')
|
|
1652
|
+
expect(res.status).toBe(400)
|
|
1653
|
+
expect(await res.text()).toBe('Error in Not Found')
|
|
1654
|
+
})
|
|
1655
|
+
})
|
|
1656
|
+
})
|
|
1657
|
+
|
|
1658
|
+
describe('Request methods with custom middleware', () => {
|
|
1659
|
+
const app = new App()
|
|
1660
|
+
|
|
1661
|
+
app.use('*', async (c, next) => {
|
|
1662
|
+
const query = c.req.query('foo')
|
|
1663
|
+
|
|
1664
|
+
// @ts-ignore
|
|
1665
|
+
const param = c.req.param('foo') // This will cause a type error.
|
|
1666
|
+
const header = c.req.header('User-Agent')
|
|
1667
|
+
await next()
|
|
1668
|
+
c.header('X-Query-2', query ?? throwExpression('missing `X-Query-2` header'))
|
|
1669
|
+
c.header('X-Param-2', param)
|
|
1670
|
+
c.header('X-Header-2', header ?? throwExpression('missing `X-Header-2` header'))
|
|
1671
|
+
})
|
|
1672
|
+
|
|
1673
|
+
app.get('/:foo', (c) => {
|
|
1674
|
+
const query = c.req.query('foo')
|
|
1675
|
+
const param = c.req.param('foo')
|
|
1676
|
+
const header = c.req.header('User-Agent')
|
|
1677
|
+
c.header('X-Query', query ?? throwExpression('missing `X-Query` header'))
|
|
1678
|
+
c.header('X-Param', param)
|
|
1679
|
+
c.header('X-Header', header ?? throwExpression('missing `X-Header` header'))
|
|
1680
|
+
return c.body('App')
|
|
1681
|
+
})
|
|
1682
|
+
|
|
1683
|
+
it('query', async () => {
|
|
1684
|
+
const url = new URL('http://localhost/bar')
|
|
1685
|
+
url.searchParams.append('foo', 'bar')
|
|
1686
|
+
const req = new Request(url.toString())
|
|
1687
|
+
req.headers.append('User-Agent', 'bar')
|
|
1688
|
+
const res = await app.request(req)
|
|
1689
|
+
|
|
1690
|
+
expect(res.status).toBe(200)
|
|
1691
|
+
expect(res.headers.get('X-Query')).toBe('bar')
|
|
1692
|
+
expect(res.headers.get('X-Param')).toBe('bar')
|
|
1693
|
+
expect(res.headers.get('X-Header')).toBe('bar')
|
|
1694
|
+
|
|
1695
|
+
expect(res.headers.get('X-Query-2')).toBe('bar')
|
|
1696
|
+
expect(res.headers.get('X-Param-2')).toBe(null)
|
|
1697
|
+
expect(res.headers.get('X-Header-2')).toBe('bar')
|
|
1698
|
+
})
|
|
1699
|
+
})
|
|
1700
|
+
|
|
1701
|
+
describe('Middleware + c.json(0, requestInit)', () => {
|
|
1702
|
+
const app = new App()
|
|
1703
|
+
app.use('/', async (c, next) => {
|
|
1704
|
+
await next()
|
|
1705
|
+
})
|
|
1706
|
+
app.get('/', (c) => {
|
|
1707
|
+
return c.json(0, {
|
|
1708
|
+
status: 200,
|
|
1709
|
+
headers: {
|
|
1710
|
+
foo: 'bar',
|
|
1711
|
+
},
|
|
1712
|
+
})
|
|
1713
|
+
})
|
|
1714
|
+
it('Should return a correct headers', async () => {
|
|
1715
|
+
const res = await app.request('/')
|
|
1716
|
+
expect(res.headers.get('content-type')).toMatch(/^application\/json/)
|
|
1717
|
+
expect(res.headers.get('foo')).toBe('bar')
|
|
1718
|
+
})
|
|
1719
|
+
})
|
|
1720
|
+
|
|
1721
|
+
describe('App with `app.route`', () => {
|
|
1722
|
+
describe('Basic', () => {
|
|
1723
|
+
const app = new App()
|
|
1724
|
+
const api = new App()
|
|
1725
|
+
const middleware = new App()
|
|
1726
|
+
|
|
1727
|
+
api.use('*', async (c, next) => {
|
|
1728
|
+
await next()
|
|
1729
|
+
c.res.headers.append('x-custom-a', 'a')
|
|
1730
|
+
})
|
|
1731
|
+
|
|
1732
|
+
api.get('/posts', (c) => c.text('List'))
|
|
1733
|
+
api.post('/posts', (c) => c.text('Create'))
|
|
1734
|
+
api.get('/posts/:id', (c) => c.text(`GET ${c.req.param('id')}`))
|
|
1735
|
+
|
|
1736
|
+
middleware.use('*', async (c, next) => {
|
|
1737
|
+
await next()
|
|
1738
|
+
c.res.headers.append('x-custom-b', 'b')
|
|
1739
|
+
})
|
|
1740
|
+
|
|
1741
|
+
app.route('/api', middleware)
|
|
1742
|
+
app.route('/api', api)
|
|
1743
|
+
|
|
1744
|
+
app.get('/foo', (c) => c.text('bar'))
|
|
1745
|
+
|
|
1746
|
+
it('Should return not found response', async () => {
|
|
1747
|
+
const res = await app.request('http://localhost/')
|
|
1748
|
+
expect(res.status).toBe(404)
|
|
1749
|
+
})
|
|
1750
|
+
|
|
1751
|
+
it('Should return not found response', async () => {
|
|
1752
|
+
const res = await app.request('http://localhost/posts')
|
|
1753
|
+
expect(res.status).toBe(404)
|
|
1754
|
+
})
|
|
1755
|
+
|
|
1756
|
+
test('GET /api/posts', async () => {
|
|
1757
|
+
const res = await app.request('http://localhost/api/posts')
|
|
1758
|
+
expect(res.status).toBe(200)
|
|
1759
|
+
expect(await res.text()).toBe('List')
|
|
1760
|
+
})
|
|
1761
|
+
|
|
1762
|
+
test('Custom header by middleware', async () => {
|
|
1763
|
+
const res = await app.request('http://localhost/api/posts')
|
|
1764
|
+
expect(res.status).toBe(200)
|
|
1765
|
+
expect(res.headers.get('x-custom-a')).toBe('a')
|
|
1766
|
+
expect(res.headers.get('x-custom-b')).toBe('b')
|
|
1767
|
+
})
|
|
1768
|
+
|
|
1769
|
+
test('POST /api/posts', async () => {
|
|
1770
|
+
const res = await app.request('http://localhost/api/posts', { method: 'POST' })
|
|
1771
|
+
expect(res.status).toBe(200)
|
|
1772
|
+
expect(await res.text()).toBe('Create')
|
|
1773
|
+
})
|
|
1774
|
+
|
|
1775
|
+
test('GET /api/posts/123', async () => {
|
|
1776
|
+
const res = await app.request('http://localhost/api/posts/123')
|
|
1777
|
+
expect(res.status).toBe(200)
|
|
1778
|
+
expect(await res.text()).toBe('GET 123')
|
|
1779
|
+
})
|
|
1780
|
+
|
|
1781
|
+
test('GET /foo', async () => {
|
|
1782
|
+
const res = await app.request('http://localhost/foo')
|
|
1783
|
+
expect(res.status).toBe(200)
|
|
1784
|
+
expect(await res.text()).toBe('bar')
|
|
1785
|
+
})
|
|
1786
|
+
|
|
1787
|
+
describe('With app.get(...handler)', () => {
|
|
1788
|
+
const app = new App()
|
|
1789
|
+
const about = new App()
|
|
1790
|
+
about.get((c) => c.text('me'))
|
|
1791
|
+
const subApp = new App()
|
|
1792
|
+
subApp.route('/about', about)
|
|
1793
|
+
app.route('/', subApp)
|
|
1794
|
+
|
|
1795
|
+
it('Should return 200 response - /about', async () => {
|
|
1796
|
+
const res = await app.request('/about')
|
|
1797
|
+
expect(res.status).toBe(200)
|
|
1798
|
+
expect(await res.text()).toBe('me')
|
|
1799
|
+
})
|
|
1800
|
+
|
|
1801
|
+
test('Should return 404 response /about/foo', async () => {
|
|
1802
|
+
const res = await app.request('/about/foo')
|
|
1803
|
+
expect(res.status).toBe(404)
|
|
1804
|
+
})
|
|
1805
|
+
})
|
|
1806
|
+
|
|
1807
|
+
describe('With app.get(...handler) and app.basePath()', () => {
|
|
1808
|
+
const app = new App()
|
|
1809
|
+
const about = new App().basePath('/about')
|
|
1810
|
+
about.get((c) => c.text('me'))
|
|
1811
|
+
app.route('/', about)
|
|
1812
|
+
|
|
1813
|
+
it('Should return 200 response - /about', async () => {
|
|
1814
|
+
const res = await app.request('/about')
|
|
1815
|
+
expect(res.status).toBe(200)
|
|
1816
|
+
expect(await res.text()).toBe('me')
|
|
1817
|
+
})
|
|
1818
|
+
|
|
1819
|
+
test('Should return 404 response /about/foo', async () => {
|
|
1820
|
+
const res = await app.request('/about/foo')
|
|
1821
|
+
expect(res.status).toBe(404)
|
|
1822
|
+
})
|
|
1823
|
+
})
|
|
1824
|
+
})
|
|
1825
|
+
|
|
1826
|
+
describe('Chaining', () => {
|
|
1827
|
+
const app = new App()
|
|
1828
|
+
const route = new App()
|
|
1829
|
+
route.get('/post', (c) => c.text('GET /POST v2')).post((c) => c.text('POST /POST v2'))
|
|
1830
|
+
app.route('/v2', route)
|
|
1831
|
+
|
|
1832
|
+
it('Should return 200 response - GET /v2/post', async () => {
|
|
1833
|
+
const res = await app.request('http://localhost/v2/post')
|
|
1834
|
+
expect(res.status).toBe(200)
|
|
1835
|
+
expect(await res.text()).toBe('GET /POST v2')
|
|
1836
|
+
})
|
|
1837
|
+
|
|
1838
|
+
it('Should return 200 response - POST /v2/post', async () => {
|
|
1839
|
+
const res = await app.request('http://localhost/v2/post', { method: 'POST' })
|
|
1840
|
+
expect(res.status).toBe(200)
|
|
1841
|
+
expect(await res.text()).toBe('POST /POST v2')
|
|
1842
|
+
})
|
|
1843
|
+
|
|
1844
|
+
it('Should return 404 response - DELETE /v2/post', async () => {
|
|
1845
|
+
const res = await app.request('http://localhost/v2/post', { method: 'DELETE' })
|
|
1846
|
+
expect(res.status).toBe(404)
|
|
1847
|
+
})
|
|
1848
|
+
})
|
|
1849
|
+
|
|
1850
|
+
describe('Nested', () => {
|
|
1851
|
+
const app = new App()
|
|
1852
|
+
const api = new App()
|
|
1853
|
+
const book = new App()
|
|
1854
|
+
|
|
1855
|
+
book.get('/', (c) => c.text('list books'))
|
|
1856
|
+
book.get('/:id', (c) => c.text(`book ${c.req.param('id')}`))
|
|
1857
|
+
|
|
1858
|
+
api.get('/', (c) => c.text('this is API'))
|
|
1859
|
+
api.route('/book', book)
|
|
1860
|
+
|
|
1861
|
+
app.get('/', (c) => c.text('root'))
|
|
1862
|
+
app.route('/v2', api)
|
|
1863
|
+
|
|
1864
|
+
it('Should return 200 response - GET /', async () => {
|
|
1865
|
+
const res = await app.request('http://localhost/')
|
|
1866
|
+
expect(res.status).toBe(200)
|
|
1867
|
+
expect(await res.text()).toBe('root')
|
|
1868
|
+
})
|
|
1869
|
+
|
|
1870
|
+
it('Should return 200 response - GET /v2', async () => {
|
|
1871
|
+
const res = await app.request('http://localhost/v2')
|
|
1872
|
+
expect(res.status).toBe(200)
|
|
1873
|
+
expect(await res.text()).toBe('this is API')
|
|
1874
|
+
})
|
|
1875
|
+
|
|
1876
|
+
it('Should return 200 response - GET /v2/book', async () => {
|
|
1877
|
+
const res = await app.request('http://localhost/v2/book')
|
|
1878
|
+
expect(res.status).toBe(200)
|
|
1879
|
+
expect(await res.text()).toBe('list books')
|
|
1880
|
+
})
|
|
1881
|
+
|
|
1882
|
+
it('Should return 200 response - GET /v2/book/123', async () => {
|
|
1883
|
+
const res = await app.request('http://localhost/v2/book/123')
|
|
1884
|
+
expect(res.status).toBe(200)
|
|
1885
|
+
expect(await res.text()).toBe('book 123')
|
|
1886
|
+
})
|
|
1887
|
+
})
|
|
1888
|
+
|
|
1889
|
+
describe('onError', () => {
|
|
1890
|
+
const app = new App()
|
|
1891
|
+
const sub = new App()
|
|
1892
|
+
|
|
1893
|
+
app.use('*', async (c, next) => {
|
|
1894
|
+
await next()
|
|
1895
|
+
if (c.req.query('app-error')) {
|
|
1896
|
+
throw new Error('This is Error')
|
|
1897
|
+
}
|
|
1898
|
+
})
|
|
1899
|
+
|
|
1900
|
+
app.onError((err, c) => {
|
|
1901
|
+
return c.text('onError by app', 500)
|
|
1902
|
+
})
|
|
1903
|
+
|
|
1904
|
+
sub.get('/posts/:id', async (c, next) => {
|
|
1905
|
+
c.header('handler-chain', '1')
|
|
1906
|
+
await next()
|
|
1907
|
+
})
|
|
1908
|
+
|
|
1909
|
+
sub.get('/posts/:id', (c) => {
|
|
1910
|
+
return c.text(`post: ${c.req.param('id')}`)
|
|
1911
|
+
})
|
|
1912
|
+
|
|
1913
|
+
sub.get('/error', () => {
|
|
1914
|
+
throw new Error('This is Error')
|
|
1915
|
+
})
|
|
1916
|
+
|
|
1917
|
+
sub.onError((err, c) => {
|
|
1918
|
+
return c.text('onError by sub', 500)
|
|
1919
|
+
})
|
|
1920
|
+
|
|
1921
|
+
app.route('/sub', sub)
|
|
1922
|
+
|
|
1923
|
+
it('GET /posts/123 for sub', async () => {
|
|
1924
|
+
const res = await app.request('https://example.com/sub/posts/123')
|
|
1925
|
+
expect(res.status).toBe(200)
|
|
1926
|
+
expect(res.headers.get('handler-chain')).toBe('1')
|
|
1927
|
+
expect(await res.text()).toBe('post: 123')
|
|
1928
|
+
})
|
|
1929
|
+
|
|
1930
|
+
it('should be handled by app', async () => {
|
|
1931
|
+
const res = await app.request('https://example.com/sub/ok?app-error=1')
|
|
1932
|
+
expect(res.status).toBe(500)
|
|
1933
|
+
expect(await res.text()).toBe('onError by app')
|
|
1934
|
+
})
|
|
1935
|
+
|
|
1936
|
+
it('should be handled by sub', async () => {
|
|
1937
|
+
const res = await app.request('https://example.com/sub/error')
|
|
1938
|
+
expect(res.status).toBe(500)
|
|
1939
|
+
expect(await res.text()).toBe('onError by sub')
|
|
1940
|
+
})
|
|
1941
|
+
})
|
|
1942
|
+
|
|
1943
|
+
describe('onError for a single handler', () => {
|
|
1944
|
+
const app = new App()
|
|
1945
|
+
const sub = new App()
|
|
1946
|
+
|
|
1947
|
+
sub.get('/ok', (c) => c.text('OK'))
|
|
1948
|
+
|
|
1949
|
+
sub.get('/error', () => {
|
|
1950
|
+
throw new Error('This is Error')
|
|
1951
|
+
})
|
|
1952
|
+
|
|
1953
|
+
sub.onError((err, c) => {
|
|
1954
|
+
return c.text('onError by sub', 500)
|
|
1955
|
+
})
|
|
1956
|
+
|
|
1957
|
+
app.route('/sub', sub)
|
|
1958
|
+
|
|
1959
|
+
it('ok', async () => {
|
|
1960
|
+
const res = await app.request('https://example.com/sub/ok')
|
|
1961
|
+
expect(res.status).toBe(200)
|
|
1962
|
+
})
|
|
1963
|
+
|
|
1964
|
+
it('error', async () => {
|
|
1965
|
+
const res = await app.request('https://example.com/sub/error')
|
|
1966
|
+
expect(res.status).toBe(500)
|
|
1967
|
+
expect(await res.text()).toBe('onError by sub')
|
|
1968
|
+
})
|
|
1969
|
+
})
|
|
1970
|
+
|
|
1971
|
+
describe('notFound', () => {
|
|
1972
|
+
const app = new App()
|
|
1973
|
+
const sub = new App()
|
|
1974
|
+
|
|
1975
|
+
app.get('/explicit-404', async (c) => {
|
|
1976
|
+
c.header('explicit', '1')
|
|
1977
|
+
})
|
|
1978
|
+
|
|
1979
|
+
app.notFound((c) => {
|
|
1980
|
+
return c.text('404 Not Found by app', 404)
|
|
1981
|
+
})
|
|
1982
|
+
|
|
1983
|
+
sub.get('/ok', (c) => {
|
|
1984
|
+
return c.text('ok')
|
|
1985
|
+
})
|
|
1986
|
+
|
|
1987
|
+
sub.get('/explicit-404', async (c) => {
|
|
1988
|
+
c.header('explicit', '1')
|
|
1989
|
+
})
|
|
1990
|
+
|
|
1991
|
+
sub.notFound((c) => {
|
|
1992
|
+
return c.text('404 Not Found by sub', 404)
|
|
1993
|
+
})
|
|
1994
|
+
|
|
1995
|
+
app.route('/sub', sub)
|
|
1996
|
+
|
|
1997
|
+
it('/explicit-404 should be handled on app', async () => {
|
|
1998
|
+
const res = await app.request('https://example.com/explicit-404')
|
|
1999
|
+
expect(res.status).toBe(404)
|
|
2000
|
+
expect(res.headers.get('explicit')).toBe('1')
|
|
2001
|
+
expect(await res.text()).toBe('404 Not Found by app')
|
|
2002
|
+
})
|
|
2003
|
+
|
|
2004
|
+
it('/sub/explicit-404 should be handled on app', async () => {
|
|
2005
|
+
const res = await app.request('https://example.com/sub/explicit-404')
|
|
2006
|
+
expect(res.status).toBe(404)
|
|
2007
|
+
expect(res.headers.get('explicit')).toBe('1')
|
|
2008
|
+
expect(await res.text()).toBe('404 Not Found by app')
|
|
2009
|
+
})
|
|
2010
|
+
|
|
2011
|
+
it('/implicit-404 should be handled by app', async () => {
|
|
2012
|
+
const res = await app.request('https://example.com/implicit-404')
|
|
2013
|
+
expect(res.status).toBe(404)
|
|
2014
|
+
expect(res.headers.get('explicit')).toBe(null)
|
|
2015
|
+
expect(await res.text()).toBe('404 Not Found by app')
|
|
2016
|
+
})
|
|
2017
|
+
|
|
2018
|
+
it('/sub/implicit-404 should be handled by sub', async () => {
|
|
2019
|
+
const res = await app.request('https://example.com/sub/implicit-404')
|
|
2020
|
+
expect(res.status).toBe(404)
|
|
2021
|
+
expect(res.headers.get('explicit')).toBe(null)
|
|
2022
|
+
expect(await res.text()).toBe('404 Not Found by app')
|
|
2023
|
+
})
|
|
2024
|
+
})
|
|
2025
|
+
})
|
|
2026
|
+
|
|
2027
|
+
describe('Using other methods with `app.on`', () => {
|
|
2028
|
+
it('Should handle PURGE method with RegExpRouter', async () => {
|
|
2029
|
+
const app = new App({ router: new RegExpRouter() })
|
|
2030
|
+
|
|
2031
|
+
app.on('PURGE', '/purge', (c) => c.text('Accepted', 202))
|
|
2032
|
+
|
|
2033
|
+
const req = new Request('http://localhost/purge', {
|
|
2034
|
+
method: 'PURGE',
|
|
2035
|
+
})
|
|
2036
|
+
const res = await app.request(req)
|
|
2037
|
+
expect(res.status).toBe(202)
|
|
2038
|
+
expect(await res.text()).toBe('Accepted')
|
|
2039
|
+
})
|
|
2040
|
+
|
|
2041
|
+
it('Should handle PURGE method with TrieRouter', async () => {
|
|
2042
|
+
const app = new App({ router: new TrieRouter() })
|
|
2043
|
+
|
|
2044
|
+
app.on('PURGE', '/purge', (c) => c.text('Accepted', 202))
|
|
2045
|
+
|
|
2046
|
+
const req = new Request('http://localhost/purge', {
|
|
2047
|
+
method: 'PURGE',
|
|
2048
|
+
})
|
|
2049
|
+
const res = await app.request(req)
|
|
2050
|
+
expect(res.status).toBe(202)
|
|
2051
|
+
expect(await res.text()).toBe('Accepted')
|
|
2052
|
+
})
|
|
2053
|
+
})
|
|
2054
|
+
|
|
2055
|
+
describe('Multiple methods with `app.on`', () => {
|
|
2056
|
+
const app = new App()
|
|
2057
|
+
app.on(['PUT', 'DELETE'], '/posts/:id', (c) => {
|
|
2058
|
+
return c.json({
|
|
2059
|
+
postId: c.req.param('id'),
|
|
2060
|
+
method: c.req.method,
|
|
2061
|
+
})
|
|
2062
|
+
})
|
|
2063
|
+
|
|
2064
|
+
it('Should return 200 with PUT', async () => {
|
|
2065
|
+
const req = new Request('http://localhost/posts/123', {
|
|
2066
|
+
method: 'PUT',
|
|
2067
|
+
})
|
|
2068
|
+
const res = await app.request(req)
|
|
2069
|
+
expect(res.status).toBe(200)
|
|
2070
|
+
expect(await res.json()).toEqual({
|
|
2071
|
+
postId: '123',
|
|
2072
|
+
method: 'PUT',
|
|
2073
|
+
})
|
|
2074
|
+
})
|
|
2075
|
+
|
|
2076
|
+
it('Should return 200 with DELETE', async () => {
|
|
2077
|
+
const req = new Request('http://localhost/posts/123', {
|
|
2078
|
+
method: 'DELETE',
|
|
2079
|
+
})
|
|
2080
|
+
const res = await app.request(req)
|
|
2081
|
+
expect(res.status).toBe(200)
|
|
2082
|
+
expect(await res.json()).toEqual({
|
|
2083
|
+
postId: '123',
|
|
2084
|
+
method: 'DELETE',
|
|
2085
|
+
})
|
|
2086
|
+
})
|
|
2087
|
+
|
|
2088
|
+
it('Should return 404 with POST', async () => {
|
|
2089
|
+
const req = new Request('http://localhost/posts/123', {
|
|
2090
|
+
method: 'POST',
|
|
2091
|
+
})
|
|
2092
|
+
const res = await app.request(req)
|
|
2093
|
+
expect(res.status).toBe(404)
|
|
2094
|
+
})
|
|
2095
|
+
})
|
|
2096
|
+
|
|
2097
|
+
describe('Using QUERY with `app.query` and `app.on`', () => {
|
|
2098
|
+
it('Should handle QUERY method with app.query()', async () => {
|
|
2099
|
+
const app = new App()
|
|
2100
|
+
|
|
2101
|
+
app.query('/query', (c) => c.text('Accepted', 202))
|
|
2102
|
+
|
|
2103
|
+
const req = new Request('http://localhost/query', {
|
|
2104
|
+
method: 'QUERY',
|
|
2105
|
+
})
|
|
2106
|
+
const res = await app.request(req)
|
|
2107
|
+
expect(res.status).toBe(202)
|
|
2108
|
+
expect(await res.text()).toBe('Accepted')
|
|
2109
|
+
})
|
|
2110
|
+
|
|
2111
|
+
it('Should handle QUERY method with RegExpRouter', async () => {
|
|
2112
|
+
const app = new App({ router: new RegExpRouter() })
|
|
2113
|
+
|
|
2114
|
+
app.on('QUERY', '/query', (c) => c.text('Accepted', 202))
|
|
2115
|
+
|
|
2116
|
+
const req = new Request('http://localhost/query', {
|
|
2117
|
+
method: 'QUERY',
|
|
2118
|
+
})
|
|
2119
|
+
const res = await app.request(req)
|
|
2120
|
+
expect(res.status).toBe(202)
|
|
2121
|
+
expect(await res.text()).toBe('Accepted')
|
|
2122
|
+
})
|
|
2123
|
+
|
|
2124
|
+
it('Should handle QUERY method with TrieRouter', async () => {
|
|
2125
|
+
const app = new App({ router: new TrieRouter() })
|
|
2126
|
+
|
|
2127
|
+
app.on('QUERY', '/query', (c) => c.text('Accepted', 202))
|
|
2128
|
+
|
|
2129
|
+
const req = new Request('http://localhost/query', {
|
|
2130
|
+
method: 'QUERY',
|
|
2131
|
+
})
|
|
2132
|
+
const res = await app.request(req)
|
|
2133
|
+
expect(res.status).toBe(202)
|
|
2134
|
+
expect(await res.text()).toBe('Accepted')
|
|
2135
|
+
})
|
|
2136
|
+
})
|
|
2137
|
+
|
|
2138
|
+
describe('Multiple paths with one handler', () => {
|
|
2139
|
+
const app = new App()
|
|
2140
|
+
|
|
2141
|
+
const paths = ['/hello', '/ja/hello', '/en/hello']
|
|
2142
|
+
app.on('GET', paths, (c) => {
|
|
2143
|
+
return c.json({
|
|
2144
|
+
path: c.req.path,
|
|
2145
|
+
routePath: c.req.routePath,
|
|
2146
|
+
})
|
|
2147
|
+
})
|
|
2148
|
+
|
|
2149
|
+
it('Should handle multiple paths', async () => {
|
|
2150
|
+
paths.map(async (path) => {
|
|
2151
|
+
const res = await app.request(path)
|
|
2152
|
+
expect(res.status).toBe(200)
|
|
2153
|
+
const data = await res.json()
|
|
2154
|
+
expect(data).toEqual({
|
|
2155
|
+
path,
|
|
2156
|
+
routePath: path,
|
|
2157
|
+
})
|
|
2158
|
+
})
|
|
2159
|
+
})
|
|
2160
|
+
})
|
|
2161
|
+
|
|
2162
|
+
describe('Multiple handler', () => {
|
|
2163
|
+
describe('handler + handler', () => {
|
|
2164
|
+
const app = new App()
|
|
2165
|
+
|
|
2166
|
+
app.get('/posts/:id', (c) => {
|
|
2167
|
+
const id = c.req.param('id')
|
|
2168
|
+
c.header('foo', 'bar')
|
|
2169
|
+
return c.text(`id is ${id}`)
|
|
2170
|
+
})
|
|
2171
|
+
|
|
2172
|
+
app.get('/:type/:id', (c) => {
|
|
2173
|
+
c.status(404)
|
|
2174
|
+
c.header('foo2', 'bar2')
|
|
2175
|
+
return c.text('foo')
|
|
2176
|
+
})
|
|
2177
|
+
it('Should return response from `specialized` route', async () => {
|
|
2178
|
+
const res = await app.request('http://localhost/posts/123')
|
|
2179
|
+
expect(res.status).toBe(200)
|
|
2180
|
+
expect(await res.text()).toBe('id is 123')
|
|
2181
|
+
expect(res.headers.get('foo')).toBe('bar')
|
|
2182
|
+
expect(res.headers.get('foo2')).toBeNull()
|
|
2183
|
+
})
|
|
2184
|
+
})
|
|
2185
|
+
|
|
2186
|
+
describe('Duplicate param name', () => {
|
|
2187
|
+
describe('basic', () => {
|
|
2188
|
+
const app = new App()
|
|
2189
|
+
app.get('/:type/:url', (c) => {
|
|
2190
|
+
return c.text(`type: ${c.req.param('type')}, url: ${c.req.param('url')}`)
|
|
2191
|
+
})
|
|
2192
|
+
app.get('/foo/:type/:url', (c) => {
|
|
2193
|
+
return c.text(`foo type: ${c.req.param('type')}, url: ${c.req.param('url')}`)
|
|
2194
|
+
})
|
|
2195
|
+
|
|
2196
|
+
it('Should return a correct param - GET /car/good-car', async () => {
|
|
2197
|
+
const res = await app.request('/car/good-car')
|
|
2198
|
+
expect(res.ok).toBe(true)
|
|
2199
|
+
expect(await res.text()).toBe('type: car, url: good-car')
|
|
2200
|
+
})
|
|
2201
|
+
it('Should return a correct param - GET /foo/food/good-food', async () => {
|
|
2202
|
+
const res = await app.request('/foo/food/good-food')
|
|
2203
|
+
expect(res.ok).toBe(true)
|
|
2204
|
+
expect(await res.text()).toBe('foo type: food, url: good-food')
|
|
2205
|
+
})
|
|
2206
|
+
})
|
|
2207
|
+
|
|
2208
|
+
describe('self', () => {
|
|
2209
|
+
const app = new App()
|
|
2210
|
+
app.get('/:id/:id', (c) => {
|
|
2211
|
+
const id = c.req.param('id')
|
|
2212
|
+
return c.text(`id is ${id}`)
|
|
2213
|
+
})
|
|
2214
|
+
it('Should return 123 - GET /123/456', async () => {
|
|
2215
|
+
const res = await app.request('/123/456')
|
|
2216
|
+
expect(res.status).toBe(200)
|
|
2217
|
+
expect(await res.text()).toBe('id is 123')
|
|
2218
|
+
})
|
|
2219
|
+
})
|
|
2220
|
+
|
|
2221
|
+
describe('self with trailing static is stable across SmartRouter fallback', () => {
|
|
2222
|
+
it('Should return the first id whether or not an unrelated route is registered', async () => {
|
|
2223
|
+
const a = new App()
|
|
2224
|
+
a.get('/:id/:id/x', (c) => c.text(c.req.param('id')))
|
|
2225
|
+
const resA = await a.request('/123/456/x')
|
|
2226
|
+
expect(resA.status).toBe(200)
|
|
2227
|
+
expect(await resA.text()).toBe('123')
|
|
2228
|
+
|
|
2229
|
+
const b = new App()
|
|
2230
|
+
b.get('/:id/:id/x', (c) => c.text(c.req.param('id')))
|
|
2231
|
+
b.get('/a', (c) => c.text('a'))
|
|
2232
|
+
const resB = await b.request('/123/456/x')
|
|
2233
|
+
expect(resB.status).toBe(200)
|
|
2234
|
+
expect(await resB.text()).toBe('123')
|
|
2235
|
+
expect(b.router.name).toBe('SmartRouter + TrieRouter')
|
|
2236
|
+
})
|
|
2237
|
+
})
|
|
2238
|
+
|
|
2239
|
+
describe('hierarchy', () => {
|
|
2240
|
+
const app = new App()
|
|
2241
|
+
app.get('/posts/:id/comments/:comment_id', (c) => {
|
|
2242
|
+
return c.text(`post: ${c.req.param('id')}, comment: ${c.req.param('comment_id')}`)
|
|
2243
|
+
})
|
|
2244
|
+
app.get('/posts/:id', (c) => {
|
|
2245
|
+
return c.text(`post: ${c.req.param('id')}`)
|
|
2246
|
+
})
|
|
2247
|
+
it('Should return a correct param - GET /posts/123/comments/456', async () => {
|
|
2248
|
+
const res = await app.request('/posts/123/comments/456')
|
|
2249
|
+
expect(res.status).toBe(200)
|
|
2250
|
+
expect(await res.text()).toBe('post: 123, comment: 456')
|
|
2251
|
+
})
|
|
2252
|
+
it('Should return a correct param - GET /posts/789', async () => {
|
|
2253
|
+
const res = await app.request('/posts/789')
|
|
2254
|
+
expect(res.status).toBe(200)
|
|
2255
|
+
expect(await res.text()).toBe('post: 789')
|
|
2256
|
+
})
|
|
2257
|
+
})
|
|
2258
|
+
|
|
2259
|
+
describe('different regular expression', () => {
|
|
2260
|
+
const app = new App()
|
|
2261
|
+
app.get('/:id/:action{create|update}', (c) => {
|
|
2262
|
+
return c.text(`id: ${c.req.param('id')}, action: ${c.req.param('action')}`)
|
|
2263
|
+
})
|
|
2264
|
+
app.get('/:id/:action{delete}', (c) => {
|
|
2265
|
+
return c.text(`id: ${c.req.param('id')}, action: ${c.req.param('action')}`)
|
|
2266
|
+
})
|
|
2267
|
+
|
|
2268
|
+
it('Should return a correct param - GET /123/create', async () => {
|
|
2269
|
+
const res = await app.request('/123/create')
|
|
2270
|
+
expect(res.status).toBe(200)
|
|
2271
|
+
expect(await res.text()).toBe('id: 123, action: create')
|
|
2272
|
+
})
|
|
2273
|
+
it('Should return a correct param - GET /456/update', async () => {
|
|
2274
|
+
const res = await app.request('/467/update')
|
|
2275
|
+
expect(res.status).toBe(200)
|
|
2276
|
+
expect(await res.text()).toBe('id: 467, action: update')
|
|
2277
|
+
})
|
|
2278
|
+
it('Should return a correct param - GET /789/delete', async () => {
|
|
2279
|
+
const res = await app.request('/789/delete')
|
|
2280
|
+
expect(res.status).toBe(200)
|
|
2281
|
+
expect(await res.text()).toBe('id: 789, action: delete')
|
|
2282
|
+
})
|
|
2283
|
+
})
|
|
2284
|
+
})
|
|
2285
|
+
})
|
|
2286
|
+
|
|
2287
|
+
describe('Multiple handler - async', () => {
|
|
2288
|
+
describe('handler + handler', () => {
|
|
2289
|
+
const app = new App()
|
|
2290
|
+
app.get('/posts/:id', async (c) => {
|
|
2291
|
+
await new Promise((resolve) => setTimeout(resolve, 1))
|
|
2292
|
+
c.header('foo2', 'bar2')
|
|
2293
|
+
const id = c.req.param('id')
|
|
2294
|
+
return c.text(`id is ${id}`)
|
|
2295
|
+
})
|
|
2296
|
+
app.get('/:type/:id', async (c) => {
|
|
2297
|
+
await new Promise((resolve) => setTimeout(resolve, 1))
|
|
2298
|
+
c.header('foo', 'bar')
|
|
2299
|
+
c.status(404)
|
|
2300
|
+
return c.text('foo')
|
|
2301
|
+
})
|
|
2302
|
+
|
|
2303
|
+
it('Should return response from `specialized` route', async () => {
|
|
2304
|
+
const res = await app.request('http://localhost/posts/123')
|
|
2305
|
+
expect(res.status).toBe(200)
|
|
2306
|
+
expect(await res.text()).toBe('id is 123')
|
|
2307
|
+
expect(res.headers.get('foo')).toBeNull()
|
|
2308
|
+
expect(res.headers.get('foo2')).toBe('bar2')
|
|
2309
|
+
})
|
|
2310
|
+
})
|
|
2311
|
+
})
|
|
2312
|
+
|
|
2313
|
+
describe('Lack returning response with a single handler', () => {
|
|
2314
|
+
const app = new App()
|
|
2315
|
+
// @ts-expect-error it should return Response to type it
|
|
2316
|
+
app.get('/sync', () => {})
|
|
2317
|
+
app.get('/async', async () => {})
|
|
2318
|
+
|
|
2319
|
+
it('Should return 404 response if lacking returning response', async () => {
|
|
2320
|
+
const res = await app.request('/sync')
|
|
2321
|
+
expect(res.status).toBe(404)
|
|
2322
|
+
})
|
|
2323
|
+
|
|
2324
|
+
it('Should return 404 response if lacking returning response in an async handler', async () => {
|
|
2325
|
+
const res = await app.request('/async')
|
|
2326
|
+
expect(res.status).toBe(404)
|
|
2327
|
+
})
|
|
2328
|
+
})
|
|
2329
|
+
|
|
2330
|
+
describe('Context is not finalized', () => {
|
|
2331
|
+
it('should throw error - lack `await next()`', async () => {
|
|
2332
|
+
const app = new App()
|
|
2333
|
+
|
|
2334
|
+
// @ts-ignore
|
|
2335
|
+
app.use('*', () => {})
|
|
2336
|
+
app.get('/foo', (c) => {
|
|
2337
|
+
return c.text('foo')
|
|
2338
|
+
})
|
|
2339
|
+
app.onError((err, c) => {
|
|
2340
|
+
return c.text(err.message, 500)
|
|
2341
|
+
})
|
|
2342
|
+
const res = await app.request('http://localhost/foo')
|
|
2343
|
+
expect(res.status).toBe(500)
|
|
2344
|
+
expect(await res.text()).toMatch(/^Context is not finalized/)
|
|
2345
|
+
})
|
|
2346
|
+
|
|
2347
|
+
it('should throw error - lack `returning Response`', async () => {
|
|
2348
|
+
const app = new App()
|
|
2349
|
+
app.use('*', async (_c, next) => {
|
|
2350
|
+
await next()
|
|
2351
|
+
})
|
|
2352
|
+
|
|
2353
|
+
// @ts-ignore
|
|
2354
|
+
app.get('/foo', () => {})
|
|
2355
|
+
app.onError((err, c) => {
|
|
2356
|
+
return c.text(err.message, 500)
|
|
2357
|
+
})
|
|
2358
|
+
const res = await app.request('http://localhost/foo')
|
|
2359
|
+
expect(res.status).toBe(500)
|
|
2360
|
+
expect(await res.text()).toMatch(/^Context is not finalized/)
|
|
2361
|
+
})
|
|
2362
|
+
})
|
|
2363
|
+
|
|
2364
|
+
describe('Parse Body', () => {
|
|
2365
|
+
const app = new App()
|
|
2366
|
+
|
|
2367
|
+
app.post('/json', async (c) => {
|
|
2368
|
+
return c.json<{}, 200>(await c.req.parseBody(), 200)
|
|
2369
|
+
})
|
|
2370
|
+
app.post('/form', async (c) => {
|
|
2371
|
+
return c.json<{}, 200>(await c.req.parseBody(), 200)
|
|
2372
|
+
})
|
|
2373
|
+
|
|
2374
|
+
it('POST with JSON', async () => {
|
|
2375
|
+
const req = new Request('http://localhost/json', {
|
|
2376
|
+
method: 'POST',
|
|
2377
|
+
body: JSON.stringify({ message: 'hello acryl' }),
|
|
2378
|
+
headers: new Headers({ 'Content-Type': 'application/json' }),
|
|
2379
|
+
})
|
|
2380
|
+
const res = await app.request(req)
|
|
2381
|
+
expect(res).not.toBeNull()
|
|
2382
|
+
expect(res.status).toBe(200)
|
|
2383
|
+
})
|
|
2384
|
+
|
|
2385
|
+
it('POST with `multipart/form-data`', async () => {
|
|
2386
|
+
const formData = new FormData()
|
|
2387
|
+
formData.append('message', 'hello')
|
|
2388
|
+
const req = new Request('https://localhost/form', {
|
|
2389
|
+
method: 'POST',
|
|
2390
|
+
body: formData,
|
|
2391
|
+
})
|
|
2392
|
+
|
|
2393
|
+
const res = await app.request(req)
|
|
2394
|
+
expect(res).not.toBeNull()
|
|
2395
|
+
expect(res.status).toBe(200)
|
|
2396
|
+
expect(await res.json()).toEqual({ message: 'hello' })
|
|
2397
|
+
})
|
|
2398
|
+
|
|
2399
|
+
it('POST with `application/x-www-form-urlencoded`', async () => {
|
|
2400
|
+
const searchParam = new URLSearchParams()
|
|
2401
|
+
searchParam.append('message', 'hello')
|
|
2402
|
+
const req = new Request('https://localhost/form', {
|
|
2403
|
+
method: 'POST',
|
|
2404
|
+
body: searchParam,
|
|
2405
|
+
headers: {
|
|
2406
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
2407
|
+
},
|
|
2408
|
+
})
|
|
2409
|
+
|
|
2410
|
+
const res = await app.request(req)
|
|
2411
|
+
expect(res).not.toBeNull()
|
|
2412
|
+
expect(res.status).toBe(200)
|
|
2413
|
+
expect(await res.json()).toEqual({ message: 'hello' })
|
|
2414
|
+
})
|
|
2415
|
+
})
|
|
2416
|
+
|
|
2417
|
+
describe('Both two middleware returning response', () => {
|
|
2418
|
+
it('Should return correct Content-Type`', async () => {
|
|
2419
|
+
const app = new App()
|
|
2420
|
+
app.use('*', async (c, next) => {
|
|
2421
|
+
await next()
|
|
2422
|
+
return c.html('Foo')
|
|
2423
|
+
})
|
|
2424
|
+
app.get('/', (c) => {
|
|
2425
|
+
return c.text('Bar')
|
|
2426
|
+
})
|
|
2427
|
+
const res = await app.request('http://localhost/')
|
|
2428
|
+
expect(res).not.toBeNull()
|
|
2429
|
+
expect(res.status).toBe(200)
|
|
2430
|
+
expect(await res.text()).toBe('Bar')
|
|
2431
|
+
expect(res.headers.get('Content-Type')).toMatch(/^text\/plain/)
|
|
2432
|
+
})
|
|
2433
|
+
})
|
|
2434
|
+
|
|
2435
|
+
describe('Count of logger called', () => {
|
|
2436
|
+
// It will be added `2` each time the logger is called once.
|
|
2437
|
+
let count = 0
|
|
2438
|
+
let log = ''
|
|
2439
|
+
|
|
2440
|
+
const app = new App()
|
|
2441
|
+
|
|
2442
|
+
const logFn = (str: string) => {
|
|
2443
|
+
count++
|
|
2444
|
+
log = str
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
app.use('*', logger(logFn))
|
|
2448
|
+
app.get('/', (c) => c.text('foo'))
|
|
2449
|
+
|
|
2450
|
+
it('Should be called two times', async () => {
|
|
2451
|
+
const res = await app.request('http://localhost/not-found')
|
|
2452
|
+
expect(res).not.toBeNull()
|
|
2453
|
+
expect(res.status).toBe(404)
|
|
2454
|
+
expect(await res.text()).toBe('404 Not Found')
|
|
2455
|
+
expect(count).toBe(2)
|
|
2456
|
+
expect(log).toMatch(/404/)
|
|
2457
|
+
})
|
|
2458
|
+
|
|
2459
|
+
it('Should be called two times / Custom Not Found', async () => {
|
|
2460
|
+
app.notFound((c) => c.text('Custom Not Found', 404))
|
|
2461
|
+
const res = await app.request('http://localhost/custom-not-found')
|
|
2462
|
+
expect(res).not.toBeNull()
|
|
2463
|
+
expect(res.status).toBe(404)
|
|
2464
|
+
expect(await res.text()).toBe('Custom Not Found')
|
|
2465
|
+
expect(count).toBe(4)
|
|
2466
|
+
expect(log).toMatch(/404/)
|
|
2467
|
+
})
|
|
2468
|
+
})
|
|
2469
|
+
|
|
2470
|
+
describe('Context set/get variables', () => {
|
|
2471
|
+
type Variables = {
|
|
2472
|
+
id: number
|
|
2473
|
+
title: string
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
const app = new App<{ Variables: Variables }>()
|
|
2477
|
+
|
|
2478
|
+
it('Should set and get variables with correct types', async () => {
|
|
2479
|
+
app.use('*', async (c, next) => {
|
|
2480
|
+
c.set('id', 123)
|
|
2481
|
+
c.set('title', 'Hello')
|
|
2482
|
+
await next()
|
|
2483
|
+
})
|
|
2484
|
+
app.get('/', (c) => {
|
|
2485
|
+
const id = c.get('id')
|
|
2486
|
+
const title = c.get('title')
|
|
2487
|
+
// type verifyID = Expect<Equal<number, typeof id>>
|
|
2488
|
+
expectTypeOf(id).toEqualTypeOf<number>()
|
|
2489
|
+
// type verifyTitle = Expect<Equal<string, typeof title>>
|
|
2490
|
+
expectTypeOf(title).toEqualTypeOf<string>()
|
|
2491
|
+
return c.text(`${id} is ${title}`)
|
|
2492
|
+
})
|
|
2493
|
+
const res = await app.request('http://localhost/')
|
|
2494
|
+
expect(res.status).toBe(200)
|
|
2495
|
+
expect(await res.text()).toBe('123 is Hello')
|
|
2496
|
+
})
|
|
2497
|
+
})
|
|
2498
|
+
|
|
2499
|
+
describe('Context binding variables', () => {
|
|
2500
|
+
type Bindings = {
|
|
2501
|
+
USER_ID: number
|
|
2502
|
+
USER_NAME: string
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
const app = new App<{ Bindings: Bindings }>()
|
|
2506
|
+
|
|
2507
|
+
it('Should get binding variables with correct types', async () => {
|
|
2508
|
+
app.get('/', (c) => {
|
|
2509
|
+
expectTypeOf(c.env).toEqualTypeOf<Bindings>()
|
|
2510
|
+
return c.text('These are verified')
|
|
2511
|
+
})
|
|
2512
|
+
const res = await app.request('http://localhost/')
|
|
2513
|
+
expect(res.status).toBe(200)
|
|
2514
|
+
})
|
|
2515
|
+
})
|
|
2516
|
+
|
|
2517
|
+
describe('Handler as variables', () => {
|
|
2518
|
+
const app = new App()
|
|
2519
|
+
|
|
2520
|
+
it('Should be typed correctly', async () => {
|
|
2521
|
+
const handler: Handler = (c) => {
|
|
2522
|
+
const id = c.req.param('id')
|
|
2523
|
+
return c.text(`Post id is ${id}`)
|
|
2524
|
+
}
|
|
2525
|
+
app.get('/posts/:id', handler)
|
|
2526
|
+
|
|
2527
|
+
const res = await app.request('http://localhost/posts/123')
|
|
2528
|
+
expect(res.status).toBe(200)
|
|
2529
|
+
expect(await res.text()).toBe('Post id is 123')
|
|
2530
|
+
})
|
|
2531
|
+
})
|
|
2532
|
+
|
|
2533
|
+
describe('json', () => {
|
|
2534
|
+
const api = new App()
|
|
2535
|
+
|
|
2536
|
+
api.get('/message', (c) => {
|
|
2537
|
+
return c.json({
|
|
2538
|
+
message: 'Hello',
|
|
2539
|
+
})
|
|
2540
|
+
})
|
|
2541
|
+
|
|
2542
|
+
api.get('/message-async', async (c) => {
|
|
2543
|
+
return c.json({
|
|
2544
|
+
message: 'Hello',
|
|
2545
|
+
})
|
|
2546
|
+
})
|
|
2547
|
+
|
|
2548
|
+
describe('Single handler', () => {
|
|
2549
|
+
const app = new App()
|
|
2550
|
+
app.route('/api', api)
|
|
2551
|
+
|
|
2552
|
+
it('Should return 200 response', async () => {
|
|
2553
|
+
const res = await app.request('http://localhost/api/message')
|
|
2554
|
+
expect(res.status).toBe(200)
|
|
2555
|
+
expect(await res.json()).toEqual({
|
|
2556
|
+
message: 'Hello',
|
|
2557
|
+
})
|
|
2558
|
+
})
|
|
2559
|
+
|
|
2560
|
+
it('Should return 200 response - with async', async () => {
|
|
2561
|
+
const res = await app.request('http://localhost/api/message-async')
|
|
2562
|
+
expect(res.status).toBe(200)
|
|
2563
|
+
expect(await res.json()).toEqual({
|
|
2564
|
+
message: 'Hello',
|
|
2565
|
+
})
|
|
2566
|
+
})
|
|
2567
|
+
})
|
|
2568
|
+
|
|
2569
|
+
describe('With middleware', () => {
|
|
2570
|
+
const app = new App()
|
|
2571
|
+
app.use('*', async (_c, next) => {
|
|
2572
|
+
await next()
|
|
2573
|
+
})
|
|
2574
|
+
app.route('/api', api)
|
|
2575
|
+
|
|
2576
|
+
it('Should return 200 response', async () => {
|
|
2577
|
+
const res = await app.request('http://localhost/api/message')
|
|
2578
|
+
expect(res.status).toBe(200)
|
|
2579
|
+
expect(await res.json()).toEqual({
|
|
2580
|
+
message: 'Hello',
|
|
2581
|
+
})
|
|
2582
|
+
})
|
|
2583
|
+
|
|
2584
|
+
it('Should return 200 response - with async', async () => {
|
|
2585
|
+
const res = await app.request('http://localhost/api/message-async')
|
|
2586
|
+
expect(res.status).toBe(200)
|
|
2587
|
+
expect(await res.json()).toEqual({
|
|
2588
|
+
message: 'Hello',
|
|
2589
|
+
})
|
|
2590
|
+
})
|
|
2591
|
+
})
|
|
2592
|
+
})
|
|
2593
|
+
|
|
2594
|
+
describe('Optional parameters', () => {
|
|
2595
|
+
const app = new App()
|
|
2596
|
+
app.get('/api/:version/animal/:type?', (c) => {
|
|
2597
|
+
const type1 = c.req.param('type')
|
|
2598
|
+
expectTypeOf(type1).toEqualTypeOf<string | undefined>()
|
|
2599
|
+
const { type, version } = c.req.param()
|
|
2600
|
+
expectTypeOf(version).toEqualTypeOf<string>()
|
|
2601
|
+
expectTypeOf(type).toEqualTypeOf<string | undefined>()
|
|
2602
|
+
|
|
2603
|
+
return c.json({
|
|
2604
|
+
type: type,
|
|
2605
|
+
})
|
|
2606
|
+
})
|
|
2607
|
+
|
|
2608
|
+
it('Should match with an optional parameter', async () => {
|
|
2609
|
+
const res = await app.request('http://localhost/api/v1/animal/bird')
|
|
2610
|
+
expect(res.status).toBe(200)
|
|
2611
|
+
expect(await res.json()).toEqual({
|
|
2612
|
+
type: 'bird',
|
|
2613
|
+
})
|
|
2614
|
+
})
|
|
2615
|
+
|
|
2616
|
+
it('Should match without an optional parameter', async () => {
|
|
2617
|
+
const res = await app.request('http://localhost/api/v1/animal')
|
|
2618
|
+
expect(res.status).toBe(200)
|
|
2619
|
+
expect(await res.json()).toEqual({
|
|
2620
|
+
type: undefined,
|
|
2621
|
+
})
|
|
2622
|
+
})
|
|
2623
|
+
|
|
2624
|
+
it('Should have a correct type with an optional parameter in a regexp path', async () => {
|
|
2625
|
+
const app = new App()
|
|
2626
|
+
app.get('/url/:url{.*}?', (c) => {
|
|
2627
|
+
const url = c.req.param('url')
|
|
2628
|
+
expectTypeOf(url).toEqualTypeOf<string | undefined>()
|
|
2629
|
+
return c.json(0)
|
|
2630
|
+
})
|
|
2631
|
+
})
|
|
2632
|
+
})
|
|
2633
|
+
|
|
2634
|
+
describe('app.mount()', () => {
|
|
2635
|
+
describe('Basic', () => {
|
|
2636
|
+
const anotherApp = (req: Request, ...params: unknown[]) => {
|
|
2637
|
+
const path = getPath(req)
|
|
2638
|
+
if (path === '/') {
|
|
2639
|
+
return new Response('AnotherApp')
|
|
2640
|
+
}
|
|
2641
|
+
if (path === '/hello') {
|
|
2642
|
+
return new Response('Hello from AnotherApp')
|
|
2643
|
+
}
|
|
2644
|
+
if (path === '/header') {
|
|
2645
|
+
const message = req.headers.get('x-message')
|
|
2646
|
+
return new Response(message)
|
|
2647
|
+
}
|
|
2648
|
+
if (path === '/with-query') {
|
|
2649
|
+
const queryStrings = new URL(req.url).searchParams.toString()
|
|
2650
|
+
return new Response(queryStrings)
|
|
2651
|
+
}
|
|
2652
|
+
if (path == '/with-params') {
|
|
2653
|
+
return new Response(
|
|
2654
|
+
JSON.stringify({
|
|
2655
|
+
params,
|
|
2656
|
+
}),
|
|
2657
|
+
{
|
|
2658
|
+
headers: {
|
|
2659
|
+
'Content-Type': 'application.json',
|
|
2660
|
+
},
|
|
2661
|
+
}
|
|
2662
|
+
)
|
|
2663
|
+
}
|
|
2664
|
+
if (path === '/undefined') {
|
|
2665
|
+
return undefined as unknown as Response
|
|
2666
|
+
}
|
|
2667
|
+
return new Response('Not Found from AnotherApp', {
|
|
2668
|
+
status: 404,
|
|
2669
|
+
})
|
|
2670
|
+
}
|
|
2671
|
+
|
|
2672
|
+
const app = new App()
|
|
2673
|
+
app.use('*', async (c, next) => {
|
|
2674
|
+
await next()
|
|
2675
|
+
c.header('x-message', 'Foo')
|
|
2676
|
+
})
|
|
2677
|
+
app.get('/', (c) => c.text('App'))
|
|
2678
|
+
app.notFound((c) => {
|
|
2679
|
+
return c.text('Not Found from App', 404)
|
|
2680
|
+
})
|
|
2681
|
+
|
|
2682
|
+
app.mount('/another-app', anotherApp, () => {
|
|
2683
|
+
return 'params'
|
|
2684
|
+
})
|
|
2685
|
+
app.mount('/another-app-with-array-option', anotherApp, () => {
|
|
2686
|
+
return ['param1', 'param2']
|
|
2687
|
+
})
|
|
2688
|
+
app.mount('/another-app2/sub-slash/', anotherApp)
|
|
2689
|
+
|
|
2690
|
+
const api = new App().basePath('/api')
|
|
2691
|
+
api.mount('/another-app', anotherApp)
|
|
2692
|
+
|
|
2693
|
+
it('Should return responses from App app', async () => {
|
|
2694
|
+
const res = await app.request('/')
|
|
2695
|
+
expect(res.status).toBe(200)
|
|
2696
|
+
expect(res.headers.get('x-message')).toBe('Foo')
|
|
2697
|
+
expect(await res.text()).toBe('App')
|
|
2698
|
+
})
|
|
2699
|
+
|
|
2700
|
+
it('Should return responses from AnotherApp', async () => {
|
|
2701
|
+
let res = await app.request('/another-app')
|
|
2702
|
+
expect(res.status).toBe(200)
|
|
2703
|
+
expect(res.headers.get('x-message')).toBe('Foo')
|
|
2704
|
+
expect(await res.text()).toBe('AnotherApp')
|
|
2705
|
+
|
|
2706
|
+
res = await app.request('/another-app/hello')
|
|
2707
|
+
expect(res.status).toBe(200)
|
|
2708
|
+
expect(res.headers.get('x-message')).toBe('Foo')
|
|
2709
|
+
expect(await res.text()).toBe('Hello from AnotherApp')
|
|
2710
|
+
|
|
2711
|
+
const req = new Request('http://localhost/another-app/header', {
|
|
2712
|
+
headers: {
|
|
2713
|
+
'x-message': 'Message Foo!',
|
|
2714
|
+
},
|
|
2715
|
+
})
|
|
2716
|
+
res = await app.request(req)
|
|
2717
|
+
expect(res.status).toBe(200)
|
|
2718
|
+
expect(res.headers.get('x-message')).toBe('Foo')
|
|
2719
|
+
expect(await res.text()).toBe('Message Foo!')
|
|
2720
|
+
|
|
2721
|
+
res = await app.request('/another-app/not-found')
|
|
2722
|
+
expect(res.status).toBe(404)
|
|
2723
|
+
expect(res.headers.get('x-message')).toBe('Foo')
|
|
2724
|
+
expect(await res.text()).toBe('Not Found from AnotherApp')
|
|
2725
|
+
|
|
2726
|
+
res = await app.request('/another-app/with-query?foo=bar&baz=qux')
|
|
2727
|
+
expect(res.status).toBe(200)
|
|
2728
|
+
expect(await res.text()).toBe('foo=bar&baz=qux')
|
|
2729
|
+
|
|
2730
|
+
res = await app.request('/another-app/with-params')
|
|
2731
|
+
expect(res.status).toBe(200)
|
|
2732
|
+
expect(await res.json()).toEqual({
|
|
2733
|
+
params: ['params'],
|
|
2734
|
+
})
|
|
2735
|
+
|
|
2736
|
+
res = await app.request('/another-app/undefined')
|
|
2737
|
+
expect(res.status).toBe(404)
|
|
2738
|
+
expect(await res.text()).toBe('Not Found from App')
|
|
2739
|
+
})
|
|
2740
|
+
|
|
2741
|
+
it('Should return response from Another app with an array option', async () => {
|
|
2742
|
+
const res = await app.request('/another-app-with-array-option/with-params')
|
|
2743
|
+
expect(res.status).toBe(200)
|
|
2744
|
+
expect(await res.json()).toEqual({
|
|
2745
|
+
params: ['param1', 'param2'],
|
|
2746
|
+
})
|
|
2747
|
+
})
|
|
2748
|
+
|
|
2749
|
+
it('Should return responses from AnotherApp - sub + slash', async () => {
|
|
2750
|
+
const res = await app.request('/another-app2/sub-slash')
|
|
2751
|
+
expect(res.status).toBe(200)
|
|
2752
|
+
expect(await res.text()).toBe('AnotherApp')
|
|
2753
|
+
})
|
|
2754
|
+
|
|
2755
|
+
it('Should return responses from AnotherApp - with `basePath()`', async () => {
|
|
2756
|
+
const res = await api.request('/api/another-app')
|
|
2757
|
+
expect(res.status).toBe(200)
|
|
2758
|
+
expect(await res.text()).toBe('AnotherApp')
|
|
2759
|
+
})
|
|
2760
|
+
})
|
|
2761
|
+
|
|
2762
|
+
describe('With encoded paths', () => {
|
|
2763
|
+
const anotherApp = (req: Request) => new Response(getPath(req))
|
|
2764
|
+
|
|
2765
|
+
it('Should strip a decoded non-ASCII mount prefix', async () => {
|
|
2766
|
+
const app = new App()
|
|
2767
|
+
app.mount('/api/é', anotherApp)
|
|
2768
|
+
|
|
2769
|
+
const res = await app.request('/api/%C3%A9/hello')
|
|
2770
|
+
|
|
2771
|
+
expect(res.status).toBe(200)
|
|
2772
|
+
expect(await res.text()).toBe('/hello')
|
|
2773
|
+
})
|
|
2774
|
+
|
|
2775
|
+
it('Should preserve an encoded slash as a literal path segment after stripping the prefix', async () => {
|
|
2776
|
+
const app = new App()
|
|
2777
|
+
app.mount('/api/v1', anotherApp)
|
|
2778
|
+
|
|
2779
|
+
const res = await app.request('/api/v1/admin%2Fsecret')
|
|
2780
|
+
|
|
2781
|
+
expect(res.status).toBe(200)
|
|
2782
|
+
expect(await res.text()).toBe('/admin%2Fsecret')
|
|
2783
|
+
})
|
|
2784
|
+
|
|
2785
|
+
it('Should preserve encoded percent characters after stripping the prefix', async () => {
|
|
2786
|
+
const app = new App()
|
|
2787
|
+
app.mount('/api', anotherApp)
|
|
2788
|
+
|
|
2789
|
+
const res = await app.request('/api/foo%252Fbar')
|
|
2790
|
+
|
|
2791
|
+
expect(res.status).toBe(200)
|
|
2792
|
+
expect(await res.text()).toBe('/foo%252Fbar')
|
|
2793
|
+
})
|
|
2794
|
+
})
|
|
2795
|
+
|
|
2796
|
+
describe('With fetch', () => {
|
|
2797
|
+
const anotherApp = async (req: Request, env: {}, executionContext: ExecutionContext) => {
|
|
2798
|
+
const path = getPath(req)
|
|
2799
|
+
if (path === '/') {
|
|
2800
|
+
return new Response(
|
|
2801
|
+
JSON.stringify({
|
|
2802
|
+
env,
|
|
2803
|
+
executionContext,
|
|
2804
|
+
}),
|
|
2805
|
+
{
|
|
2806
|
+
headers: {
|
|
2807
|
+
'Content-Type': 'application/json',
|
|
2808
|
+
},
|
|
2809
|
+
}
|
|
2810
|
+
)
|
|
2811
|
+
}
|
|
2812
|
+
return new Response('Not Found from AnotherApp', {
|
|
2813
|
+
status: 404,
|
|
2814
|
+
})
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2817
|
+
const app = new App()
|
|
2818
|
+
app.mount('/another-app', anotherApp)
|
|
2819
|
+
|
|
2820
|
+
it('Should handle Env and ExecuteContext', async () => {
|
|
2821
|
+
const request = new Request('http://localhost/another-app')
|
|
2822
|
+
const res = await app.fetch(
|
|
2823
|
+
request,
|
|
2824
|
+
{
|
|
2825
|
+
TOKEN: 'foo',
|
|
2826
|
+
},
|
|
2827
|
+
{
|
|
2828
|
+
// Force mocking!
|
|
2829
|
+
|
|
2830
|
+
// @ts-ignore
|
|
2831
|
+
waitUntil: 'waitUntil',
|
|
2832
|
+
|
|
2833
|
+
// @ts-ignore
|
|
2834
|
+
passThroughOnException: 'passThroughOnException',
|
|
2835
|
+
}
|
|
2836
|
+
)
|
|
2837
|
+
expect(res.status).toBe(200)
|
|
2838
|
+
expect(await res.json()).toEqual({
|
|
2839
|
+
env: {
|
|
2840
|
+
TOKEN: 'foo',
|
|
2841
|
+
},
|
|
2842
|
+
executionContext: {
|
|
2843
|
+
waitUntil: 'waitUntil',
|
|
2844
|
+
passThroughOnException: 'passThroughOnException',
|
|
2845
|
+
},
|
|
2846
|
+
})
|
|
2847
|
+
})
|
|
2848
|
+
})
|
|
2849
|
+
|
|
2850
|
+
describe('Mount on `/`', () => {
|
|
2851
|
+
const anotherApp = (req: Request, params: unknown) => {
|
|
2852
|
+
const path = getPath(req)
|
|
2853
|
+
if (path === '/') {
|
|
2854
|
+
return new Response('AnotherApp')
|
|
2855
|
+
}
|
|
2856
|
+
if (path === '/hello') {
|
|
2857
|
+
return new Response('Hello from AnotherApp')
|
|
2858
|
+
}
|
|
2859
|
+
if (path === '/good/night') {
|
|
2860
|
+
return new Response('Good Night from AnotherApp')
|
|
2861
|
+
}
|
|
2862
|
+
return new Response('Not Found from AnotherApp', {
|
|
2863
|
+
status: 404,
|
|
2864
|
+
})
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2867
|
+
const app = new App()
|
|
2868
|
+
app.mount('/', anotherApp)
|
|
2869
|
+
|
|
2870
|
+
it('Should return responses from AnotherApp - mount on `/`', async () => {
|
|
2871
|
+
let res = await app.request('/')
|
|
2872
|
+
expect(res.status).toBe(200)
|
|
2873
|
+
expect(await res.text()).toBe('AnotherApp')
|
|
2874
|
+
res = await app.request('/hello')
|
|
2875
|
+
expect(res.status).toBe(200)
|
|
2876
|
+
expect(await res.text()).toBe('Hello from AnotherApp')
|
|
2877
|
+
res = await app.request('/good/night')
|
|
2878
|
+
expect(res.status).toBe(200)
|
|
2879
|
+
expect(await res.text()).toBe('Good Night from AnotherApp')
|
|
2880
|
+
res = await app.request('/not-found')
|
|
2881
|
+
expect(res.status).toBe(404)
|
|
2882
|
+
expect(await res.text()).toBe('Not Found from AnotherApp')
|
|
2883
|
+
})
|
|
2884
|
+
})
|
|
2885
|
+
|
|
2886
|
+
describe('With replaceRequest option', () => {
|
|
2887
|
+
const anotherApp = (req: Request) => {
|
|
2888
|
+
const path = getPath(req)
|
|
2889
|
+
if (path === '/app') {
|
|
2890
|
+
return new Response(getPath(req))
|
|
2891
|
+
}
|
|
2892
|
+
return new Response(null, { status: 404 })
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
const app = new App()
|
|
2896
|
+
app.mount('/app', anotherApp, {
|
|
2897
|
+
replaceRequest: (req) => req,
|
|
2898
|
+
})
|
|
2899
|
+
|
|
2900
|
+
it('Should return 200 response with the correct path', async () => {
|
|
2901
|
+
const res = await app.request('/app')
|
|
2902
|
+
expect(res.status).toBe(200)
|
|
2903
|
+
expect(await res.text()).toBe('/app')
|
|
2904
|
+
})
|
|
2905
|
+
})
|
|
2906
|
+
|
|
2907
|
+
describe('With replaceRequest: false', () => {
|
|
2908
|
+
const anotherApp = (req: Request) => {
|
|
2909
|
+
const path = getPath(req)
|
|
2910
|
+
if (path === '/app') {
|
|
2911
|
+
return new Response(getPath(req))
|
|
2912
|
+
}
|
|
2913
|
+
return new Response(null, { status: 404 })
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
const app = new App()
|
|
2917
|
+
app.mount('/app', anotherApp, { replaceRequest: false })
|
|
2918
|
+
|
|
2919
|
+
it('Should return 200 response with the correct path', async () => {
|
|
2920
|
+
const res = await app.request('/app')
|
|
2921
|
+
expect(res.status).toBe(200)
|
|
2922
|
+
expect(await res.text()).toBe('/app')
|
|
2923
|
+
})
|
|
2924
|
+
})
|
|
2925
|
+
})
|
|
2926
|
+
|
|
2927
|
+
describe('HEAD method', () => {
|
|
2928
|
+
const app = new App()
|
|
2929
|
+
|
|
2930
|
+
app.get('/page', (c) => {
|
|
2931
|
+
c.header('X-Message', 'Foo')
|
|
2932
|
+
c.header('X-Method', c.req.method)
|
|
2933
|
+
return c.text('/page')
|
|
2934
|
+
})
|
|
2935
|
+
|
|
2936
|
+
it('Should return 200 response with body - GET /page', async () => {
|
|
2937
|
+
const res = await app.request('/page')
|
|
2938
|
+
expect(res.status).toBe(200)
|
|
2939
|
+
expect(res.headers.get('X-Message')).toBe('Foo')
|
|
2940
|
+
expect(res.headers.get('X-Method')).toBe('GET')
|
|
2941
|
+
expect(await res.text()).toBe('/page')
|
|
2942
|
+
})
|
|
2943
|
+
|
|
2944
|
+
it('Should return 200 response without body - HEAD /page', async () => {
|
|
2945
|
+
const req = new Request('http://localhost/page', {
|
|
2946
|
+
method: 'HEAD',
|
|
2947
|
+
})
|
|
2948
|
+
const res = await app.request(req)
|
|
2949
|
+
expect(res.status).toBe(200)
|
|
2950
|
+
expect(res.headers.get('X-Message')).toBe('Foo')
|
|
2951
|
+
expect(res.headers.get('X-Method')).toBe('HEAD')
|
|
2952
|
+
expect(res.body).toBe(null)
|
|
2953
|
+
})
|
|
2954
|
+
})
|
|
2955
|
+
|
|
2956
|
+
declare module './context.ts' {
|
|
2957
|
+
interface ContextRenderer {
|
|
2958
|
+
(content: string | Promise<string>, head: { title: string }): Response | Promise<Response>
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
describe('app.request()', () => {
|
|
2963
|
+
it('Should return response with Request and RequestInit as args', async () => {
|
|
2964
|
+
const app = new App()
|
|
2965
|
+
app.get('/foo', (c) => {
|
|
2966
|
+
return c.json(c.req.header('x-message'))
|
|
2967
|
+
})
|
|
2968
|
+
const req = new Request('http://localhost/foo')
|
|
2969
|
+
const headers = new Headers()
|
|
2970
|
+
headers.append('x-message', 'hello')
|
|
2971
|
+
const res = await app.request(req, {
|
|
2972
|
+
headers,
|
|
2973
|
+
})
|
|
2974
|
+
expect(res.status).toBe(200)
|
|
2975
|
+
expect(await res.text()).toBe('"hello"')
|
|
2976
|
+
})
|
|
2977
|
+
})
|
|
2978
|
+
|
|
2979
|
+
describe('app.fire()', () => {
|
|
2980
|
+
it('Should call global.addEventListener', () => {
|
|
2981
|
+
const app = new App()
|
|
2982
|
+
const addEventListener = vi.fn()
|
|
2983
|
+
global.addEventListener = addEventListener
|
|
2984
|
+
app.fire()
|
|
2985
|
+
expect(addEventListener).toHaveBeenCalledWith('fetch', expect.any(Function))
|
|
2986
|
+
|
|
2987
|
+
const fetchEventListener = addEventListener.mock.calls[0][1]
|
|
2988
|
+
const respondWith = vi.fn()
|
|
2989
|
+
const request = new Request('http://localhost')
|
|
2990
|
+
fetchEventListener({ respondWith, request })
|
|
2991
|
+
expect(respondWith).toHaveBeenCalledWith(expect.any(Promise))
|
|
2992
|
+
})
|
|
2993
|
+
})
|
|
2994
|
+
|
|
2995
|
+
describe('Context render and setRenderer', () => {
|
|
2996
|
+
const app = new App()
|
|
2997
|
+
app.get('/default', (c) => {
|
|
2998
|
+
return c.render('<h1>content</h1>', { title: 'dummy ' })
|
|
2999
|
+
})
|
|
3000
|
+
app.use('/page', async (c, next) => {
|
|
3001
|
+
c.setRenderer((content, head) => {
|
|
3002
|
+
return new Response(
|
|
3003
|
+
`<html><head><title>${head.title}</title></head><body><h1>${content}</h1></body></html>`
|
|
3004
|
+
)
|
|
3005
|
+
})
|
|
3006
|
+
await next()
|
|
3007
|
+
})
|
|
3008
|
+
app.get('/page', (c) => {
|
|
3009
|
+
return c.render('page content', {
|
|
3010
|
+
title: 'page title',
|
|
3011
|
+
})
|
|
3012
|
+
})
|
|
3013
|
+
|
|
3014
|
+
it('Should return a Response from the default renderer', async () => {
|
|
3015
|
+
const res = await app.request('/default')
|
|
3016
|
+
expect(await res.text()).toBe('<h1>content</h1>')
|
|
3017
|
+
})
|
|
3018
|
+
|
|
3019
|
+
it('Should return a Response from the custom renderer', async () => {
|
|
3020
|
+
const res = await app.request('/page')
|
|
3021
|
+
expect(await res.text()).toBe(
|
|
3022
|
+
'<html><head><title>page title</title></head><body><h1>page content</h1></body></html>'
|
|
3023
|
+
)
|
|
3024
|
+
})
|
|
3025
|
+
})
|
|
3026
|
+
|
|
3027
|
+
describe('c.var - with testing types', () => {
|
|
3028
|
+
const app = new App<{
|
|
3029
|
+
Bindings: {
|
|
3030
|
+
Token: string
|
|
3031
|
+
}
|
|
3032
|
+
}>()
|
|
3033
|
+
|
|
3034
|
+
const mw =
|
|
3035
|
+
(): MiddlewareHandler<{
|
|
3036
|
+
Variables: {
|
|
3037
|
+
echo: (str: string) => string
|
|
3038
|
+
}
|
|
3039
|
+
}> =>
|
|
3040
|
+
async (c, next) => {
|
|
3041
|
+
c.set('echo', (str) => str)
|
|
3042
|
+
await next()
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
const mw2 =
|
|
3046
|
+
(): MiddlewareHandler<{
|
|
3047
|
+
Variables: {
|
|
3048
|
+
echo2: (str: string) => string
|
|
3049
|
+
}
|
|
3050
|
+
}> =>
|
|
3051
|
+
async (c, next) => {
|
|
3052
|
+
c.set('echo2', (str) => str)
|
|
3053
|
+
await next()
|
|
3054
|
+
}
|
|
3055
|
+
|
|
3056
|
+
const mw3 =
|
|
3057
|
+
(): MiddlewareHandler<{
|
|
3058
|
+
Variables: {
|
|
3059
|
+
echo3: (str: string) => string
|
|
3060
|
+
}
|
|
3061
|
+
}> =>
|
|
3062
|
+
async (c, next) => {
|
|
3063
|
+
c.set('echo3', (str) => str)
|
|
3064
|
+
await next()
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3067
|
+
const mw4 =
|
|
3068
|
+
(): MiddlewareHandler<{
|
|
3069
|
+
Variables: {
|
|
3070
|
+
echo4: (str: string) => string
|
|
3071
|
+
}
|
|
3072
|
+
}> =>
|
|
3073
|
+
async (c, next) => {
|
|
3074
|
+
c.set('echo4', (str) => str)
|
|
3075
|
+
await next()
|
|
3076
|
+
}
|
|
3077
|
+
|
|
3078
|
+
const mw5 =
|
|
3079
|
+
(): MiddlewareHandler<{
|
|
3080
|
+
Variables: {
|
|
3081
|
+
echo5: (str: string) => string
|
|
3082
|
+
}
|
|
3083
|
+
}> =>
|
|
3084
|
+
async (c, next) => {
|
|
3085
|
+
c.set('echo5', (str) => str)
|
|
3086
|
+
await next()
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
const mw6 =
|
|
3090
|
+
(): MiddlewareHandler<{
|
|
3091
|
+
Variables: {
|
|
3092
|
+
echo6: (str: string) => string
|
|
3093
|
+
}
|
|
3094
|
+
}> =>
|
|
3095
|
+
async (c, next) => {
|
|
3096
|
+
c.set('echo6', (str) => str)
|
|
3097
|
+
await next()
|
|
3098
|
+
}
|
|
3099
|
+
|
|
3100
|
+
const mw7 =
|
|
3101
|
+
(): MiddlewareHandler<{
|
|
3102
|
+
Variables: {
|
|
3103
|
+
echo7: (str: string) => string
|
|
3104
|
+
}
|
|
3105
|
+
}> =>
|
|
3106
|
+
async (c, next) => {
|
|
3107
|
+
c.set('echo7', (str) => str)
|
|
3108
|
+
await next()
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
const mw8 =
|
|
3112
|
+
(): MiddlewareHandler<{
|
|
3113
|
+
Variables: {
|
|
3114
|
+
echo8: (str: string) => string
|
|
3115
|
+
}
|
|
3116
|
+
}> =>
|
|
3117
|
+
async (c, next) => {
|
|
3118
|
+
c.set('echo8', (str) => str)
|
|
3119
|
+
await next()
|
|
3120
|
+
}
|
|
3121
|
+
|
|
3122
|
+
const mw9 =
|
|
3123
|
+
(): MiddlewareHandler<{
|
|
3124
|
+
Variables: {
|
|
3125
|
+
echo9: (str: string) => string
|
|
3126
|
+
}
|
|
3127
|
+
}> =>
|
|
3128
|
+
async (c, next) => {
|
|
3129
|
+
c.set('echo9', (str) => str)
|
|
3130
|
+
await next()
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
const mw10 =
|
|
3134
|
+
(): MiddlewareHandler<{
|
|
3135
|
+
Variables: {
|
|
3136
|
+
echo10: (str: string) => string
|
|
3137
|
+
}
|
|
3138
|
+
}> =>
|
|
3139
|
+
async (c, next) => {
|
|
3140
|
+
c.set('echo10', (str) => str)
|
|
3141
|
+
await next()
|
|
3142
|
+
}
|
|
3143
|
+
|
|
3144
|
+
app.use('/no-path/1').get(mw(), (c) => {
|
|
3145
|
+
return c.text(c.var.echo('hello'))
|
|
3146
|
+
})
|
|
3147
|
+
|
|
3148
|
+
app.use('/no-path/2').get(mw(), mw2(), (c) => {
|
|
3149
|
+
return c.text(c.var.echo('hello') + c.var.echo2('hello2'))
|
|
3150
|
+
})
|
|
3151
|
+
|
|
3152
|
+
app.use('/no-path/3').get(mw(), mw2(), mw3(), (c) => {
|
|
3153
|
+
return c.text(c.var.echo('hello') + c.var.echo2('hello2') + c.var.echo3('hello3'))
|
|
3154
|
+
})
|
|
3155
|
+
|
|
3156
|
+
app.use('/no-path/4').get(mw(), mw2(), mw3(), mw4(), (c) => {
|
|
3157
|
+
return c.text(
|
|
3158
|
+
c.var.echo('hello') + c.var.echo2('hello2') + c.var.echo3('hello3') + c.var.echo4('hello4')
|
|
3159
|
+
)
|
|
3160
|
+
})
|
|
3161
|
+
|
|
3162
|
+
app.use('/no-path/5').get(mw(), mw2(), mw3(), mw4(), mw5(), (c) => {
|
|
3163
|
+
return c.text(
|
|
3164
|
+
c.var.echo('hello') +
|
|
3165
|
+
c.var.echo2('hello2') +
|
|
3166
|
+
c.var.echo3('hello3') +
|
|
3167
|
+
c.var.echo4('hello4') +
|
|
3168
|
+
c.var.echo5('hello5')
|
|
3169
|
+
)
|
|
3170
|
+
})
|
|
3171
|
+
|
|
3172
|
+
app.use('/no-path/6').get(mw(), mw2(), mw3(), mw4(), mw5(), mw6(), (c) => {
|
|
3173
|
+
return c.text(
|
|
3174
|
+
c.var.echo('hello') +
|
|
3175
|
+
c.var.echo2('hello2') +
|
|
3176
|
+
c.var.echo3('hello3') +
|
|
3177
|
+
c.var.echo4('hello4') +
|
|
3178
|
+
c.var.echo5('hello5') +
|
|
3179
|
+
c.var.echo6('hello6')
|
|
3180
|
+
)
|
|
3181
|
+
})
|
|
3182
|
+
|
|
3183
|
+
app.use('/no-path/7').get(mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), (c) => {
|
|
3184
|
+
return c.text(
|
|
3185
|
+
c.var.echo('hello') +
|
|
3186
|
+
c.var.echo2('hello2') +
|
|
3187
|
+
c.var.echo3('hello3') +
|
|
3188
|
+
c.var.echo4('hello4') +
|
|
3189
|
+
c.var.echo5('hello5') +
|
|
3190
|
+
c.var.echo6('hello6') +
|
|
3191
|
+
c.var.echo7('hello7')
|
|
3192
|
+
)
|
|
3193
|
+
})
|
|
3194
|
+
|
|
3195
|
+
app.use('/no-path/8').get(mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), mw8(), (c) => {
|
|
3196
|
+
return c.text(
|
|
3197
|
+
c.var.echo('hello') +
|
|
3198
|
+
c.var.echo2('hello2') +
|
|
3199
|
+
c.var.echo3('hello3') +
|
|
3200
|
+
c.var.echo4('hello4') +
|
|
3201
|
+
c.var.echo5('hello5') +
|
|
3202
|
+
c.var.echo6('hello6') +
|
|
3203
|
+
c.var.echo7('hello7') +
|
|
3204
|
+
c.var.echo8('hello8')
|
|
3205
|
+
)
|
|
3206
|
+
})
|
|
3207
|
+
|
|
3208
|
+
app.use('/no-path/9').get(mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), mw8(), mw9(), (c) => {
|
|
3209
|
+
return c.text(
|
|
3210
|
+
c.var.echo('hello') +
|
|
3211
|
+
c.var.echo2('hello2') +
|
|
3212
|
+
c.var.echo3('hello3') +
|
|
3213
|
+
c.var.echo4('hello4') +
|
|
3214
|
+
c.var.echo5('hello5') +
|
|
3215
|
+
c.var.echo6('hello6') +
|
|
3216
|
+
c.var.echo7('hello7') +
|
|
3217
|
+
c.var.echo8('hello8') +
|
|
3218
|
+
c.var.echo9('hello9')
|
|
3219
|
+
)
|
|
3220
|
+
})
|
|
3221
|
+
|
|
3222
|
+
app.use('/no-path/10').get(
|
|
3223
|
+
// @ts-expect-error The handlers are more than 10
|
|
3224
|
+
mw(),
|
|
3225
|
+
mw2(),
|
|
3226
|
+
mw3(),
|
|
3227
|
+
mw4(),
|
|
3228
|
+
mw5(),
|
|
3229
|
+
mw6(),
|
|
3230
|
+
mw7(),
|
|
3231
|
+
mw8(),
|
|
3232
|
+
mw9(),
|
|
3233
|
+
mw10(),
|
|
3234
|
+
(c) => {
|
|
3235
|
+
return c.text(
|
|
3236
|
+
// @ts-expect-error
|
|
3237
|
+
c.var.echo('hello') +
|
|
3238
|
+
c.var.echo2('hello2') +
|
|
3239
|
+
c.var.echo3('hello3') +
|
|
3240
|
+
c.var.echo4('hello4') +
|
|
3241
|
+
c.var.echo5('hello5') +
|
|
3242
|
+
c.var.echo6('hello6') +
|
|
3243
|
+
c.var.echo7('hello7') +
|
|
3244
|
+
c.var.echo8('hello8') +
|
|
3245
|
+
c.var.echo9('hello9') +
|
|
3246
|
+
c.var.echo10('hello10')
|
|
3247
|
+
)
|
|
3248
|
+
}
|
|
3249
|
+
)
|
|
3250
|
+
|
|
3251
|
+
app.get('*', mw())
|
|
3252
|
+
|
|
3253
|
+
app.get('/path/1', mw(), (c) => {
|
|
3254
|
+
return c.text(c.var.echo('hello'))
|
|
3255
|
+
})
|
|
3256
|
+
|
|
3257
|
+
app.get('/path/2', mw(), mw2(), (c) => {
|
|
3258
|
+
return c.text(c.var.echo('hello') + c.var.echo2('hello2'))
|
|
3259
|
+
})
|
|
3260
|
+
|
|
3261
|
+
app.get('/path/3', mw(), mw2(), mw3(), (c) => {
|
|
3262
|
+
return c.text(c.var.echo('hello') + c.var.echo2('hello2') + c.var.echo3('hello3'))
|
|
3263
|
+
})
|
|
3264
|
+
|
|
3265
|
+
app.get('/path/4', mw(), mw2(), mw3(), mw4(), (c) => {
|
|
3266
|
+
return c.text(
|
|
3267
|
+
c.var.echo('hello') + c.var.echo2('hello2') + c.var.echo3('hello3') + c.var.echo4('hello4')
|
|
3268
|
+
)
|
|
3269
|
+
})
|
|
3270
|
+
|
|
3271
|
+
app.get('/path/5', mw(), mw2(), mw3(), mw4(), mw5(), (c) => {
|
|
3272
|
+
return c.text(
|
|
3273
|
+
c.var.echo('hello') +
|
|
3274
|
+
c.var.echo2('hello2') +
|
|
3275
|
+
c.var.echo3('hello3') +
|
|
3276
|
+
c.var.echo4('hello4') +
|
|
3277
|
+
c.var.echo5('hello5')
|
|
3278
|
+
)
|
|
3279
|
+
})
|
|
3280
|
+
|
|
3281
|
+
app.get('/path/6', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), (c) => {
|
|
3282
|
+
return c.text(
|
|
3283
|
+
c.var.echo('hello') +
|
|
3284
|
+
c.var.echo2('hello2') +
|
|
3285
|
+
c.var.echo3('hello3') +
|
|
3286
|
+
c.var.echo4('hello4') +
|
|
3287
|
+
c.var.echo5('hello5') +
|
|
3288
|
+
c.var.echo6('hello6')
|
|
3289
|
+
)
|
|
3290
|
+
})
|
|
3291
|
+
|
|
3292
|
+
app.get('/path/7', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), (c) => {
|
|
3293
|
+
return c.text(
|
|
3294
|
+
c.var.echo('hello') +
|
|
3295
|
+
c.var.echo2('hello2') +
|
|
3296
|
+
c.var.echo3('hello3') +
|
|
3297
|
+
c.var.echo4('hello4') +
|
|
3298
|
+
c.var.echo5('hello5') +
|
|
3299
|
+
c.var.echo6('hello6') +
|
|
3300
|
+
c.var.echo7('hello7')
|
|
3301
|
+
)
|
|
3302
|
+
})
|
|
3303
|
+
|
|
3304
|
+
app.get('/path/8', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), mw8(), (c) => {
|
|
3305
|
+
return c.text(
|
|
3306
|
+
c.var.echo('hello') +
|
|
3307
|
+
c.var.echo2('hello2') +
|
|
3308
|
+
c.var.echo3('hello3') +
|
|
3309
|
+
c.var.echo4('hello4') +
|
|
3310
|
+
c.var.echo5('hello5') +
|
|
3311
|
+
c.var.echo6('hello6') +
|
|
3312
|
+
c.var.echo7('hello7') +
|
|
3313
|
+
c.var.echo8('hello8')
|
|
3314
|
+
)
|
|
3315
|
+
})
|
|
3316
|
+
|
|
3317
|
+
app.get('/path/9', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), mw8(), mw9(), (c) => {
|
|
3318
|
+
return c.text(
|
|
3319
|
+
c.var.echo('hello') +
|
|
3320
|
+
c.var.echo2('hello2') +
|
|
3321
|
+
c.var.echo3('hello3') +
|
|
3322
|
+
c.var.echo4('hello4') +
|
|
3323
|
+
c.var.echo5('hello5') +
|
|
3324
|
+
c.var.echo6('hello6') +
|
|
3325
|
+
c.var.echo7('hello7') +
|
|
3326
|
+
c.var.echo8('hello8') +
|
|
3327
|
+
c.var.echo9('hello9')
|
|
3328
|
+
)
|
|
3329
|
+
})
|
|
3330
|
+
|
|
3331
|
+
// @ts-expect-error
|
|
3332
|
+
app.get('/path/10', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), mw8(), mw9(), mw10(), (c) => {
|
|
3333
|
+
return c.text(
|
|
3334
|
+
// @ts-expect-error
|
|
3335
|
+
c.var.echo('hello') +
|
|
3336
|
+
// @ts-expect-error
|
|
3337
|
+
c.var.echo2('hello2') +
|
|
3338
|
+
// @ts-expect-error
|
|
3339
|
+
c.var.echo3('hello3') +
|
|
3340
|
+
// @ts-expect-error
|
|
3341
|
+
c.var.echo4('hello4') +
|
|
3342
|
+
// @ts-expect-error
|
|
3343
|
+
c.var.echo5('hello5') +
|
|
3344
|
+
// @ts-expect-error
|
|
3345
|
+
c.var.echo6('hello6') +
|
|
3346
|
+
// @ts-expect-error
|
|
3347
|
+
c.var.echo7('hello7') +
|
|
3348
|
+
// @ts-expect-error
|
|
3349
|
+
c.var.echo8('hello8') +
|
|
3350
|
+
// @ts-expect-error
|
|
3351
|
+
c.var.echo9('hello9') +
|
|
3352
|
+
// @ts-expect-error
|
|
3353
|
+
c.var.echo10('hello10')
|
|
3354
|
+
)
|
|
3355
|
+
})
|
|
3356
|
+
|
|
3357
|
+
app.on('GET', '/on/1', mw(), (c) => {
|
|
3358
|
+
return c.text(c.var.echo('hello'))
|
|
3359
|
+
})
|
|
3360
|
+
|
|
3361
|
+
app.on('GET', '/on/2', mw(), mw2(), (c) => {
|
|
3362
|
+
return c.text(c.var.echo('hello') + c.var.echo2('hello2'))
|
|
3363
|
+
})
|
|
3364
|
+
|
|
3365
|
+
app.on('GET', '/on/3', mw(), mw2(), mw3(), (c) => {
|
|
3366
|
+
return c.text(c.var.echo('hello') + c.var.echo2('hello2') + c.var.echo3('hello3'))
|
|
3367
|
+
})
|
|
3368
|
+
|
|
3369
|
+
app.on('GET', '/on/4', mw(), mw2(), mw3(), mw4(), (c) => {
|
|
3370
|
+
return c.text(
|
|
3371
|
+
c.var.echo('hello') + c.var.echo2('hello2') + c.var.echo3('hello3') + c.var.echo4('hello4')
|
|
3372
|
+
)
|
|
3373
|
+
})
|
|
3374
|
+
|
|
3375
|
+
app.on('GET', '/on/5', mw(), mw2(), mw3(), mw4(), mw5(), (c) => {
|
|
3376
|
+
return c.text(
|
|
3377
|
+
c.var.echo('hello') +
|
|
3378
|
+
c.var.echo2('hello2') +
|
|
3379
|
+
c.var.echo3('hello3') +
|
|
3380
|
+
c.var.echo4('hello4') +
|
|
3381
|
+
c.var.echo5('hello5')
|
|
3382
|
+
)
|
|
3383
|
+
})
|
|
3384
|
+
|
|
3385
|
+
app.on('GET', '/on/6', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), (c) => {
|
|
3386
|
+
return c.text(
|
|
3387
|
+
c.var.echo('hello') +
|
|
3388
|
+
c.var.echo2('hello2') +
|
|
3389
|
+
c.var.echo3('hello3') +
|
|
3390
|
+
c.var.echo4('hello4') +
|
|
3391
|
+
c.var.echo5('hello5') +
|
|
3392
|
+
c.var.echo6('hello6')
|
|
3393
|
+
)
|
|
3394
|
+
})
|
|
3395
|
+
|
|
3396
|
+
app.on('GET', '/on/7', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), (c) => {
|
|
3397
|
+
return c.text(
|
|
3398
|
+
c.var.echo('hello') +
|
|
3399
|
+
c.var.echo2('hello2') +
|
|
3400
|
+
c.var.echo3('hello3') +
|
|
3401
|
+
c.var.echo4('hello4') +
|
|
3402
|
+
c.var.echo5('hello5') +
|
|
3403
|
+
c.var.echo6('hello6') +
|
|
3404
|
+
c.var.echo7('hello7')
|
|
3405
|
+
)
|
|
3406
|
+
})
|
|
3407
|
+
|
|
3408
|
+
app.on('GET', '/on/8', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), mw8(), (c) => {
|
|
3409
|
+
return c.text(
|
|
3410
|
+
c.var.echo('hello') +
|
|
3411
|
+
c.var.echo2('hello2') +
|
|
3412
|
+
c.var.echo3('hello3') +
|
|
3413
|
+
c.var.echo4('hello4') +
|
|
3414
|
+
c.var.echo5('hello5') +
|
|
3415
|
+
c.var.echo6('hello6') +
|
|
3416
|
+
c.var.echo7('hello7') +
|
|
3417
|
+
c.var.echo8('hello8')
|
|
3418
|
+
)
|
|
3419
|
+
})
|
|
3420
|
+
|
|
3421
|
+
app.on('GET', '/on/9', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), mw8(), mw9(), (c) => {
|
|
3422
|
+
return c.text(
|
|
3423
|
+
c.var.echo('hello') +
|
|
3424
|
+
c.var.echo2('hello2') +
|
|
3425
|
+
c.var.echo3('hello3') +
|
|
3426
|
+
c.var.echo4('hello4') +
|
|
3427
|
+
c.var.echo5('hello5') +
|
|
3428
|
+
c.var.echo6('hello6') +
|
|
3429
|
+
c.var.echo7('hello7') +
|
|
3430
|
+
c.var.echo8('hello8') +
|
|
3431
|
+
c.var.echo9('hello9')
|
|
3432
|
+
)
|
|
3433
|
+
})
|
|
3434
|
+
|
|
3435
|
+
// @ts-expect-error
|
|
3436
|
+
app.on(
|
|
3437
|
+
'GET',
|
|
3438
|
+
'/on/10',
|
|
3439
|
+
mw(),
|
|
3440
|
+
mw2(),
|
|
3441
|
+
mw3(),
|
|
3442
|
+
mw4(),
|
|
3443
|
+
mw5(),
|
|
3444
|
+
mw6(),
|
|
3445
|
+
mw7(),
|
|
3446
|
+
mw8(),
|
|
3447
|
+
mw9(),
|
|
3448
|
+
mw10(),
|
|
3449
|
+
(c) => {
|
|
3450
|
+
return c.text(
|
|
3451
|
+
// @ts-expect-error
|
|
3452
|
+
c.var.echo('hello') +
|
|
3453
|
+
// @ts-expect-error
|
|
3454
|
+
c.var.echo2('hello2') +
|
|
3455
|
+
// @ts-expect-error
|
|
3456
|
+
c.var.echo3('hello3') +
|
|
3457
|
+
// @ts-expect-error
|
|
3458
|
+
c.var.echo4('hello4') +
|
|
3459
|
+
// @ts-expect-error
|
|
3460
|
+
c.var.echo5('hello5') +
|
|
3461
|
+
// @ts-expect-error
|
|
3462
|
+
c.var.echo6('hello6') +
|
|
3463
|
+
// @ts-expect-error
|
|
3464
|
+
c.var.echo7('hello7') +
|
|
3465
|
+
// @ts-expect-error
|
|
3466
|
+
c.var.echo8('hello8') +
|
|
3467
|
+
// @ts-expect-error
|
|
3468
|
+
c.var.echo9('hello9') +
|
|
3469
|
+
// @ts-expect-error
|
|
3470
|
+
c.var.echo10('hello10')
|
|
3471
|
+
)
|
|
3472
|
+
}
|
|
3473
|
+
)
|
|
3474
|
+
|
|
3475
|
+
app.on(['GET', 'POST'], '/on/1', mw(), (c) => {
|
|
3476
|
+
return c.text(c.var.echo('hello'))
|
|
3477
|
+
})
|
|
3478
|
+
|
|
3479
|
+
app.on(['GET', 'POST'], '/on/2', mw(), mw2(), (c) => {
|
|
3480
|
+
return c.text(c.var.echo('hello') + c.var.echo2('hello2'))
|
|
3481
|
+
})
|
|
3482
|
+
|
|
3483
|
+
app.on(['GET', 'POST'], '/on/3', mw(), mw2(), mw3(), (c) => {
|
|
3484
|
+
return c.text(c.var.echo('hello') + c.var.echo2('hello2') + c.var.echo3('hello3'))
|
|
3485
|
+
})
|
|
3486
|
+
|
|
3487
|
+
app.on(['GET', 'POST'], '/on/4', mw(), mw2(), mw3(), mw4(), (c) => {
|
|
3488
|
+
return c.text(
|
|
3489
|
+
c.var.echo('hello') + c.var.echo2('hello2') + c.var.echo3('hello3') + c.var.echo4('hello4')
|
|
3490
|
+
)
|
|
3491
|
+
})
|
|
3492
|
+
|
|
3493
|
+
app.on(['GET', 'POST'], '/on/5', mw(), mw2(), mw3(), mw4(), mw5(), (c) => {
|
|
3494
|
+
return c.text(
|
|
3495
|
+
c.var.echo('hello') +
|
|
3496
|
+
c.var.echo2('hello2') +
|
|
3497
|
+
c.var.echo3('hello3') +
|
|
3498
|
+
c.var.echo4('hello4') +
|
|
3499
|
+
c.var.echo5('hello5')
|
|
3500
|
+
)
|
|
3501
|
+
})
|
|
3502
|
+
|
|
3503
|
+
app.on(['GET', 'POST'], '/on/6', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), (c) => {
|
|
3504
|
+
return c.text(
|
|
3505
|
+
c.var.echo('hello') +
|
|
3506
|
+
c.var.echo2('hello2') +
|
|
3507
|
+
c.var.echo3('hello3') +
|
|
3508
|
+
c.var.echo4('hello4') +
|
|
3509
|
+
c.var.echo5('hello5') +
|
|
3510
|
+
c.var.echo6('hello6')
|
|
3511
|
+
)
|
|
3512
|
+
})
|
|
3513
|
+
|
|
3514
|
+
app.on(['GET', 'POST'], '/on/7', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), (c) => {
|
|
3515
|
+
return c.text(
|
|
3516
|
+
c.var.echo('hello') +
|
|
3517
|
+
c.var.echo2('hello2') +
|
|
3518
|
+
c.var.echo3('hello3') +
|
|
3519
|
+
c.var.echo4('hello4') +
|
|
3520
|
+
c.var.echo5('hello5') +
|
|
3521
|
+
c.var.echo6('hello6') +
|
|
3522
|
+
c.var.echo7('hello7')
|
|
3523
|
+
)
|
|
3524
|
+
})
|
|
3525
|
+
|
|
3526
|
+
app.on(['GET', 'POST'], '/on/8', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), mw8(), (c) => {
|
|
3527
|
+
return c.text(
|
|
3528
|
+
c.var.echo('hello') +
|
|
3529
|
+
c.var.echo2('hello2') +
|
|
3530
|
+
c.var.echo3('hello3') +
|
|
3531
|
+
c.var.echo4('hello4') +
|
|
3532
|
+
c.var.echo5('hello5') +
|
|
3533
|
+
c.var.echo6('hello6') +
|
|
3534
|
+
c.var.echo7('hello7') +
|
|
3535
|
+
c.var.echo8('hello8')
|
|
3536
|
+
)
|
|
3537
|
+
})
|
|
3538
|
+
|
|
3539
|
+
app.on(
|
|
3540
|
+
['GET', 'POST'],
|
|
3541
|
+
'/on/9',
|
|
3542
|
+
mw(),
|
|
3543
|
+
mw2(),
|
|
3544
|
+
mw3(),
|
|
3545
|
+
mw4(),
|
|
3546
|
+
mw5(),
|
|
3547
|
+
mw6(),
|
|
3548
|
+
mw7(),
|
|
3549
|
+
mw8(),
|
|
3550
|
+
mw9(),
|
|
3551
|
+
(c) => {
|
|
3552
|
+
return c.text(
|
|
3553
|
+
c.var.echo('hello') +
|
|
3554
|
+
c.var.echo2('hello2') +
|
|
3555
|
+
c.var.echo3('hello3') +
|
|
3556
|
+
c.var.echo4('hello4') +
|
|
3557
|
+
c.var.echo5('hello5') +
|
|
3558
|
+
c.var.echo6('hello6') +
|
|
3559
|
+
c.var.echo7('hello7') +
|
|
3560
|
+
c.var.echo8('hello8') +
|
|
3561
|
+
c.var.echo9('hello9')
|
|
3562
|
+
)
|
|
3563
|
+
}
|
|
3564
|
+
)
|
|
3565
|
+
|
|
3566
|
+
// @ts-expect-error
|
|
3567
|
+
app.on(
|
|
3568
|
+
['GET', 'POST'],
|
|
3569
|
+
'/on/10',
|
|
3570
|
+
mw(),
|
|
3571
|
+
mw2(),
|
|
3572
|
+
mw3(),
|
|
3573
|
+
mw4(),
|
|
3574
|
+
mw5(),
|
|
3575
|
+
mw6(),
|
|
3576
|
+
mw7(),
|
|
3577
|
+
mw8(),
|
|
3578
|
+
mw9(),
|
|
3579
|
+
mw10(),
|
|
3580
|
+
(c) => {
|
|
3581
|
+
return c.text(
|
|
3582
|
+
// @ts-expect-error
|
|
3583
|
+
c.var.echo('hello') +
|
|
3584
|
+
// @ts-expect-error
|
|
3585
|
+
c.var.echo2('hello2') +
|
|
3586
|
+
// @ts-expect-error
|
|
3587
|
+
c.var.echo3('hello3') +
|
|
3588
|
+
// @ts-expect-error
|
|
3589
|
+
c.var.echo4('hello4') +
|
|
3590
|
+
// @ts-expect-error
|
|
3591
|
+
c.var.echo5('hello5') +
|
|
3592
|
+
// @ts-expect-error
|
|
3593
|
+
c.var.echo6('hello6') +
|
|
3594
|
+
// @ts-expect-error
|
|
3595
|
+
c.var.echo7('hello7') +
|
|
3596
|
+
// @ts-expect-error
|
|
3597
|
+
c.var.echo8('hello8') +
|
|
3598
|
+
// @ts-expect-error
|
|
3599
|
+
c.var.echo9('hello9') +
|
|
3600
|
+
// @ts-expect-error
|
|
3601
|
+
c.var.echo10('hello10')
|
|
3602
|
+
)
|
|
3603
|
+
}
|
|
3604
|
+
)
|
|
3605
|
+
|
|
3606
|
+
it('Should return the correct response - no-path', async () => {
|
|
3607
|
+
let res = await app.request('/no-path/1')
|
|
3608
|
+
expect(res.status).toBe(200)
|
|
3609
|
+
expect(await res.text()).toBe('hello')
|
|
3610
|
+
|
|
3611
|
+
res = await app.request('/no-path/2')
|
|
3612
|
+
expect(res.status).toBe(200)
|
|
3613
|
+
expect(await res.text()).toBe('hellohello2')
|
|
3614
|
+
|
|
3615
|
+
res = await app.request('/no-path/3')
|
|
3616
|
+
expect(res.status).toBe(200)
|
|
3617
|
+
expect(await res.text()).toBe('hellohello2hello3')
|
|
3618
|
+
|
|
3619
|
+
res = await app.request('/no-path/4')
|
|
3620
|
+
expect(res.status).toBe(200)
|
|
3621
|
+
expect(await res.text()).toBe('hellohello2hello3hello4')
|
|
3622
|
+
|
|
3623
|
+
res = await app.request('/no-path/5')
|
|
3624
|
+
expect(res.status).toBe(200)
|
|
3625
|
+
expect(await res.text()).toBe('hellohello2hello3hello4hello5')
|
|
3626
|
+
})
|
|
3627
|
+
|
|
3628
|
+
it('Should return the correct response - path', async () => {
|
|
3629
|
+
let res = await app.request('/path/1')
|
|
3630
|
+
expect(res.status).toBe(200)
|
|
3631
|
+
expect(await res.text()).toBe('hello')
|
|
3632
|
+
|
|
3633
|
+
res = await app.request('/path/2')
|
|
3634
|
+
expect(res.status).toBe(200)
|
|
3635
|
+
expect(await res.text()).toBe('hellohello2')
|
|
3636
|
+
|
|
3637
|
+
res = await app.request('/path/3')
|
|
3638
|
+
expect(res.status).toBe(200)
|
|
3639
|
+
expect(await res.text()).toBe('hellohello2hello3')
|
|
3640
|
+
|
|
3641
|
+
res = await app.request('/path/4')
|
|
3642
|
+
expect(res.status).toBe(200)
|
|
3643
|
+
expect(await res.text()).toBe('hellohello2hello3hello4')
|
|
3644
|
+
|
|
3645
|
+
res = await app.request('/path/5')
|
|
3646
|
+
expect(res.status).toBe(200)
|
|
3647
|
+
expect(await res.text()).toBe('hellohello2hello3hello4hello5')
|
|
3648
|
+
})
|
|
3649
|
+
|
|
3650
|
+
it('Should return the correct response - on', async () => {
|
|
3651
|
+
let res = await app.request('/on/1')
|
|
3652
|
+
expect(res.status).toBe(200)
|
|
3653
|
+
expect(await res.text()).toBe('hello')
|
|
3654
|
+
|
|
3655
|
+
res = await app.request('/on/2')
|
|
3656
|
+
expect(res.status).toBe(200)
|
|
3657
|
+
expect(await res.text()).toBe('hellohello2')
|
|
3658
|
+
|
|
3659
|
+
res = await app.request('/on/3')
|
|
3660
|
+
expect(res.status).toBe(200)
|
|
3661
|
+
expect(await res.text()).toBe('hellohello2hello3')
|
|
3662
|
+
|
|
3663
|
+
res = await app.request('/on/4')
|
|
3664
|
+
expect(res.status).toBe(200)
|
|
3665
|
+
expect(await res.text()).toBe('hellohello2hello3hello4')
|
|
3666
|
+
|
|
3667
|
+
res = await app.request('/on/5')
|
|
3668
|
+
expect(res.status).toBe(200)
|
|
3669
|
+
expect(await res.text()).toBe('hellohello2hello3hello4hello5')
|
|
3670
|
+
})
|
|
3671
|
+
|
|
3672
|
+
it('Should not throw type errors', () => {
|
|
3673
|
+
const app = new App<{
|
|
3674
|
+
Variables: {
|
|
3675
|
+
hello: () => string
|
|
3676
|
+
}
|
|
3677
|
+
}>()
|
|
3678
|
+
|
|
3679
|
+
app.get(mw())
|
|
3680
|
+
app.get(mw(), mw2())
|
|
3681
|
+
app.get(mw(), mw2(), mw3())
|
|
3682
|
+
app.get(mw(), mw2(), mw3(), mw4())
|
|
3683
|
+
app.get(mw(), mw2(), mw3(), mw4(), mw5())
|
|
3684
|
+
|
|
3685
|
+
app.get('/', mw())
|
|
3686
|
+
app.get('/', mw(), mw2())
|
|
3687
|
+
app.get('/', mw(), mw2(), mw3())
|
|
3688
|
+
app.get('/', mw(), mw2(), mw3(), mw4())
|
|
3689
|
+
app.get('/', mw(), mw2(), mw3(), mw4(), mw5())
|
|
3690
|
+
})
|
|
3691
|
+
|
|
3692
|
+
it('Should be a read-only', () => {
|
|
3693
|
+
expect(() => {
|
|
3694
|
+
app.get('/path/1', mw(), (c) => {
|
|
3695
|
+
// @ts-expect-error
|
|
3696
|
+
c.var.echo = 'hello'
|
|
3697
|
+
return c.text(c.var.echo('hello'))
|
|
3698
|
+
})
|
|
3699
|
+
}).toThrow()
|
|
3700
|
+
})
|
|
3701
|
+
|
|
3702
|
+
it('Should not throw a type error', (c) => {
|
|
3703
|
+
const app = new App<{
|
|
3704
|
+
Bindings: {
|
|
3705
|
+
TOKEN: string
|
|
3706
|
+
}
|
|
3707
|
+
}>()
|
|
3708
|
+
|
|
3709
|
+
app.get('/', poweredBy(), async (c) => {
|
|
3710
|
+
expectTypeOf(c.env.TOKEN).toEqualTypeOf<string>()
|
|
3711
|
+
})
|
|
3712
|
+
|
|
3713
|
+
app.get('/', async (c, next) => {
|
|
3714
|
+
expectTypeOf(c.env.TOKEN).toEqualTypeOf<string>()
|
|
3715
|
+
const mw = poweredBy()
|
|
3716
|
+
await mw(c, next)
|
|
3717
|
+
})
|
|
3718
|
+
|
|
3719
|
+
app.use(mw())
|
|
3720
|
+
app.use('*', mw())
|
|
3721
|
+
|
|
3722
|
+
const route = app.get('/posts', mw(), (c) => c.json(0))
|
|
3723
|
+
const client = hc<typeof route>('/')
|
|
3724
|
+
type key = keyof typeof client
|
|
3725
|
+
type verify = Expect<Equal<'posts', key>>
|
|
3726
|
+
})
|
|
3727
|
+
|
|
3728
|
+
it('Should throw type errors', (c) => {
|
|
3729
|
+
try {
|
|
3730
|
+
// @ts-expect-error
|
|
3731
|
+
app.get(['foo', 'bar'], poweredBy())
|
|
3732
|
+
// @ts-expect-error
|
|
3733
|
+
app.use(['foo', 'bar'], poweredBy())
|
|
3734
|
+
} catch {}
|
|
3735
|
+
})
|
|
3736
|
+
})
|
|
3737
|
+
|
|
3738
|
+
describe('Compatible with extended App classes, such Zod OpenAPI App.', () => {
|
|
3739
|
+
class ExtendedApp extends App {
|
|
3740
|
+
// @ts-ignore
|
|
3741
|
+
route(path: string, app?: App) {
|
|
3742
|
+
// @ts-ignore
|
|
3743
|
+
super.route(path, app)
|
|
3744
|
+
return this
|
|
3745
|
+
}
|
|
3746
|
+
// @ts-ignore
|
|
3747
|
+
basePath(path: string) {
|
|
3748
|
+
return new ExtendedApp(super.basePath(path))
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
const a = new ExtendedApp()
|
|
3752
|
+
const sub = new App()
|
|
3753
|
+
sub.get('/foo', (c) => c.text('foo'))
|
|
3754
|
+
a.route('/sub', sub)
|
|
3755
|
+
|
|
3756
|
+
it('Should return 200 response', async () => {
|
|
3757
|
+
const res = await a.request('/sub/foo')
|
|
3758
|
+
expect(res.status).toBe(200)
|
|
3759
|
+
})
|
|
3760
|
+
})
|
|
3761
|
+
|
|
3762
|
+
describe('Generics for Bindings and Variables', () => {
|
|
3763
|
+
interface CloudflareBindings {
|
|
3764
|
+
MY_VARIABLE: string
|
|
3765
|
+
}
|
|
3766
|
+
|
|
3767
|
+
it('Should not throw type errors', () => {
|
|
3768
|
+
// @ts-expect-error Bindings should extend object
|
|
3769
|
+
new App<{
|
|
3770
|
+
Bindings: number
|
|
3771
|
+
}>()
|
|
3772
|
+
|
|
3773
|
+
const appWithInterface = new App<{
|
|
3774
|
+
Bindings: CloudflareBindings
|
|
3775
|
+
}>()
|
|
3776
|
+
|
|
3777
|
+
appWithInterface.get('/', (c) => {
|
|
3778
|
+
expectTypeOf(c.env.MY_VARIABLE).toMatchTypeOf<string>()
|
|
3779
|
+
return c.text('/')
|
|
3780
|
+
})
|
|
3781
|
+
|
|
3782
|
+
const appWithType = new App<{
|
|
3783
|
+
Bindings: {
|
|
3784
|
+
foo: string
|
|
3785
|
+
}
|
|
3786
|
+
}>()
|
|
3787
|
+
|
|
3788
|
+
appWithType.get('/', (c) => {
|
|
3789
|
+
expectTypeOf(c.env.foo).toMatchTypeOf<string>()
|
|
3790
|
+
return c.text('Hello App!')
|
|
3791
|
+
})
|
|
3792
|
+
})
|
|
3793
|
+
})
|
|
3794
|
+
|
|
3795
|
+
describe('app.basePath() with the internal #clone()', () => {
|
|
3796
|
+
const app = new App()
|
|
3797
|
+
.notFound((c) => {
|
|
3798
|
+
return c.text('Custom not found', 404)
|
|
3799
|
+
})
|
|
3800
|
+
.onError((error, c) => {
|
|
3801
|
+
return c.text(`Custom error "${error.message}"`, 500)
|
|
3802
|
+
})
|
|
3803
|
+
.basePath('/api')
|
|
3804
|
+
.get('/test', async () => {
|
|
3805
|
+
throw new Error('API Test error')
|
|
3806
|
+
})
|
|
3807
|
+
|
|
3808
|
+
it('Should return the custom not found', async () => {
|
|
3809
|
+
const res = await app.request('/api/not-found')
|
|
3810
|
+
expect(res.status).toBe(404)
|
|
3811
|
+
expect(await res.text()).toBe('Custom not found')
|
|
3812
|
+
})
|
|
3813
|
+
|
|
3814
|
+
it('Should return the custom error', async () => {
|
|
3815
|
+
const res = await app.request('/api/test')
|
|
3816
|
+
expect(res.status).toBe(500)
|
|
3817
|
+
expect(await res.text()).toBe('Custom error "API Test error"')
|
|
3818
|
+
})
|
|
3819
|
+
})
|
|
3820
|
+
|
|
3821
|
+
describe('Catch-all route with empty segment', () => {
|
|
3822
|
+
it('Should return empty string for empty catch-all param', async () => {
|
|
3823
|
+
const app = new App()
|
|
3824
|
+
app.get('/:remaining{.*}', (c) => {
|
|
3825
|
+
const remaining = c.req.param('remaining')
|
|
3826
|
+
return c.json({ type: typeof remaining, value: remaining })
|
|
3827
|
+
})
|
|
3828
|
+
const res = await app.request('http://localhost/')
|
|
3829
|
+
expect(res.status).toBe(200)
|
|
3830
|
+
const json = await res.json()
|
|
3831
|
+
expect(json).toEqual({ type: 'string', value: '' })
|
|
3832
|
+
})
|
|
3833
|
+
})
|