├── .commitlintrc.js ├── .eslintignore ├── .eslintrc.js ├── .github ├── issue_template.md └── pull_request_template.md ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .mocharc.js ├── .prettierrc.js ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── OSSMETADATA ├── README.md ├── docs ├── .nojekyll ├── _coverpage.md ├── _sidebar.md ├── adapters │ ├── custom.md │ ├── fetch.md │ ├── node-http.md │ ├── playwright.md │ ├── puppeteer.md │ └── xhr.md ├── api.md ├── assets │ ├── images │ │ ├── Netflix_Logo.png │ │ ├── favicon.ico │ │ ├── logo.png │ │ ├── wordmark-logo-alt-twitter.png │ │ ├── wordmark-logo-alt.png │ │ └── wordmark-logo.png │ └── styles.css ├── cli │ ├── commands.md │ └── overview.md ├── configuration.md ├── examples.md ├── frameworks │ └── ember-cli.md ├── index.html ├── node-server │ ├── express-integrations.md │ └── overview.md ├── persisters │ ├── custom.md │ ├── fs.md │ ├── local-storage.md │ └── rest.md ├── quick-start.md ├── server │ ├── api.md │ ├── event.md │ ├── events-and-middleware.md │ ├── overview.md │ ├── request.md │ ├── response.md │ └── route-handler.md └── test-frameworks │ ├── jest-jasmine.md │ ├── mocha.md │ └── qunit.md ├── examples ├── .eslintrc.js ├── client-server │ ├── index.html │ ├── package.json │ └── tests │ │ ├── events.test.js │ │ ├── intercept.test.js │ │ └── setup.js ├── dummy-app │ ├── .eslintrc.js │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ └── src │ │ ├── App.js │ │ ├── index.css │ │ ├── index.js │ │ ├── posts.js │ │ ├── todos.js │ │ └── users.js ├── jest-node-fetch │ ├── .eslintrc.js │ ├── __recordings__ │ │ └── jest-node-fetch_1142061259 │ │ │ ├── posts_1278140380 │ │ │ └── should-return-post_148615714 │ │ │ │ └── recording.har │ │ │ └── users_1585235219 │ │ │ └── should-return-user_4259424139 │ │ │ └── recording.har │ ├── __tests__ │ │ └── index.test.js │ ├── package.json │ └── src │ │ ├── index.js │ │ ├── posts.js │ │ └── users.js ├── jest-puppeteer │ ├── .eslintrc.js │ ├── __recordings__ │ │ └── jest-puppeteer_2726822272 │ │ │ └── should-be-able-to-navigate-to-all-routes_1130491217 │ │ │ └── recording.har │ ├── __tests__ │ │ └── dummy-app.test.js │ ├── jest-puppeteer.config.js │ ├── jest.config.js │ └── package.json ├── node-fetch │ ├── package.json │ ├── recordings │ │ └── node-fetch_2851505768 │ │ │ └── should-work_3457346403 │ │ │ └── recording.har │ └── tests │ │ └── node-fetch.test.js ├── puppeteer │ ├── index.js │ ├── package.json │ └── recordings │ │ └── puppeteer_2155046665 │ │ └── recording.har ├── rest-persister │ ├── index.html │ ├── package.json │ ├── recordings │ │ └── REST-Persister_2289553200 │ │ │ └── should-work_3457346403 │ │ │ └── recording.har │ └── tests │ │ ├── rest-persister.test.js │ │ └── setup.js └── typescript-jest-node-fetch │ ├── __recordings__ │ └── github-api-client_2139812550 │ │ └── getUser_1648904580 │ │ └── recording.har │ ├── jest.config.ts │ ├── package.json │ ├── src │ ├── github-api.test.ts │ ├── github-api.ts │ └── utils │ │ └── auto-setup-polly.ts │ └── tsconfig.json ├── jest.config.js ├── lerna.json ├── package.json ├── packages └── @pollyjs │ ├── adapter-fetch │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rollup.config.test.js │ ├── src │ │ ├── index.js │ │ └── utils │ │ │ └── serializer-headers.js │ ├── tests │ │ ├── integration │ │ │ ├── adapter-test.js │ │ │ ├── persister-local-storage-test.js │ │ │ ├── persister-rest-test.js │ │ │ └── server-test.js │ │ └── utils │ │ │ └── polly-config.js │ └── types.d.ts │ ├── adapter-node-http │ ├── .eslintrc.js │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── rollup.config.shared.js │ ├── rollup.config.test.js │ ├── src │ │ ├── index.js │ │ └── utils │ │ │ ├── get-url-from-options.js │ │ │ ├── merge-chunks.js │ │ │ └── url-to-options.js │ ├── tests │ │ ├── integration │ │ │ ├── adapter-node-fetch-test.js │ │ │ ├── adapter-test.js │ │ │ └── persister-fs-test.js │ │ ├── jest │ │ │ └── integration │ │ │ │ ├── fetch-test.js │ │ │ │ └── xhr-test.js │ │ ├── unit │ │ │ └── utils │ │ │ │ └── merge-chunks-test.js │ │ └── utils │ │ │ ├── get-buffer-from-stream.js │ │ │ ├── get-response-from-request.js │ │ │ ├── native-request.js │ │ │ └── polly-config.js │ └── types.d.ts │ ├── adapter-puppeteer │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── rollup.config.test.js │ ├── src │ │ └── index.js │ ├── tests │ │ ├── helpers │ │ │ └── fetch.js │ │ ├── integration │ │ │ └── adapter-test.js │ │ └── unit │ │ │ └── adapter-test.js │ └── types.d.ts │ ├── adapter-xhr │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rollup.config.test.js │ ├── src │ │ ├── index.js │ │ └── utils │ │ │ ├── resolve-xhr.js │ │ │ └── serialize-response-headers.js │ ├── tests │ │ ├── integration │ │ │ └── adapter-test.js │ │ └── utils │ │ │ └── xhr-request.js │ └── types.d.ts │ ├── adapter │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rollup.config.test.js │ ├── src │ │ ├── index.js │ │ └── utils │ │ │ ├── dehumanize-time.js │ │ │ ├── is-expired.js │ │ │ ├── normalize-recorded-response.js │ │ │ └── stringify-request.js │ ├── tests │ │ └── unit │ │ │ ├── adapter-test.js │ │ │ └── utils │ │ │ ├── dehumanize-time-test.js │ │ │ └── is-expired-test.js │ └── types.d.ts │ ├── cli │ ├── .eslintrc.js │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── bin │ │ └── cli.js │ └── package.json │ ├── core │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rollup.config.test.js │ ├── src │ │ ├── -private │ │ │ ├── container.js │ │ │ ├── event-emitter.js │ │ │ ├── event.js │ │ │ ├── http-base.js │ │ │ ├── interceptor.js │ │ │ ├── logger.js │ │ │ ├── request.js │ │ │ └── response.js │ │ ├── defaults │ │ │ └── config.js │ │ ├── index.js │ │ ├── polly.js │ │ ├── server │ │ │ ├── handler.js │ │ │ ├── index.js │ │ │ ├── middleware.js │ │ │ └── route.js │ │ ├── test-helpers │ │ │ ├── lib.js │ │ │ ├── mocha.js │ │ │ └── qunit.js │ │ └── utils │ │ │ ├── cancel-fn-after-n-times.js │ │ │ ├── deferred-promise.js │ │ │ ├── guid-for-recording.js │ │ │ ├── http-headers.js │ │ │ ├── merge-configs.js │ │ │ ├── normalize-request.js │ │ │ ├── parse-url.js │ │ │ ├── remove-host-from-url.js │ │ │ ├── timing.js │ │ │ └── validators.js │ ├── tests │ │ └── unit │ │ │ ├── -private │ │ │ ├── container-test.js │ │ │ ├── event-emitter-test.js │ │ │ ├── event-test.js │ │ │ ├── http-base-test.js │ │ │ ├── interceptor-test.js │ │ │ └── response-test.js │ │ │ ├── index-test.js │ │ │ ├── polly-test.js │ │ │ ├── server │ │ │ ├── handler-test.js │ │ │ └── server-test.js │ │ │ ├── test-helpers │ │ │ └── mocha-test.js │ │ │ └── utils │ │ │ ├── deferred-promise-test.js │ │ │ ├── guid-for-recording-test.js │ │ │ ├── http-headers-test.js │ │ │ ├── merge-configs-test.js │ │ │ ├── normalize-request-test.js │ │ │ ├── parse-url-test.js │ │ │ ├── remove-host-from-url-test.js │ │ │ └── timing-test.js │ └── types.d.ts │ ├── ember │ ├── .editorconfig │ ├── .ember-cli │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .npmignore │ ├── .prettierignore │ ├── .prettierrc.js │ ├── .template-lintrc.js │ ├── .watchmanconfig │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── addon │ │ └── -private │ │ │ └── preconfigure.js │ ├── blueprints │ │ └── @pollyjs │ │ │ └── ember │ │ │ ├── files │ │ │ └── config │ │ │ │ └── polly.js │ │ │ └── index.js │ ├── config │ │ ├── ember-try.js │ │ ├── environment.js │ │ └── polly.js │ ├── ember-cli-build.js │ ├── index.js │ ├── package.json │ ├── testem.js │ ├── tests │ │ ├── dummy │ │ │ ├── app │ │ │ │ ├── app.js │ │ │ │ ├── components │ │ │ │ │ └── .gitkeep │ │ │ │ ├── controllers │ │ │ │ │ └── .gitkeep │ │ │ │ ├── helpers │ │ │ │ │ └── .gitkeep │ │ │ │ ├── index.html │ │ │ │ ├── models │ │ │ │ │ └── .gitkeep │ │ │ │ ├── router.js │ │ │ │ ├── routes │ │ │ │ │ └── .gitkeep │ │ │ │ ├── styles │ │ │ │ │ └── app.css │ │ │ │ └── templates │ │ │ │ │ └── application.hbs │ │ │ ├── config │ │ │ │ ├── ember-cli-update.json │ │ │ │ ├── environment.js │ │ │ │ ├── optional-features.json │ │ │ │ └── targets.js │ │ │ └── public │ │ │ │ └── robots.txt │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── index.html │ │ ├── integration │ │ │ └── .gitkeep │ │ ├── test-helper.js │ │ └── unit │ │ │ └── polly-test.js │ └── vendor │ │ └── .gitkeep │ ├── node-server │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── api.js │ │ ├── config.js │ │ ├── express │ │ │ └── register-api.js │ │ ├── index.js │ │ └── server.js │ └── types.d.ts │ ├── persister-fs │ ├── .eslintrc.js │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── rollup.config.test.js │ ├── src │ │ └── index.js │ ├── tests │ │ └── unit │ │ │ └── persister-test.js │ └── types.d.ts │ ├── persister-in-memory │ ├── CHANGELOG.md │ ├── LICENSE │ ├── package.json │ ├── rollup.config.test.js │ ├── src │ │ └── index.js │ └── types.d.ts │ ├── persister-local-storage │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rollup.config.test.js │ ├── src │ │ └── index.js │ └── types.d.ts │ ├── persister-rest │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rollup.config.test.js │ ├── src │ │ ├── ajax.js │ │ └── index.js │ └── types.d.ts │ ├── persister │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rollup.config.test.js │ ├── src │ │ ├── har │ │ │ ├── entry.js │ │ │ ├── index.js │ │ │ ├── log.js │ │ │ ├── request.js │ │ │ ├── response.js │ │ │ └── utils │ │ │ │ ├── get-first-header.js │ │ │ │ └── to-nv-pairs.js │ │ └── index.js │ ├── tests │ │ └── unit │ │ │ ├── har-test.js │ │ │ └── persister-test.js │ └── types.d.ts │ └── utils │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rollup.config.test.js │ ├── src │ ├── constants │ │ ├── actions.js │ │ ├── expiry-strategies.js │ │ ├── http-methods.js │ │ ├── http-status-codes.js │ │ └── modes.js │ ├── index.js │ └── utils │ │ ├── assert.js │ │ ├── build-url.js │ │ ├── clone-arraybuffer.js │ │ ├── is-buffer-utf8-representable.js │ │ ├── polly-error.js │ │ ├── serializers │ │ ├── blob.js │ │ ├── buffer.js │ │ ├── form-data.js │ │ └── index.js │ │ ├── timeout.js │ │ ├── timestamp.js │ │ └── url.js │ ├── tests │ ├── browser │ │ └── unit │ │ │ └── utils │ │ │ └── serializers │ │ │ ├── blob.js │ │ │ └── form-data.js │ ├── node │ │ └── unit │ │ │ └── utils │ │ │ └── serializers │ │ │ └── buffer.js │ ├── serializer-tests.js │ └── unit │ │ └── utils │ │ ├── assert-test.js │ │ ├── build-url-test.js │ │ ├── polly-error-test.js │ │ ├── timeout-test.js │ │ ├── timestamp-test.js │ │ └── url-test.js │ └── types.d.ts ├── scripts ├── require-clean-work-tree.sh ├── require-test-build.sh └── rollup │ ├── browser.config.js │ ├── browser.test.config.js │ ├── default.config.js │ ├── jest.test.config.js │ ├── node.config.js │ ├── node.test.config.js │ └── utils.js ├── testem.js ├── tests ├── assets │ └── 32x32.png ├── helpers │ ├── file.js │ ├── global-node-fetch.js │ ├── setup-fetch-record.js │ └── setup-persister.js ├── index.mustache ├── integration │ ├── adapter-browser-tests.js │ ├── adapter-identifier-tests.js │ ├── adapter-node-tests.js │ ├── adapter-polly-tests.js │ ├── adapter-tests.js │ └── persister-tests.js ├── middleware.js └── node-setup.js └── yarn.lock /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | module.exports = { 4 | extends: [ 5 | '@commitlint/config-lerna-scopes', 6 | '@commitlint/config-conventional' 7 | ], 8 | rules: { 9 | 'subject-case': [2, 'always', ['sentence-case']] 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /packages/@pollyjs/ember/tests/**/index.html 2 | CHANGELOG.md 3 | package.json 4 | node_modules 5 | tmp 6 | build 7 | dist 8 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | 7 | ## Motivation and Context 8 | 9 | 14 | 15 | ## Types of Changes 16 | 17 | 20 | 21 | - [ ] Bug fix (non-breaking change which fixes an issue) 22 | - [ ] New feature (non-breaking change which adds functionality) 23 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 24 | 25 | ## Checklist 26 | 27 | 32 | 33 | - [ ] I have added tests to cover my changes. 34 | - [ ] My change requires a change to the documentation. 35 | - [ ] I have updated the documentation accordingly. 36 | - [ ] My code follows the code style of this project. 37 | - [ ] My commits and the title of this PR follow the [Conventional Commits Specification](https://www.conventionalcommits.org). 38 | - [ ] I have read the [contributing guidelines](https://github.com/Netflix/pollyjs/blob/master/CONTRIBUTING.md). 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | package-lock.json 4 | lerna-debug.log 5 | packages/**/dist/ 6 | yarn-error.log 7 | tmp 8 | build 9 | dist 10 | *.lerna_backup 11 | .npmrc 12 | 13 | # Test recordings can write be written here if the test job did not 14 | # get a chance to run to completion. The test will cleans these files up afterwards. 15 | /recordings 16 | 17 | # Examples 18 | examples/**/*/yarn.lock 19 | 20 | # IDE 21 | .vscode/ 22 | .tool-versions 23 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | -------------------------------------------------------------------------------- /.mocharc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | spec: './packages/@pollyjs/*/build/node/*.js', 3 | ui: 'bdd', 4 | require: 'tests/node-setup.js' 5 | }; 6 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | singleQuote: true, 5 | trailingComma: 'none' 6 | }; 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '12' 4 | - '14' 5 | - '16' 6 | 7 | addons: 8 | chrome: stable 9 | 10 | cache: 11 | yarn: true 12 | 13 | before_install: 14 | - curl -o- -L https://yarnpkg.com/install.sh | bash 15 | - export PATH=$HOME/.yarn/bin:$PATH 16 | 17 | install: 18 | - yarn install --frozen-lockfile --non-interactive 19 | 20 | script: 21 | - commitlint-travis 22 | - yarn run test:ci 23 | - ./scripts/require-clean-work-tree.sh 24 | -------------------------------------------------------------------------------- /OSSMETADATA: -------------------------------------------------------------------------------- 1 | osslifecycle=active 2 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/pollyjs/d031861625f58423169b396e5afc8a0110d1b232/docs/.nojekyll -------------------------------------------------------------------------------- /docs/_coverpage.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | ![logo](assets/images/wordmark-logo-alt.png) 6 | 7 | > Record, replay, and stub HTTP interactions. 8 | 9 | - 🚀 Node & Browser Support 10 | - ⚡️️ Simple, Powerful, & Intuitive API 11 | - 💎 First Class Mocha & QUnit Test Helpers 12 | - 🔥 Intercept, Pass-Through, and Attach Events 13 | - 📼 Record to Disk or Local Storage 14 | - ⏱ Slow Down or Speed Up Time 15 | 16 |
17 | GitHub 18 | Get Started 19 |
20 | 21 | ![color](#ffffff) 22 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | - Getting Started 2 | 3 | - [Overview](README.md) 4 | - [Quick Start](quick-start.md) 5 | - [Examples](examples.md) 6 | 7 | - Test Frameworks 8 | 9 | - [Mocha](test-frameworks/mocha.md) 10 | - [QUnit](test-frameworks/qunit.md) 11 | - [Jest & Jasmine](test-frameworks/jest-jasmine.md) 12 | 13 | - Frameworks 14 | 15 | - [Ember CLI](frameworks/ember-cli.md) 16 | 17 | - Adapters 18 | 19 | - [Fetch](adapters/fetch.md) 20 | - [Node HTTP](adapters/node-http.md) 21 | - [Playwright](adapters/playwright.md) 22 | - [Puppeteer](adapters/puppeteer.md) 23 | - [XHR](adapters/xhr.md) 24 | - [Custom](adapters/custom.md) 25 | 26 | - Persisters 27 | 28 | - [File System](persisters/fs.md) 29 | - [Local Storage](persisters/local-storage.md) 30 | - [REST](persisters/rest.md) 31 | - [Custom](persisters/custom.md) 32 | 33 | - Client Server 34 | 35 | - [Overview](server/overview.md) 36 | - [API](server/api.md) 37 | - [Events & Middleware](server/events-and-middleware.md) 38 | - [Route Handler](server/route-handler.md) 39 | - [Request](server/request.md) 40 | - [Response](server/response.md) 41 | - [Event](server/event.md) 42 | 43 | - Node Server 44 | 45 | - [Overview](node-server/overview.md) 46 | - [Express Integrations](node-server/express-integrations.md) 47 | 48 | - CLI 49 | 50 | - [Overview](cli/overview.md) 51 | - [Commands](cli/commands.md) 52 | 53 | - Reference 54 | 55 | - [API](api.md) 56 | - [Configuration](configuration.md) 57 | 58 | - [Contributing](CONTRIBUTING.md) 59 | -------------------------------------------------------------------------------- /docs/adapters/fetch.md: -------------------------------------------------------------------------------- 1 | # Fetch Adapter 2 | 3 | The fetch adapter wraps the global fetch method for seamless 4 | recording and replaying of requests. 5 | 6 | ## Installation 7 | 8 | _Note that you must have node (and npm) installed._ 9 | 10 | ```bash 11 | npm install @pollyjs/adapter-fetch -D 12 | ``` 13 | 14 | If you want to install it with [yarn](https://yarnpkg.com): 15 | 16 | ```bash 17 | yarn add @pollyjs/adapter-fetch -D 18 | ``` 19 | 20 | ## Usage 21 | 22 | Use the [configure](api#configure), [connectTo](api#connectto), and 23 | [disconnectFrom](api#disconnectfrom) APIs to connect or disconnect from the 24 | adapter. 25 | 26 | ```js 27 | import { Polly } from '@pollyjs/core'; 28 | import FetchAdapter from '@pollyjs/adapter-fetch'; 29 | 30 | // Register the fetch adapter so its accessible by all future polly instances 31 | Polly.register(FetchAdapter); 32 | 33 | const polly = new Polly('', { 34 | adapters: ['fetch'] 35 | }); 36 | 37 | // Disconnect using the `configure` API 38 | polly.configure({ adapters: [] }); 39 | 40 | // Reconnect using the `connectTo` API 41 | polly.connectTo('fetch'); 42 | 43 | // Disconnect using the `disconnectFrom` API 44 | polly.disconnectFrom('fetch'); 45 | ``` 46 | 47 | ## Options 48 | 49 | ### context 50 | 51 | _Type_: `Object` 52 | _Default_: `global|self|window` 53 | 54 | The context object which contains the fetch API. Typically this is `window` or `self` in the browser and `global` in node. 55 | 56 | **Example** 57 | 58 | ```js 59 | polly.configure({ 60 | adapters: ['fetch'], 61 | adapterOptions: { 62 | fetch: { 63 | context: window 64 | } 65 | } 66 | }); 67 | ``` 68 | -------------------------------------------------------------------------------- /docs/adapters/node-http.md: -------------------------------------------------------------------------------- 1 | # Node HTTP Adapter 2 | 3 | The node-http adapter provides a low level nodejs http request adapter that uses [nock](https://github.com/nock/nock) to patch the [http](https://nodejs.org/api/http.html) and [https](https://nodejs.org/api/https.html) modules in nodejs for seamless recording and replaying of requests. 4 | 5 | ## Installation 6 | 7 | _Note that you must have node (and npm) installed._ 8 | 9 | ```bash 10 | npm install @pollyjs/adapter-node-http -D 11 | ``` 12 | 13 | If you want to install it with [yarn](https://yarnpkg.com): 14 | 15 | ```bash 16 | yarn add @pollyjs/adapter-node-http -D 17 | ``` 18 | 19 | ## Usage 20 | 21 | Use the [configure](api#configure), [connectTo](api#connectto), and 22 | [disconnectFrom](api#disconnectfrom) APIs to connect or disconnect from the 23 | adapter. 24 | 25 | ```js 26 | import { Polly } from '@pollyjs/core'; 27 | import NodeHttpAdapter from '@pollyjs/adapter-node-http'; 28 | 29 | // Register the node http adapter so its accessible by all future polly instances 30 | Polly.register(NodeHttpAdapter); 31 | 32 | const polly = new Polly('', { 33 | adapters: ['node-http'] 34 | }); 35 | 36 | // Disconnect using the `configure` API 37 | polly.configure({ adapters: [] }); 38 | 39 | // Reconnect using the `connectTo` API 40 | polly.connectTo('node-http'); 41 | 42 | // Disconnect using the `disconnectFrom` API 43 | polly.disconnectFrom('node-http'); 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/adapters/playwright.md: -------------------------------------------------------------------------------- 1 | # Playwright Adapter 2 | 3 | The 3rd party [Playwright](https://playwright.dev/) adapter is provided by [@gribnoysup](https://github.com/redabacha). Please follow the readme below for installation and usage instructions. 4 | 5 | [README.md](https://raw.githubusercontent.com/redabacha/polly-adapter-playwright/master/README.md ':include :type=markdown') 6 | -------------------------------------------------------------------------------- /docs/adapters/xhr.md: -------------------------------------------------------------------------------- 1 | # XHR Adapter 2 | 3 | The XHR adapter uses Sinon's [Nise](https://github.com/sinonjs/nise) library 4 | to fake the global `XMLHttpRequest` object while wrapping the native one to allow 5 | for seamless recording and replaying of requests. 6 | 7 | ## Installation 8 | 9 | _Note that you must have node (and npm) installed._ 10 | 11 | ```bash 12 | npm install @pollyjs/adapter-xhr -D 13 | ``` 14 | 15 | If you want to install it with [yarn](https://yarnpkg.com): 16 | 17 | ```bash 18 | yarn add @pollyjs/adapter-xhr -D 19 | ``` 20 | 21 | ## Usage 22 | 23 | Use the [configure](api#configure), [connectTo](api#connectto), and 24 | [disconnectFrom](api#disconnectfrom) APIs to connect or disconnect from the 25 | adapter. 26 | 27 | ```js 28 | import { Polly } from '@pollyjs/core'; 29 | import XHRAdapter from '@pollyjs/adapter-xhr'; 30 | 31 | // Register the xhr adapter so its accessible by all future polly instances 32 | Polly.register(XHRAdapter); 33 | 34 | const polly = new Polly('', { 35 | adapters: ['xhr'] 36 | }); 37 | 38 | // Disconnect using the `configure` API 39 | polly.configure({ adapters: [] }); 40 | 41 | // Reconnect using the `connectTo` API 42 | polly.connectTo('xhr'); 43 | 44 | // Disconnect using the `disconnectFrom` API 45 | polly.disconnectFrom('xhr'); 46 | ``` 47 | 48 | ## Options 49 | 50 | ### context 51 | 52 | _Type_: `Object` 53 | _Default_: `global|self|window` 54 | 55 | The context object which contains the XMLHttpRequest object. Typically this is `window` or `self` in the browser and `global` in node. 56 | 57 | **Example** 58 | 59 | ```js 60 | polly.configure({ 61 | adapters: ['xhr'], 62 | adapterOptions: { 63 | xhr: { 64 | context: window 65 | } 66 | } 67 | }); 68 | ``` 69 | -------------------------------------------------------------------------------- /docs/assets/images/Netflix_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/pollyjs/d031861625f58423169b396e5afc8a0110d1b232/docs/assets/images/Netflix_Logo.png -------------------------------------------------------------------------------- /docs/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/pollyjs/d031861625f58423169b396e5afc8a0110d1b232/docs/assets/images/favicon.ico -------------------------------------------------------------------------------- /docs/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/pollyjs/d031861625f58423169b396e5afc8a0110d1b232/docs/assets/images/logo.png -------------------------------------------------------------------------------- /docs/assets/images/wordmark-logo-alt-twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/pollyjs/d031861625f58423169b396e5afc8a0110d1b232/docs/assets/images/wordmark-logo-alt-twitter.png -------------------------------------------------------------------------------- /docs/assets/images/wordmark-logo-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/pollyjs/d031861625f58423169b396e5afc8a0110d1b232/docs/assets/images/wordmark-logo-alt.png -------------------------------------------------------------------------------- /docs/assets/images/wordmark-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/pollyjs/d031861625f58423169b396e5afc8a0110d1b232/docs/assets/images/wordmark-logo.png -------------------------------------------------------------------------------- /docs/cli/commands.md: -------------------------------------------------------------------------------- 1 | # Commands 2 | 3 | As of right now, the Polly CLI only knows one command but expect to see more 4 | in the near future! 5 | 6 | ## listen 7 | 8 | Start up a node server and listen for Polly requests via the 9 | [REST Persister](persisters/rest) to be able to record and replay recordings 10 | to and from disk. 11 | 12 | ### Usage 13 | 14 | ```text 15 | Usage: polly listen|l [options] 16 | 17 | start the server and listen for requests 18 | 19 | Options: 20 | 21 | -H, --host host 22 | -p, --port port number (default: 3000) 23 | -n, --api-namespace api namespace (default: polly) 24 | -d, --recordings-dir recordings directory (default: recordings) 25 | -q, --quiet disable the logging 26 | -h, --help output usage information 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/cli/overview.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | The `@pollyjs/cli` package provides a standalone CLI to quickly get you setup 4 | and ready to go. 5 | 6 | ## Installation 7 | 8 | _Note that you must have node (and npm) installed._ 9 | 10 | ```bash 11 | npm install @pollyjs/cli -g 12 | ``` 13 | 14 | If you want to install it with [yarn](https://yarnpkg.com): 15 | 16 | ```bash 17 | yarn global add @pollyjs/cli 18 | ``` 19 | 20 | ## Usage 21 | 22 | ```text 23 | Usage: polly [options] [command] 24 | 25 | Options: 26 | 27 | -v, --version output the version number 28 | -h, --help output usage information 29 | 30 | Commands: 31 | 32 | listen|l [options] start the server and listen for requests 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/frameworks/ember-cli.md: -------------------------------------------------------------------------------- 1 | # Ember CLI 2 | 3 | Installing the `@pollyjs/ember` addon will import and vendor the necessary 4 | Polly.JS packages as well as register the [Express API](node-server/express-integrations) 5 | required by the [REST Persister](persisters/rest). 6 | 7 | ?> **NOTE:** By default, this addon installs and registers the 8 | [XHR](adapters/xhr) & [Fetch](adapters/fetch) adapters as well as the 9 | [REST](persisters/rest) & [Local Storage](persisters/local-storage) persisters. 10 | 11 | ## Installation 12 | 13 | ```bash 14 | ember install @pollyjs/ember 15 | ``` 16 | 17 | ## Configuration 18 | 19 | Addon and [server API configuration](node-server/overview#api-configuration) can be 20 | be specified in `/config/polly.js`. The default configuration options are shown below. 21 | 22 | ```js 23 | module.exports = function(env) { 24 | return { 25 | // Addon Configuration Options 26 | enabled: env !== 'production', 27 | 28 | // Server Configuration Options 29 | server: { 30 | apiNamespace: '/polly', 31 | recordingsDir: 'recordings' 32 | } 33 | }; 34 | }; 35 | ``` 36 | 37 | ## Usage 38 | 39 | Once installed and configured, you can import and use Polly as documented. Check 40 | out the [Quick Start](quick-start#usage) documentation to get started. 41 | 42 | ?> For an even better testing experience, check out the provided 43 | [QUnit Test Helper](test-frameworks/qunit)! 44 | -------------------------------------------------------------------------------- /docs/node-server/express-integrations.md: -------------------------------------------------------------------------------- 1 | # Express Integrations 2 | 3 | The `@pollyjs/node-server` package exports a `registerExpressAPI` method which 4 | takes in an [Express](http://expressjs.com/) app and a config to register the 5 | necessary routes to be used with the REST Persister. 6 | 7 | ```js 8 | const { registerExpressAPI } = require('@pollyjs/node-server'); 9 | 10 | registerExpressAPI(app, config); 11 | ``` 12 | 13 | ## Webpack DevServer 14 | 15 | ```js 16 | const path = require('path'); 17 | const { registerExpressAPI } = require('@pollyjs/node-server'); 18 | 19 | const config = { 20 | devServer: { 21 | before(app) { 22 | registerExpressAPI(app, config); 23 | } 24 | } 25 | }; 26 | 27 | module.exports = config; 28 | ``` 29 | 30 | ## Ember CLI 31 | 32 | See the [Ember CLI Addon](frameworks/ember-cli) documentation for more details. 33 | -------------------------------------------------------------------------------- /docs/persisters/fs.md: -------------------------------------------------------------------------------- 1 | # File System Persister 2 | 3 | Read and write recordings to and from the file system. 4 | 5 | ## Installation 6 | 7 | _Note that you must have node (and npm) installed._ 8 | 9 | ```bash 10 | npm install @pollyjs/persister-fs -D 11 | ``` 12 | 13 | If you want to install it with [yarn](https://yarnpkg.com): 14 | 15 | ```bash 16 | yarn add @pollyjs/persister-fs -D 17 | ``` 18 | 19 | ## Usage 20 | 21 | ```js 22 | import { Polly } from '@pollyjs/core'; 23 | import FSPersister from '@pollyjs/persister-fs'; 24 | 25 | // Register the fs persister so its accessible by all future polly instances 26 | Polly.register(FSPersister); 27 | 28 | new Polly('', { 29 | persister: 'fs' 30 | }); 31 | ``` 32 | 33 | ## Options 34 | 35 | ### recordingsDir 36 | 37 | _Type_: `String` 38 | _Default_: `'recordings'` 39 | 40 | The root directory to store all recordings. Supports both relative and 41 | absolute paths. 42 | 43 | **Example** 44 | 45 | ```js 46 | polly.configure({ 47 | persisterOptions: { 48 | fs: { 49 | recordingsDir: '__recordings__' 50 | } 51 | } 52 | }); 53 | ``` 54 | -------------------------------------------------------------------------------- /docs/persisters/local-storage.md: -------------------------------------------------------------------------------- 1 | # Local Storage Persister 2 | 3 | Read and write recordings to and from the browser's Local Storage. 4 | 5 | ## Installation 6 | 7 | _Note that you must have node (and npm) installed._ 8 | 9 | ```bash 10 | npm install @pollyjs/persister-local-storage -D 11 | ``` 12 | 13 | If you want to install it with [yarn](https://yarnpkg.com): 14 | 15 | ```bash 16 | yarn add @pollyjs/persister-local-storage -D 17 | ``` 18 | 19 | ## Usage 20 | 21 | ```js 22 | import { Polly } from '@pollyjs/core'; 23 | import LocalStoragePersister from '@pollyjs/persister-local-storage'; 24 | 25 | // Register the local-storage persister so its accessible by all future polly instances 26 | Polly.register(LocalStoragePersister); 27 | 28 | new Polly('', { 29 | persister: 'local-storage' 30 | }); 31 | ``` 32 | 33 | ## Options 34 | 35 | ### context 36 | 37 | _Type_: `Object` 38 | _Default_: `global|self|window` 39 | 40 | The context object which contains the localStorage API. 41 | Typically this is `window` or `self` in the browser and `global` in node. 42 | 43 | **Example** 44 | 45 | ```js 46 | polly.configure({ 47 | persisterOptions: { 48 | 'local-storage': { 49 | context: window 50 | } 51 | } 52 | }); 53 | ``` 54 | 55 | ### key 56 | 57 | _Type_: `String` 58 | _Default_: `'pollyjs'` 59 | 60 | The localStorage key to store the recordings data under. 61 | 62 | **Example** 63 | 64 | ```js 65 | polly.configure({ 66 | persisterOptions: { 67 | 'local-storage': { 68 | key: '__pollyjs__' 69 | } 70 | } 71 | }); 72 | ``` 73 | -------------------------------------------------------------------------------- /docs/server/event.md: -------------------------------------------------------------------------------- 1 | # Event 2 | 3 | ## Properties 4 | 5 | ### type 6 | 7 | _Type_: `String` 8 | 9 | The event type. (e.g. `request`, `response`, `beforePersist`) 10 | 11 | ## Methods 12 | 13 | ### stopPropagation 14 | 15 | If several event listeners are attached to the same event type, they are called in the order in which they were added. If `stopPropagation` is invoked during one such call, no remaining listeners will be called. 16 | 17 | **Example** 18 | 19 | ```js 20 | server.get('/session/:id').on('beforeResponse', (req, res, event) => { 21 | event.stopPropagation(); 22 | res.setHeader('X-SESSION-ID', 'ABC123'); 23 | }); 24 | 25 | server.get('/session/:id').on('beforeResponse', (req, res, event) => { 26 | // This will never be reached 27 | res.setHeader('X-SESSION-ID', 'XYZ456'); 28 | }); 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/test-frameworks/jest-jasmine.md: -------------------------------------------------------------------------------- 1 | # Jest & Jasmine 2 | 3 | Due to the nature of the Jest & Jasmine APIs and their restrictions on accessing 4 | the current running test and its parent modules, we've decided to keep this test helper 5 | as a 3rd party library provided by [@gribnoysup](https://github.com/gribnoysup). 6 | 7 | The [setup-polly-jest](https://github.com/gribnoysup/setup-polly-jest) package provides a `setupPolly` utility which will setup a new polly instance for each test as well as stop it once the test has ended. 8 | The Polly instance's recording name is derived from the current test name as well as its 9 | parent module(s). 10 | 11 | [README.md](https://raw.githubusercontent.com/gribnoysup/setup-polly-jest/master/README.md ':include :type=markdown') 12 | -------------------------------------------------------------------------------- /examples/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | node: true, 4 | browser: true 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /examples/client-server/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Client Server Tests 7 | 8 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/client-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pollyjs/client-server-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "license": "Apache-2.0", 6 | "scripts": { 7 | "test": "http-server -p 3000 -o -c-1 -s" 8 | }, 9 | "devDependencies": { 10 | "@pollyjs/adapter-fetch": "*", 11 | "@pollyjs/core": "*", 12 | "@pollyjs/persister-local-storage": "*", 13 | "chai": "*", 14 | "http-server": "*", 15 | "mocha": "*" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/client-server/tests/events.test.js: -------------------------------------------------------------------------------- 1 | /* global setupPolly */ 2 | 3 | describe('Events', function () { 4 | setupPolly({ 5 | adapters: ['fetch'], 6 | persister: 'local-storage' 7 | }); 8 | 9 | it('can help test dynamic data', async function () { 10 | const { server } = this.polly; 11 | let numPosts = 0; 12 | 13 | server 14 | .get('https://jsonplaceholder.typicode.com/posts') 15 | .on('response', (_, res) => { 16 | numPosts = res.jsonBody().length; 17 | }); 18 | 19 | const res = await fetch('https://jsonplaceholder.typicode.com/posts'); 20 | const posts = await res.json(); 21 | 22 | expect(res.status).to.equal(200); 23 | expect(posts.length).to.equal(numPosts); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /examples/client-server/tests/setup.js: -------------------------------------------------------------------------------- 1 | // Expose common globals 2 | window.PollyJS = window['@pollyjs/core']; 3 | window.setupPolly = window.PollyJS.setupMocha; 4 | window.expect = window.chai.expect; 5 | 6 | // Register the fetch adapter and local-storage persister 7 | window.PollyJS.Polly.register(window['@pollyjs/adapter-fetch']); 8 | window.PollyJS.Polly.register(window['@pollyjs/persister-local-storage']); 9 | 10 | // Setup Mocha 11 | mocha.setup({ ui: 'bdd', noHighlighting: true }); 12 | -------------------------------------------------------------------------------- /examples/dummy-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['plugin:react/recommended'], 3 | settings: { 4 | react: { 5 | version: '16.5.1' 6 | } 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /examples/dummy-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /examples/dummy-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pollyjs/dummy-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "license": "Apache-2.0", 6 | "dependencies": { 7 | "prop-types": "^15.6.2", 8 | "ra-data-json-server": "^2.3.1", 9 | "react": "^16.5.1", 10 | "react-admin": "^2.3.1", 11 | "react-dom": "^16.5.1", 12 | "react-scripts": "^1.1.5" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "start:ci": "BROWSER=none CI=true yarn start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test --env=jsdom", 19 | "eject": "react-scripts eject" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/dummy-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/pollyjs/d031861625f58423169b396e5afc8a0110d1b232/examples/dummy-app/public/favicon.ico -------------------------------------------------------------------------------- /examples/dummy-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 16 | 25 | Dummy App 26 | 27 | 28 | 29 |
30 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/dummy-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Dummy App", 3 | "name": "Polly.JS Examples Dummy App", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /examples/dummy-app/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Admin, Resource } from 'react-admin'; 3 | import jsonServerProvider from 'ra-data-json-server'; 4 | import PostIcon from '@material-ui/icons/Book'; 5 | import TodoIcon from '@material-ui/icons/ViewList'; 6 | import UserIcon from '@material-ui/icons/Group'; 7 | import { createMuiTheme } from '@material-ui/core/styles'; 8 | 9 | import { UserList, UserShow } from './users'; 10 | import { TodoList, TodoShow, TodoEdit, TodoCreate } from './todos'; 11 | import { PostList, PostShow, PostEdit, PostCreate } from './posts'; 12 | 13 | const dataProvider = jsonServerProvider('https://jsonplaceholder.typicode.com'); 14 | const theme = createMuiTheme({ 15 | palette: { 16 | primary: { 17 | light: '#ff5740', 18 | main: '#e50914', 19 | dark: '#aa0000', 20 | contrastText: '#fff' 21 | }, 22 | secondary: { 23 | light: '#ff5740', 24 | main: '#e50914', 25 | dark: '#aa0000', 26 | contrastText: '#fff' 27 | } 28 | } 29 | }); 30 | 31 | const App = () => ( 32 | 33 | 41 | 49 | 50 | 51 | ); 52 | 53 | export default App; 54 | -------------------------------------------------------------------------------- /examples/dummy-app/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | -------------------------------------------------------------------------------- /examples/dummy-app/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import './index.css'; 5 | import App from './App'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | -------------------------------------------------------------------------------- /examples/dummy-app/src/users.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { 4 | List, 5 | Datagrid, 6 | EmailField, 7 | TextField, 8 | Show, 9 | SimpleShowLayout, 10 | ShowButton 11 | } from 'react-admin'; 12 | 13 | const UserTitle = ({ record }) => { 14 | return Users - {record ? `${record.name}` : ''}; 15 | }; 16 | 17 | UserTitle.propTypes = { 18 | record: PropTypes.PropTypes.shape({ 19 | name: PropTypes.string 20 | }) 21 | }; 22 | 23 | export const UserList = (props) => ( 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | ); 33 | 34 | export const UserShow = (props) => ( 35 | } {...props}> 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ); 44 | -------------------------------------------------------------------------------- /examples/jest-node-fetch/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /examples/jest-node-fetch/__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { Polly } = require('@pollyjs/core'); 4 | const { setupPolly } = require('setup-polly-jest'); 5 | const NodeHttpAdapter = require('@pollyjs/adapter-node-http'); 6 | const FSPersister = require('@pollyjs/persister-fs'); 7 | 8 | Polly.register(NodeHttpAdapter); 9 | Polly.register(FSPersister); 10 | 11 | const { posts, users } = require('../src'); 12 | 13 | describe('jest-node-fetch', () => { 14 | setupPolly({ 15 | adapters: ['node-http'], 16 | persister: 'fs', 17 | persisterOptions: { 18 | fs: { 19 | recordingsDir: path.resolve(__dirname, '../__recordings__') 20 | } 21 | } 22 | }); 23 | 24 | describe('posts', () => { 25 | it('should return post', async () => { 26 | const post = await posts('1'); 27 | 28 | expect(post.id).toBe(1); 29 | expect(post.title).toBe( 30 | 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit' 31 | ); 32 | }); 33 | }); 34 | 35 | describe('users', () => { 36 | it('should return user', async () => { 37 | const user = await users('1'); 38 | 39 | expect(user.id).toBe(1); 40 | expect(user.name).toBe('Leanne Graham'); 41 | expect(user.email).toBe('Sincere@april.biz'); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /examples/jest-node-fetch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pollyjs/jest-node-fetch-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "license": "Apache-2.0", 6 | "scripts": { 7 | "test": "jest" 8 | }, 9 | "devDependencies": { 10 | "@pollyjs/adapter-node-http": "*", 11 | "@pollyjs/core": "*", 12 | "@pollyjs/persister-fs": "*", 13 | "jest": "*", 14 | "node-fetch": "*", 15 | "setup-polly-jest": "*" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/jest-node-fetch/src/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | posts: require('./posts'), 3 | users: require('./users') 4 | }; 5 | -------------------------------------------------------------------------------- /examples/jest-node-fetch/src/posts.js: -------------------------------------------------------------------------------- 1 | const fetch = require('node-fetch'); 2 | 3 | module.exports = async (id) => { 4 | const response = await fetch( 5 | `https://jsonplaceholder.typicode.com/posts/${id}` 6 | ); 7 | 8 | return await response.json(); 9 | }; 10 | -------------------------------------------------------------------------------- /examples/jest-node-fetch/src/users.js: -------------------------------------------------------------------------------- 1 | const fetch = require('node-fetch'); 2 | 3 | module.exports = async (id) => { 4 | const response = await fetch( 5 | `https://jsonplaceholder.typicode.com/users/${id}` 6 | ); 7 | 8 | return await response.json(); 9 | }; 10 | -------------------------------------------------------------------------------- /examples/jest-puppeteer/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | }, 5 | globals: { 6 | page: true, 7 | browser: true, 8 | jestPuppeteer: true 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /examples/jest-puppeteer/jest-puppeteer.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | launch: { 3 | headless: true 4 | }, 5 | server: { 6 | command: '(cd ../dummy-app && yarn start:ci)', 7 | port: 3000, 8 | launchTimeout: 60000 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /examples/jest-puppeteer/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'jest-puppeteer' 3 | }; 4 | -------------------------------------------------------------------------------- /examples/jest-puppeteer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pollyjs/jest-puppeteer-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "license": "Apache-2.0", 6 | "scripts": { 7 | "postinstall": "(cd ../dummy-app && yarn install)", 8 | "test": "jest" 9 | }, 10 | "devDependencies": { 11 | "@pollyjs/adapter-puppeteer": "^4.0.2", 12 | "@pollyjs/core": "^4.0.2", 13 | "@pollyjs/persister-fs": "^4.0.2", 14 | "jest": "^24.0.0", 15 | "jest-puppeteer": "^4.0.0", 16 | "puppeteer": "1.10.0", 17 | "setup-polly-jest": "^0.6.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/node-fetch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pollyjs/node-fetch-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "license": "Apache-2.0", 6 | "scripts": { 7 | "test": "mocha './tests/*.test.js'" 8 | }, 9 | "devDependencies": { 10 | "@pollyjs/adapter-node-http": "*", 11 | "@pollyjs/core": "*", 12 | "@pollyjs/persister-fs": "*", 13 | "chai": "*", 14 | "mocha": "*", 15 | "node-fetch": "*" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/node-fetch/tests/node-fetch.test.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const NodeHttpAdapter = require('@pollyjs/adapter-node-http'); 4 | const FSPersister = require('@pollyjs/persister-fs'); 5 | const fetch = require('node-fetch'); 6 | const { Polly, setupMocha: setupPolly } = require('@pollyjs/core'); 7 | const { expect } = require('chai'); 8 | 9 | Polly.register(NodeHttpAdapter); 10 | Polly.register(FSPersister); 11 | 12 | describe('node-fetch', function () { 13 | setupPolly({ 14 | adapters: ['node-http'], 15 | persister: 'fs', 16 | persisterOptions: { 17 | fs: { 18 | recordingsDir: path.resolve(__dirname, '../recordings') 19 | } 20 | } 21 | }); 22 | 23 | it('should work', async function () { 24 | const res = await fetch('https://jsonplaceholder.typicode.com/posts/1'); 25 | const post = await res.json(); 26 | 27 | expect(res.status).to.equal(200); 28 | expect(post.id).to.equal(1); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /examples/puppeteer/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const puppeteer = require('puppeteer'); 4 | const { Polly } = require('@pollyjs/core'); 5 | const PuppeteerAdapter = require('@pollyjs/adapter-puppeteer'); 6 | const FSPersister = require('@pollyjs/persister-fs'); 7 | 8 | Polly.register(PuppeteerAdapter); 9 | Polly.register(FSPersister); 10 | 11 | (async () => { 12 | const browser = await puppeteer.launch({ headless: false }); 13 | const page = await browser.newPage(); 14 | 15 | await page.setRequestInterception(true); 16 | 17 | const polly = new Polly('puppeteer', { 18 | adapters: ['puppeteer'], 19 | adapterOptions: { puppeteer: { page } }, 20 | persister: 'fs', 21 | persisterOptions: { 22 | fs: { 23 | recordingsDir: path.join(__dirname, 'recordings') 24 | } 25 | } 26 | }); 27 | 28 | const { server } = polly; 29 | 30 | server.host('http://localhost:3000', () => { 31 | server.get('/favicon.ico').passthrough(); 32 | server.get('/sockjs-node/*').intercept((_, res) => res.sendStatus(200)); 33 | }); 34 | 35 | await page.goto('http://localhost:3000', { waitUntil: 'networkidle0' }); 36 | 37 | await polly.flush(); 38 | await polly.stop(); 39 | await browser.close(); 40 | })(); 41 | -------------------------------------------------------------------------------- /examples/puppeteer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pollyjs/puppeteer-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "license": "Apache-2.0", 6 | "scripts": { 7 | "postinstall": "(cd ../dummy-app && yarn install)", 8 | "start": "start-server-and-test start:server http://localhost:3000 start:puppeteer", 9 | "start:server": "(cd ../dummy-app && yarn start:ci)", 10 | "start:puppeteer": "node index.js" 11 | }, 12 | "devDependencies": { 13 | "@pollyjs/adapter-puppeteer": "*", 14 | "@pollyjs/core": "*", 15 | "@pollyjs/persister-fs": "*", 16 | "puppeteer": "*", 17 | "start-server-and-test": "*" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/rest-persister/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | REST Persister Tests 7 | 8 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/rest-persister/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pollyjs/rest-persister-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "license": "Apache-2.0", 6 | "scripts": { 7 | "test": "start-server-and-test test:polly-server http://localhost:3000 test:server", 8 | "test:server": "http-server -p 4000 -o -c-1 -s", 9 | "test:polly-server": "polly listen" 10 | }, 11 | "devDependencies": { 12 | "@pollyjs/adapter-fetch": "*", 13 | "@pollyjs/cli": "*", 14 | "@pollyjs/core": "*", 15 | "@pollyjs/persister-rest": "*", 16 | "chai": "*", 17 | "http-server": "*", 18 | "mocha": "*", 19 | "start-server-and-test": "*" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/rest-persister/tests/rest-persister.test.js: -------------------------------------------------------------------------------- 1 | /* global setupPolly */ 2 | 3 | describe('REST Persister', function () { 4 | setupPolly({ 5 | adapters: ['fetch'], 6 | persister: 'rest' 7 | }); 8 | 9 | it('should work', async function () { 10 | const res = await fetch('https://jsonplaceholder.typicode.com/posts/1'); 11 | const post = await res.json(); 12 | 13 | expect(res.status).to.equal(200); 14 | expect(post.id).to.equal(1); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /examples/rest-persister/tests/setup.js: -------------------------------------------------------------------------------- 1 | // Expose common globals 2 | window.PollyJS = window['@pollyjs/core']; 3 | window.setupPolly = window.PollyJS.setupMocha; 4 | window.expect = window.chai.expect; 5 | 6 | // Register the fetch adapter and REST persister 7 | window.PollyJS.Polly.register(window['@pollyjs/adapter-fetch']); 8 | window.PollyJS.Polly.register(window['@pollyjs/persister-rest']); 9 | 10 | // Setup Mocha 11 | mocha.setup({ ui: 'bdd', noHighlighting: true }); 12 | -------------------------------------------------------------------------------- /examples/typescript-jest-node-fetch/jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "@jest/types"; 2 | 3 | const config: Config.InitialOptions = { 4 | rootDir: ".", 5 | preset: "ts-jest", 6 | testEnvironment: "setup-polly-jest/jest-environment-jsdom", 7 | verbose: true, 8 | testPathIgnorePatterns: ["node_modules", "dist"], 9 | resetModules: true, 10 | globals: { 11 | "ts-jest": { 12 | useESM: true, 13 | }, 14 | }, 15 | transform: { }, 16 | }; 17 | 18 | export default config; 19 | -------------------------------------------------------------------------------- /examples/typescript-jest-node-fetch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pollyjs/typescript-jest-node-fetch-example", 3 | "version": "1.0.0", 4 | "private": true, 5 | "main": "./dist/index.js", 6 | "type": "commonjs", 7 | "exports": "./dist/index.js", 8 | "scripts": { 9 | "test": "jest --runInBand", 10 | "test:record": "POLLY_MODE=record jest --runInBand --verbose" 11 | }, 12 | "keywords": [ 13 | "pollyjs", 14 | "test-mocking", 15 | "jest", 16 | "node", 17 | "typescript", 18 | "fetch" 19 | ], 20 | "author": "", 21 | "license": "Apache-2.0", 22 | "dependencies": { 23 | "node-fetch": "^2.6.6" 24 | }, 25 | "devDependencies": { 26 | "@pollyjs/adapter-fetch": "^5.1.1", 27 | "@pollyjs/adapter-node-http": "^5.1.1", 28 | "@pollyjs/core": "^5.1.1", 29 | "@pollyjs/node-server": "^5.1.1", 30 | "@pollyjs/persister-fs": "^5.1.1", 31 | "@types/jest": "^26.0.0", 32 | "@types/node": "^16.11.11", 33 | "@types/node-fetch": "^2.5.12", 34 | "@types/pollyjs__adapter": "^4.3.1", 35 | "@types/pollyjs__adapter-fetch": "^2.0.1", 36 | "@types/pollyjs__adapter-node-http": "^2.0.1", 37 | "@types/pollyjs__core": "^4.3.3", 38 | "@types/pollyjs__persister": "^4.3.1", 39 | "@types/pollyjs__persister-fs": "^2.0.1", 40 | "@types/pollyjs__utils": "^2.6.1", 41 | "@types/setup-polly-jest": "^0.5.1", 42 | "jest": "^26.6.0", 43 | "nodemon": "^2.0.15", 44 | "setup-polly-jest": "^0.10.0", 45 | "ts-jest": "^26.5.6", 46 | "ts-node": "^10.4.0", 47 | "typescript": "^4.5.2" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/typescript-jest-node-fetch/src/github-api.test.ts: -------------------------------------------------------------------------------- 1 | /** @jest-environment setup-polly-jest/jest-environment-node */ 2 | import autoSetupPolly from './utils/auto-setup-polly'; 3 | import { getUser } from './github-api'; 4 | 5 | describe('github-api client', () => { 6 | let pollyContext = autoSetupPolly(); 7 | 8 | beforeEach(() => { 9 | // Intercept /ping healthcheck requests (example) 10 | pollyContext.polly.server 11 | .any("/ping") 12 | .intercept((req, res) => void res.sendStatus(200)); 13 | }); 14 | 15 | it('getUser', async () => { 16 | const user: any = await getUser('netflix'); 17 | expect(typeof user).toBe('object'); 18 | expect(user?.login).toBe('Netflix'); 19 | }); 20 | 21 | it('getUser: custom interceptor', async () => { 22 | expect.assertions(1); 23 | pollyContext.polly.server 24 | .get('https://api.github.com/users/failing_request_trigger') 25 | .intercept((req, res) => void res.sendStatus(500)); 26 | 27 | await expect(getUser('failing_request_trigger')).rejects.toThrow( 28 | 'Http Error: 500' 29 | ); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /examples/typescript-jest-node-fetch/src/github-api.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import type { Response } from "node-fetch"; 3 | 4 | export const getUser = async (username: string): Promise => { 5 | return fetch(`https://api.github.com/users/${username}`, { 6 | headers: { 7 | "Accept": "application/json+vnd.github.v3.raw", 8 | "Content-type": "application/json", 9 | }, 10 | }) 11 | .then(checkErrorAndReturnJson); 12 | }; 13 | 14 | function checkErrorAndReturnJson(response: Response) { 15 | return response.ok 16 | ? response.json() 17 | : Promise.reject(new Error(`Http Error: ${response.status}`)); 18 | } 19 | -------------------------------------------------------------------------------- /examples/typescript-jest-node-fetch/src/utils/auto-setup-polly.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { setupPolly } from "setup-polly-jest"; 3 | import { Polly, PollyConfig } from "@pollyjs/core"; 4 | import NodeHttpAdapter from "@pollyjs/adapter-node-http"; 5 | import FSPersister from "@pollyjs/persister-fs"; 6 | 7 | Polly.register(NodeHttpAdapter); 8 | Polly.register(FSPersister); 9 | 10 | let recordIfMissing = true; 11 | let mode: PollyConfig['mode'] = 'replay'; 12 | 13 | switch (process.env.POLLY_MODE) { 14 | case 'record': 15 | mode = 'record'; 16 | break; 17 | case 'replay': 18 | mode = 'replay'; 19 | break; 20 | case 'offline': 21 | mode = 'replay'; 22 | recordIfMissing = false; 23 | break; 24 | } 25 | 26 | export default function autoSetupPolly() { 27 | /** 28 | * This persister can be adapted for both Node.js and Browser environments. 29 | * 30 | * TODO: Customize your config. 31 | */ 32 | return setupPolly({ 33 | // 🟡 Note: In node, most `fetch` like libraries use the http/https modules. 34 | // `node-fetch` is handled by `NodeHttpAdapter`, NOT the `FetchAdapter`. 35 | adapters: ["node-http"], 36 | mode, 37 | recordIfMissing, 38 | flushRequestsOnStop: true, 39 | logging: false, 40 | recordFailedRequests: true, 41 | persister: "fs", 42 | persisterOptions: { 43 | fs: { 44 | recordingsDir: path.resolve(__dirname, "../../__recordings__"), 45 | }, 46 | }, 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /examples/typescript-jest-node-fetch/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES5", 4 | "lib": ["esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "alwaysStrict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "module": "ESNext", 14 | "moduleResolution": "node", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "sourceMap": true, 18 | "baseUrl": ".", 19 | "rootDir": ".", 20 | "outDir": "dist", 21 | "useUnknownInCatchVariables": false 22 | }, 23 | "include": ["src"] 24 | } 25 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | module.exports = { 4 | testURL: 'http://localhost:4000/api', 5 | testMatch: ['**/@pollyjs/*/build/jest/*.js'], 6 | roots: ['/packages/@pollyjs'], 7 | reporters: ['jest-tap-reporter'], 8 | testEnvironment: 'jsdom' 9 | }; 10 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "6.0.7", 3 | "npmClient": "yarn", 4 | "useWorkspaces": true, 5 | "packages": ["packages/@pollyjs/*"], 6 | "command": { 7 | "publish": { 8 | "allowBranch": "master", 9 | "conventionalCommits": true, 10 | "message": "chore: Publish %s", 11 | "ignoreChanges": ["docs/**", "examples/**", "**/tests/**", "**/*.md"] 12 | } 13 | }, 14 | "changelog": { 15 | "repo": "Netflix/pollyjs", 16 | "labels": { 17 | "Tag: Breaking Change": ":boom: Breaking Change", 18 | "Tag: Enhancement": ":rocket: Enhancement", 19 | "Tag: Bug Fix": ":bug: Bug Fix", 20 | "Tag: Documentation": ":memo: Documentation" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-fetch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pollyjs/adapter-fetch", 3 | "version": "6.0.7", 4 | "description": "Fetch adapter for @pollyjs", 5 | "main": "dist/cjs/pollyjs-adapter-fetch.js", 6 | "module": "dist/es/pollyjs-adapter-fetch.js", 7 | "browser": "dist/umd/pollyjs-adapter-fetch.js", 8 | "types": "types.d.ts", 9 | "files": [ 10 | "src", 11 | "dist", 12 | "types.d.ts" 13 | ], 14 | "repository": "https://github.com/netflix/pollyjs/tree/master/packages/@pollyjs/adapter-fetch", 15 | "license": "Apache-2.0", 16 | "contributors": [ 17 | { 18 | "name": "Jason Mitchell", 19 | "email": "jason.mitchell.w@gmail.com" 20 | }, 21 | { 22 | "name": "Offir Golan", 23 | "email": "offirgolan@gmail.com" 24 | } 25 | ], 26 | "keywords": [ 27 | "polly", 28 | "pollyjs", 29 | "record", 30 | "replay", 31 | "fetch", 32 | "adapter" 33 | ], 34 | "publishConfig": { 35 | "access": "public" 36 | }, 37 | "scripts": { 38 | "build": "rollup -c ../../../scripts/rollup/default.config.js", 39 | "test:build": "rollup -c rollup.config.test.js", 40 | "test:build:watch": "rollup -c rollup.config.test.js -w", 41 | "build:watch": "yarn build -w", 42 | "watch-all": "npm-run-all --parallel build:watch test:build:watch" 43 | }, 44 | "dependencies": { 45 | "@pollyjs/adapter": "^6.0.6", 46 | "@pollyjs/utils": "^6.0.6", 47 | "to-arraybuffer": "^1.0.1" 48 | }, 49 | "devDependencies": { 50 | "@pollyjs/core": "^6.0.6", 51 | "@pollyjs/persister-local-storage": "^6.0.6", 52 | "@pollyjs/persister-rest": "^6.0.6", 53 | "rollup": "^1.14.6" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-fetch/rollup.config.test.js: -------------------------------------------------------------------------------- 1 | import createBrowserTestConfig from '../../../scripts/rollup/browser.test.config'; 2 | 3 | export default [createBrowserTestConfig()]; 4 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-fetch/src/utils/serializer-headers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Serialize a Headers instance into a pojo since it cannot be stringified. 3 | * @param {*} headers 4 | */ 5 | export default function serializeHeaders(headers) { 6 | if (headers && typeof headers.forEach === 'function') { 7 | const serializedHeaders = {}; 8 | 9 | headers.forEach((value, key) => (serializedHeaders[key] = value)); 10 | 11 | return serializedHeaders; 12 | } 13 | 14 | return headers || {}; 15 | } 16 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-fetch/tests/integration/persister-local-storage-test.js: -------------------------------------------------------------------------------- 1 | import { setupMocha as setupPolly } from '@pollyjs/core'; 2 | import LocalStoragePersister from '@pollyjs/persister-local-storage'; 3 | import setupFetchRecord from '@pollyjs-tests/helpers/setup-fetch-record'; 4 | import setupPersister from '@pollyjs-tests/helpers/setup-persister'; 5 | import persisterTests from '@pollyjs-tests/integration/persister-tests'; 6 | 7 | import pollyConfig from '../utils/polly-config'; 8 | 9 | describe('Integration | Local Storage Persister', function () { 10 | setupPolly.beforeEach({ 11 | ...pollyConfig, 12 | persister: LocalStoragePersister 13 | }); 14 | 15 | setupFetchRecord(); 16 | setupPersister(); 17 | setupPolly.afterEach(); 18 | 19 | persisterTests(); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-fetch/tests/integration/persister-rest-test.js: -------------------------------------------------------------------------------- 1 | import { setupMocha as setupPolly } from '@pollyjs/core'; 2 | import RESTPersister from '@pollyjs/persister-rest'; 3 | import setupFetchRecord from '@pollyjs-tests/helpers/setup-fetch-record'; 4 | import setupPersister from '@pollyjs-tests/helpers/setup-persister'; 5 | import persisterTests from '@pollyjs-tests/integration/persister-tests'; 6 | 7 | import pollyConfig from '../utils/polly-config'; 8 | 9 | describe('Integration | REST Persister', function () { 10 | setupPolly.beforeEach({ 11 | ...pollyConfig, 12 | persister: RESTPersister, 13 | persisterOptions: { 14 | rest: { host: '' } 15 | } 16 | }); 17 | 18 | setupFetchRecord(); 19 | setupPersister(); 20 | setupPolly.afterEach(); 21 | 22 | persisterTests(); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-fetch/tests/utils/polly-config.js: -------------------------------------------------------------------------------- 1 | import InMemoryPersister from '@pollyjs/persister-in-memory'; 2 | 3 | import FetchAdapter from '../../src'; 4 | 5 | export default { 6 | recordFailedRequests: true, 7 | adapters: [FetchAdapter], 8 | persister: InMemoryPersister 9 | }; 10 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-fetch/types.d.ts: -------------------------------------------------------------------------------- 1 | import Adapter from '@pollyjs/adapter'; 2 | 3 | export default class FetchAdapter extends Adapter<{ 4 | context?: any; 5 | }> {} 6 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-node-http/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: false, 4 | node: true 5 | }, 6 | overrides: [ 7 | { 8 | files: ['tests/jest/**/*.js'], 9 | env: { 10 | browser: true 11 | } 12 | } 13 | ] 14 | }; 15 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-node-http/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pollyjs/adapter-node-http", 3 | "version": "6.0.6", 4 | "description": "Node HTTP adapter for @pollyjs", 5 | "main": "dist/cjs/pollyjs-adapter-node-http.js", 6 | "module": "dist/es/pollyjs-adapter-node-http.js", 7 | "types": "types.d.ts", 8 | "files": [ 9 | "src", 10 | "dist", 11 | "types.d.ts" 12 | ], 13 | "repository": "https://github.com/netflix/pollyjs/tree/master/packages/@pollyjs/adapter-node-http", 14 | "license": "Apache-2.0", 15 | "contributors": [ 16 | { 17 | "name": "Yasin Uslu", 18 | "email": "a.yasin.uslu@gmail.com" 19 | }, 20 | { 21 | "name": "Jason Mitchell", 22 | "email": "jason.mitchell.w@gmail.com" 23 | }, 24 | { 25 | "name": "Offir Golan", 26 | "email": "offirgolan@gmail.com" 27 | } 28 | ], 29 | "keywords": [ 30 | "polly", 31 | "pollyjs", 32 | "record", 33 | "replay", 34 | "http", 35 | "adapter" 36 | ], 37 | "publishConfig": { 38 | "access": "public" 39 | }, 40 | "scripts": { 41 | "build": "rollup -c", 42 | "test:build": "rollup -c rollup.config.test.js", 43 | "test:build:watch": "rollup -c rollup.config.test.js -w", 44 | "build:watch": "yarn build -w", 45 | "watch-all": "npm-run-all --parallel build:watch test:build:watch" 46 | }, 47 | "dependencies": { 48 | "@pollyjs/adapter": "^6.0.6", 49 | "@pollyjs/utils": "^6.0.6", 50 | "lodash-es": "^4.17.21", 51 | "nock": "^13.2.1" 52 | }, 53 | "devDependencies": { 54 | "@pollyjs/core": "^6.0.6", 55 | "@pollyjs/persister-fs": "^6.0.6", 56 | "form-data": "^4.0.0", 57 | "get-stream": "^6.0.1", 58 | "node-fetch": "^2.6.6", 59 | "rollup": "^1.14.6" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-node-http/rollup.config.js: -------------------------------------------------------------------------------- 1 | import createNodeConfig from '../../../scripts/rollup/node.config'; 2 | 3 | import { external } from './rollup.config.shared'; 4 | 5 | export default createNodeConfig({ external }); 6 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-node-http/rollup.config.shared.js: -------------------------------------------------------------------------------- 1 | export const external = [ 2 | 'http', 3 | 'https', 4 | 'url', 5 | 'stream', 6 | 'timers', 7 | 'tty', 8 | 'util', 9 | 'os' 10 | ]; 11 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-node-http/rollup.config.test.js: -------------------------------------------------------------------------------- 1 | import createNodeTestConfig from '../../../scripts/rollup/node.test.config'; 2 | import createJestTestConfig from '../../../scripts/rollup/jest.test.config'; 3 | 4 | import { external } from './rollup.config.shared'; 5 | 6 | const testExternal = [...external, 'fs', 'path']; 7 | 8 | export default [ 9 | createNodeTestConfig({ external: testExternal }), 10 | createJestTestConfig({ external: testExternal }) 11 | ]; 12 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-node-http/src/utils/get-url-from-options.js: -------------------------------------------------------------------------------- 1 | import { URL } from '@pollyjs/utils'; 2 | 3 | /** 4 | * Generate an absolute url from options passed into `new http.ClientRequest`. 5 | * 6 | * @export 7 | * @param {Object} [options] 8 | * @returns {string} 9 | */ 10 | export default function getUrlFromOptions(options = {}) { 11 | if (options.href) { 12 | return options.href; 13 | } 14 | 15 | const protocol = options.protocol || `${options.proto}:` || 'http:'; 16 | const host = options.hostname || options.host || 'localhost'; 17 | const { path, port } = options; 18 | const url = new URL(); 19 | 20 | url.set('protocol', protocol); 21 | url.set('host', host); 22 | url.set('pathname', path); 23 | 24 | if ( 25 | port && 26 | !host.includes(':') && 27 | (port !== 80 || protocol !== 'http:') && 28 | (port !== 443 || protocol !== 'https:') 29 | ) { 30 | url.set('port', port); 31 | } 32 | 33 | return url.href; 34 | } 35 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-node-http/src/utils/merge-chunks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Merge an array of strings into a single string or concat an array 3 | * of buffers into a single buffer. 4 | * 5 | * @export 6 | * @param {string[] | Buffer[]} [chunks] 7 | * @returns {string | Buffer} 8 | */ 9 | export default function mergeChunks(chunks) { 10 | if (!chunks || chunks.length === 0) { 11 | return Buffer.alloc(0); 12 | } 13 | 14 | // We assume that all chunks are Buffer objects if the first is buffer object. 15 | if (!Buffer.isBuffer(chunks[0])) { 16 | // When the chunks are not buffers we assume that they are strings. 17 | return chunks.join(''); 18 | } 19 | 20 | // Merge all the buffers into a single Buffer object. 21 | return Buffer.concat(chunks); 22 | } 23 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-node-http/src/utils/url-to-options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility function that converts a URL object into an ordinary 3 | * options object as expected by the http.request and https.request APIs. 4 | * 5 | * This was copied from Node's source 6 | * https://github.com/nodejs/node/blob/908292cf1f551c614a733d858528ffb13fb3a524/lib/internal/url.js#L1257 7 | */ 8 | export default function urlToOptions(url) { 9 | const options = { 10 | protocol: url.protocol, 11 | hostname: 12 | typeof url.hostname === 'string' && url.hostname.startsWith('[') 13 | ? url.hostname.slice(1, -1) 14 | : url.hostname, 15 | hash: url.hash, 16 | search: url.search, 17 | pathname: url.pathname, 18 | path: `${url.pathname}${url.search || ''}`, 19 | href: url.href 20 | }; 21 | 22 | if (url.port !== '') { 23 | options.port = Number(url.port); 24 | } 25 | 26 | if (url.username || url.password) { 27 | options.auth = `${url.username}:${url.password}`; 28 | } 29 | 30 | return options; 31 | } 32 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-node-http/tests/integration/adapter-node-fetch-test.js: -------------------------------------------------------------------------------- 1 | import '@pollyjs-tests/helpers/global-node-fetch'; 2 | 3 | import setupFetchRecord from '@pollyjs-tests/helpers/setup-fetch-record'; 4 | import adapterTests from '@pollyjs-tests/integration/adapter-tests'; 5 | import adapterNodeTests from '@pollyjs-tests/integration/adapter-node-tests'; 6 | import { setupMocha as setupPolly } from '@pollyjs/core'; 7 | 8 | import pollyConfig from '../utils/polly-config'; 9 | 10 | describe('Integration | Node Http Adapter | node-fetch', function () { 11 | setupPolly.beforeEach(pollyConfig); 12 | 13 | setupFetchRecord({ host: 'http://localhost:4000' }); 14 | setupPolly.afterEach(); 15 | 16 | adapterTests(); 17 | adapterNodeTests(); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-node-http/tests/integration/persister-fs-test.js: -------------------------------------------------------------------------------- 1 | import http from 'http'; 2 | 3 | import setupPersister from '@pollyjs-tests/helpers/setup-persister'; 4 | import setupFetchRecord from '@pollyjs-tests/helpers/setup-fetch-record'; 5 | import persisterTests from '@pollyjs-tests/integration/persister-tests'; 6 | import { setupMocha as setupPolly } from '@pollyjs/core'; 7 | import FSPersister from '@pollyjs/persister-fs'; 8 | 9 | import nativeRequest from '../utils/native-request'; 10 | import pollyConfig from '../utils/polly-config'; 11 | 12 | describe('Integration | FS Persister', function () { 13 | setupPolly.beforeEach({ 14 | ...pollyConfig, 15 | persister: FSPersister, 16 | persisterOptions: { 17 | fs: { recordingsDir: 'tests/recordings' } 18 | } 19 | }); 20 | 21 | setupFetchRecord({ 22 | host: 'http://localhost:4000', 23 | fetch: nativeRequest.bind(undefined, http) 24 | }); 25 | 26 | setupPersister(); 27 | setupPolly.afterEach(); 28 | 29 | persisterTests(); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-node-http/tests/jest/integration/fetch-test.js: -------------------------------------------------------------------------------- 1 | import '@pollyjs-tests/helpers/global-node-fetch'; 2 | 3 | import { Polly } from '@pollyjs/core'; 4 | 5 | import pollyConfig from '../../utils/polly-config'; 6 | 7 | describe('Integration | Jest | Fetch', function () { 8 | let polly; 9 | 10 | beforeEach(() => { 11 | polly = new Polly('Integration | Jest | Fetch', pollyConfig); 12 | }); 13 | 14 | afterEach(async () => await polly.stop()); 15 | 16 | test('it works', async () => { 17 | polly.recordingName += '/it works'; 18 | 19 | const { persister, recordingId } = polly; 20 | 21 | expect((await fetch('http://localhost:4000/api/db/foo')).status).toBe(404); 22 | await persister.persist(); 23 | 24 | const har = await persister.findRecording(recordingId); 25 | 26 | expect(har).toBeDefined(); 27 | expect(har.log.entries.length).toBe(1); 28 | expect(har.log.entries[0].request.url.includes('/api/db/foo')).toBe(true); 29 | expect(har.log.entries[0].response.status).toBe(404); 30 | 31 | await persister.deleteRecording(recordingId); 32 | expect(persister.findRecording(recordingId)).resolves.toBeNull(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-node-http/tests/jest/integration/xhr-test.js: -------------------------------------------------------------------------------- 1 | import { Polly } from '@pollyjs/core'; 2 | 3 | import pollyConfig from '../../utils/polly-config'; 4 | 5 | function request(url) { 6 | return new Promise((resolve, reject) => { 7 | const xhr = new XMLHttpRequest(); 8 | 9 | xhr.addEventListener('load', function () { 10 | resolve(xhr); 11 | }); 12 | 13 | xhr.addEventListener('error', reject); 14 | xhr.open('GET', url, true); 15 | xhr.send(); 16 | }); 17 | } 18 | 19 | describe('Integration | Jest | XHR', function () { 20 | let polly; 21 | 22 | beforeEach(() => { 23 | polly = new Polly('Integration | Jest | XHR', pollyConfig); 24 | }); 25 | 26 | afterEach(async () => await polly.stop()); 27 | 28 | test('it works', async () => { 29 | polly.recordingName += '/it works'; 30 | 31 | const { persister, recordingId } = polly; 32 | 33 | expect((await request('/api/db/foo')).status).toBe(404); 34 | await persister.persist(); 35 | 36 | const har = await persister.findRecording(recordingId); 37 | 38 | expect(har).toBeDefined(); 39 | expect(har.log.entries.length).toBe(1); 40 | expect(har.log.entries[0].request.url.includes('/api/db/foo')).toBe(true); 41 | expect(har.log.entries[0].response.status).toBe(404); 42 | 43 | await persister.deleteRecording(recordingId); 44 | expect(persister.findRecording(recordingId)).resolves.toBeNull(); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-node-http/tests/unit/utils/merge-chunks-test.js: -------------------------------------------------------------------------------- 1 | import mergeChunks from '../../../src/utils/merge-chunks'; 2 | 3 | describe('Unit | Utils | mergeChunks', function () { 4 | it('should exist', function () { 5 | expect(mergeChunks).to.be.a('function'); 6 | }); 7 | 8 | it('should work', function () { 9 | [null, []].forEach((chunks) => { 10 | const buffer = mergeChunks(chunks); 11 | 12 | expect(Buffer.isBuffer(buffer)).to.be.true; 13 | expect(buffer.toString()).to.have.lengthOf(0); 14 | }); 15 | 16 | const str = mergeChunks(['T', 'e', 's', 't']); 17 | 18 | expect(Buffer.isBuffer(str)).to.be.false; 19 | expect(str).to.equal('Test'); 20 | 21 | const buffer = mergeChunks(['T', 'e', 's', 't'].map((c) => Buffer.from(c))); 22 | 23 | expect(Buffer.isBuffer(buffer)).to.be.true; 24 | expect(buffer.toString()).to.equal('Test'); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-node-http/tests/utils/get-buffer-from-stream.js: -------------------------------------------------------------------------------- 1 | export default function getBufferFromStream(stream) { 2 | return new Promise((resolve) => { 3 | const chunks = []; 4 | 5 | stream.on('data', (chunk) => { 6 | chunks.push(chunk); 7 | }); 8 | 9 | stream.on('end', () => { 10 | resolve(Buffer.concat(chunks)); 11 | }); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-node-http/tests/utils/get-response-from-request.js: -------------------------------------------------------------------------------- 1 | export default function getResponseFromRequest(req, data) { 2 | return new Promise((resolve, reject) => { 3 | req.once('response', resolve); 4 | req.once('error', reject); 5 | req.once('abort', reject); 6 | 7 | req.end(data); 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-node-http/tests/utils/native-request.js: -------------------------------------------------------------------------------- 1 | import Url from 'url'; 2 | 3 | import { Response } from 'node-fetch'; 4 | 5 | import getResponseFromRequest from './get-response-from-request'; 6 | 7 | export default async function nativeRequest(transport, url, options) { 8 | const opts = { 9 | ...(options || {}), 10 | ...Url.parse(url) 11 | }; 12 | let reqBody; 13 | 14 | if (opts.body) { 15 | reqBody = opts.body; 16 | delete opts.body; 17 | } 18 | 19 | const response = await getResponseFromRequest( 20 | transport.request(opts), 21 | reqBody 22 | ); 23 | 24 | return new Response(response, { 25 | status: response.statusCode, 26 | headers: response.headers 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-node-http/tests/utils/polly-config.js: -------------------------------------------------------------------------------- 1 | import InMemoryPersister from '@pollyjs/persister-in-memory'; 2 | 3 | import NodeHttpAdapter from '../../src'; 4 | 5 | export default { 6 | recordFailedRequests: true, 7 | adapters: [NodeHttpAdapter], 8 | persister: InMemoryPersister, 9 | persisterOptions: {} 10 | }; 11 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-node-http/types.d.ts: -------------------------------------------------------------------------------- 1 | import Adapter from '@pollyjs/adapter'; 2 | 3 | export default class NodeHttpAdapter extends Adapter {} 4 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-puppeteer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pollyjs/adapter-puppeteer", 3 | "version": "6.0.6", 4 | "description": "File system persister for @pollyjs", 5 | "main": "dist/cjs/pollyjs-adapter-puppeteer.js", 6 | "module": "dist/es/pollyjs-adapter-puppeteer.js", 7 | "types": "types.d.ts", 8 | "files": [ 9 | "src", 10 | "dist", 11 | "types.d.ts" 12 | ], 13 | "repository": "https://github.com/netflix/pollyjs/tree/master/packages/@pollyjs/adapter-puppeteer", 14 | "license": "Apache-2.0", 15 | "contributors": [ 16 | { 17 | "name": "Jason Mitchell", 18 | "email": "jason.mitchell.w@gmail.com" 19 | }, 20 | { 21 | "name": "Offir Golan", 22 | "email": "offirgolan@gmail.com" 23 | } 24 | ], 25 | "keywords": [ 26 | "polly", 27 | "pollyjs", 28 | "record", 29 | "replay", 30 | "fs", 31 | "file" 32 | ], 33 | "publishConfig": { 34 | "access": "public" 35 | }, 36 | "scripts": { 37 | "build": "rollup -c ../../../scripts/rollup/default.config.js", 38 | "build:watch": "yarn build -w", 39 | "test:build": "rollup -c rollup.config.test.js", 40 | "test:build:watch": "rollup -c rollup.config.test.js -w", 41 | "watch-all": "npm-run-all --parallel build:watch test:build:watch" 42 | }, 43 | "dependencies": { 44 | "@pollyjs/adapter": "^6.0.6", 45 | "@pollyjs/utils": "^6.0.6" 46 | }, 47 | "devDependencies": { 48 | "@pollyjs/core": "^6.0.6", 49 | "@pollyjs/persister-fs": "^6.0.6", 50 | "node-fetch": "^2.6.6", 51 | "puppeteer": "1.10.0", 52 | "rollup": "^1.14.6" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-puppeteer/rollup.config.js: -------------------------------------------------------------------------------- 1 | import createNodeConfig from '../../../scripts/rollup/node.config'; 2 | 3 | export default createNodeConfig(); 4 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-puppeteer/rollup.config.test.js: -------------------------------------------------------------------------------- 1 | import createNodeTestConfig from '../../../scripts/rollup/node.test.config'; 2 | 3 | export default createNodeTestConfig({ 4 | external: ['puppeteer'] 5 | }); 6 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-puppeteer/tests/helpers/fetch.js: -------------------------------------------------------------------------------- 1 | import { Response } from 'node-fetch'; 2 | 3 | export default async function fetch() { 4 | const res = await this.page.evaluate((...args) => { 5 | // This is run within the browser's context meaning it's using the 6 | // browser's native window.fetch method. 7 | return fetch(...args).then((res) => { 8 | const { url, status, headers } = res; 9 | 10 | return res.text().then((body) => { 11 | return { url, status, body, headers }; 12 | }); 13 | }); 14 | }, ...arguments); 15 | 16 | return new Response(res.body, res); 17 | } 18 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-puppeteer/tests/unit/adapter-test.js: -------------------------------------------------------------------------------- 1 | import { setupMocha as setupPolly } from '@pollyjs/core'; 2 | import { PollyError } from '@pollyjs/utils'; 3 | 4 | import PuppeteerAdapter from '../../src'; 5 | 6 | describe('Unit | Puppeteer Adapter', function () { 7 | setupPolly(); 8 | 9 | it('should throw without a page instance', function () { 10 | expect(() => 11 | this.polly.configure({ 12 | adapters: [PuppeteerAdapter] 13 | }) 14 | ).to.throw(PollyError, /A puppeteer page instance is required/); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-puppeteer/types.d.ts: -------------------------------------------------------------------------------- 1 | import Adapter from '@pollyjs/adapter'; 2 | 3 | export default class PuppeteerAdapter extends Adapter<{ 4 | page: any; 5 | requestResourceTypes?: string[]; 6 | }> {} 7 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-xhr/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pollyjs/adapter-xhr", 3 | "version": "6.0.6", 4 | "description": "XHR adapter for @pollyjs", 5 | "main": "dist/cjs/pollyjs-adapter-xhr.js", 6 | "module": "dist/es/pollyjs-adapter-xhr.js", 7 | "browser": "dist/umd/pollyjs-adapter-xhr.js", 8 | "types": "types.d.ts", 9 | "files": [ 10 | "src", 11 | "dist", 12 | "types.d.ts" 13 | ], 14 | "repository": "https://github.com/netflix/pollyjs/tree/master/packages/@pollyjs/adapter-xhr", 15 | "license": "Apache-2.0", 16 | "contributors": [ 17 | { 18 | "name": "Jason Mitchell", 19 | "email": "jason.mitchell.w@gmail.com" 20 | }, 21 | { 22 | "name": "Offir Golan", 23 | "email": "offirgolan@gmail.com" 24 | } 25 | ], 26 | "keywords": [ 27 | "polly", 28 | "pollyjs", 29 | "record", 30 | "replay", 31 | "xhr", 32 | "adapter" 33 | ], 34 | "publishConfig": { 35 | "access": "public" 36 | }, 37 | "scripts": { 38 | "build": "rollup -c ../../../scripts/rollup/default.config.js", 39 | "build:watch": "yarn build -w", 40 | "test:build": "rollup -c rollup.config.test.js", 41 | "test:build:watch": "rollup -c rollup.config.test.js -w", 42 | "watch-all": "npm-run-all --parallel build:watch test:build:watch" 43 | }, 44 | "dependencies": { 45 | "@offirgolan/nise": "^4.1.0", 46 | "@pollyjs/adapter": "^6.0.6", 47 | "@pollyjs/utils": "^6.0.6", 48 | "to-arraybuffer": "^1.0.1" 49 | }, 50 | "devDependencies": { 51 | "@pollyjs/core": "^6.0.6", 52 | "@pollyjs/persister-rest": "^6.0.6", 53 | "rollup": "^1.14.6" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-xhr/rollup.config.test.js: -------------------------------------------------------------------------------- 1 | import createBrowserTestConfig from '../../../scripts/rollup/browser.test.config'; 2 | 3 | export default [createBrowserTestConfig()]; 4 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-xhr/src/utils/resolve-xhr.js: -------------------------------------------------------------------------------- 1 | export default function resolveXhr(xhr, body) { 2 | return new Promise((resolve) => { 3 | xhr.send(body); 4 | 5 | if (xhr.async) { 6 | const { onreadystatechange } = xhr; 7 | 8 | xhr.onreadystatechange = (...args) => { 9 | onreadystatechange && onreadystatechange.apply(xhr, ...args); 10 | xhr.readyState === XMLHttpRequest.DONE && resolve(); 11 | }; 12 | } else { 13 | resolve(); 14 | } 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-xhr/src/utils/serialize-response-headers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Serialize response headers which is received as a string, into a pojo 3 | * 4 | * @param {String} responseHeaders 5 | */ 6 | export default function serializeResponseHeaders(responseHeaders) { 7 | if (typeof responseHeaders !== 'string') { 8 | return responseHeaders; 9 | } 10 | 11 | return responseHeaders.split('\n').reduce((headers, header) => { 12 | const [key, value] = header.split(':'); 13 | 14 | if (key) { 15 | headers[key] = value.replace(/(\r|\n|^\s+)/g, ''); 16 | } 17 | 18 | return headers; 19 | }, {}); 20 | } 21 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-xhr/tests/utils/xhr-request.js: -------------------------------------------------------------------------------- 1 | import serializeResponseHeaders from '../../src/utils/serialize-response-headers'; 2 | 3 | export default function request(url, obj = {}) { 4 | return new Promise((resolve) => { 5 | const xhr = obj.xhr || new XMLHttpRequest(); 6 | 7 | xhr.open(obj.method || 'GET', url); 8 | 9 | if (obj.headers) { 10 | for (const h in obj.headers) { 11 | xhr.setRequestHeader(h, obj.headers[h]); 12 | } 13 | } 14 | 15 | if (obj.responseType) { 16 | xhr.responseType = obj.responseType; 17 | } 18 | 19 | xhr.onreadystatechange = () => 20 | xhr.readyState === XMLHttpRequest.DONE && resolve(xhr); 21 | xhr.onerror = () => resolve(xhr); 22 | 23 | xhr.send(obj.body); 24 | }).then((xhr) => { 25 | const responseBody = 26 | xhr.status === 204 && xhr.response === '' ? null : xhr.response; 27 | 28 | return new Response(responseBody, { 29 | status: xhr.status || 500, 30 | statusText: xhr.statusText, 31 | headers: serializeResponseHeaders(xhr.getAllResponseHeaders()) 32 | }); 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter-xhr/types.d.ts: -------------------------------------------------------------------------------- 1 | import Adapter from '@pollyjs/adapter'; 2 | 3 | export default class XHRAdapter extends Adapter<{ 4 | context?: any; 5 | }> {} 6 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pollyjs/adapter", 3 | "version": "6.0.6", 4 | "description": "Extendable base adapter class used by @pollyjs", 5 | "main": "dist/cjs/pollyjs-adapter.js", 6 | "module": "dist/es/pollyjs-adapter.js", 7 | "browser": "dist/umd/pollyjs-adapter.js", 8 | "types": "types.d.ts", 9 | "files": [ 10 | "src", 11 | "dist", 12 | "types.d.ts" 13 | ], 14 | "repository": "https://github.com/netflix/pollyjs/tree/master/packages/@pollyjs/adapter", 15 | "scripts": { 16 | "build": "rollup -c ../../../scripts/rollup/default.config.js", 17 | "test:build": "rollup -c rollup.config.test.js", 18 | "test:build:watch": "rollup -c rollup.config.test.js -w", 19 | "build:watch": "yarn build -w", 20 | "watch-all": "npm-run-all --parallel build:watch test:build:watch" 21 | }, 22 | "keywords": [ 23 | "polly", 24 | "pollyjs", 25 | "adapter" 26 | ], 27 | "publishConfig": { 28 | "access": "public" 29 | }, 30 | "contributors": [ 31 | { 32 | "name": "Jason Mitchell", 33 | "email": "jason.mitchell.w@gmail.com" 34 | }, 35 | { 36 | "name": "Offir Golan", 37 | "email": "offirgolan@gmail.com" 38 | } 39 | ], 40 | "license": "Apache-2.0", 41 | "dependencies": { 42 | "@pollyjs/utils": "^6.0.6" 43 | }, 44 | "devDependencies": { 45 | "rollup": "^1.14.6" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter/rollup.config.test.js: -------------------------------------------------------------------------------- 1 | import createNodeTestConfig from '../../../scripts/rollup/node.test.config'; 2 | import createBrowserTestConfig from '../../../scripts/rollup/browser.test.config'; 3 | 4 | export default [createNodeTestConfig(), createBrowserTestConfig()]; 5 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter/src/utils/dehumanize-time.js: -------------------------------------------------------------------------------- 1 | const ALPHA_NUMERIC_DOT = /([0-9.]+)([a-zA-Z]+)/g; 2 | const TIMES = { 3 | ms: 1, 4 | millisecond: 1, 5 | milliseconds: 1, 6 | s: 1000, 7 | sec: 1000, 8 | secs: 1000, 9 | second: 1000, 10 | seconds: 1000, 11 | m: 60000, 12 | min: 60000, 13 | mins: 60000, 14 | minute: 60000, 15 | minutes: 60000, 16 | h: 3600000, 17 | hr: 3600000, 18 | hrs: 3600000, 19 | hour: 3600000, 20 | hours: 3600000, 21 | d: 86400000, 22 | day: 86400000, 23 | days: 86400000, 24 | w: 604800000, 25 | wk: 604800000, 26 | wks: 604800000, 27 | week: 604800000, 28 | weeks: 604800000, 29 | y: 31536000000, 30 | yr: 31536000000, 31 | yrs: 31536000000, 32 | year: 31536000000, 33 | years: 31536000000 34 | }; 35 | 36 | export default function dehumanizeTime(input) { 37 | if (typeof input !== 'string') { 38 | return NaN; 39 | } 40 | 41 | const parts = input.replace(/ /g, '').match(ALPHA_NUMERIC_DOT); 42 | const sets = parts.map((part) => 43 | part.split(ALPHA_NUMERIC_DOT).filter((o) => o) 44 | ); 45 | 46 | return sets.reduce((accum, [number, unit]) => { 47 | return accum + parseFloat(number) * TIMES[unit]; 48 | }, 0); 49 | } 50 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter/src/utils/is-expired.js: -------------------------------------------------------------------------------- 1 | import dehumanizeTime from './dehumanize-time'; 2 | 3 | export default function isExpired(recordedOn, expiresIn) { 4 | if (recordedOn && expiresIn) { 5 | return ( 6 | new Date() > 7 | new Date(new Date(recordedOn).getTime() + dehumanizeTime(expiresIn)) 8 | ); 9 | } 10 | 11 | return false; 12 | } 13 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter/src/utils/normalize-recorded-response.js: -------------------------------------------------------------------------------- 1 | const { isArray } = Array; 2 | 3 | export default function normalizeRecordedResponse(response) { 4 | const { status, statusText, headers, content } = response; 5 | 6 | return { 7 | statusText, 8 | statusCode: status, 9 | headers: normalizeHeaders(headers), 10 | body: content && content.text, 11 | encoding: content && content.encoding 12 | }; 13 | } 14 | 15 | function normalizeHeaders(headers) { 16 | return (headers || []).reduce((accum, { name, value, _fromType }) => { 17 | const existingValue = accum[name]; 18 | 19 | if (existingValue) { 20 | if (!isArray(existingValue)) { 21 | accum[name] = [existingValue]; 22 | } 23 | 24 | accum[name].push(value); 25 | } else { 26 | accum[name] = _fromType === 'array' ? [value] : value; 27 | } 28 | 29 | return accum; 30 | }, {}); 31 | } 32 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter/src/utils/stringify-request.js: -------------------------------------------------------------------------------- 1 | export default function stringifyRequest(req, ...args) { 2 | const config = { ...req.config }; 3 | 4 | // Remove all adapter & persister config options as they can cause a circular 5 | // structure to the final JSON 6 | ['adapter', 'adapterOptions', 'persister', 'persisterOptions'].forEach( 7 | (k) => delete config[k] 8 | ); 9 | 10 | return JSON.stringify( 11 | { 12 | url: req.url, 13 | method: req.method, 14 | headers: req.headers, 15 | body: req.body, 16 | recordingName: req.recordingName, 17 | id: req.id, 18 | order: req.order, 19 | identifiers: req.identifiers, 20 | config 21 | }, 22 | ...args 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter/tests/unit/adapter-test.js: -------------------------------------------------------------------------------- 1 | import Adapter from '../../src'; 2 | 3 | describe('Unit | Adapter', function () { 4 | it('should exist', function () { 5 | expect(Adapter).to.be.a('function'); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter/tests/unit/utils/dehumanize-time-test.js: -------------------------------------------------------------------------------- 1 | import dehumanizeTime from '../../../src/utils/dehumanize-time'; 2 | 3 | describe('Unit | Utils | dehumanizeTime', function () { 4 | it('should exist', function () { 5 | expect(dehumanizeTime).to.be.a('function'); 6 | }); 7 | 8 | it('should work', function () { 9 | expect(dehumanizeTime(null)).to.be.NaN; 10 | expect(dehumanizeTime(undefined)).to.be.NaN; 11 | expect(dehumanizeTime(true)).to.be.NaN; 12 | expect(dehumanizeTime(false)).to.be.NaN; 13 | 14 | [ 15 | [['1ms', '1millisecond', '1 milliseconds'], 1], 16 | [['10ms', '10millisecond', '10 milliseconds'], 10], 17 | [['100ms', '100millisecond', '100 milliseconds'], 100], 18 | [['1s', '1sec', '1secs', '1 second', '1 seconds'], 1000], 19 | [['1.5s', '1.5sec', '1.5secs', '1.5 second', '1.5 seconds'], 1500], 20 | [['1m', '1min', '1mins', '1 minute', '1 minutes'], 1000 * 60], 21 | [['1h', '1hr', '1hrs', '1 hour', '1 hours'], 1000 * 60 * 60], 22 | [['1d', '1day', '1 days'], 1000 * 60 * 60 * 24], 23 | [['1w', '1wk', '1wks', '1 week', '1 weeks'], 1000 * 60 * 60 * 24 * 7], 24 | [['1y', '1yr', '1 yrs', '1 year', '1 years'], 1000 * 60 * 60 * 24 * 365], 25 | [ 26 | [ 27 | '1y 2 wks 3day 4 minutes 5secs 6 ms', 28 | '6 millisecond 5s 4 min 3d 2 weeks 1yrs' 29 | ], 30 | 33005045006 31 | ] 32 | ].forEach(([inputs, value]) => { 33 | inputs.forEach((str) => expect(dehumanizeTime(str)).to.equal(value)); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter/tests/unit/utils/is-expired-test.js: -------------------------------------------------------------------------------- 1 | import isExpired from '../../../src/utils/is-expired'; 2 | 3 | describe('Unit | Utils | isExpired', function () { 4 | it('should exist', function () { 5 | expect(isExpired).to.be.a('function'); 6 | }); 7 | 8 | it('should work', function () { 9 | [ 10 | [undefined, undefined, false], 11 | [null, null, false], 12 | [new Date(), undefined, false], 13 | [undefined, '1 day', false], 14 | [new Date(), '1 day', false], 15 | [new Date('1/1/2018'), '100 years', false], 16 | [new Date('1/1/2017'), '1y', true], 17 | [new Date('1/1/2018'), '1m5d10h', true] 18 | ].forEach(([recordedOn, expiresIn, value]) => { 19 | expect(isExpired(recordedOn, expiresIn)).to.equal(value); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/@pollyjs/adapter/types.d.ts: -------------------------------------------------------------------------------- 1 | import { Polly, Request, Interceptor, Response } from '@pollyjs/core'; 2 | 3 | export default class Adapter< 4 | TOptions extends {} = {}, 5 | TRequest extends Request = Request 6 | > { 7 | static readonly id: string; 8 | static readonly type: string; 9 | constructor(polly: Polly); 10 | polly: Polly; 11 | isConnected: boolean; 12 | readonly defaultOptions: TOptions; 13 | readonly options: TOptions; 14 | persister: Polly['persister']; 15 | connect(): void; 16 | onConnect(): void; 17 | disconnect(): void; 18 | onDisconnect(): void; 19 | private timeout(request: TRequest, options: { time: number }): Promise; 20 | handleRequest( 21 | request: Pick< 22 | TRequest, 23 | 'url' | 'method' | 'headers' | 'body' | 'requestArguments' 24 | > 25 | ): Promise; 26 | private passthrough(request: TRequest): Promise; 27 | onPassthrough(request: TRequest): Promise; 28 | private intercept(request: TRequest, interceptor: Interceptor): Promise; 29 | onIntercept(request: TRequest, interceptor: Interceptor): Promise; 30 | private record(request: TRequest): Promise; 31 | onRecord(request: TRequest): Promise; 32 | private replay(request: TRequest): Promise; 33 | onReplay(request: TRequest): Promise; 34 | assert(message: string, condition?: boolean): void; 35 | onFetchResponse( 36 | pollyRequest: TRequest 37 | ): Promise>; 38 | onRespond(request: TRequest, error?: Error): Promise; 39 | onIdentifyRequest(request: TRequest): Promise; 40 | onRequest(request: TRequest): Promise; 41 | onRequestFinished(request: TRequest): Promise; 42 | onRequestFailed(request: TRequest): Promise; 43 | } 44 | -------------------------------------------------------------------------------- /packages/@pollyjs/cli/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | sourceType: 'script', 4 | ecmaVersion: 2020 5 | }, 6 | env: { 7 | browser: false, 8 | node: true 9 | }, 10 | plugins: ['node'], 11 | extends: ['plugin:node/recommended'] 12 | }; 13 | -------------------------------------------------------------------------------- /packages/@pollyjs/cli/bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // Provide a title to the process in `ps` 4 | process.title = 'polly'; 5 | 6 | const Polly = require('@pollyjs/node-server'); 7 | const { program } = require('commander'); 8 | 9 | const version = require('../package.json').version; 10 | 11 | program.name('polly').version(version, '-v, --version'); 12 | 13 | program 14 | .command('listen') 15 | .alias('l') 16 | .description('start the server and listen for requests') 17 | .option('-H, --host ', 'host') 18 | .option('-p, --port ', 'port number', Polly.Defaults.port) 19 | .option( 20 | '-n, --api-namespace ', 21 | 'api namespace', 22 | Polly.Defaults.apiNamespace 23 | ) 24 | .option( 25 | '-d, --recordings-dir ', 26 | 'recordings directory', 27 | Polly.Defaults.recordingsDir 28 | ) 29 | .option( 30 | '-s, --recording-size-limit ', 31 | 'recording size limit', 32 | Polly.Defaults.recordingSizeLimit 33 | ) 34 | .option('-q, --quiet', 'disable logging') 35 | .action(function (options) { 36 | new Polly.Server(options).listen(); 37 | }); 38 | 39 | program.parse(process.argv); 40 | 41 | // if cli was called with no arguments, show help. 42 | if (program.args.length === 0) { 43 | program.help(); 44 | } 45 | -------------------------------------------------------------------------------- /packages/@pollyjs/cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pollyjs/cli", 3 | "version": "6.0.6", 4 | "description": "@pollyjs CLI", 5 | "files": [ 6 | "bin" 7 | ], 8 | "repository": "https://github.com/netflix/pollyjs/tree/master/packages/@pollyjs/cli", 9 | "keywords": [ 10 | "polly", 11 | "pollyjs", 12 | "cli", 13 | "server", 14 | "listen" 15 | ], 16 | "publishConfig": { 17 | "access": "public" 18 | }, 19 | "contributors": [ 20 | { 21 | "name": "Jason Mitchell", 22 | "email": "jason.mitchell.w@gmail.com" 23 | }, 24 | { 25 | "name": "Offir Golan", 26 | "email": "offirgolan@gmail.com" 27 | } 28 | ], 29 | "license": "Apache-2.0", 30 | "dependencies": { 31 | "@pollyjs/node-server": "^6.0.6", 32 | "commander": "^8.3.0" 33 | }, 34 | "devDependencies": { 35 | "npm-run-all": "^4.1.5", 36 | "rimraf": "^3.0.2", 37 | "rollup": "^1.14.6" 38 | }, 39 | "bin": { 40 | "polly": "./bin/cli.js" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pollyjs/core", 3 | "version": "6.0.6", 4 | "description": "Record, replay, and stub HTTP Interactions", 5 | "main": "dist/cjs/pollyjs-core.js", 6 | "module": "dist/es/pollyjs-core.js", 7 | "browser": "dist/umd/pollyjs-core.js", 8 | "types": "types.d.ts", 9 | "files": [ 10 | "src", 11 | "dist", 12 | "types.d.ts" 13 | ], 14 | "repository": "https://github.com/netflix/pollyjs/tree/master/packages/@pollyjs/core", 15 | "scripts": { 16 | "build": "rollup -c ../../../scripts/rollup/default.config.js", 17 | "test:build": "rollup -c rollup.config.test.js", 18 | "test:build:watch": "rollup -c rollup.config.test.js -w", 19 | "build:watch": "yarn build -w", 20 | "watch-all": "npm-run-all --parallel build:watch test:build:watch" 21 | }, 22 | "keywords": [ 23 | "polly", 24 | "pollyjs", 25 | "vcr", 26 | "record", 27 | "replay", 28 | "recorder", 29 | "test", 30 | "mock" 31 | ], 32 | "publishConfig": { 33 | "access": "public" 34 | }, 35 | "contributors": [ 36 | { 37 | "name": "Jason Mitchell", 38 | "email": "jason.mitchell.w@gmail.com" 39 | }, 40 | { 41 | "name": "Offir Golan", 42 | "email": "offirgolan@gmail.com" 43 | } 44 | ], 45 | "license": "Apache-2.0", 46 | "dependencies": { 47 | "@pollyjs/utils": "^6.0.6", 48 | "@sindresorhus/fnv1a": "^2.0.1", 49 | "blueimp-md5": "^2.19.0", 50 | "fast-json-stable-stringify": "^2.1.0", 51 | "is-absolute-url": "^3.0.3", 52 | "lodash-es": "^4.17.21", 53 | "loglevel": "^1.8.0", 54 | "route-recognizer": "^0.3.4", 55 | "slugify": "^1.6.3" 56 | }, 57 | "devDependencies": { 58 | "@pollyjs/adapter": "^6.0.6", 59 | "@pollyjs/persister": "^6.0.6", 60 | "rollup": "^1.14.6" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/rollup.config.test.js: -------------------------------------------------------------------------------- 1 | import createNodeTestConfig from '../../../scripts/rollup/node.test.config'; 2 | import createBrowserTestConfig from '../../../scripts/rollup/browser.test.config'; 3 | 4 | export default [createNodeTestConfig(), createBrowserTestConfig()]; 5 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/src/-private/event.js: -------------------------------------------------------------------------------- 1 | import { assert } from '@pollyjs/utils'; 2 | 3 | const STOP_PROPAGATION = Symbol(); 4 | 5 | export default class Event { 6 | constructor(type, props) { 7 | assert( 8 | `Invalid type provided. Expected a non-empty string, received: "${typeof type}".`, 9 | type && typeof type === 'string' 10 | ); 11 | 12 | Object.defineProperty(this, 'type', { value: type }); 13 | // eslint-disable-next-line no-restricted-properties 14 | Object.assign(this, props || {}); 15 | 16 | this[STOP_PROPAGATION] = false; 17 | } 18 | 19 | stopPropagation() { 20 | this[STOP_PROPAGATION] = true; 21 | } 22 | 23 | get shouldStopPropagating() { 24 | return this[STOP_PROPAGATION]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/src/-private/interceptor.js: -------------------------------------------------------------------------------- 1 | import Event from './event'; 2 | 3 | const ABORT = Symbol(); 4 | const PASSTHROUGH = Symbol(); 5 | 6 | function setDefaults(interceptor) { 7 | interceptor[ABORT] = false; 8 | interceptor[PASSTHROUGH] = false; 9 | } 10 | 11 | export default class Interceptor extends Event { 12 | constructor() { 13 | super('intercept'); 14 | setDefaults(this); 15 | } 16 | 17 | abort() { 18 | setDefaults(this); 19 | this[ABORT] = true; 20 | } 21 | 22 | passthrough() { 23 | setDefaults(this); 24 | this[PASSTHROUGH] = true; 25 | } 26 | 27 | get shouldAbort() { 28 | return this[ABORT]; 29 | } 30 | 31 | get shouldPassthrough() { 32 | return this[PASSTHROUGH]; 33 | } 34 | 35 | get shouldIntercept() { 36 | return !this.shouldAbort && !this.shouldPassthrough; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/src/-private/response.js: -------------------------------------------------------------------------------- 1 | import { assert, HTTP_STATUS_CODES } from '@pollyjs/utils'; 2 | 3 | import HTTPBase from './http-base'; 4 | 5 | const DEFAULT_STATUS_CODE = 200; 6 | 7 | export default class PollyResponse extends HTTPBase { 8 | constructor(statusCode, headers, body, encoding) { 9 | super(); 10 | this.status(statusCode || DEFAULT_STATUS_CODE); 11 | this.setHeaders(headers); 12 | this.body = body; 13 | this.encoding = encoding; 14 | } 15 | 16 | get ok() { 17 | return this.statusCode && this.statusCode >= 200 && this.statusCode < 300; 18 | } 19 | 20 | get statusText() { 21 | return ( 22 | HTTP_STATUS_CODES[this.statusCode] || 23 | HTTP_STATUS_CODES[DEFAULT_STATUS_CODE] 24 | ); 25 | } 26 | 27 | status(statusCode) { 28 | const status = parseInt(statusCode, 10); 29 | 30 | assert( 31 | `[Response] Invalid status code: ${status}`, 32 | status >= 100 && status < 600 33 | ); 34 | 35 | this.statusCode = status; 36 | 37 | return this; 38 | } 39 | 40 | sendStatus(status) { 41 | this.status(status); 42 | this.type('text/plain'); 43 | 44 | return this.send(this.statusText); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/src/defaults/config.js: -------------------------------------------------------------------------------- 1 | import { MODES, EXPIRY_STRATEGIES } from '@pollyjs/utils'; 2 | import logLevel from 'loglevel'; 3 | 4 | import Timing from '../utils/timing'; 5 | 6 | export default { 7 | mode: MODES.REPLAY, 8 | 9 | adapters: [], 10 | adapterOptions: {}, 11 | 12 | persister: null, 13 | persisterOptions: { 14 | keepUnusedRequests: false, 15 | disableSortingHarEntries: false 16 | }, 17 | 18 | logLevel: logLevel.levels.WARN, 19 | flushRequestsOnStop: false, 20 | 21 | recordIfMissing: true, 22 | recordFailedRequests: false, 23 | 24 | expiresIn: null, 25 | expiryStrategy: EXPIRY_STRATEGIES.WARN, 26 | timing: Timing.fixed(0), 27 | 28 | matchRequestsBy: { 29 | method: true, 30 | headers: true, 31 | body: true, 32 | order: true, 33 | 34 | url: { 35 | protocol: true, 36 | username: true, 37 | password: true, 38 | hostname: true, 39 | port: true, 40 | pathname: true, 41 | query: true, 42 | hash: false 43 | } 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/src/index.js: -------------------------------------------------------------------------------- 1 | export { default as Polly } from './polly'; 2 | export { default as Timing } from './utils/timing'; 3 | 4 | export { default as setupQunit } from './test-helpers/qunit'; 5 | export { default as setupMocha } from './test-helpers/mocha'; 6 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/src/server/middleware.js: -------------------------------------------------------------------------------- 1 | import RouteRecognizer from 'route-recognizer'; 2 | 3 | import Route from './route'; 4 | 5 | const GLOBAL = '__GLOBAL__'; 6 | 7 | export default class Middleware { 8 | constructor({ host, paths, global, handler }) { 9 | this.global = Boolean(global); 10 | this.handler = handler; 11 | this.host = host; 12 | this.paths = this.global ? [GLOBAL] : paths; 13 | this._routeRecognizer = new RouteRecognizer(); 14 | 15 | this.paths.forEach((path) => 16 | this._routeRecognizer.add([{ path, handler: [handler] }]) 17 | ); 18 | } 19 | 20 | match(host, path) { 21 | if (this.global) { 22 | return new Route(this._routeRecognizer.recognize(GLOBAL)); 23 | } 24 | 25 | if (this.host === host) { 26 | const recognizeResult = this._routeRecognizer.recognize(path); 27 | 28 | return recognizeResult && new Route(recognizeResult); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/src/test-helpers/lib.js: -------------------------------------------------------------------------------- 1 | import { PollyError } from '@pollyjs/utils'; 2 | 3 | import Polly from '../polly'; 4 | 5 | const { defineProperty } = Object; 6 | 7 | export function beforeEach(context, recordingName, defaults) { 8 | defineProperty(context, 'polly', { 9 | writable: true, 10 | enumerable: true, 11 | configurable: true, 12 | value: new Polly(recordingName, defaults) 13 | }); 14 | } 15 | 16 | export async function afterEach(context, framework) { 17 | await context.polly.stop(); 18 | 19 | defineProperty(context, 'polly', { 20 | enumerable: true, 21 | configurable: true, 22 | get() { 23 | throw new PollyError( 24 | `You are trying to access an instance of Polly that is no longer available.\n` + 25 | `See: https://netflix.github.io/pollyjs/#/test-frameworks/${framework}?id=test-hook-ordering` 26 | ); 27 | } 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/src/test-helpers/mocha.js: -------------------------------------------------------------------------------- 1 | import { afterEach, beforeEach } from './lib'; 2 | 3 | function generateRecordingName(context) { 4 | const { currentTest } = context; 5 | const parts = [currentTest.title]; 6 | let parent = currentTest.parent; 7 | 8 | while (parent && parent.title) { 9 | parts.push(parent.title); 10 | parent = parent.parent; 11 | } 12 | 13 | return parts.reverse().join('/'); 14 | } 15 | 16 | export default function setupMocha(defaults = {}, ctx = global) { 17 | setupMocha.beforeEach(defaults, ctx); 18 | setupMocha.afterEach(ctx); 19 | } 20 | 21 | setupMocha.beforeEach = function setupMochaBeforeEach(defaults, ctx = global) { 22 | ctx.beforeEach(function () { 23 | return beforeEach(this, generateRecordingName(this), defaults); 24 | }); 25 | }; 26 | 27 | setupMocha.afterEach = function setupMochaAfterEach(ctx = global) { 28 | ctx.afterEach(function () { 29 | return afterEach(this, 'mocha'); 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/src/test-helpers/qunit.js: -------------------------------------------------------------------------------- 1 | import { afterEach, beforeEach } from './lib'; 2 | 3 | function generateRecordingName(assert) { 4 | return assert.test.testReport.fullName.join('/'); 5 | } 6 | 7 | export default function setupQunit(hooks, defaults = {}) { 8 | setupQunit.beforeEach(hooks, defaults); 9 | setupQunit.afterEach(hooks); 10 | } 11 | 12 | setupQunit.beforeEach = function setupQunitBeforeEach(hooks, defaults = {}) { 13 | hooks.beforeEach(function () { 14 | return beforeEach(this, generateRecordingName(...arguments), defaults); 15 | }); 16 | }; 17 | 18 | setupQunit.afterEach = function setupQunitAfterEach(hooks) { 19 | hooks.afterEach(function () { 20 | return afterEach(this, 'qunit'); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/src/utils/cancel-fn-after-n-times.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Create a function that will execute the given fn and call the cancel 3 | * callback after being called n times. 4 | * 5 | * @export 6 | * @param {Function} fn 7 | * @param {Number} nTimes 8 | * @param {Function} cancel 9 | * @returns 10 | */ 11 | export default function cancelFnAfterNTimes(fn, nTimes, cancel) { 12 | let callCount = 0; 13 | 14 | return function (...args) { 15 | if (++callCount >= nTimes) { 16 | cancel(); 17 | } 18 | 19 | return fn(...args); 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/src/utils/deferred-promise.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Create a deferred promise with `resolve` and `reject` methods. 3 | */ 4 | export default function defer() { 5 | let _resolve; 6 | let _reject; 7 | 8 | const promise = new Promise((resolve, reject) => { 9 | _resolve = resolve; 10 | _reject = reject; 11 | }); 12 | 13 | // Prevent unhandled rejection warnings 14 | promise.catch(() => {}); 15 | 16 | promise.resolve = _resolve; 17 | promise.reject = _reject; 18 | 19 | return promise; 20 | } 21 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/src/utils/guid-for-recording.js: -------------------------------------------------------------------------------- 1 | import fnv1a from '@sindresorhus/fnv1a'; 2 | import slugify from 'slugify'; 3 | 4 | function sanitize(str) { 5 | // Strip non-alphanumeric chars (\W is the equivalent of [^0-9a-zA-Z_]) 6 | return str.replace(/\W/g, '-'); 7 | } 8 | 9 | function guidFor(str) { 10 | const hash = fnv1a(str).toString(); 11 | let slug = slugify(sanitize(str)); 12 | 13 | // Max the slug at 100 char 14 | slug = slug.substring(0, 100 - hash.length - 1); 15 | 16 | return `${slug}_${hash}`; 17 | } 18 | 19 | export default function guidForRecording(recording) { 20 | return (recording || '').split('/').map(guidFor).join('/'); 21 | } 22 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/src/utils/http-headers.js: -------------------------------------------------------------------------------- 1 | import isObjectLike from 'lodash-es/isObjectLike'; 2 | 3 | const { keys } = Object; 4 | 5 | const HANDLER = { 6 | get(obj, prop) { 7 | // `prop` can be a Symbol so only lower-case string based props. 8 | return obj[typeof prop === 'string' ? prop.toLowerCase() : prop]; 9 | }, 10 | 11 | set(obj, prop, value) { 12 | if (typeof prop !== 'string') { 13 | return false; 14 | } 15 | 16 | if (value === null || typeof value === 'undefined') { 17 | delete obj[prop.toLowerCase()]; 18 | } else { 19 | obj[prop.toLowerCase()] = value; 20 | } 21 | 22 | return true; 23 | }, 24 | 25 | deleteProperty(obj, prop) { 26 | if (typeof prop !== 'string') { 27 | return false; 28 | } 29 | 30 | delete obj[prop.toLowerCase()]; 31 | 32 | return true; 33 | } 34 | }; 35 | 36 | export default function HTTPHeaders(headers) { 37 | const proxy = new Proxy({}, HANDLER); 38 | 39 | if (isObjectLike(headers)) { 40 | keys(headers).forEach((h) => (proxy[h] = headers[h])); 41 | } 42 | 43 | return proxy; 44 | } 45 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/src/utils/merge-configs.js: -------------------------------------------------------------------------------- 1 | import mergeWith from 'lodash-es/mergeWith'; 2 | 3 | function customizer(objValue, srcValue, key) { 4 | // Arrays and `context` options should just replace the existing value 5 | // and not be deep merged. 6 | if (Array.isArray(objValue) || ['context'].includes(key)) { 7 | return srcValue; 8 | } 9 | } 10 | 11 | export default function mergeConfigs(...configs) { 12 | return mergeWith({}, ...configs, customizer); 13 | } 14 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/src/utils/parse-url.js: -------------------------------------------------------------------------------- 1 | import isAbsoluteUrl from 'is-absolute-url'; 2 | import { URL } from '@pollyjs/utils'; 3 | 4 | import removeHostFromUrl from './remove-host-from-url'; 5 | 6 | /** 7 | * Creates an exact representation of the passed url string with url-parse. 8 | * 9 | * @param {String} url 10 | * @param {...args} args Arguments to pass through to the URL constructor 11 | * @returns {URL} A url-parse URL instance 12 | */ 13 | export default function parseUrl(url, ...args) { 14 | const parsedUrl = new URL(url, ...args); 15 | 16 | if (!isAbsoluteUrl(url)) { 17 | if (url.startsWith('//')) { 18 | /* 19 | If the url is protocol-relative, strip out the protocol 20 | */ 21 | parsedUrl.set('protocol', ''); 22 | } else { 23 | /* 24 | If the url is relative, setup the parsed url to reflect just that 25 | by removing the host. By default URL sets the host via window.location if 26 | it does not exist. 27 | */ 28 | removeHostFromUrl(parsedUrl); 29 | } 30 | } 31 | 32 | return parsedUrl; 33 | } 34 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/src/utils/remove-host-from-url.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Remove the host, protocol, and slashes from a URL instance. 3 | * 4 | * @param {URL} url 5 | */ 6 | export default function removeHostFromUrl(url) { 7 | url.set('protocol', ''); 8 | url.set('host', ''); 9 | url.set('slashes', false); 10 | 11 | return url; 12 | } 13 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/src/utils/timing.js: -------------------------------------------------------------------------------- 1 | import { timeout } from '@pollyjs/utils'; 2 | 3 | export default { 4 | fixed(ms) { 5 | return () => timeout(ms); 6 | }, 7 | 8 | relative(ratio) { 9 | return (ms) => timeout(ratio * ms); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/src/utils/validators.js: -------------------------------------------------------------------------------- 1 | import isObjectLike from 'lodash-es/isObjectLike'; 2 | import { assert } from '@pollyjs/utils'; 3 | 4 | export function validateRecordingName(name) { 5 | assert( 6 | `Invalid recording name provided. Expected string, received: "${typeof name}".`, 7 | typeof name === 'string' 8 | ); 9 | 10 | assert( 11 | `Invalid recording name provided. Received An empty or blank string.`, 12 | name.trim().length > 0 13 | ); 14 | } 15 | 16 | export function validateRequestConfig(config) { 17 | assert( 18 | `Invalid config provided. Expected object, received: "${typeof config}".`, 19 | isObjectLike(config) && !Array.isArray(config) 20 | ); 21 | 22 | // The following options cannot be overridden on a per request basis 23 | [ 24 | 'mode', 25 | 'adapters', 26 | 'adapterOptions', 27 | 'persister', 28 | 'persisterOptions' 29 | ].forEach((key) => 30 | assert( 31 | `Invalid configuration option provided. The "${key}" option cannot be overridden using the server configuration API.`, 32 | !(key in config) 33 | ) 34 | ); 35 | } 36 | 37 | export function validateTimesOption(times) { 38 | assert( 39 | `Invalid number provided. Expected number, received: "${typeof times}".`, 40 | typeof times === 'number' 41 | ); 42 | 43 | assert( 44 | `Invalid number provided. The number must be greater than 0, received "${typeof times}".`, 45 | times > 0 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/tests/unit/-private/event-test.js: -------------------------------------------------------------------------------- 1 | import { PollyError } from '@pollyjs/utils'; 2 | 3 | import Event from '../../../src/-private/event'; 4 | 5 | const EVENT_TYPE = 'foo'; 6 | 7 | describe('Unit | Event', function () { 8 | it('should exist', function () { 9 | expect(Event).to.be.a('function'); 10 | }); 11 | 12 | it('should throw if no type is specified', function () { 13 | expect(() => new Event()).to.throw(PollyError, /Invalid type provided/); 14 | }); 15 | 16 | it('should have the correct defaults', function () { 17 | const event = new Event(EVENT_TYPE); 18 | 19 | expect(event.type).to.equal(EVENT_TYPE); 20 | expect(event.shouldStopPropagating).to.be.false; 21 | }); 22 | 23 | it('should not be able to edit the type', function () { 24 | const event = new Event(EVENT_TYPE); 25 | 26 | expect(event.type).to.equal(EVENT_TYPE); 27 | expect(() => (event.type = 'bar')).to.throw(Error); 28 | }); 29 | 30 | it('should be able to attach any other properties', function () { 31 | const event = new Event(EVENT_TYPE, { foo: 1, bar: 2 }); 32 | 33 | expect(event.foo).to.equal(1); 34 | expect(event.bar).to.equal(2); 35 | }); 36 | 37 | it('.stopPropagation()', function () { 38 | const event = new Event(EVENT_TYPE); 39 | 40 | expect(event.shouldStopPropagating).to.be.false; 41 | event.stopPropagation(); 42 | expect(event.shouldStopPropagating).to.be.true; 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/tests/unit/index-test.js: -------------------------------------------------------------------------------- 1 | import * as PollyExports from '../../src'; 2 | 3 | describe('Unit | Index', function () { 4 | it('should export all the necessary modules', function () { 5 | ['Polly', 'Timing', 'setupQunit', 'setupMocha'].forEach((name) => { 6 | expect(PollyExports[name]).to.be.ok; 7 | }); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/tests/unit/test-helpers/mocha-test.js: -------------------------------------------------------------------------------- 1 | import setupPolly from '../../../src/test-helpers/mocha'; 2 | 3 | class Sandbox { 4 | constructor(context) { 5 | this.beforeEachCalled = new Set(); 6 | this.afterEachCalled = new Set(); 7 | this.context = context || { currentTest: { title: 'mockname' } }; 8 | } 9 | 10 | beforeEach(fn) { 11 | this.beforeEachCalled.add(fn); 12 | fn.call(this.context, undefined); 13 | } 14 | 15 | afterEach(fn) { 16 | this.afterEachCalled.add(fn); 17 | fn.call(this.context, undefined); 18 | } 19 | } 20 | 21 | describe('Unit | Test Helpers | mocha', function () { 22 | it('should exist', function () { 23 | expect(setupPolly).to.be.a('function'); 24 | expect(setupPolly.beforeEach).to.be.a('function'); 25 | expect(setupPolly.afterEach).to.be.a('function'); 26 | }); 27 | 28 | it('should invoke beforeEach and afterEach', function () { 29 | const stub = new Sandbox(); 30 | 31 | setupPolly({}, stub); 32 | expect(stub.beforeEachCalled.size).to.equal(1); 33 | expect(stub.afterEachCalled.size).to.equal(1); 34 | }); 35 | 36 | it('should create a polly property and set recordingName', function () { 37 | const stub = new Sandbox(); 38 | 39 | setupPolly({}, stub); 40 | expect(stub.context.polly).to.be.a('object'); 41 | expect(stub.context.polly.recordingName).to.equal('mockname'); 42 | }); 43 | 44 | it('should concat title if test is deeply nested', function () { 45 | const stub = new Sandbox({ 46 | currentTest: { 47 | title: 'foo', 48 | parent: { 49 | title: 'bar', 50 | parent: { 51 | title: 'baz' 52 | } 53 | } 54 | } 55 | }); 56 | 57 | setupPolly({}, stub); 58 | expect(stub.context.polly.recordingName).to.equal('baz/bar/foo'); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/tests/unit/utils/deferred-promise-test.js: -------------------------------------------------------------------------------- 1 | import defer from '../../../src/utils/deferred-promise'; 2 | 3 | describe('Unit | Utils | DeferredPromise', function () { 4 | it('should exist', function () { 5 | expect(defer).to.be.a('function'); 6 | expect(defer().resolve).to.be.a('function'); 7 | expect(defer().reject).to.be.a('function'); 8 | }); 9 | 10 | it('should resolve when calling .resolve()', async function () { 11 | const promise = defer(); 12 | 13 | promise.resolve(42); 14 | expect(await promise).to.equal(42); 15 | }); 16 | 17 | it('should reject when calling .reject()', async function () { 18 | const promise = defer(); 19 | 20 | promise.reject(new Error('42')); 21 | 22 | try { 23 | await promise; 24 | } catch (error) { 25 | expect(error).to.be.an('error'); 26 | expect(error.message).to.equal('42'); 27 | } 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/tests/unit/utils/guid-for-recording-test.js: -------------------------------------------------------------------------------- 1 | import guidForRecording from '../../../src/utils/guid-for-recording'; 2 | 3 | describe('Unit | Utils | guidForRecording', function () { 4 | it('should exist', function () { 5 | expect(guidForRecording).to.be.a('function'); 6 | }); 7 | 8 | it('should remove illegal file system characters', function () { 9 | expect(guidForRecording(`'?<>\\:*|"`)).to.equal('_3218500777'); 10 | }); 11 | 12 | it('should create a guid for each segment of the name', function () { 13 | const name = guidForRecording(`foo!/bar%/baz..`); 14 | 15 | expect(name).to.equal('foo_2152783170/bar_567945773/baz_1682401886'); 16 | }); 17 | 18 | it('should trim name to 100 characters', function () { 19 | const name = guidForRecording(new Array(200).fill('A').join('')); 20 | 21 | expect(name.length).to.be.lte(100); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/tests/unit/utils/merge-configs-test.js: -------------------------------------------------------------------------------- 1 | import mergeConfigs from '../../../src/utils/merge-configs'; 2 | 3 | describe('Unit | Utils | mergeConfigs', function () { 4 | it('should exist', function () { 5 | expect(mergeConfigs).to.be.a('function'); 6 | }); 7 | 8 | it('should not deep merge context objects', async function () { 9 | const context = {}; 10 | const config = mergeConfigs( 11 | { fetch: {}, xhr: {} }, 12 | { fetch: { context } }, 13 | { xhr: { context } }, 14 | { fetch: {}, xhr: {} } 15 | ); 16 | 17 | expect(config.fetch.context).to.equal(context); 18 | expect(config.xhr.context).to.equal(context); 19 | }); 20 | 21 | it('should not deep merge arrays', async function () { 22 | const array = [{}]; 23 | const config = mergeConfigs({ array: [] }, { array }); 24 | 25 | expect(config.array).to.equal(array); 26 | expect(config.array[0]).to.equal(array[0]); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/tests/unit/utils/parse-url-test.js: -------------------------------------------------------------------------------- 1 | import parseUrl from '../../../src/utils/parse-url'; 2 | 3 | describe('Unit | Utils | parseUrl', function () { 4 | it('should exist', function () { 5 | expect(parseUrl).to.be.a('function'); 6 | }); 7 | 8 | it('should exactly match passed urls', function () { 9 | [ 10 | '/movies/1', 11 | 'http://netflix.com/movies/1', 12 | 'http://netflix.com/movies/1?sort=title&dir=asc' 13 | ].forEach((url) => expect(parseUrl(url).href).to.equal(url)); 14 | }); 15 | 16 | it('should passthrough arguments to url-parse', function () { 17 | // Passing true tells url-parse to transform the querystring into an object 18 | expect(parseUrl('/movies/1?sort=title&dir=asc', true).query).to.deep.equal({ 19 | sort: 'title', 20 | dir: 'asc' 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/tests/unit/utils/remove-host-from-url-test.js: -------------------------------------------------------------------------------- 1 | import { URL } from '@pollyjs/utils'; 2 | 3 | import removeHost from '../../../src/utils/remove-host-from-url'; 4 | 5 | describe('Unit | Utils | removeHostFromUrl', function () { 6 | it('should exist', function () { 7 | expect(removeHost).to.be.a('function'); 8 | }); 9 | 10 | it('should remove hostname', function () { 11 | const url = removeHost(new URL('http://foo.com/bar/baz/')); 12 | 13 | expect(url.toString()).to.equal('/bar/baz/'); 14 | }); 15 | 16 | it('should remove hostname without a tld', function () { 17 | const url = removeHost(new URL('http://foo/bar/baz/')); 18 | 19 | expect(url.toString()).to.equal('/bar/baz/'); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/@pollyjs/core/tests/unit/utils/timing-test.js: -------------------------------------------------------------------------------- 1 | import Timing from '../../../src/utils/timing'; 2 | 3 | function fixedTest(ms) { 4 | it(`should handle ${ms}ms`, async function () { 5 | // Fail the test if it exceeds ms + 10ms buffer 6 | this.timeout(ms + 50); 7 | 8 | const promise = Timing.fixed(ms)(); 9 | let resolved = false; 10 | 11 | promise.then(() => (resolved = true)); 12 | 13 | if (ms) { 14 | setTimeout(() => expect(resolved).to.be.false, ms / 2); 15 | } 16 | setTimeout(() => expect(resolved).to.be.true, ms + 1); 17 | 18 | await promise; 19 | }); 20 | } 21 | 22 | function relativeTest(ratio) { 23 | const timeout = ratio * 100; 24 | 25 | it(`should handle a ratio of ${ratio}`, async function () { 26 | // Fail the test if it exceeds timeout + 10ms buffer 27 | this.timeout(timeout + 50); 28 | 29 | const promise = Timing.relative(ratio)(100); 30 | let resolved = false; 31 | 32 | promise.then(() => (resolved = true)); 33 | 34 | if (timeout) { 35 | setTimeout(() => expect(resolved).to.be.false, timeout / 2); 36 | } 37 | setTimeout(() => expect(resolved).to.be.true, timeout + 1); 38 | 39 | await promise; 40 | }); 41 | } 42 | 43 | describe('Unit | Utils | Timing', function () { 44 | it('should exist', function () { 45 | expect(Timing).to.be.a('object'); 46 | expect(Timing.fixed).to.be.a('function'); 47 | expect(Timing.relative).to.be.a('function'); 48 | }); 49 | 50 | describe('fixed', function () { 51 | fixedTest(0); 52 | fixedTest(50); 53 | fixedTest(100); 54 | }); 55 | 56 | describe('relative', function () { 57 | relativeTest(0); 58 | relativeTest(0.5); 59 | relativeTest(1.0); 60 | relativeTest(2.0); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.hbs] 16 | insert_final_newline = false 17 | 18 | [*.{diff,md}] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .*/ 17 | .eslintcache 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /package.json.ember-try 23 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | root: true, 5 | parser: '@babel/eslint-parser', 6 | parserOptions: { 7 | ecmaVersion: 2018, 8 | sourceType: 'module', 9 | requireConfigFile: false, 10 | ecmaFeatures: { 11 | legacyDecorators: true 12 | } 13 | }, 14 | plugins: ['ember'], 15 | extends: [ 16 | 'eslint:recommended', 17 | 'plugin:ember/recommended', 18 | 'plugin:prettier/recommended' 19 | ], 20 | env: { 21 | browser: true, 22 | node: true 23 | }, 24 | rules: {}, 25 | overrides: [ 26 | // node files 27 | { 28 | files: [ 29 | './.eslintrc.js', 30 | './.prettierrc.js', 31 | './.template-lintrc.js', 32 | './ember-cli-build.js', 33 | './index.js', 34 | './testem.js', 35 | './blueprints/*/index.js', 36 | './config/**/*.js', 37 | './tests/dummy/config/**/*.js' 38 | ], 39 | parserOptions: { 40 | sourceType: 'script' 41 | }, 42 | env: { 43 | browser: false, 44 | node: true 45 | }, 46 | plugins: ['node'], 47 | extends: ['plugin:node/recommended'] 48 | }, 49 | { 50 | // Test files: 51 | files: ['tests/**/*-test.{js,ts}'], 52 | extends: ['plugin:qunit/recommended'] 53 | } 54 | ] 55 | }; 56 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist/ 5 | /tmp/ 6 | 7 | # dependencies 8 | /bower_components/ 9 | /node_modules/ 10 | 11 | # misc 12 | /.env* 13 | /.pnp* 14 | /.sass-cache 15 | /.eslintcache 16 | /connect.lock 17 | /coverage/ 18 | /libpeerconnection.log 19 | /npm-debug.log* 20 | /testem.log 21 | /yarn-error.log 22 | 23 | # ember-try 24 | /.node_modules.ember-try/ 25 | /bower.json.ember-try 26 | /package.json.ember-try 27 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/.npmignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist/ 3 | /tmp/ 4 | 5 | # dependencies 6 | /bower_components/ 7 | 8 | # misc 9 | /.bowerrc 10 | /.editorconfig 11 | /.ember-cli 12 | /.env* 13 | /.eslintcache 14 | /.eslintignore 15 | /.eslintrc.js 16 | /.git/ 17 | /.gitignore 18 | /.prettierignore 19 | /.prettierrc.js 20 | /.template-lintrc.js 21 | /.travis.yml 22 | /.watchmanconfig 23 | /bower.json 24 | /config/ember-try.js 25 | /CONTRIBUTING.md 26 | /ember-cli-build.js 27 | /testem.js 28 | /tests/ 29 | /yarn-error.log 30 | /yarn.lock 31 | .gitkeep 32 | 33 | # ember-try 34 | /.node_modules.ember-try/ 35 | /bower.json.ember-try 36 | /package.json.ember-try 37 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .eslintcache 17 | 18 | # ember-try 19 | /.node_modules.ember-try/ 20 | /bower.json.ember-try 21 | /package.json.ember-try 22 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | singleQuote: true, 5 | trailingComma: 'none' 6 | }; 7 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | }; 6 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist", "tests/recordings"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/addon/-private/preconfigure.js: -------------------------------------------------------------------------------- 1 | import { Polly } from '@pollyjs/core'; 2 | import XHRAdapter from '@pollyjs/adapter-xhr'; 3 | import FetchAdapter from '@pollyjs/adapter-fetch'; 4 | import RESTPersister from '@pollyjs/persister-rest'; 5 | import LocalStoragePersister from '@pollyjs/persister-local-storage'; 6 | 7 | Polly.register(XHRAdapter); 8 | Polly.register(FetchAdapter); 9 | Polly.register(RESTPersister); 10 | Polly.register(LocalStoragePersister); 11 | 12 | Polly.on('create', (polly) => { 13 | polly.configure({ 14 | adapters: ['xhr', 'fetch'], 15 | persister: 'rest', 16 | /** 17 | * @pollyjs/ember mounts the express middleware onto to the running 18 | * testem and ember-cli express server and a relative host (an empty `host`) 19 | * is preferred here. Can be overridden at runtime in cases where the 20 | * Polly server is externally hosted. 21 | */ 22 | persisterOptions: { rest: { host: '' } } 23 | }); 24 | }); 25 | 26 | export default Polly; 27 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/blueprints/@pollyjs/ember/files/config/polly.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | 'use strict'; 4 | 5 | module.exports = function (env) { 6 | return { 7 | enabled: env !== 'production', 8 | server: { 9 | apiNamespace: '/polly', 10 | recordingsDir: 'recordings' 11 | } 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/blueprints/@pollyjs/ember/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | description: 'Setup @pollyjs/ember', 5 | normalizeEntityName() {}, 6 | afterInstall() { 7 | return this.addPackagesToProject([ 8 | { name: '@pollyjs/adapter-fetch' }, 9 | { name: '@pollyjs/adapter-xhr' }, 10 | { name: '@pollyjs/core' }, 11 | { name: '@pollyjs/node-server' }, 12 | { name: '@pollyjs/persister-local-storage' }, 13 | { name: '@pollyjs/persister-rest' } 14 | ]); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function () { 4 | return {}; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/config/polly.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | 'use strict'; 4 | 5 | module.exports = function (env) { 6 | // See: https://netflix.github.io/pollyjs/#/frameworks/ember-cli?id=configuration 7 | return { 8 | enabled: env !== 'production', 9 | server: { 10 | apiNamespace: '/polly', 11 | recordingsDir: 'recordings' 12 | } 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); 4 | 5 | module.exports = function (defaults) { 6 | let app = new EmberAddon(defaults, { 7 | // Add options here 8 | }); 9 | 10 | /* 11 | This build file specifies the options for the dummy test app of this 12 | addon, located in `/tests/dummy` 13 | This build file does *not* influence how the addon or the app using it 14 | behave. You most likely want to be modifying `./index.js` or app's build file 15 | */ 16 | 17 | const { maybeEmbroider } = require('@embroider/test-setup'); 18 | return maybeEmbroider(app, { 19 | skipBabel: [ 20 | { 21 | package: 'qunit' 22 | } 23 | ] 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/testem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | test_page: 'tests/index.html?hidepassed', 5 | disable_watching: true, 6 | launch_in_ci: ['Chrome'], 7 | launch_in_dev: ['Chrome'], 8 | browser_start_timeout: 120, 9 | browser_args: { 10 | Chrome: { 11 | ci: [ 12 | // --no-sandbox is needed when running Chrome inside a container 13 | process.env.CI ? '--no-sandbox' : null, 14 | '--headless', 15 | '--disable-dev-shm-usage', 16 | '--disable-software-rasterizer', 17 | '--mute-audio', 18 | '--remote-debugging-port=0', 19 | '--window-size=1440,900' 20 | ].filter(Boolean) 21 | } 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/tests/dummy/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from 'ember-resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from 'dummy/config/environment'; 5 | 6 | export default class App extends Application { 7 | modulePrefix = config.modulePrefix; 8 | podModulePrefix = config.podModulePrefix; 9 | Resolver = Resolver; 10 | } 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/tests/dummy/app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/pollyjs/d031861625f58423169b396e5afc8a0110d1b232/packages/@pollyjs/ember/tests/dummy/app/components/.gitkeep -------------------------------------------------------------------------------- /packages/@pollyjs/ember/tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/pollyjs/d031861625f58423169b396e5afc8a0110d1b232/packages/@pollyjs/ember/tests/dummy/app/controllers/.gitkeep -------------------------------------------------------------------------------- /packages/@pollyjs/ember/tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/pollyjs/d031861625f58423169b396e5afc8a0110d1b232/packages/@pollyjs/ember/tests/dummy/app/helpers/.gitkeep -------------------------------------------------------------------------------- /packages/@pollyjs/ember/tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | 12 | 13 | 14 | 15 | {{content-for "head-footer"}} 16 | 17 | 18 | {{content-for "body"}} 19 | 20 | 21 | 22 | 23 | {{content-for "body-footer"}} 24 | 25 | 26 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/tests/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/pollyjs/d031861625f58423169b396e5afc8a0110d1b232/packages/@pollyjs/ember/tests/dummy/app/models/.gitkeep -------------------------------------------------------------------------------- /packages/@pollyjs/ember/tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from 'dummy/config/environment'; 3 | 4 | export default class Router extends EmberRouter { 5 | location = config.locationType; 6 | rootURL = config.rootURL; 7 | } 8 | 9 | Router.map(function () {}); 10 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/tests/dummy/app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/pollyjs/d031861625f58423169b396e5afc8a0110d1b232/packages/@pollyjs/ember/tests/dummy/app/routes/.gitkeep -------------------------------------------------------------------------------- /packages/@pollyjs/ember/tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/pollyjs/d031861625f58423169b396e5afc8a0110d1b232/packages/@pollyjs/ember/tests/dummy/app/styles/app.css -------------------------------------------------------------------------------- /packages/@pollyjs/ember/tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 | {{page-title "Dummy"}} 2 | 3 |

Welcome to Ember

4 | 5 | {{outlet}} -------------------------------------------------------------------------------- /packages/@pollyjs/ember/tests/dummy/config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "packages": [ 4 | { 5 | "name": "ember-cli", 6 | "version": "3.28.4", 7 | "blueprints": [ 8 | { 9 | "name": "addon", 10 | "outputRepo": "https://github.com/ember-cli/ember-addon-output", 11 | "codemodsSource": "ember-addon-codemods-manifest@1", 12 | "isBaseBlueprint": true, 13 | "options": [] 14 | } 15 | ] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (environment) { 4 | let ENV = { 5 | modulePrefix: 'dummy', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true 13 | }, 14 | EXTEND_PROTOTYPES: { 15 | // Prevent Ember Data from overriding Date.parse. 16 | Date: false 17 | } 18 | }, 19 | 20 | APP: { 21 | // Here you can pass flags/options to your application instance 22 | // when it is created 23 | } 24 | }; 25 | 26 | if (environment === 'development') { 27 | // ENV.APP.LOG_RESOLVER = true; 28 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 29 | // ENV.APP.LOG_TRANSITIONS = true; 30 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 31 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 32 | } 33 | 34 | if (environment === 'test') { 35 | // Testem prefers this... 36 | ENV.locationType = 'none'; 37 | 38 | // keep test console output quieter 39 | ENV.APP.LOG_ACTIVE_GENERATION = false; 40 | ENV.APP.LOG_VIEW_LOOKUPS = false; 41 | 42 | ENV.APP.rootElement = '#ember-testing'; 43 | ENV.APP.autoboot = false; 44 | } 45 | 46 | if (environment === 'production') { 47 | // here you can enable a production-specific feature 48 | } 49 | 50 | return ENV; 51 | }; 52 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/tests/dummy/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "application-template-wrapper": false, 3 | "default-async-observers": true, 4 | "jquery-integration": false, 5 | "template-only-glimmer-components": true 6 | } 7 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/tests/dummy/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions' 7 | ]; 8 | 9 | // Ember's browser support policy is changing, and IE11 support will end in 10 | // v4.0 onwards. 11 | // 12 | // See https://deprecations.emberjs.com/v3.x#toc_3-0-browser-support-policy 13 | // 14 | // If you need IE11 support on a version of Ember that still offers support 15 | // for it, uncomment the code block below. 16 | // 17 | // const isCI = Boolean(process.env.CI); 18 | // const isProduction = process.env.EMBER_ENV === 'production'; 19 | // 20 | // if (isCI || isProduction) { 21 | // browsers.push('ie 11'); 22 | // } 23 | 24 | module.exports = { 25 | browsers 26 | }; 27 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/tests/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/pollyjs/d031861625f58423169b396e5afc8a0110d1b232/packages/@pollyjs/ember/tests/helpers/.gitkeep -------------------------------------------------------------------------------- /packages/@pollyjs/ember/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy Tests 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | {{content-for "test-head"}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for "head-footer"}} 18 | {{content-for "test-head-footer"}} 19 | 20 | 21 | {{content-for "body"}} 22 | {{content-for "test-body"}} 23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {{content-for "body-footer"}} 38 | {{content-for "test-body-footer"}} 39 | 40 | 41 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/tests/integration/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/pollyjs/d031861625f58423169b396e5afc8a0110d1b232/packages/@pollyjs/ember/tests/integration/.gitkeep -------------------------------------------------------------------------------- /packages/@pollyjs/ember/tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from 'dummy/app'; 2 | import config from 'dummy/config/environment'; 3 | import * as QUnit from 'qunit'; 4 | import { setApplication } from '@ember/test-helpers'; 5 | import { setup } from 'qunit-dom'; 6 | import { start } from 'ember-qunit'; 7 | 8 | setApplication(Application.create(config.APP)); 9 | 10 | setup(QUnit.assert); 11 | 12 | start(); 13 | -------------------------------------------------------------------------------- /packages/@pollyjs/ember/vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/pollyjs/d031861625f58423169b396e5afc8a0110d1b232/packages/@pollyjs/ember/vendor/.gitkeep -------------------------------------------------------------------------------- /packages/@pollyjs/node-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pollyjs/node-server", 3 | "version": "6.0.6", 4 | "description": "Standalone node server and express integration for @pollyjs", 5 | "main": "dist/cjs/pollyjs-node-server.js", 6 | "module": "dist/es/pollyjs-node-server.js", 7 | "types": "types.d.ts", 8 | "files": [ 9 | "src", 10 | "dist", 11 | "types.d.ts" 12 | ], 13 | "repository": "https://github.com/netflix/pollyjs/tree/master/packages/@pollyjs/node-server", 14 | "license": "Apache-2.0", 15 | "contributors": [ 16 | { 17 | "name": "Jason Mitchell", 18 | "email": "jason.mitchell.w@gmail.com" 19 | }, 20 | { 21 | "name": "Offir Golan", 22 | "email": "offirgolan@gmail.com" 23 | } 24 | ], 25 | "keywords": [ 26 | "polly", 27 | "pollyjs", 28 | "server", 29 | "record", 30 | "replay", 31 | "express" 32 | ], 33 | "publishConfig": { 34 | "access": "public" 35 | }, 36 | "scripts": { 37 | "build": "rollup -c", 38 | "watch": "yarn build -w", 39 | "watch-all": "yarn build" 40 | }, 41 | "dependencies": { 42 | "@pollyjs/utils": "^6.0.6", 43 | "body-parser": "^1.19.0", 44 | "cors": "^2.8.5", 45 | "express": "^4.17.1", 46 | "fs-extra": "^10.0.0", 47 | "http-graceful-shutdown": "^3.1.5", 48 | "morgan": "^1.10.0", 49 | "nocache": "^3.0.1" 50 | }, 51 | "devDependencies": { 52 | "rollup": "^1.14.6" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/@pollyjs/node-server/rollup.config.js: -------------------------------------------------------------------------------- 1 | import createNodeConfig from '../../../scripts/rollup/node.config'; 2 | 3 | export default createNodeConfig({ 4 | external: ['path'] 5 | }); 6 | -------------------------------------------------------------------------------- /packages/@pollyjs/node-server/src/api.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | import fs from 'fs-extra'; 4 | import { assert } from '@pollyjs/utils'; 5 | 6 | export default class API { 7 | constructor(options = {}) { 8 | const { recordingsDir } = options; 9 | 10 | assert( 11 | `Invalid recordings directory provided. Expected string, received: "${typeof recordingsDir}".`, 12 | typeof recordingsDir === 'string' 13 | ); 14 | 15 | this.recordingsDir = recordingsDir; 16 | } 17 | 18 | getRecording(recording) { 19 | const recordingFilename = this.filenameFor(recording); 20 | 21 | if (fs.existsSync(recordingFilename)) { 22 | return this.respond(200, fs.readJsonSync(recordingFilename)); 23 | } 24 | 25 | return this.respond(204); 26 | } 27 | 28 | saveRecording(recording, data) { 29 | fs.outputJsonSync(this.filenameFor(recording), data, { 30 | spaces: 2 31 | }); 32 | 33 | return this.respond(201); 34 | } 35 | 36 | deleteRecording(recording) { 37 | const recordingFilename = this.filenameFor(recording); 38 | 39 | if (fs.existsSync(recordingFilename)) { 40 | fs.removeSync(recordingFilename); 41 | } 42 | 43 | return this.respond(200); 44 | } 45 | 46 | filenameFor(recording) { 47 | return path.join(this.recordingsDir, recording, 'recording.har'); 48 | } 49 | 50 | respond(status, body) { 51 | return { status, body }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/@pollyjs/node-server/src/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | port: 3000, 3 | quiet: false, 4 | recordingSizeLimit: '50mb', 5 | recordingsDir: 'recordings', 6 | apiNamespace: '/polly' 7 | }; 8 | -------------------------------------------------------------------------------- /packages/@pollyjs/node-server/src/express/register-api.js: -------------------------------------------------------------------------------- 1 | import bodyParser from 'body-parser'; 2 | import express from 'express'; 3 | import nocache from 'nocache'; 4 | 5 | import API from '../api'; 6 | import DefaultConfig from '../config'; 7 | 8 | function prependSlash(slash = '') { 9 | if (slash.startsWith('/')) { 10 | return slash; 11 | } 12 | 13 | return `/${slash}`; 14 | } 15 | 16 | export default function registerAPI(app, config) { 17 | config = { ...DefaultConfig, ...config }; 18 | config.apiNamespace = prependSlash(config.apiNamespace); 19 | 20 | const router = express.Router(); 21 | const api = new API({ recordingsDir: config.recordingsDir }); 22 | 23 | router.use(nocache()); 24 | 25 | router.get('/:recording', function (req, res) { 26 | const { recording } = req.params; 27 | const { status, body } = api.getRecording(recording); 28 | 29 | res.status(status); 30 | 31 | if (status === 200) { 32 | res.json(body); 33 | } else { 34 | res.end(); 35 | } 36 | }); 37 | 38 | router.post( 39 | '/:recording', 40 | bodyParser.json({ limit: config.recordingSizeLimit }), 41 | function (req, res) { 42 | const { recording } = req.params; 43 | const { status, body } = api.saveRecording(recording, req.body); 44 | 45 | res.status(status).send(body); 46 | } 47 | ); 48 | 49 | router.delete('/:recording', function (req, res) { 50 | const { recording } = req.params; 51 | const { status, body } = api.deleteRecording(recording); 52 | 53 | res.status(status).send(body); 54 | }); 55 | 56 | app.use(config.apiNamespace, router); 57 | } 58 | -------------------------------------------------------------------------------- /packages/@pollyjs/node-server/src/index.js: -------------------------------------------------------------------------------- 1 | export { default as API } from './api'; 2 | export { default as Server } from './server'; 3 | export { default as Defaults } from './config'; 4 | export { default as registerExpressAPI } from './express/register-api'; 5 | -------------------------------------------------------------------------------- /packages/@pollyjs/node-server/src/server.js: -------------------------------------------------------------------------------- 1 | /* global process */ 2 | 3 | import cors from 'cors'; 4 | import morgan from 'morgan'; 5 | import express from 'express'; 6 | import gracefulShutdown from 'http-graceful-shutdown'; 7 | 8 | import registerAPI from './express/register-api'; 9 | import DefaultConfig from './config'; 10 | 11 | export default class Server { 12 | constructor(config = {}) { 13 | this.config = { ...DefaultConfig, ...config }; 14 | this.app = express(); 15 | this.app.use(cors(this.config.corsOptions)); 16 | 17 | if (!this.config.quiet) { 18 | this.app.use(morgan('dev')); 19 | } 20 | 21 | // Return 200 on root GET & HEAD to pass health checks 22 | this.app.get('/', (req, res) => res.sendStatus(200)); 23 | this.app.head('/', (req, res) => res.sendStatus(200)); 24 | 25 | registerAPI(this.app, { 26 | recordingsDir: this.config.recordingsDir, 27 | apiNamespace: this.config.apiNamespace 28 | }); 29 | } 30 | 31 | listen(port, host) { 32 | if (this.server) { 33 | return; 34 | } 35 | 36 | port = port || this.config.port; 37 | host = host || this.config.host; 38 | 39 | this.server = this.app 40 | .listen(port, host) 41 | .on('listening', () => { 42 | if (!this.config.quiet) { 43 | console.log(`Listening on http://${host || 'localhost'}:${port}/\n`); 44 | } 45 | }) 46 | .on('error', (e) => { 47 | if (e.code === 'EADDRINUSE') { 48 | console.error(`Port ${port} already in use.`); 49 | process.exit(1); 50 | } else { 51 | console.error(e); 52 | } 53 | }); 54 | 55 | gracefulShutdown(this.server); 56 | 57 | return this.server; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/@pollyjs/node-server/types.d.ts: -------------------------------------------------------------------------------- 1 | import * as http from 'http'; 2 | import * as express from 'express'; 3 | import * as cors from 'cors'; 4 | 5 | export interface Config { 6 | port: number; 7 | quiet: boolean; 8 | recordingSizeLimit: string; 9 | recordingsDir: string; 10 | apiNamespace: string; 11 | } 12 | 13 | export interface ServerConfig extends Config { 14 | corsOptions?: cors.CorsOptions | undefined; 15 | } 16 | 17 | export const Defaults: Config; 18 | 19 | export interface APIResponse { 20 | status: number; 21 | body?: any; 22 | } 23 | 24 | export class API { 25 | constructor(options: Pick); 26 | getRecordings(recording: string): APIResponse; 27 | saveRecording(recording: string, data: any): APIResponse; 28 | deleteRecording(recording: string): APIResponse; 29 | filenameFor(recording: string): string; 30 | respond(status: number, data?: any): APIResponse; 31 | } 32 | 33 | export class Server { 34 | config: ServerConfig; 35 | app: express.Express; 36 | server?: http.Server | undefined; 37 | 38 | constructor(options?: Partial); 39 | listen(port?: number, host?: string): http.Server; 40 | } 41 | 42 | export function registerExpressAPI( 43 | app: express.Express, 44 | config: Partial 45 | ): void; 46 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister-fs/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: false, 4 | node: true 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister-fs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pollyjs/persister-fs", 3 | "version": "6.0.6", 4 | "description": "File system persister for @pollyjs", 5 | "main": "dist/cjs/pollyjs-persister-fs.js", 6 | "module": "dist/es/pollyjs-persister-fs.js", 7 | "types": "types.d.ts", 8 | "files": [ 9 | "src", 10 | "dist", 11 | "types.d.ts" 12 | ], 13 | "repository": "https://github.com/netflix/pollyjs/tree/master/packages/@pollyjs/persister-fs", 14 | "license": "Apache-2.0", 15 | "contributors": [ 16 | { 17 | "name": "Jason Mitchell", 18 | "email": "jason.mitchell.w@gmail.com" 19 | }, 20 | { 21 | "name": "Offir Golan", 22 | "email": "offirgolan@gmail.com" 23 | } 24 | ], 25 | "keywords": [ 26 | "polly", 27 | "pollyjs", 28 | "record", 29 | "replay", 30 | "fs", 31 | "file" 32 | ], 33 | "publishConfig": { 34 | "access": "public" 35 | }, 36 | "scripts": { 37 | "build": "rollup -c", 38 | "build:watch": "yarn build -w", 39 | "test:build": "rollup -c rollup.config.test.js", 40 | "test:build:watch": "rollup -c rollup.config.test.js -w", 41 | "watch-all": "npm-run-all --parallel build:watch test:build:watch" 42 | }, 43 | "dependencies": { 44 | "@pollyjs/node-server": "^6.0.6", 45 | "@pollyjs/persister": "^6.0.6" 46 | }, 47 | "devDependencies": { 48 | "fixturify": "^2.1.1", 49 | "rimraf": "^3.0.2", 50 | "rollup": "^1.14.6" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister-fs/rollup.config.js: -------------------------------------------------------------------------------- 1 | import createNodeConfig from '../../../scripts/rollup/node.config'; 2 | 3 | export default createNodeConfig(); 4 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister-fs/rollup.config.test.js: -------------------------------------------------------------------------------- 1 | import createNodeTestConfig from '../../../scripts/rollup/node.test.config'; 2 | 3 | export default createNodeTestConfig({ 4 | external: ['rimraf', 'fixturify'] 5 | }); 6 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister-fs/src/index.js: -------------------------------------------------------------------------------- 1 | import Persister from '@pollyjs/persister'; 2 | import { API, Defaults } from '@pollyjs/node-server'; 3 | 4 | const { parse } = JSON; 5 | 6 | export default class FSPersister extends Persister { 7 | constructor() { 8 | super(...arguments); 9 | this.api = new API(this.options); 10 | } 11 | 12 | static get id() { 13 | return 'fs'; 14 | } 15 | 16 | get defaultOptions() { 17 | return { 18 | recordingsDir: Defaults.recordingsDir 19 | }; 20 | } 21 | 22 | onFindRecording(recordingId) { 23 | return this.api.getRecording(recordingId).body || null; 24 | } 25 | 26 | onSaveRecording(recordingId, data) { 27 | /* 28 | Pass the data through the base persister's stringify method so 29 | the output will be consistent with the rest of the persisters. 30 | */ 31 | this.api.saveRecording(recordingId, parse(this.stringify(data))); 32 | } 33 | 34 | onDeleteRecording(recordingId) { 35 | this.api.deleteRecording(recordingId); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister-fs/types.d.ts: -------------------------------------------------------------------------------- 1 | import Persister from '@pollyjs/persister'; 2 | 3 | export default class FSPersister extends Persister<{ 4 | recordingsDir?: string; 5 | }> {} 6 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister-in-memory/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pollyjs/persister-in-memory", 3 | "version": "6.0.6", 4 | "private": true, 5 | "description": "In memory storage persister for @pollyjs", 6 | "main": "dist/cjs/pollyjs-persister-in-memory.js", 7 | "module": "dist/es/pollyjs-persister-in-memory.js", 8 | "browser": "dist/umd/pollyjs-persister-in-memory.js", 9 | "types": "types.d.ts", 10 | "files": [ 11 | "src", 12 | "dist", 13 | "types.d.ts" 14 | ], 15 | "repository": "https://github.com/netflix/pollyjs/tree/master/packages/@pollyjs/persister-in-memory", 16 | "license": "Apache-2.0", 17 | "contributors": [ 18 | { 19 | "name": "Jason Mitchell", 20 | "email": "jason.mitchell.w@gmail.com" 21 | }, 22 | { 23 | "name": "Offir Golan", 24 | "email": "offirgolan@gmail.com" 25 | } 26 | ], 27 | "keywords": [ 28 | "polly", 29 | "pollyjs", 30 | "record", 31 | "replay", 32 | "persister" 33 | ], 34 | "publishConfig": { 35 | "access": "private" 36 | }, 37 | "scripts": { 38 | "build": "rollup -c ../../../scripts/rollup/default.config.js", 39 | "test:build": "rollup -c rollup.config.test.js", 40 | "test:build:watch": "rollup -c rollup.config.test.js -w", 41 | "build:watch": "yarn build -w", 42 | "watch-all": "npm-run-all --parallel build:watch test:build:watch" 43 | }, 44 | "dependencies": { 45 | "@pollyjs/persister": "^6.0.6" 46 | }, 47 | "devDependencies": { 48 | "rollup": "^1.14.6" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister-in-memory/rollup.config.test.js: -------------------------------------------------------------------------------- 1 | import createNodeTestConfig from '../../../scripts/rollup/node.test.config'; 2 | import createBrowserTestConfig from '../../../scripts/rollup/browser.test.config'; 3 | 4 | export default [createNodeTestConfig(), createBrowserTestConfig()]; 5 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister-in-memory/src/index.js: -------------------------------------------------------------------------------- 1 | import Persister from '@pollyjs/persister'; 2 | 3 | const store = new Map(); 4 | 5 | export default class InMemoryPersister extends Persister { 6 | static get id() { 7 | return 'in-memory-persister'; 8 | } 9 | 10 | onFindRecording(recordingId) { 11 | return store.get(recordingId) || null; 12 | } 13 | 14 | onSaveRecording(recordingId, data) { 15 | store.set(recordingId, data); 16 | } 17 | 18 | onDeleteRecording(recordingId) { 19 | store.delete(recordingId); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister-in-memory/types.d.ts: -------------------------------------------------------------------------------- 1 | import Persister from '@pollyjs/persister'; 2 | 3 | export default class InMemoryPersister extends Persister {} 4 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister-local-storage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pollyjs/persister-local-storage", 3 | "version": "6.0.6", 4 | "description": "Local storage persister for @pollyjs", 5 | "main": "dist/cjs/pollyjs-persister-local-storage.js", 6 | "module": "dist/es/pollyjs-persister-local-storage.js", 7 | "browser": "dist/umd/pollyjs-persister-local-storage.js", 8 | "types": "types.d.ts", 9 | "files": [ 10 | "src", 11 | "dist", 12 | "types.d.ts" 13 | ], 14 | "repository": "https://github.com/netflix/pollyjs/tree/master/packages/@pollyjs/persister-local-storage", 15 | "license": "Apache-2.0", 16 | "contributors": [ 17 | { 18 | "name": "Jason Mitchell", 19 | "email": "jason.mitchell.w@gmail.com" 20 | }, 21 | { 22 | "name": "Offir Golan", 23 | "email": "offirgolan@gmail.com" 24 | } 25 | ], 26 | "keywords": [ 27 | "polly", 28 | "pollyjs", 29 | "record", 30 | "replay", 31 | "local-storage", 32 | "persister" 33 | ], 34 | "publishConfig": { 35 | "access": "public" 36 | }, 37 | "scripts": { 38 | "build": "rollup -c ../../../scripts/rollup/default.config.js", 39 | "build:watch": "yarn build -w", 40 | "test:build": "rollup -c rollup.config.test.js", 41 | "test:build:watch": "rollup -c rollup.config.test.js -w", 42 | "watch-all": "npm-run-all --parallel build:watch test:build:watch" 43 | }, 44 | "dependencies": { 45 | "@pollyjs/persister": "^6.0.6" 46 | }, 47 | "devDependencies": { 48 | "rollup": "^1.14.6" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister-local-storage/rollup.config.test.js: -------------------------------------------------------------------------------- 1 | import createBrowserTestConfig from '../../../scripts/rollup/browser.test.config'; 2 | 3 | export default createBrowserTestConfig(); 4 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister-local-storage/src/index.js: -------------------------------------------------------------------------------- 1 | import Persister from '@pollyjs/persister'; 2 | 3 | const { parse } = JSON; 4 | 5 | export default class LocalStoragePersister extends Persister { 6 | static get id() { 7 | return 'local-storage'; 8 | } 9 | 10 | get defaultOptions() { 11 | return { 12 | key: 'pollyjs', 13 | context: global 14 | }; 15 | } 16 | 17 | get localStorage() { 18 | const { context } = this.options; 19 | 20 | this.assert( 21 | `Could not find "localStorage" on the given context "${context}".`, 22 | context && context.localStorage 23 | ); 24 | 25 | return context.localStorage; 26 | } 27 | 28 | get db() { 29 | const items = this.localStorage.getItem(this.options.key); 30 | 31 | return items ? parse(items) : {}; 32 | } 33 | 34 | set db(db) { 35 | this.localStorage.setItem(this.options.key, this.stringify(db)); 36 | } 37 | 38 | onFindRecording(recordingId) { 39 | return this.db[recordingId] || null; 40 | } 41 | 42 | onSaveRecording(recordingId, data) { 43 | const { db } = this; 44 | 45 | db[recordingId] = data; 46 | this.db = db; 47 | } 48 | 49 | onDeleteRecording(recordingId) { 50 | const { db } = this; 51 | 52 | delete db[recordingId]; 53 | this.db = db; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister-local-storage/types.d.ts: -------------------------------------------------------------------------------- 1 | import Persister from '@pollyjs/persister'; 2 | 3 | export default class LocalStoragePersister extends Persister<{ 4 | context?: any; 5 | key?: string; 6 | }> {} 7 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister-rest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pollyjs/persister-rest", 3 | "version": "6.0.6", 4 | "description": "REST persister for @pollyjs", 5 | "main": "dist/cjs/pollyjs-persister-rest.js", 6 | "module": "dist/es/pollyjs-persister-rest.js", 7 | "browser": "dist/umd/pollyjs-persister-rest.js", 8 | "types": "types.d.ts", 9 | "files": [ 10 | "src", 11 | "dist", 12 | "types.d.ts" 13 | ], 14 | "repository": "https://github.com/netflix/pollyjs/tree/master/packages/@pollyjs/persister-rest", 15 | "license": "Apache-2.0", 16 | "contributors": [ 17 | { 18 | "name": "Jason Mitchell", 19 | "email": "jason.mitchell.w@gmail.com" 20 | }, 21 | { 22 | "name": "Offir Golan", 23 | "email": "offirgolan@gmail.com" 24 | } 25 | ], 26 | "keywords": [ 27 | "polly", 28 | "pollyjs", 29 | "record", 30 | "replay", 31 | "rest", 32 | "persister" 33 | ], 34 | "publishConfig": { 35 | "access": "public" 36 | }, 37 | "scripts": { 38 | "build": "rollup -c ../../../scripts/rollup/default.config.js", 39 | "test:build": "rollup -c rollup.config.test.js", 40 | "test:build:watch": "rollup -c rollup.config.test.js -w", 41 | "build:watch": "yarn build -w", 42 | "watch-all": "npm-run-all --parallel build:watch test:build:watch" 43 | }, 44 | "dependencies": { 45 | "@pollyjs/persister": "^6.0.6", 46 | "@pollyjs/utils": "^6.0.6" 47 | }, 48 | "devDependencies": { 49 | "rollup": "^1.14.6" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister-rest/rollup.config.test.js: -------------------------------------------------------------------------------- 1 | import createBrowserTestConfig from '../../../scripts/rollup/browser.test.config'; 2 | 3 | export default createBrowserTestConfig(); 4 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister-rest/src/ajax.js: -------------------------------------------------------------------------------- 1 | const { keys } = Object; 2 | const REQUEST_ASYNC = 3 | !('navigator' in global) || !/PhantomJS/.test(global.navigator.userAgent); 4 | const NativeXMLHttpRequest = global.XMLHttpRequest; 5 | 6 | export default function ajax(url, options = {}) { 7 | return new Promise((resolve, reject) => { 8 | const xhr = new NativeXMLHttpRequest(); 9 | 10 | xhr.open(options.method || 'GET', url, REQUEST_ASYNC); 11 | 12 | keys(options.headers || {}).forEach((k) => 13 | xhr.setRequestHeader(k, options.headers[k]) 14 | ); 15 | 16 | xhr.send(options.body); 17 | 18 | if (REQUEST_ASYNC) { 19 | xhr.onreadystatechange = () => { 20 | if (xhr.readyState === NativeXMLHttpRequest.DONE) { 21 | handleResponse(xhr, resolve, reject); 22 | } 23 | }; 24 | 25 | xhr.onerror = () => reject(xhr); 26 | } else { 27 | handleResponse(xhr, resolve, reject); 28 | } 29 | }); 30 | } 31 | 32 | function handleResponse(xhr, resolve, reject) { 33 | let body = xhr.response || xhr.responseText; 34 | 35 | if (body && typeof body === 'string') { 36 | try { 37 | body = JSON.parse(body); 38 | } catch (e) { 39 | if (!(e instanceof SyntaxError)) { 40 | console.error(e); 41 | } 42 | } 43 | } 44 | 45 | return xhr.status >= 200 && xhr.status < 300 46 | ? resolve({ body, xhr }) 47 | : reject(xhr); 48 | } 49 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister-rest/src/index.js: -------------------------------------------------------------------------------- 1 | import Persister from '@pollyjs/persister'; 2 | import { buildUrl } from '@pollyjs/utils'; 3 | 4 | import ajax from './ajax'; 5 | 6 | export default class RestPersister extends Persister { 7 | static get id() { 8 | return 'rest'; 9 | } 10 | 11 | get defaultOptions() { 12 | return { 13 | host: 'http://localhost:3000', 14 | apiNamespace: '/polly' 15 | }; 16 | } 17 | 18 | ajax(url, ...args) { 19 | const { host, apiNamespace } = this.options; 20 | 21 | return ajax(buildUrl(host, apiNamespace, url), ...args); 22 | } 23 | 24 | async onFindRecording(recordingId) { 25 | const response = await this.ajax(`/${encodeURIComponent(recordingId)}`, { 26 | Accept: 'application/json; charset=utf-8' 27 | }); 28 | 29 | return this._normalize(response); 30 | } 31 | 32 | async onSaveRecording(recordingId, data) { 33 | await this.ajax(`/${encodeURIComponent(recordingId)}`, { 34 | method: 'POST', 35 | body: this.stringify(data), 36 | headers: { 37 | 'Content-Type': 'application/json; charset=utf-8', 38 | Accept: 'application/json; charset=utf-8' 39 | } 40 | }); 41 | } 42 | 43 | async onDeleteRecording(recordingId) { 44 | await this.ajax(`/${encodeURIComponent(recordingId)}`, { 45 | method: 'DELETE' 46 | }); 47 | } 48 | 49 | _normalize({ xhr, body }) { 50 | /** 51 | * 204 - No Content. Polly uses this status code in place of 404 52 | * when interacting with our Rest server to prevent throwing 53 | * request errors in consumer's stdout (console.log) 54 | */ 55 | if (xhr.status === 204) { 56 | /* return null when a record was not found */ 57 | return null; 58 | } 59 | 60 | return body; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister-rest/types.d.ts: -------------------------------------------------------------------------------- 1 | import Persister from '@pollyjs/persister'; 2 | 3 | export default class RESTPersister extends Persister<{ 4 | host?: string; 5 | apiNamespace?: string; 6 | }> {} 7 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pollyjs/persister", 3 | "version": "6.0.6", 4 | "description": "Extendable base persister class used by @pollyjs", 5 | "main": "dist/cjs/pollyjs-persister.js", 6 | "module": "dist/es/pollyjs-persister.js", 7 | "browser": "dist/umd/pollyjs-persister.js", 8 | "types": "types.d.ts", 9 | "files": [ 10 | "src", 11 | "dist", 12 | "types.d.ts" 13 | ], 14 | "repository": "https://github.com/netflix/pollyjs/tree/master/packages/@pollyjs/persister", 15 | "scripts": { 16 | "build": "rollup -c ../../../scripts/rollup/default.config.js", 17 | "test:build": "rollup -c rollup.config.test.js", 18 | "test:build:watch": "rollup -c rollup.config.test.js -w", 19 | "build:watch": "yarn build -w", 20 | "watch-all": "npm-run-all --parallel build:watch test:build:watch" 21 | }, 22 | "keywords": [ 23 | "polly", 24 | "pollyjs", 25 | "persister" 26 | ], 27 | "publishConfig": { 28 | "access": "public" 29 | }, 30 | "contributors": [ 31 | { 32 | "name": "Jason Mitchell", 33 | "email": "jason.mitchell.w@gmail.com" 34 | }, 35 | { 36 | "name": "Offir Golan", 37 | "email": "offirgolan@gmail.com" 38 | } 39 | ], 40 | "license": "Apache-2.0", 41 | "dependencies": { 42 | "@pollyjs/utils": "^6.0.6", 43 | "@types/set-cookie-parser": "^2.4.1", 44 | "bowser": "^2.4.0", 45 | "fast-json-stable-stringify": "^2.1.0", 46 | "lodash-es": "^4.17.21", 47 | "set-cookie-parser": "^2.4.8", 48 | "utf8-byte-length": "^1.0.4" 49 | }, 50 | "devDependencies": { 51 | "har-validator": "^5.1.5", 52 | "rollup": "^1.14.6" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister/rollup.config.test.js: -------------------------------------------------------------------------------- 1 | import createNodeTestConfig from '../../../scripts/rollup/node.test.config'; 2 | import createBrowserTestConfig from '../../../scripts/rollup/browser.test.config'; 3 | 4 | export default [createNodeTestConfig(), createBrowserTestConfig()]; 5 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister/src/har/entry.js: -------------------------------------------------------------------------------- 1 | import Request from './request'; 2 | import Response from './response'; 3 | 4 | const { keys } = Object; 5 | 6 | function totalTime(timings = {}) { 7 | return keys(timings).reduce( 8 | (total, k) => (timings[k] > 0 ? (total += timings[k]) : total), 9 | 0 10 | ); 11 | } 12 | 13 | export default class Entry { 14 | constructor(request) { 15 | this._id = request.id; 16 | this._order = request.order; 17 | this.startedDateTime = request.timestamp; 18 | this.request = new Request(request); 19 | this.response = new Response(request.response); 20 | this.cache = {}; 21 | this.timings = { 22 | blocked: -1, 23 | dns: -1, 24 | connect: -1, 25 | send: 0, 26 | wait: request.responseTime, 27 | receive: 0, 28 | ssl: -1 29 | }; 30 | this.time = totalTime(this.timings); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister/src/har/index.js: -------------------------------------------------------------------------------- 1 | import Log from './log'; 2 | 3 | export default class HAR { 4 | constructor(opts = {}) { 5 | this.log = new Log(opts.log); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister/src/har/log.js: -------------------------------------------------------------------------------- 1 | import uniqWith from 'lodash-es/uniqWith'; 2 | import Bowser from 'bowser'; 3 | 4 | const bowser = 5 | global.navigator && global.navigator.userAgent 6 | ? Bowser.getParser(global.navigator.userAgent).getBrowser() 7 | : null; 8 | const browser = 9 | bowser && bowser.name && bowser.version 10 | ? { name: bowser.name, version: bowser.version } 11 | : null; 12 | 13 | export default class Log { 14 | constructor(opts = {}) { 15 | // eslint-disable-next-line no-restricted-properties 16 | Object.assign( 17 | this, 18 | { 19 | version: '1.2', 20 | entries: [], 21 | pages: [] 22 | }, 23 | opts 24 | ); 25 | 26 | if (!this.browser && browser) { 27 | this.browser = browser; 28 | } 29 | } 30 | 31 | addEntries(entries = []) { 32 | this.entries = uniqWith( 33 | // Add the new entries to the front so they take priority 34 | [...entries, ...this.entries], 35 | (a, b) => a._id === b._id && a._order === b._order 36 | ); 37 | } 38 | 39 | sortEntries() { 40 | this.entries = this.entries.sort( 41 | (a, b) => new Date(a.startedDateTime) - new Date(b.startedDateTime) 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister/src/har/request.js: -------------------------------------------------------------------------------- 1 | import getByteLength from 'utf8-byte-length'; 2 | import setCookies from 'set-cookie-parser'; 3 | 4 | import toNVPairs from './utils/to-nv-pairs'; 5 | import getFirstHeader from './utils/get-first-header'; 6 | 7 | function headersSize(request) { 8 | const keys = []; 9 | const values = []; 10 | 11 | request.headers.forEach(({ name, value }) => { 12 | keys.push(name); 13 | values.push(value); 14 | }); 15 | 16 | const headersString = 17 | request.method + request.url + keys.join() + values.join(); 18 | 19 | // startline: [method] [url] HTTP/1.1\r\n = 12 20 | // endline: \r\n = 2 21 | // every header + \r\n = * 2 22 | // add 2 for each combined header 23 | return getByteLength(headersString) + keys.length * 2 + 2 + 12 + 2; 24 | } 25 | 26 | export default class Request { 27 | constructor(request) { 28 | this.httpVersion = 'HTTP/1.1'; 29 | this.url = request.absoluteUrl; 30 | this.method = request.method; 31 | this.headers = toNVPairs(request.headers); 32 | this.headersSize = headersSize(this); 33 | this.queryString = toNVPairs(request.query); 34 | this.cookies = setCookies.parse(request.getHeader('Set-Cookie')); 35 | 36 | if (request.body) { 37 | this.postData = { 38 | mimeType: getFirstHeader(request, 'Content-Type') || 'text/plain', 39 | params: [] 40 | }; 41 | 42 | if (typeof request.body === 'string') { 43 | this.postData.text = request.body; 44 | } 45 | } 46 | 47 | const contentLength = getFirstHeader(request, 'Content-Length'); 48 | 49 | if (contentLength) { 50 | this.bodySize = parseInt(contentLength, 10); 51 | } else { 52 | this.bodySize = 53 | this.postData && this.postData.text 54 | ? getByteLength(this.postData.text) 55 | : 0; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister/src/har/response.js: -------------------------------------------------------------------------------- 1 | import getByteLength from 'utf8-byte-length'; 2 | import setCookies from 'set-cookie-parser'; 3 | 4 | import toNVPairs from './utils/to-nv-pairs'; 5 | import getFirstHeader from './utils/get-first-header'; 6 | 7 | function headersSize(response) { 8 | const keys = []; 9 | const values = []; 10 | 11 | response.headers.forEach(({ name, value }) => { 12 | keys.push(name); 13 | values.push(value); 14 | }); 15 | 16 | const headersString = keys.join() + values.join(); 17 | 18 | // endline: \r\n = 2 19 | // every header + \r\n = * 2 20 | // add 2 for each combined header 21 | return getByteLength(headersString) + keys.length * 2 + 2 + 2; 22 | } 23 | 24 | export default class Response { 25 | constructor(response) { 26 | this.httpVersion = 'HTTP/1.1'; 27 | this.status = response.statusCode; 28 | this.statusText = response.statusText; 29 | this.headers = toNVPairs(response.headers); 30 | this.headersSize = headersSize(this); 31 | this.cookies = setCookies.parse(response.getHeader('Set-Cookie')); 32 | this.redirectURL = getFirstHeader(response, 'Location') || ''; 33 | 34 | this.content = { 35 | mimeType: getFirstHeader(response, 'Content-Type') || 'text/plain' 36 | }; 37 | 38 | if (response.body && typeof response.body === 'string') { 39 | this.content.text = response.body; 40 | 41 | if (response.encoding) { 42 | this.content.encoding = response.encoding; 43 | } 44 | } 45 | 46 | const contentLength = getFirstHeader(response, 'Content-Length'); 47 | 48 | if (contentLength) { 49 | this.content.size = parseInt(contentLength, 10); 50 | } else { 51 | this.content.size = this.content.text 52 | ? getByteLength(this.content.text) 53 | : 0; 54 | } 55 | 56 | this.bodySize = this.content.size; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister/src/har/utils/get-first-header.js: -------------------------------------------------------------------------------- 1 | const { isArray } = Array; 2 | 3 | /** 4 | * Get the value of the given header name. If the value is an array, 5 | * get the first value. 6 | * 7 | * @export 8 | * @param {PollyRequest | PollyResponse} r 9 | * @param {string} name 10 | * @returns {string | undefined} 11 | */ 12 | export default function getFirstHeader(r, name) { 13 | const value = r.getHeader(name); 14 | 15 | if (isArray(value)) { 16 | return value.length > 0 ? value[0] : ''; 17 | } 18 | 19 | return value; 20 | } 21 | -------------------------------------------------------------------------------- /packages/@pollyjs/persister/src/har/utils/to-nv-pairs.js: -------------------------------------------------------------------------------- 1 | const { keys } = Object; 2 | const { isArray } = Array; 3 | 4 | export default function toNVPairs(o) { 5 | return keys(o || {}).reduce((pairs, name) => { 6 | const value = o[name]; 7 | 8 | if (isArray(value)) { 9 | // _fromType is used to convert the stored header back into the 10 | // type it originally was. Take the following example: 11 | // { 'content-type': ['text/htm'] } 12 | // => { name: 'content-type', value: 'text/html', _fromType: 'array } 13 | // => { 'content-type': ['text/htm'] } 14 | pairs.push(...value.map((v) => ({ name, value: v, _fromType: 'array' }))); 15 | } else { 16 | pairs.push({ name, value }); 17 | } 18 | 19 | return pairs; 20 | }, []); 21 | } 22 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/README.md: -------------------------------------------------------------------------------- 1 |

2 | Polly.JS 3 |

4 |

Record, Replay, and Stub HTTP Interactions

5 | 6 | [![Build Status](https://travis-ci.com/Netflix/pollyjs.svg?branch=master)](https://travis-ci.com/Netflix/pollyjs) 7 | [![npm version](https://badge.fury.io/js/%40pollyjs%2Futils.svg)](https://badge.fury.io/js/%40pollyjs%2Futils) 8 | [![license](https://img.shields.io/github/license/Netflix/pollyjs.svg)](http://www.apache.org/licenses/LICENSE-2.0) 9 | 10 | The `@pollyjs/utils` package provides utilities and constants for other @pollyjs packages. 11 | 12 | ## Installation 13 | 14 | _Note that you must have node (and npm) installed._ 15 | 16 | ```bash 17 | npm install @pollyjs/utils -D 18 | ``` 19 | 20 | If you want to install it with [yarn](https://yarnpkg.com): 21 | 22 | ```bash 23 | yarn add @pollyjs/utils -D 24 | ``` 25 | 26 | ## License 27 | 28 | Copyright (c) 2018 Netflix, Inc. 29 | 30 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 31 | 32 | [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) 33 | 34 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 35 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pollyjs/utils", 3 | "version": "6.0.6", 4 | "description": "Shared utilities and constants between @pollyjs packages", 5 | "main": "dist/cjs/pollyjs-utils.js", 6 | "module": "dist/es/pollyjs-utils.js", 7 | "browser": "dist/umd/pollyjs-utils.js", 8 | "types": "types.d.ts", 9 | "files": [ 10 | "src", 11 | "dist", 12 | "types.d.ts" 13 | ], 14 | "repository": "https://github.com/netflix/pollyjs/tree/master/packages/@pollyjs/utils", 15 | "scripts": { 16 | "build": "rollup -c ../../../scripts/rollup/default.config.js", 17 | "test:build": "rollup -c rollup.config.test.js", 18 | "test:build:watch": "rollup -c rollup.config.test.js -w", 19 | "build:watch": "yarn build -w", 20 | "watch-all": "npm-run-all --parallel build:watch test:build:watch" 21 | }, 22 | "keywords": [ 23 | "polly", 24 | "pollyjs", 25 | "utils" 26 | ], 27 | "publishConfig": { 28 | "access": "public" 29 | }, 30 | "contributors": [ 31 | { 32 | "name": "Jason Mitchell", 33 | "email": "jason.mitchell.w@gmail.com" 34 | }, 35 | { 36 | "name": "Offir Golan", 37 | "email": "offirgolan@gmail.com" 38 | } 39 | ], 40 | "license": "Apache-2.0", 41 | "dependencies": { 42 | "qs": "^6.10.1", 43 | "url-parse": "^1.5.3" 44 | }, 45 | "devDependencies": { 46 | "rollup": "^1.14.6" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/rollup.config.test.js: -------------------------------------------------------------------------------- 1 | import createNodeTestConfig from '../../../scripts/rollup/node.test.config'; 2 | import createBrowserTestConfig from '../../../scripts/rollup/browser.test.config'; 3 | 4 | export default [createNodeTestConfig(), createBrowserTestConfig()]; 5 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/src/constants/actions.js: -------------------------------------------------------------------------------- 1 | export default { 2 | RECORD: 'record', 3 | REPLAY: 'replay', 4 | INTERCEPT: 'intercept', 5 | PASSTHROUGH: 'passthrough' 6 | }; 7 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/src/constants/expiry-strategies.js: -------------------------------------------------------------------------------- 1 | export default { 2 | RECORD: 'record', 3 | WARN: 'warn', 4 | ERROR: 'error' 5 | }; 6 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/src/constants/http-methods.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | 'GET', 3 | 'PUT', 4 | 'POST', 5 | 'DELETE', 6 | 'PATCH', 7 | 'MERGE', 8 | 'HEAD', 9 | 'OPTIONS' 10 | ]; 11 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/src/constants/http-status-codes.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 100: 'Continue', 3 | 101: 'Switching Protocols', 4 | 200: 'OK', 5 | 201: 'Created', 6 | 202: 'Accepted', 7 | 203: 'Non-Authoritative Information', 8 | 204: 'No Content', 9 | 205: 'Reset Content', 10 | 206: 'Partial Content', 11 | 207: 'Multi-Status', 12 | 300: 'Multiple Choice', 13 | 301: 'Moved Permanently', 14 | 302: 'Found', 15 | 303: 'See Other', 16 | 304: 'Not Modified', 17 | 305: 'Use Proxy', 18 | 307: 'Temporary Redirect', 19 | 400: 'Bad Request', 20 | 401: 'Unauthorized', 21 | 402: 'Payment Required', 22 | 403: 'Forbidden', 23 | 404: 'Not Found', 24 | 405: 'Method Not Allowed', 25 | 406: 'Not Acceptable', 26 | 407: 'Proxy Authentication Required', 27 | 408: 'Request Timeout', 28 | 409: 'Conflict', 29 | 410: 'Gone', 30 | 411: 'Length Required', 31 | 412: 'Precondition Failed', 32 | 413: 'Request Entity Too Large', 33 | 414: 'Request-URI Too Long', 34 | 415: 'Unsupported Media Type', 35 | 416: 'Requested Range Not Satisfiable', 36 | 417: 'Expectation Failed', 37 | 422: 'Unprocessable Entity', 38 | 500: 'Internal Server Error', 39 | 501: 'Not Implemented', 40 | 502: 'Bad Gateway', 41 | 503: 'Service Unavailable', 42 | 504: 'Gateway Timeout', 43 | 505: 'HTTP Version Not Supported' 44 | }; 45 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/src/constants/modes.js: -------------------------------------------------------------------------------- 1 | export default { 2 | RECORD: 'record', 3 | REPLAY: 'replay', 4 | PASSTHROUGH: 'passthrough', 5 | STOPPED: 'stopped' 6 | }; 7 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/src/index.js: -------------------------------------------------------------------------------- 1 | export { default as MODES } from './constants/modes'; 2 | export { default as ACTIONS } from './constants/actions'; 3 | export { default as HTTP_METHODS } from './constants/http-methods'; 4 | export { default as HTTP_STATUS_CODES } from './constants/http-status-codes'; 5 | export { default as EXPIRY_STRATEGIES } from './constants/expiry-strategies'; 6 | 7 | export { default as assert } from './utils/assert'; 8 | export { default as timeout } from './utils/timeout'; 9 | export { default as timestamp } from './utils/timestamp'; 10 | export { default as buildUrl } from './utils/build-url'; 11 | 12 | export { default as PollyError } from './utils/polly-error'; 13 | export { default as Serializers } from './utils/serializers'; 14 | 15 | export { default as URL } from './utils/url'; 16 | 17 | export { default as isBufferUtf8Representable } from './utils/is-buffer-utf8-representable'; 18 | export { default as cloneArrayBuffer } from './utils/clone-arraybuffer'; 19 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/src/utils/assert.js: -------------------------------------------------------------------------------- 1 | import PollyError from './polly-error'; 2 | 3 | export default function (msg, condition) { 4 | if (!condition) { 5 | throw new PollyError(msg); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/src/utils/build-url.js: -------------------------------------------------------------------------------- 1 | import URL from './url'; 2 | 3 | export default function buildUrl(...paths) { 4 | const url = new URL( 5 | paths 6 | .map((p) => p && (p + '').trim()) // Trim each string 7 | .filter(Boolean) // Remove empty strings or other falsy paths 8 | .join('/') 9 | ); 10 | 11 | // Replace 2+ consecutive slashes with 1. (e.g. `///` --> `/`) 12 | url.set('pathname', url.pathname.replace(/\/{2,}/g, '/')); 13 | 14 | return url.href; 15 | } 16 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/src/utils/clone-arraybuffer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Clone an array buffer 3 | * 4 | * @param {ArrayBuffer} arrayBuffer 5 | */ 6 | export default function cloneArrayBuffer(arrayBuffer) { 7 | const clonedArrayBuffer = new ArrayBuffer(arrayBuffer.byteLength); 8 | 9 | new Uint8Array(clonedArrayBuffer).set(new Uint8Array(arrayBuffer)); 10 | 11 | return clonedArrayBuffer; 12 | } 13 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/src/utils/is-buffer-utf8-representable.js: -------------------------------------------------------------------------------- 1 | import { Buffer } from 'buffer'; 2 | 3 | /** 4 | * Determine if the given buffer is utf8. 5 | * @param {Buffer} buffer 6 | */ 7 | export default function isBufferUtf8Representable(buffer) { 8 | const utfEncodedBuffer = buffer.toString('utf8'); 9 | const reconstructedBuffer = Buffer.from(utfEncodedBuffer, 'utf8'); 10 | 11 | return reconstructedBuffer.equals(buffer); 12 | } 13 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/src/utils/polly-error.js: -------------------------------------------------------------------------------- 1 | export default class PollyError extends Error { 2 | constructor(message, ...args) { 3 | super(`[Polly] ${message}`, ...args); 4 | 5 | // Maintains proper stack trace for where our error was thrown (only available on V8) 6 | if (Error.captureStackTrace) { 7 | Error.captureStackTrace(this, PollyError); 8 | } 9 | 10 | this.name = 'PollyError'; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/src/utils/serializers/blob.js: -------------------------------------------------------------------------------- 1 | export const supportsBlob = (() => { 2 | try { 3 | return !!new Blob(); 4 | } catch (e) { 5 | return false; 6 | } 7 | })(); 8 | 9 | export function readBlob(blob) { 10 | return new Promise((resolve, reject) => { 11 | const reader = new FileReader(); 12 | 13 | reader.onend = reject; 14 | reader.onabort = reject; 15 | reader.onload = () => resolve(reader.result); 16 | reader.readAsDataURL(new Blob([blob], { type: blob.type })); 17 | }); 18 | } 19 | 20 | export async function serialize(body) { 21 | if (supportsBlob && body instanceof Blob) { 22 | return await readBlob(body); 23 | } 24 | 25 | return body; 26 | } 27 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/src/utils/serializers/buffer.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | export const supportsBuffer = typeof Buffer !== 'undefined'; 4 | export const supportsArrayBuffer = typeof ArrayBuffer !== 'undefined'; 5 | 6 | export function serialize(body) { 7 | if (supportsBuffer && body) { 8 | let buffer; 9 | 10 | if (Buffer.isBuffer(body)) { 11 | buffer = body; 12 | } else if (Array.isArray(body) && body.some((c) => Buffer.isBuffer(c))) { 13 | // Body is a chunked array 14 | const chunks = body.map((c) => Buffer.from(c)); 15 | 16 | buffer = Buffer.concat(chunks); 17 | } else if (`${body}` === '[object ArrayBuffer]') { 18 | buffer = Buffer.from(body); 19 | } else if (supportsArrayBuffer && ArrayBuffer.isView(body)) { 20 | buffer = Buffer.from(body.buffer, body.byteOffset, body.byteLength); 21 | } 22 | 23 | if (Buffer.isBuffer(buffer)) { 24 | return buffer.toString('base64'); 25 | } 26 | } 27 | 28 | return body; 29 | } 30 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/src/utils/serializers/form-data.js: -------------------------------------------------------------------------------- 1 | import { supportsBlob, readBlob } from './blob'; 2 | 3 | export const supportsFormData = typeof FormData !== 'undefined'; 4 | 5 | export async function serialize(body) { 6 | if (supportsFormData && body instanceof FormData) { 7 | const data = []; 8 | 9 | for (const [key, value] of body.entries()) { 10 | if (supportsBlob && value instanceof Blob) { 11 | const blobContent = await readBlob(value); 12 | 13 | data.push(`${key}=${blobContent}`); 14 | } else { 15 | data.push(`${key}=${value}`); 16 | } 17 | } 18 | 19 | return data.join('\r\n'); 20 | } 21 | 22 | return body; 23 | } 24 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/src/utils/serializers/index.js: -------------------------------------------------------------------------------- 1 | import { serialize as blob } from './blob'; 2 | import { serialize as formData } from './form-data'; 3 | import { serialize as buffer } from './buffer'; 4 | 5 | export default { blob, formData, buffer }; 6 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/src/utils/timeout.js: -------------------------------------------------------------------------------- 1 | export default function timeout(time) { 2 | const ms = parseInt(time, 10); 3 | 4 | return new Promise((resolve) => 5 | ms > 0 ? setTimeout(resolve, ms) : resolve() 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/src/utils/timestamp.js: -------------------------------------------------------------------------------- 1 | export default function timestamp() { 2 | return new Date().toISOString(); 3 | } 4 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/tests/browser/unit/utils/serializers/blob.js: -------------------------------------------------------------------------------- 1 | import File from '@pollyjs-tests/helpers/file'; 2 | 3 | import { serialize } from '../../../../../src/utils/serializers/blob'; 4 | import serializerTests from '../../../../serializer-tests'; 5 | 6 | describe('Unit | Utils | Serializers | blob', function () { 7 | serializerTests(serialize); 8 | 9 | it('should noop if Blob is not found', function () { 10 | const Blob = Blob; 11 | const blob = new Blob(['blob'], { type: 'text/plain' }); 12 | 13 | global.Blob = undefined; 14 | expect(serialize(blob)).to.be.equal(blob); 15 | global.Blob = Blob; 16 | }); 17 | 18 | it('should handle blobs', async function () { 19 | expect( 20 | await serialize(new Blob(['blob'], { type: 'text/plain' })) 21 | ).to.equal(`data:text/plain;base64,${btoa('blob')}`); 22 | }); 23 | 24 | it('should handle files', async function () { 25 | expect( 26 | await serialize( 27 | new File(['file'], 'file.txt', { 28 | type: 'text/plain' 29 | }) 30 | ) 31 | ).to.equal(`data:text/plain;base64,${btoa('file')}`); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/tests/browser/unit/utils/serializers/form-data.js: -------------------------------------------------------------------------------- 1 | import File from '@pollyjs-tests/helpers/file'; 2 | 3 | import { serialize } from '../../../../../src/utils/serializers/form-data'; 4 | import serializerTests from '../../../../serializer-tests'; 5 | 6 | describe('Unit | Utils | Serializers | form-data', function () { 7 | serializerTests(serialize); 8 | 9 | it('should noop if FormData is not found', function () { 10 | const FormData = FormData; 11 | const formData = new FormData(); 12 | 13 | global.FormData = undefined; 14 | expect(serialize(formData)).to.be.equal(formData); 15 | global.FormData = FormData; 16 | }); 17 | 18 | it('should handle form-data', async function () { 19 | const formData = new FormData(); 20 | 21 | formData.append('string', 'string'); 22 | formData.append('array', [1, 2]); 23 | formData.append('blob', new Blob(['blob'], { type: 'text/plain' })); 24 | formData.append( 25 | 'file', 26 | new File(['file'], 'file.txt', { type: 'text/plain' }) 27 | ); 28 | 29 | const data = await serialize(formData); 30 | 31 | expect(data).to.include('string=string'); 32 | expect(data).to.include('array=1,2'); 33 | expect(data).to.include(`blob=data:text/plain;base64,${btoa('blob')}`); 34 | expect(data).to.include(`file=data:text/plain;base64,${btoa('file')}`); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/tests/serializer-tests.js: -------------------------------------------------------------------------------- 1 | export default function serializerTests(serialize) { 2 | it('should exist', function () { 3 | expect(serialize).to.be.a('function'); 4 | }); 5 | 6 | it('should handle empty argument', async function () { 7 | expect(await serialize()).to.be.undefined; 8 | expect(await serialize(null)).to.be.null; 9 | }); 10 | 11 | it('should handle strings', async function () { 12 | expect(await serialize('')).to.be.equal(''); 13 | expect(await serialize('foo')).to.be.equal('foo'); 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/tests/unit/utils/assert-test.js: -------------------------------------------------------------------------------- 1 | import assert from '../../../src/utils/assert'; 2 | import PollyError from '../../../src/utils/polly-error'; 3 | 4 | describe('Unit | Utils | assert', function () { 5 | it('should exist', function () { 6 | expect(assert).to.be.a('function'); 7 | }); 8 | 9 | it('should throw with a false condition', function () { 10 | expect(() => assert('Test', false)).to.throw(PollyError, /Test/); 11 | }); 12 | 13 | it('should throw without a condition', function () { 14 | expect(() => assert('Test')).to.throw(PollyError, /Test/); 15 | }); 16 | 17 | it('should not throw with a true condition', function () { 18 | expect(() => assert('Test', true)).to.not.throw(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/tests/unit/utils/build-url-test.js: -------------------------------------------------------------------------------- 1 | import buildUrl from '../../../src/utils/build-url'; 2 | 3 | const origin = (global.location && global.location.origin) || ''; 4 | 5 | describe('Unit | Utils | buildUrl', function () { 6 | it('should exist', function () { 7 | expect(buildUrl).to.be.a('function'); 8 | }); 9 | 10 | it('should remove consecutive slashes', function () { 11 | expect(buildUrl('http://foo.com///bar/baz/')).to.equal( 12 | 'http://foo.com/bar/baz/' 13 | ); 14 | }); 15 | 16 | it('should remove empty fragments of the url', function () { 17 | expect(buildUrl('http://foo///bar/////baz')).to.equal('http://foo/bar/baz'); 18 | }); 19 | 20 | it('should remove empty fragments of the url', function () { 21 | expect(buildUrl('/foo/bar/baz')).to.equal(`${origin}/foo/bar/baz`); 22 | }); 23 | 24 | it('should concat multiple paths together', function () { 25 | expect(buildUrl('/foo', '/bar', null, undefined, false, '/baz')).to.equal( 26 | `${origin}/foo/bar/baz` 27 | ); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/tests/unit/utils/polly-error-test.js: -------------------------------------------------------------------------------- 1 | import PollyError from '../../../src/utils/polly-error'; 2 | 3 | describe('Unit | Utils | PollyError', function () { 4 | it('should exist', function () { 5 | expect(PollyError).to.be.a('function'); 6 | }); 7 | 8 | it('should set the name to PollyError', function () { 9 | const error = new PollyError('Test'); 10 | 11 | expect(error.name).to.equal('PollyError'); 12 | }); 13 | 14 | it('should prefix the message with [Polly]', function () { 15 | const error = new PollyError('Test'); 16 | 17 | expect(error.message).to.equal('[Polly] Test'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/tests/unit/utils/timeout-test.js: -------------------------------------------------------------------------------- 1 | import timeout from '../../../src/utils/timeout'; 2 | 3 | describe('Unit | Utils | timeout', function () { 4 | it('should exist', function () { 5 | expect(timeout).to.be.a('function'); 6 | }); 7 | 8 | it('should return a promise', async function () { 9 | const promise = timeout(10); 10 | 11 | expect(promise).to.be.a('promise'); 12 | 13 | await promise; 14 | }); 15 | 16 | it('should timeout for the correct amount of ms', async function () { 17 | this.timeout(110); 18 | 19 | const promise = timeout(100); 20 | let resolved = false; 21 | 22 | promise.then(() => (resolved = true)); 23 | 24 | setTimeout(() => expect(resolved).to.be.false, 50); 25 | setTimeout(() => expect(resolved).to.be.true, 101); 26 | 27 | await promise; 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/tests/unit/utils/timestamp-test.js: -------------------------------------------------------------------------------- 1 | import timestamp from '../../../src/utils/timestamp'; 2 | 3 | describe('Unit | Utils | timestamp', function () { 4 | it('should exist', function () { 5 | expect(timestamp).to.be.a('function'); 6 | }); 7 | 8 | it('should return a string', function () { 9 | expect(timestamp()).to.be.a('string'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/@pollyjs/utils/types.d.ts: -------------------------------------------------------------------------------- 1 | export enum MODES { 2 | RECORD = 'record', 3 | REPLAY = 'replay', 4 | PASSTHROUGH = 'passthrough', 5 | STOPPED = 'stopped' 6 | } 7 | 8 | export enum ACTIONS { 9 | RECORD = 'record', 10 | REPLAY = 'replay', 11 | INTERCEPT = 'intercept', 12 | PASSTHROUGH = 'passthrough' 13 | } 14 | 15 | export enum EXPIRY_STRATEGIES { 16 | RECORD = 'record', 17 | WARN = 'warn', 18 | ERROR = 'error' 19 | } 20 | -------------------------------------------------------------------------------- /scripts/require-clean-work-tree.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | require_clean_work_tree () { 4 | git rev-parse --verify HEAD >/dev/null || exit 1 5 | git update-index -q --ignore-submodules --refresh 6 | 7 | # Disallow unstaged changes in the working tree 8 | if ! git diff-files --quiet --ignore-submodules 9 | then 10 | echo "There are unstaged changes." 11 | git diff-files --name-status -r --ignore-submodules -- 12 | exit 1 13 | fi 14 | 15 | # Disallow uncommitted changes in the index 16 | if ! git diff-index --cached --quiet --ignore-submodules HEAD -- 17 | then 18 | echo "The index contains uncommitted changes." 19 | git diff-index --cached --name-status -r --ignore-submodules HEAD -- 20 | exit 1 21 | fi 22 | } 23 | 24 | require_clean_work_tree 25 | -------------------------------------------------------------------------------- /scripts/require-test-build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ ! -f "./packages/@pollyjs/node-server/dist/cjs/pollyjs-node-server.js" ]; then 4 | echo "Test server build not found. Run either '$ yarn watch' or '$ yarn build:server'" 5 | exit 1 6 | fi 7 | 8 | if [ ! -f "./packages/@pollyjs/core/dist/cjs/pollyjs-core.js" ]; then 9 | echo "Build not found. Run either '$ yarn watch' or '$ yarn build'" 10 | exit 1 11 | fi 12 | 13 | if [ ! -f "./packages/@pollyjs/core/build/node/test-bundle.cjs.js" ]; then 14 | echo "Test build not found. Run either '$ yarn watch' or '$ yarn test:build'" 15 | exit 1 16 | fi 17 | -------------------------------------------------------------------------------- /scripts/rollup/browser.test.config.js: -------------------------------------------------------------------------------- 1 | import deepmerge from 'deepmerge'; 2 | import multiEntry from 'rollup-plugin-multi-entry'; 3 | import alias from 'rollup-plugin-alias'; 4 | 5 | import createBrowserConfig from './browser.config'; 6 | import { pkg, testsPath } from './utils'; 7 | 8 | export default function createBrowserTestConfig(options = {}) { 9 | return deepmerge( 10 | createBrowserConfig( 11 | { 12 | input: 'tests/!(node|jest)/**/*-test.js', 13 | output: { 14 | format: 'es', 15 | name: `${pkg.name}-tests`, 16 | file: `./build/browser/test-bundle.es.js`, 17 | intro: ` 18 | 'use strict' 19 | describe('${pkg.name}', function() { 20 | `, 21 | outro: '});' 22 | }, 23 | plugins: [alias({ '@pollyjs-tests': testsPath }), multiEntry()] 24 | }, 25 | /* target override */ 26 | { 27 | browsers: ['last 2 Chrome versions'] 28 | } 29 | ), 30 | options 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /scripts/rollup/default.config.js: -------------------------------------------------------------------------------- 1 | import createBrowserConfig from './browser.config'; 2 | import createNodeConfig from './node.config'; 3 | 4 | export default [createBrowserConfig(), createNodeConfig()]; 5 | -------------------------------------------------------------------------------- /scripts/rollup/jest.test.config.js: -------------------------------------------------------------------------------- 1 | import deepmerge from 'deepmerge'; 2 | 3 | import createNodeTestConfig from './node.test.config'; 4 | import { pkg } from './utils'; 5 | 6 | export default function createJestTestConfig(options = {}) { 7 | return deepmerge( 8 | createNodeTestConfig({ 9 | input: 'tests/jest/**/*-test.js', 10 | output: { 11 | format: 'cjs', 12 | name: `${pkg.name}-tests`, 13 | file: `./build/jest/test-bundle.cjs.js`, 14 | intro: `describe('${pkg.name}', function() {`, 15 | outro: '});' 16 | } 17 | }), 18 | options 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /scripts/rollup/node.config.js: -------------------------------------------------------------------------------- 1 | import deepmerge from 'deepmerge'; 2 | import json from 'rollup-plugin-json'; 3 | import babel from 'rollup-plugin-babel'; 4 | import { terser } from 'rollup-plugin-terser'; 5 | import commonjs from 'rollup-plugin-commonjs'; 6 | import resolve from 'rollup-plugin-node-resolve'; 7 | 8 | import { input, output, pkg, minify } from './utils'; 9 | 10 | const external = Object.keys(pkg.dependencies || {}); 11 | 12 | export default function createNodeConfig(options = {}) { 13 | return deepmerge( 14 | { 15 | input, 16 | output: [output('cjs'), output('es')], 17 | external, 18 | plugins: [ 19 | json(), 20 | resolve({ preferBuiltins: true }), 21 | commonjs(), 22 | babel({ 23 | babelrc: false, 24 | runtimeHelpers: true, 25 | exclude: ['../../../node_modules/**'], 26 | presets: [ 27 | [ 28 | '@babel/preset-env', 29 | { 30 | modules: false, 31 | targets: { 32 | node: '12.0.0' 33 | } 34 | } 35 | ] 36 | ], 37 | plugins: [ 38 | '@babel/plugin-external-helpers', 39 | ['@babel/plugin-transform-runtime', { corejs: 2 }], 40 | ['@babel/plugin-proposal-object-rest-spread', { useBuiltIns: true }] 41 | ] 42 | }), 43 | minify && terser() 44 | ] 45 | }, 46 | options 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /scripts/rollup/node.test.config.js: -------------------------------------------------------------------------------- 1 | import deepmerge from 'deepmerge'; 2 | import multiEntry from 'rollup-plugin-multi-entry'; 3 | import alias from 'rollup-plugin-alias'; 4 | 5 | import createNodeConfig from './node.config'; 6 | import { pkg, testsPath } from './utils'; 7 | 8 | const pollyDependencies = Object.keys(pkg.devDependencies || {}).filter((d) => 9 | d.startsWith('@pollyjs') 10 | ); 11 | 12 | export default function createNodeTestConfig(options = {}) { 13 | return deepmerge( 14 | createNodeConfig({ 15 | input: 'tests/!(browser|jest)/**/*-test.js', 16 | output: { 17 | format: 'cjs', 18 | name: `${pkg.name}-tests`, 19 | file: `./build/node/test-bundle.cjs.js`, 20 | intro: `describe('${pkg.name}', function() {`, 21 | outro: '});' 22 | }, 23 | plugins: [alias({ '@pollyjs-tests': testsPath }), multiEntry()], 24 | external: [...pollyDependencies, 'node-fetch', 'chai'] 25 | }), 26 | options 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /scripts/rollup/utils.js: -------------------------------------------------------------------------------- 1 | /* globals require process */ 2 | 3 | import path from 'path'; 4 | 5 | export const pkg = require(path.resolve(process.cwd(), './package.json')); 6 | export const production = process.env.NODE_ENV === 'production'; 7 | export const minify = process.env.MINIFY === 'true'; 8 | 9 | const banner = `/** 10 | * ${pkg.name} v${pkg.version} 11 | * 12 | * https://github.com/netflix/pollyjs 13 | * 14 | * Released under the ${pkg.license} License. 15 | */`; 16 | 17 | export const input = './src/index.js'; 18 | export const output = (format) => { 19 | return { 20 | format, 21 | file: `./dist/${format}/${pkg.name.replace('@pollyjs/', 'pollyjs-')}.${ 22 | minify ? 'min.js' : 'js' 23 | }`, 24 | sourcemap: production, 25 | banner 26 | }; 27 | }; 28 | 29 | export const testsPath = path.resolve(process.cwd(), '../../../tests'); 30 | -------------------------------------------------------------------------------- /testem.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | const attachMiddleware = require('./tests/middleware'); 3 | 4 | module.exports = { 5 | port: 4000, 6 | fail_on_zero_tests: true, 7 | test_page: 'tests/index.mustache', 8 | launch_in_ci: ['Chrome', 'Node', 'Jest', 'Ember', 'ESLint'], 9 | launch_in_dev: ['Chrome', 'Node', 'Jest', 'Ember', 'ESLint'], 10 | watch_files: [ 11 | './scripts/rollup/*', 12 | './packages/@pollyjs/*/build/**/*', 13 | './packages/@pollyjs/*/dist/**/*' 14 | ], 15 | serve_files: ['./packages/@pollyjs/*/build/browser/*.js'], 16 | browser_args: { 17 | Chrome: { 18 | ci: [ 19 | // --no-sandbox is needed when running Chrome inside a container 20 | process.env.CI ? '--no-sandbox' : null, 21 | '--headless', 22 | '--disable-gpu', 23 | '--disable-dev-shm-usage', 24 | '--disable-software-rasterizer', 25 | '--mute-audio', 26 | '--remote-debugging-port=0', 27 | '--window-size=1440,900' 28 | ].filter(Boolean) 29 | } 30 | }, 31 | middleware: [attachMiddleware], 32 | launchers: { 33 | 'Node:debug': { 34 | command: 'mocha --inspect-brk' 35 | }, 36 | Node: { 37 | command: 'mocha --reporter tap', 38 | protocol: 'tap' 39 | }, 40 | Jest: { 41 | command: 'jest', 42 | protocol: 'tap' 43 | }, 44 | Ember: { 45 | command: 'yarn workspace @pollyjs/ember run test', 46 | protocol: 'tap' 47 | }, 48 | ESLint: { 49 | command: 'yarn lint --format tap', 50 | protocol: 'tap' 51 | } 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /tests/assets/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/pollyjs/d031861625f58423169b396e5afc8a0110d1b232/tests/assets/32x32.png -------------------------------------------------------------------------------- /tests/helpers/file.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Special thanks to the FormData project. 3 | * Full credit: https://github.com/jimmywarting/FormData (MIT) 4 | */ 5 | 6 | const { defineProperties, defineProperty } = Object; 7 | const stringTag = Symbol && Symbol.toStringTag; 8 | 9 | let _File; 10 | 11 | try { 12 | new File([], ''); 13 | _File = File; 14 | } catch (e) { 15 | /** 16 | * @see http://www.w3.org/TR/FileAPI/#dfn-file 17 | * @param {!Array=} chunks 18 | * @param {string=} filename 19 | * @param {{type: (string|undefined), lastModified: (number|undefined)}=} 20 | * opts 21 | * @constructor 22 | * @extends {Blob} 23 | */ 24 | _File = function File(chunks, filename, opts = {}) { 25 | const _this = new Blob(chunks, opts); 26 | const modified = 27 | opts && opts.lastModified !== undefined 28 | ? new Date(opts.lastModified) 29 | : new Date(); 30 | 31 | defineProperties(_this, { 32 | name: { 33 | value: filename 34 | }, 35 | lastModifiedDate: { 36 | value: modified 37 | }, 38 | lastModified: { 39 | value: +modified 40 | }, 41 | toString: { 42 | value() { 43 | return '[object File]'; 44 | } 45 | } 46 | }); 47 | 48 | if (stringTag) { 49 | defineProperty(_this, Symbol.toStringTag, { 50 | value: 'File' 51 | }); 52 | } 53 | 54 | return _this; 55 | }; 56 | } 57 | 58 | export default _File; 59 | -------------------------------------------------------------------------------- /tests/helpers/global-node-fetch.js: -------------------------------------------------------------------------------- 1 | import fetch, { Response, Request, Headers } from 'node-fetch'; 2 | 3 | global.fetch = fetch; 4 | global.Request = Request; 5 | global.Response = Response; 6 | global.Headers = Headers; 7 | -------------------------------------------------------------------------------- /tests/helpers/setup-fetch-record.js: -------------------------------------------------------------------------------- 1 | const defaultOptions = { 2 | host: '', 3 | fetch() { 4 | return global.fetch(...arguments); 5 | } 6 | }; 7 | 8 | function setupFetchRecord(options) { 9 | setupFetchRecord.beforeEach(options); 10 | setupFetchRecord.afterEach(); 11 | } 12 | 13 | setupFetchRecord.beforeEach = function (options = {}) { 14 | options = { ...defaultOptions, ...options }; 15 | 16 | beforeEach(function () { 17 | const { host, fetch } = options; 18 | 19 | this.fetch = fetch; 20 | 21 | this.relativeFetch = (url, options) => this.fetch(`${host}${url}`, options); 22 | 23 | this.recordUrl = () => 24 | `${host}/api/db/${encodeURIComponent(this.polly.recordingId)}`; 25 | 26 | this.fetchRecord = (...args) => this.fetch(this.recordUrl(), ...args); 27 | }); 28 | }; 29 | 30 | setupFetchRecord.afterEach = function () { 31 | afterEach(async function () { 32 | // Note: test setup could fail, so we cannot assume this.polly 33 | // was setup before accessing it. 34 | if (this.polly) { 35 | this.polly.pause(); 36 | await this.fetchRecord({ method: 'DELETE' }); 37 | this.polly.play(); 38 | } 39 | }); 40 | }; 41 | 42 | export default setupFetchRecord; 43 | -------------------------------------------------------------------------------- /tests/helpers/setup-persister.js: -------------------------------------------------------------------------------- 1 | function setupPersister() { 2 | setupPersister.beforeEach(); 3 | setupPersister.afterEach(); 4 | } 5 | 6 | setupPersister.beforeEach = function () {}; 7 | 8 | setupPersister.afterEach = function () { 9 | afterEach(async function () { 10 | await this.polly.persister.deleteRecording(this.polly.recordingId); 11 | }); 12 | }; 13 | 14 | export default setupPersister; 15 | -------------------------------------------------------------------------------- /tests/index.mustache: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Polly.JS Tests 5 | 6 | 7 | {{#css_files}} 8 | 9 | {{/css_files}} 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 22 | 23 | 24 | {{#serve_files}} 25 | 26 | {{/serve_files}} 27 | 28 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /tests/integration/adapter-node-tests.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | export default function adapterNodeTests() { 4 | it('should handle recording requests posting a Buffer', async function () { 5 | const { server, recordingName } = this.polly; 6 | const buffer = Buffer.from(recordingName); 7 | const url = `http://example.com/upload`; 8 | 9 | server.post(url).intercept((req, res) => { 10 | const body = req.identifiers.body; 11 | 12 | // Make sure the buffer exists in the identifiers 13 | expect(body).to.include(buffer.toString()); 14 | 15 | res.sendStatus(200); 16 | }); 17 | 18 | const res = await this.fetch(url, { method: 'POST', body: buffer }); 19 | 20 | expect(res.status).to.equal(200); 21 | }); 22 | 23 | it('should handle recording requests posting an ArrayBuffer', async function () { 24 | const { server } = this.polly; 25 | const buffer = new ArrayBuffer(8); 26 | const url = `http://example.com/upload`; 27 | 28 | server.post(url).intercept((req, res) => { 29 | const body = req.identifiers.body; 30 | 31 | // Make sure the buffer exists in the identifiers 32 | expect(body).to.include(Buffer.from(buffer).toString()); 33 | 34 | res.sendStatus(200); 35 | }); 36 | 37 | const res = await this.fetch(url, { method: 'POST', body: buffer }); 38 | 39 | expect(res.status).to.equal(200); 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /tests/integration/adapter-polly-tests.js: -------------------------------------------------------------------------------- 1 | export default function pollyTests() { 2 | it('should not handle any requests when paused', async function () { 3 | const { server } = this.polly; 4 | const requests = []; 5 | 6 | server.any().on('request', (req) => requests.push(req)); 7 | 8 | await this.fetchRecord(); 9 | await this.fetchRecord(); 10 | 11 | this.polly.pause(); 12 | await this.fetchRecord(); 13 | await this.fetchRecord(); 14 | 15 | this.polly.play(); 16 | await this.fetchRecord(); 17 | 18 | expect(requests.length).to.equal(3); 19 | expect(this.polly._requests.length).to.equal(3); 20 | expect(requests.map((r) => r.order)).to.deep.equal([0, 1, 2]); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /tests/middleware.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | const path = require('path'); 3 | 4 | const bodyParser = require('body-parser'); 5 | const compression = require('compression'); 6 | 7 | const { registerExpressAPI } = require('../packages/@pollyjs/node-server'); 8 | 9 | const DB = {}; 10 | 11 | module.exports = function attachMiddleware(app) { 12 | registerExpressAPI(app, { 13 | recordingsDir: path.join(__dirname, 'recordings') 14 | }); 15 | 16 | app.get('/assets/:name', (req, res) => { 17 | res.sendFile(path.join(__dirname, 'assets', req.params.name)); 18 | }); 19 | 20 | app.use(bodyParser.json()); 21 | 22 | app.get('/echo', (req, res) => { 23 | const status = req.query.status; 24 | 25 | if (status === '204') { 26 | res.status(204).send(); 27 | } else { 28 | res.sendStatus(req.query.status); 29 | } 30 | }); 31 | 32 | app.post('/compress', compression({ filter: () => true }), (req, res) => { 33 | res.write(JSON.stringify(req.body)); 34 | res.end(); 35 | }); 36 | 37 | app.get('/api', (req, res) => { 38 | res.sendStatus(200); 39 | }); 40 | 41 | app.get('/api/db/:id', (req, res) => { 42 | const { id } = req.params; 43 | 44 | if (DB[id]) { 45 | res.status(200).json(DB[id]); 46 | } else { 47 | res.status(404).end(); 48 | } 49 | }); 50 | 51 | app.post('/api/db/:id', (req, res) => { 52 | const { id } = req.params; 53 | 54 | DB[id] = req.body; 55 | res.status(200).json(DB[id]); 56 | }); 57 | 58 | app.delete('/api/db/:id', (req, res) => { 59 | const { id } = req.params; 60 | 61 | delete DB[id]; 62 | res.status(200).end(); 63 | }); 64 | }; 65 | -------------------------------------------------------------------------------- /tests/node-setup.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | global.expect = require('chai').expect; 4 | --------------------------------------------------------------------------------