From 23b7df7b7f0b318c03c1dc8211a14d50686722d6 Mon Sep 17 00:00:00 2001 From: Matthew Costabile Date: Wed, 8 Nov 2023 08:05:02 -0500 Subject: [PATCH] fix(ClientRequest, Fetch): support miniflare by checking "Response.type" access (#479) Co-authored-by: Artem Zakharchenko --- package.json | 1 + pnpm-lock.yaml | 314 ++++++++++++++++++ .../ClientRequest/NodeClientRequest.ts | 12 +- src/interceptors/fetch/index.ts | 6 +- src/utils/isPropertyAccessible.ts | 19 ++ test/third-party/miniflare-xhr.test.ts | 20 ++ test/third-party/miniflare.test.ts | 68 ++++ 7 files changed, 438 insertions(+), 2 deletions(-) create mode 100644 src/utils/isPropertyAccessible.ts create mode 100644 test/third-party/miniflare-xhr.test.ts create mode 100644 test/third-party/miniflare.test.ts diff --git a/package.json b/package.json index 86602bfe..cd833fda 100644 --- a/package.json +++ b/package.json @@ -132,6 +132,7 @@ "tsup": "^6.5.0", "typescript": "^4.9.4", "vitest": "^0.28.5", + "vitest-environment-miniflare": "^2.14.1", "wait-for-expect": "^3.0.2", "web-encoding": "^1.1.5", "webpack-http-server": "^0.5.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ca4a8455..04f438bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,7 @@ specifiers: tsup: ^6.5.0 typescript: ^4.9.4 vitest: ^0.28.5 + vitest-environment-miniflare: ^2.14.1 wait-for-expect: ^3.0.2 web-encoding: ^1.1.5 webpack-http-server: ^0.5.0 @@ -91,6 +92,7 @@ devDependencies: tsup: 6.7.0_typescript@4.9.5 typescript: 4.9.5 vitest: 0.28.5_happy-dom@12.10.3 + vitest-environment-miniflare: 2.14.1_vitest@0.28.5 wait-for-expect: 3.0.2 web-encoding: 1.1.5 webpack-http-server: 0.5.0 @@ -424,6 +426,10 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true + /@cloudflare/workers-types/4.20231025.0: + resolution: {integrity: sha512-TkcZkntUTOcvJ4vgmwpNfLTclpMbmbClZCe62B25/VTukmyv91joRa4eKzSjzCZUXTbFHNmVdOpmGaaJU2U3+A==} + dev: true + /@commitlint/cli/16.3.0: resolution: {integrity: sha512-P+kvONlfsuTMnxSwWE1H+ZcPMY3STFaHb2kAacsqoIkNx66O0T7sTpBxpxkMrFPyhkJiLJnJWMhk4bbvYD3BMA==} engines: {node: '>=v12'} @@ -1047,6 +1053,10 @@ packages: dev: true optional: true + /@iarna/toml/2.2.5: + resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} + dev: true + /@istanbuljs/load-nyc-config/1.1.0: resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} engines: {node: '>=8'} @@ -1298,6 +1308,166 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /@miniflare/cache/2.14.1: + resolution: {integrity: sha512-f/o6UBV6UX+MlhjcEch73/wjQvvNo37dgYmP6Pn2ax1/mEHhJ7allNAqenmonT4djNeyB3eEYV3zUl54wCEwrg==} + engines: {node: '>=16.13'} + dependencies: + '@miniflare/core': 2.14.1 + '@miniflare/shared': 2.14.1 + http-cache-semantics: 4.1.1 + undici: 5.20.0 + dev: true + + /@miniflare/core/2.14.1: + resolution: {integrity: sha512-d+SGAda/VoXq+SKz04oq8ATUwQw5755L87fgPR8pTdR2YbWkxdbmEm1z2olOpDiUjcR86aN6NtCjY6tUC7fqaw==} + engines: {node: '>=16.13'} + dependencies: + '@iarna/toml': 2.2.5 + '@miniflare/queues': 2.14.1 + '@miniflare/shared': 2.14.1 + '@miniflare/watcher': 2.14.1 + busboy: 1.6.0 + dotenv: 10.0.0 + kleur: 4.1.5 + set-cookie-parser: 2.6.0 + undici: 5.20.0 + urlpattern-polyfill: 4.0.3 + dev: true + + /@miniflare/d1/2.14.1: + resolution: {integrity: sha512-MulDDBsDD8o5DwiqdMeJZy2vLoMji+NWnLcuibSag2mayA0LJcp0eHezseZNkW+knciWR1gMP8Xpa4Q1KwkbKA==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/core': 2.14.1 + '@miniflare/shared': 2.14.1 + dev: true + + /@miniflare/durable-objects/2.14.1: + resolution: {integrity: sha512-T+oHGw5GcEIilkzrf0xDES7jzLVqcXJzSGsEIWqnBFLtdlKmrZF679ulRLBbyMVgvpQz6FRONh9jTH1XIiuObQ==} + engines: {node: '>=16.13'} + dependencies: + '@miniflare/core': 2.14.1 + '@miniflare/shared': 2.14.1 + '@miniflare/storage-memory': 2.14.1 + undici: 5.20.0 + dev: true + + /@miniflare/html-rewriter/2.14.1: + resolution: {integrity: sha512-vp4uZXuEKhtIaxoXa7jgDAPItlzjbfoUqYWp+fwDKv4J4mfQnzzs/5hwjbE7+Ihm/KNI0zNi8P0sSWjIRFl6ng==} + engines: {node: '>=16.13'} + dependencies: + '@miniflare/core': 2.14.1 + '@miniflare/shared': 2.14.1 + html-rewriter-wasm: 0.4.1 + undici: 5.20.0 + dev: true + + /@miniflare/kv/2.14.1: + resolution: {integrity: sha512-Gp07Wcszle7ptsoO8mCtKQRs0AbQnYo1rgnxUcsTL3xJJaHXEA/B9EKSADS2XzJMeY4PgUOHU6Rf08OOF2yWag==} + engines: {node: '>=16.13'} + dependencies: + '@miniflare/shared': 2.14.1 + dev: true + + /@miniflare/queues/2.14.1: + resolution: {integrity: sha512-uBzrbBkIgtNoztDpmMMISg/brYtxLHRE7oTaN8OVnq3bG+3nF9kQC42HUz+Vg+sf65UlvhSaqkjllgx+fNtOxQ==} + engines: {node: '>=16.7'} + dependencies: + '@miniflare/shared': 2.14.1 + dev: true + + /@miniflare/r2/2.14.1: + resolution: {integrity: sha512-grOMnGf2XSicbgxMYMBfWE37k/e7l5NnwXZIViQ+N06uksp+MLA8E6yKQNtvrWQS66TM8gBvMnWo96OFmYjb6Q==} + engines: {node: '>=16.13'} + dependencies: + '@miniflare/core': 2.14.1 + '@miniflare/shared': 2.14.1 + undici: 5.20.0 + dev: true + + /@miniflare/runner-vm/2.14.1: + resolution: {integrity: sha512-UobsGM0ICVPDlJD54VPDSx0EXrIY3nJMXBy2zIFuuUOz4hQKXvMQ6jtAlJ8UNKer+XXI3Mb/9R/gfU8r6kxIMA==} + engines: {node: '>=16.13'} + dependencies: + '@miniflare/shared': 2.14.1 + dev: true + + /@miniflare/shared-test-environment/2.14.1: + resolution: {integrity: sha512-hfactEWiHuHOmE29XFG8oLNCF6+HqjD6Mb80CzidcVmLlBTEtSC3PEF+DXPyvNdLXpBolZMKOuC/yzzloWvACA==} + engines: {node: '>=16.13'} + dependencies: + '@cloudflare/workers-types': 4.20231025.0 + '@miniflare/cache': 2.14.1 + '@miniflare/core': 2.14.1 + '@miniflare/d1': 2.14.1 + '@miniflare/durable-objects': 2.14.1 + '@miniflare/html-rewriter': 2.14.1 + '@miniflare/kv': 2.14.1 + '@miniflare/queues': 2.14.1 + '@miniflare/r2': 2.14.1 + '@miniflare/shared': 2.14.1 + '@miniflare/sites': 2.14.1 + '@miniflare/storage-memory': 2.14.1 + '@miniflare/web-sockets': 2.14.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + + /@miniflare/shared/2.14.1: + resolution: {integrity: sha512-73GnLtWn5iP936ctE6ZJrMqGu134KOoIIveq5Yd/B+NnbFfzpuzjCpkLrnqjkDdsxDbruXSb5eTR/SmAdpJxZQ==} + engines: {node: '>=16.13'} + dependencies: + '@types/better-sqlite3': 7.6.7 + kleur: 4.1.5 + npx-import: 1.1.4 + picomatch: 2.3.1 + dev: true + + /@miniflare/sites/2.14.1: + resolution: {integrity: sha512-AbbIcU6VBeaNqVgMiLMWN2a09eX3jZmjaEi0uKqufVDqW/QIz47/30aC0O9qTe+XYpi3jjph/Ux7uEY8Z+enMw==} + engines: {node: '>=16.13'} + dependencies: + '@miniflare/kv': 2.14.1 + '@miniflare/shared': 2.14.1 + '@miniflare/storage-file': 2.14.1 + dev: true + + /@miniflare/storage-file/2.14.1: + resolution: {integrity: sha512-faZu9tRSW6c/looVFI/ZhkdGsIc9NfNCbSl3jJRmm7xgyZ+/S+dQ5JtGVbVsUIX8YGWDyE2j3oWCGCjxGLEpkg==} + engines: {node: '>=16.13'} + dependencies: + '@miniflare/shared': 2.14.1 + '@miniflare/storage-memory': 2.14.1 + dev: true + + /@miniflare/storage-memory/2.14.1: + resolution: {integrity: sha512-lfQbQwopVWd4W5XzrYdp0rhk3dJpvSmv1Wwn9RhNO20WrcuoxpdSzbmpBahsgYVg+OheVaEbS6RpFqdmwwLTog==} + engines: {node: '>=16.13'} + dependencies: + '@miniflare/shared': 2.14.1 + dev: true + + /@miniflare/watcher/2.14.1: + resolution: {integrity: sha512-dkFvetm5wk6pwunlYb/UkI0yFNb3otLpRm5RDywMUzqObEf+rCiNNAbJe3HUspr2ncZVAaRWcEaDh82vYK5cmw==} + engines: {node: '>=16.13'} + dependencies: + '@miniflare/shared': 2.14.1 + dev: true + + /@miniflare/web-sockets/2.14.1: + resolution: {integrity: sha512-3N//L5EjF7+xXd7qCLR2ylUwm8t2MKyGPGWEtRBrQ2xqYYWhewKTjlquHCOPU5Irnnd/4BhTmFA55MNrq7m4Nw==} + engines: {node: '>=16.13'} + dependencies: + '@miniflare/core': 2.14.1 + '@miniflare/shared': 2.14.1 + undici: 5.20.0 + ws: 8.11.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + /@nodelib/fs.scandir/2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1467,6 +1637,12 @@ packages: '@babel/types': 7.22.11 dev: true + /@types/better-sqlite3/7.6.7: + resolution: {integrity: sha512-+c2YGPWY5831v3uj2/X0HRTK94u1GXU3sCnLqu7AKlxlSfawswnAiJR//TFzSL5azWsLQkG/uS+YnnqHtuZxPw==} + dependencies: + '@types/node': 20.4.7 + dev: true + /@types/body-parser/1.19.2: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} dependencies: @@ -2314,6 +2490,12 @@ packages: ieee754: 1.2.1 dev: true + /builtins/5.0.1: + resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} + dependencies: + semver: 7.5.4 + dev: true + /bundle-require/4.0.1_esbuild@0.17.19: resolution: {integrity: sha512-9NQkRHlNdNpDBGmLpngF3EFDcwodhMUuLz9PaWYciVcQF9SE4LFjM2DB/xV1Li5JiuDMv7ZUWuC3rGbqR0MAXQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -2324,6 +2506,13 @@ packages: load-tsconfig: 0.2.5 dev: true + /busboy/1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + dependencies: + streamsearch: 1.1.0 + dev: true + /bytes/3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -2972,6 +3161,11 @@ packages: is-obj: 2.0.0 dev: true + /dotenv/10.0.0: + resolution: {integrity: sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==} + engines: {node: '>=10'} + dev: true + /duplexer2/0.1.4: resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} dependencies: @@ -3223,6 +3417,21 @@ packages: strip-final-newline: 2.0.0 dev: true + /execa/6.1.0: + resolution: {integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 3.0.1 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.1.0 + onetime: 6.0.0 + signal-exit: 3.0.7 + strip-final-newline: 3.0.0 + dev: true + /exit/0.1.2: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} engines: {node: '>= 0.8.0'} @@ -3764,6 +3973,10 @@ packages: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} dev: true + /html-rewriter-wasm/0.4.1: + resolution: {integrity: sha512-lNovG8CMCCmcVB1Q7xggMSf7tqPCijZXaH4gL6iE8BFghdQCbaY5Met9i1x2Ex8m/cZHDUtXK9H6/znKamRP8Q==} + dev: true + /http-cache-semantics/4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} dev: true @@ -3813,6 +4026,11 @@ packages: engines: {node: '>=10.17.0'} dev: true + /human-signals/3.0.1: + resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==} + engines: {node: '>=12.20.0'} + dev: true + /iconv-lite/0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -4001,6 +4219,11 @@ packages: engines: {node: '>=8'} dev: true + /is-stream/3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + /is-text-path/1.0.1: resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} engines: {node: '>=0.10.0'} @@ -4693,6 +4916,11 @@ packages: engines: {node: '>=6'} dev: true + /kleur/4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + dev: true + /leven/2.1.0: resolution: {integrity: sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==} engines: {node: '>=0.10.0'} @@ -4944,6 +5172,11 @@ packages: engines: {node: '>=6'} dev: true + /mimic-fn/4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + dev: true + /mimic-response/1.0.1: resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} engines: {node: '>=4'} @@ -5100,6 +5333,22 @@ packages: path-key: 3.1.1 dev: true + /npm-run-path/5.1.0: + resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + path-key: 4.0.0 + dev: true + + /npx-import/1.1.4: + resolution: {integrity: sha512-3ShymTWOgqGyNlh5lMJAejLuIv3W1K3fbI5Ewc6YErZU3Sp0PqsNs8UIU1O8z5+KVl/Du5ag56Gza9vdorGEoA==} + dependencies: + execa: 6.1.0 + parse-package-name: 1.0.0 + semver: 7.5.4 + validate-npm-package-name: 4.0.0 + dev: true + /nwsapi/2.2.7: resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==} dev: true @@ -5137,6 +5386,13 @@ packages: mimic-fn: 2.1.0 dev: true + /onetime/6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + dependencies: + mimic-fn: 4.0.0 + dev: true + /ora/5.4.1: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} @@ -5222,6 +5478,10 @@ packages: lines-and-columns: 1.2.4 dev: true + /parse-package-name/1.0.0: + resolution: {integrity: sha512-kBeTUtcj+SkyfaW4+KBe0HtsloBJ/mKTPoxpVdA57GZiPerREsUWJOhVj9anXweFiJkm5y8FG1sxFZkZ0SN6wg==} + dev: true + /parse-passwd/1.0.0: resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} engines: {node: '>=0.10.0'} @@ -5251,6 +5511,11 @@ packages: engines: {node: '>=8'} dev: true + /path-key/4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + dev: true + /path-parse/1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true @@ -5807,6 +6072,10 @@ packages: - supports-color dev: true + /set-cookie-parser/2.6.0: + resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==} + dev: true + /setprototypeof/1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} dev: true @@ -6011,6 +6280,11 @@ packages: resolution: {integrity: sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==} dev: true + /streamsearch/1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + dev: true + /strict-event-emitter/0.5.1: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} dev: false @@ -6077,6 +6351,11 @@ packages: engines: {node: '>=6'} dev: true + /strip-final-newline/3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + dev: true + /strip-indent/3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -6575,6 +6854,13 @@ packages: resolution: {integrity: sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw==} dev: true + /undici/5.20.0: + resolution: {integrity: sha512-J3j60dYzuo6Eevbawwp1sdg16k5Tf768bxYK4TUJRH7cBM4kFCbf3mOnM/0E3vQYXvpxITbbWmBafaDbxLDz3g==} + engines: {node: '>=12.18'} + dependencies: + busboy: 1.6.0 + dev: true + /universalify/0.2.0: resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} engines: {node: '>= 4.0.0'} @@ -6614,6 +6900,10 @@ packages: requires-port: 1.0.0 dev: true + /urlpattern-polyfill/4.0.3: + resolution: {integrity: sha512-DOE84vZT2fEcl9gqCUTcnAw5ZY5Id55ikUcziSUntuEFL3pRvavg5kwDmTEUJkeCHInTlV/HexFomgYnzO5kdQ==} + dev: true + /util-deprecate/1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: true @@ -6653,6 +6943,13 @@ packages: spdx-expression-parse: 3.0.1 dev: true + /validate-npm-package-name/4.0.0: + resolution: {integrity: sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + builtins: 5.0.1 + dev: true + /vary/1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -6718,6 +7015,23 @@ packages: fsevents: 2.3.3 dev: true + /vitest-environment-miniflare/2.14.1_vitest@0.28.5: + resolution: {integrity: sha512-efMpx9XnpjHeIN1lnEMO+4Ky9xSFM0VeG8Ilf+5Uyh8U8lNuJ+qTTfr76LQ6MQcNzkLMo4byh0YxaZo8QfIYrw==} + engines: {node: '>=16.13'} + peerDependencies: + vitest: '>=0.23.0' + dependencies: + '@miniflare/queues': 2.14.1 + '@miniflare/runner-vm': 2.14.1 + '@miniflare/shared': 2.14.1 + '@miniflare/shared-test-environment': 2.14.1 + undici: 5.20.0 + vitest: 0.28.5_happy-dom@12.10.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + /vitest/0.28.5_happy-dom@12.10.3: resolution: {integrity: sha512-pyCQ+wcAOX7mKMcBNkzDwEHRGqQvHUl0XnoHR+3Pb1hytAHISgSxv9h0gUiSiYtISXUU3rMrKiKzFYDrI6ZIHA==} engines: {node: '>=v14.16.0'} diff --git a/src/interceptors/ClientRequest/NodeClientRequest.ts b/src/interceptors/ClientRequest/NodeClientRequest.ts index daccd3df..0bdec77f 100644 --- a/src/interceptors/ClientRequest/NodeClientRequest.ts +++ b/src/interceptors/ClientRequest/NodeClientRequest.ts @@ -20,6 +20,7 @@ import { toInteractiveRequest } from '../../utils/toInteractiveRequest' import { uuidv4 } from '../../utils/uuid' import { emitAsync } from '../../utils/emitAsync' import { getRawFetchHeaders } from '../../utils/getRawFetchHeaders' +import { isPropertyAccessible } from '../../utils/isPropertyAccessible' export type Protocol = 'http' | 'https' @@ -287,7 +288,16 @@ export class NodeClientRequest extends ClientRequest { this.destroyed = false // Handle mocked "Response.error" network error responses. - if (mockedResponse.type === 'error') { + if ( + /** + * @note Some environments, like Miniflare (Cloudflare) do not + * implement the "Response.type" property and throw on its access. + * Safely check if we can access "type" on "Response" before continuing. + * @see https://github.com/mswjs/msw/issues/1834 + */ + isPropertyAccessible(mockedResponse, 'type') && + mockedResponse.type === 'error' + ) { this.logger.info( 'received network error response, aborting request...' ) diff --git a/src/interceptors/fetch/index.ts b/src/interceptors/fetch/index.ts index 570b6c46..90c9fd5e 100644 --- a/src/interceptors/fetch/index.ts +++ b/src/interceptors/fetch/index.ts @@ -6,6 +6,7 @@ import { Interceptor } from '../../Interceptor' import { uuidv4 } from '../../utils/uuid' import { toInteractiveRequest } from '../../utils/toInteractiveRequest' import { emitAsync } from '../../utils/emitAsync' +import { isPropertyAccessible } from '../../utils/isPropertyAccessible' export class FetchInterceptor extends Interceptor { static symbol = Symbol('fetch') @@ -103,7 +104,10 @@ export class FetchInterceptor extends Interceptor { this.logger.info('received mocked response:', mockedResponse) // Reject the request Promise on mocked "Response.error" responses. - if (mockedResponse.type === 'error') { + if ( + isPropertyAccessible(mockedResponse, 'type') && + mockedResponse.type === 'error' + ) { this.logger.info( 'received a network error response, rejecting the request promise...' ) diff --git a/src/utils/isPropertyAccessible.ts b/src/utils/isPropertyAccessible.ts new file mode 100644 index 00000000..4a80a75b --- /dev/null +++ b/src/utils/isPropertyAccessible.ts @@ -0,0 +1,19 @@ +/** + * A function that validates if property access is possible on an object + * without throwing. It returns `true` if the property access is possible + * and `false` otherwise. + * + * Environments like miniflare will throw on property access on certain objects + * like Request and Response, for unimplemented properties. + */ +export function isPropertyAccessible>( + obj: Obj, + key: keyof Obj +) { + try { + obj[key] + return true + } catch { + return false + } +} diff --git a/test/third-party/miniflare-xhr.test.ts b/test/third-party/miniflare-xhr.test.ts new file mode 100644 index 00000000..24636a2d --- /dev/null +++ b/test/third-party/miniflare-xhr.test.ts @@ -0,0 +1,20 @@ +// @vitest-environment miniflare +import { afterAll, expect, test } from 'vitest' +import { XMLHttpRequestInterceptor } from '../../src/interceptors/XMLHttpRequest' + +let interceptor: XMLHttpRequestInterceptor + +afterAll(() => { + interceptor.dispose() +}) + +test('does not throw when applying XHR interceptor in Miniflare', async () => { + interceptor = new XMLHttpRequestInterceptor() + + /** + * @note Miniflare (Cloudflare) does not implement XMLHttpRequest. + * We must make sure we can still apply the XHR interceptor without it + * throwing an error. Interceptors check the environment before applying. + */ + expect(() => interceptor.apply()).not.toThrow() +}) diff --git a/test/third-party/miniflare.test.ts b/test/third-party/miniflare.test.ts new file mode 100644 index 00000000..377acee1 --- /dev/null +++ b/test/third-party/miniflare.test.ts @@ -0,0 +1,68 @@ +// @vitest-environment miniflare +import { afterAll, afterEach, beforeAll, expect, test, vi } from 'vitest' +import { BatchInterceptor } from '../../src' +import { ClientRequestInterceptor } from '../../src/interceptors/ClientRequest' +import { XMLHttpRequestInterceptor } from '../../src/interceptors/XMLHttpRequest' +import { FetchInterceptor } from '../../src/interceptors/fetch' +import { httpGet, httpsGet } from '../helpers' + +const interceptor = new BatchInterceptor({ + name: 'setup-server', + interceptors: [ + new ClientRequestInterceptor(), + new XMLHttpRequestInterceptor(), + new FetchInterceptor(), + ], +}) + +const requestListener = vi.fn().mockImplementation(({ request }) => { + request.respondWith(new Response('mocked-body')) +}) + +interceptor.on('request', requestListener) + +beforeAll(() => { + interceptor.apply() +}) + +afterEach(() => { + vi.clearAllMocks() +}) + +afterAll(() => { + interceptor.dispose() +}) + +test('responds to fetch', async () => { + const res = await fetch('https://example.com') + const body = await res.text() + expect(res.status).toEqual(200) + expect(body).toEqual('mocked-body') + expect(requestListener).toHaveBeenCalledTimes(1) +}) + +test('responds to http.get', async () => { + const { resBody } = await httpGet('http://example.com') + expect(resBody).toEqual('mocked-body') + expect(requestListener).toHaveBeenCalledTimes(1) +}) + +test('responds to https.get', async () => { + const { resBody } = await httpsGet('https://example.com') + expect(resBody).toEqual('mocked-body') + expect(requestListener).toHaveBeenCalledTimes(1) +}) + +test('throws when responding with a network error', async () => { + requestListener.mockImplementationOnce(({ request }) => { + /** + * @note "Response.error()" static method is NOT implemented in Miniflare. + * This expression will throw, resulting in a request error. + */ + request.respondWith(Response.error()) + }) + + await expect(() => httpGet('http://example.com')).rejects.toThrow( + 'Response.error is not a function' + ) +})