├── .npmrc ├── packages ├── parrot-core │ ├── .npmrc │ ├── .babelrc │ ├── parrot-core.png │ ├── src │ │ ├── index.js │ │ ├── utils │ │ │ ├── index.js │ │ │ ├── getParams.js │ │ │ ├── logger.js │ │ │ ├── resolveResponse.js │ │ │ ├── normalizeScenarios.js │ │ │ └── matchMock.js │ │ └── Parrot.js │ ├── jest.setup.js │ ├── package.json │ ├── __tests__ │ │ └── utils │ │ │ ├── logger.spec.js │ │ │ ├── normalizeScenarios.spec.js │ │ │ ├── matchMock.spec.js │ │ │ └── resolveResponse.spec.js │ └── README.md ├── parrot-fetch │ ├── .npmrc │ ├── .babelrc │ ├── parrot-fetch.png │ ├── package.json │ ├── __tests__ │ │ ├── index.spec.js │ │ └── ParrotFetch.spec.js │ ├── src │ │ ├── index.js │ │ └── ParrotFetch.js │ └── README.md ├── parrot-devtools │ ├── .npmrc │ ├── .babelrc │ ├── parrot-devtools.png │ ├── parrot-devtools.zip │ ├── parrot-devtools-extension.zip │ ├── screenshots │ │ ├── has-more-ships.png │ │ ├── has-one-ship.png │ │ └── has-server-error.png │ ├── src │ │ ├── assets │ │ │ └── img │ │ │ │ ├── parrot_128x.png │ │ │ │ ├── parrot_16x.png │ │ │ │ └── parrot_48x.png │ │ ├── browser │ │ │ ├── views │ │ │ │ ├── extension │ │ │ │ │ ├── devtools.html │ │ │ │ │ └── devtool-panel.html │ │ │ │ └── base │ │ │ │ │ └── index.html │ │ │ └── extension │ │ │ │ ├── manifest.json │ │ │ │ ├── devtools.js │ │ │ │ ├── background.js │ │ │ │ └── devtool-panel.js │ │ └── app │ │ │ ├── utils │ │ │ ├── index.js │ │ │ ├── constants.js │ │ │ ├── createStore.js │ │ │ └── localStorage.js │ │ │ ├── components │ │ │ ├── styled │ │ │ │ ├── Logo.jsx │ │ │ │ ├── Grid.jsx │ │ │ │ ├── Scrollable.jsx │ │ │ │ ├── Content.jsx │ │ │ │ ├── Navigation.jsx │ │ │ │ ├── Container.jsx │ │ │ │ ├── ClearButton.jsx │ │ │ │ ├── SearchBar.jsx │ │ │ │ └── index.js │ │ │ ├── ScenariosDisplay.jsx │ │ │ ├── Toolbar.jsx │ │ │ ├── Settings.jsx │ │ │ ├── DevTools.jsx │ │ │ └── Scenarios.jsx │ │ │ ├── hooks │ │ │ ├── index.js │ │ │ ├── useDevTools.js │ │ │ └── useScenarios.js │ │ │ ├── index.js │ │ │ └── ducks │ │ │ └── index.js │ ├── __tests__ │ │ ├── app │ │ │ ├── hooks │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.spec.js.snap │ │ │ │ ├── index.spec.js │ │ │ │ └── useDevTools.spec.js │ │ │ ├── components │ │ │ │ ├── __snapshots__ │ │ │ │ │ ├── Toolbar.spec.jsx.snap │ │ │ │ │ ├── Settings.spec.jsx.snap │ │ │ │ │ ├── ScenariosDisplay.spec.jsx.snap │ │ │ │ │ ├── DevTools.spec.jsx.snap │ │ │ │ │ └── Scenarios.spec.jsx.snap │ │ │ │ ├── Toolbar.spec.jsx │ │ │ │ ├── Settings.spec.jsx │ │ │ │ ├── ScenariosDisplay.spec.jsx │ │ │ │ ├── DevTools.spec.jsx │ │ │ │ └── Scenarios.spec.jsx │ │ │ ├── index.spec.js │ │ │ ├── utils │ │ │ │ ├── createStore.spec.js │ │ │ │ └── localStorage.spec.js │ │ │ └── ducks │ │ │ │ └── index.spec.js │ │ └── browser │ │ │ └── extension │ │ │ ├── devtools.spec.js │ │ │ ├── background.spec.js │ │ │ └── devtool-panel.spec.js │ ├── README.md │ ├── scripts │ │ ├── pack-base.js │ │ ├── zip-and-rm.js │ │ ├── pack-extension.js │ │ └── deploy-to-webstore.js │ ├── webpack.config.babel.js │ └── package.json ├── parrot-friendly │ ├── .npmrc │ ├── .babelrc │ ├── parrot-friendly.png │ ├── package.json │ ├── src │ │ ├── Mock.js │ │ └── index.js │ ├── __tests__ │ │ ├── Mock.spec.js │ │ └── index.spec.js │ └── README.md ├── parrot-graphql │ ├── .npmrc │ ├── parrot-graphql.png │ ├── package.json │ ├── src │ │ └── index.js │ ├── README.md │ ├── __tests__ │ │ └── index.spec.js │ └── package-lock.json ├── parrot-middleware │ ├── .npmrc │ ├── .npmrc copy │ ├── .babelrc │ ├── rollup.config.js │ ├── package.json │ ├── src │ │ ├── index.js │ │ └── ParrotMiddleware.js │ └── __tests__ │ │ ├── index.spec.js │ │ └── ParrotMiddleware.spec.js └── parrot-server │ ├── .npmrc │ ├── parrot-server.png │ ├── README.md │ ├── package.json │ ├── bin │ ├── utils │ │ └── createServer.js │ └── index.js │ ├── __fixtures__ │ └── scenarios.js │ └── __tests__ │ ├── __snapshots__ │ └── createServer.spec.js.snap │ ├── createServer.spec.js │ └── index.spec.js ├── examples └── pirate-ship-app │ ├── .npmrc │ ├── client │ ├── .env │ ├── .gitignore │ ├── public │ │ └── index.html │ ├── package.json │ └── src │ │ ├── index.js │ │ └── App.js │ ├── package.json │ ├── server.js │ └── scenarios.js ├── assets ├── parrot.png └── parrot-devtools.gif ├── .eslintignore ├── CODEOWNERS ├── lerna.json ├── .gitignore ├── husky.config.js ├── .prettierrc.json ├── .github ├── workflows │ ├── greetings.yml │ ├── stale.yml │ ├── test.yml │ ├── publish.yml │ ├── release.yml │ └── deploy-parrot-devtools-to-webstore.yml └── pull_request_template.md ├── babel.config.js ├── jest.setup.js ├── .eslintrc.json ├── commitlint.config.js ├── jest.config.js ├── lockFileLint.js ├── lernaDeploy.js ├── package.json ├── CONTRIBUTING.md ├── SCENARIOS.md └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org -------------------------------------------------------------------------------- /packages/parrot-core/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org -------------------------------------------------------------------------------- /packages/parrot-fetch/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org -------------------------------------------------------------------------------- /examples/pirate-ship-app/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org -------------------------------------------------------------------------------- /examples/pirate-ship-app/client/.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true 2 | -------------------------------------------------------------------------------- /packages/parrot-devtools/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org -------------------------------------------------------------------------------- /packages/parrot-fetch/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["amex"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/parrot-friendly/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org -------------------------------------------------------------------------------- /packages/parrot-graphql/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org -------------------------------------------------------------------------------- /packages/parrot-middleware/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org -------------------------------------------------------------------------------- /packages/parrot-server/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org -------------------------------------------------------------------------------- /packages/parrot-friendly/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["amex"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/parrot-middleware/.npmrc copy: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org -------------------------------------------------------------------------------- /assets/parrot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/americanexpress/parrot/HEAD/assets/parrot.png -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | test-results 2 | examples 3 | **/node_modules/** 4 | **/dist/** 5 | **/lib/** 6 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # https://help.github.com/en/articles/about-code-owners 2 | 3 | * @americanexpress/one -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "5.3.0" 6 | } 7 | -------------------------------------------------------------------------------- /assets/parrot-devtools.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/americanexpress/parrot/HEAD/assets/parrot-devtools.gif -------------------------------------------------------------------------------- /packages/parrot-core/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["amex"], 3 | "plugins": ["@babel/plugin-transform-runtime"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/parrot-devtools/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["amex"], 3 | "plugins": ["@babel/plugin-transform-runtime"] 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_store 2 | node_modules 3 | test-results 4 | .jest-cache 5 | **/*.log 6 | **/node_modules 7 | **/dist 8 | **/lib 9 | -------------------------------------------------------------------------------- /packages/parrot-core/parrot-core.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/americanexpress/parrot/HEAD/packages/parrot-core/parrot-core.png -------------------------------------------------------------------------------- /packages/parrot-fetch/parrot-fetch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/americanexpress/parrot/HEAD/packages/parrot-fetch/parrot-fetch.png -------------------------------------------------------------------------------- /packages/parrot-server/parrot-server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/americanexpress/parrot/HEAD/packages/parrot-server/parrot-server.png -------------------------------------------------------------------------------- /packages/parrot-graphql/parrot-graphql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/americanexpress/parrot/HEAD/packages/parrot-graphql/parrot-graphql.png -------------------------------------------------------------------------------- /packages/parrot-devtools/parrot-devtools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/americanexpress/parrot/HEAD/packages/parrot-devtools/parrot-devtools.png -------------------------------------------------------------------------------- /packages/parrot-devtools/parrot-devtools.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/americanexpress/parrot/HEAD/packages/parrot-devtools/parrot-devtools.zip -------------------------------------------------------------------------------- /packages/parrot-friendly/parrot-friendly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/americanexpress/parrot/HEAD/packages/parrot-friendly/parrot-friendly.png -------------------------------------------------------------------------------- /packages/parrot-middleware/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["amex", { 3 | "preset-env": { 4 | "modules": false 5 | } 6 | }]] 7 | } 8 | -------------------------------------------------------------------------------- /husky.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | hooks: { 3 | 'pre-commit': 'npm run test', 4 | 'commit-msg': 'commitlint -E HUSKY_GIT_PARAMS', 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/parrot-devtools/parrot-devtools-extension.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/americanexpress/parrot/HEAD/packages/parrot-devtools/parrot-devtools-extension.zip -------------------------------------------------------------------------------- /packages/parrot-devtools/screenshots/has-more-ships.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/americanexpress/parrot/HEAD/packages/parrot-devtools/screenshots/has-more-ships.png -------------------------------------------------------------------------------- /packages/parrot-devtools/screenshots/has-one-ship.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/americanexpress/parrot/HEAD/packages/parrot-devtools/screenshots/has-one-ship.png -------------------------------------------------------------------------------- /packages/parrot-devtools/src/assets/img/parrot_128x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/americanexpress/parrot/HEAD/packages/parrot-devtools/src/assets/img/parrot_128x.png -------------------------------------------------------------------------------- /packages/parrot-devtools/src/assets/img/parrot_16x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/americanexpress/parrot/HEAD/packages/parrot-devtools/src/assets/img/parrot_16x.png -------------------------------------------------------------------------------- /packages/parrot-devtools/src/assets/img/parrot_48x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/americanexpress/parrot/HEAD/packages/parrot-devtools/src/assets/img/parrot_48x.png -------------------------------------------------------------------------------- /packages/parrot-devtools/screenshots/has-server-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/americanexpress/parrot/HEAD/packages/parrot-devtools/screenshots/has-server-error.png -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "bracketSpacing": true, 5 | "jsxBracketSameLine": false, 6 | "printWidth": 100 7 | } 8 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/browser/views/extension/devtools.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/parrot-devtools/__tests__/app/hooks/__snapshots__/index.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`hooks should consistently export hooks 1`] = ` 4 | Array [ 5 | Array [ 6 | "useDevTools", 7 | [Function], 8 | ], 9 | Array [ 10 | "useScenarios", 11 | [Function], 12 | ], 13 | ] 14 | `; 15 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/browser/views/base/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/browser/views/extension/devtool-panel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/pirate-ship-app/client/.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 | -------------------------------------------------------------------------------- /packages/parrot-middleware/rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve'; 2 | import babel from '@rollup/plugin-babel'; 3 | 4 | import pkg from './package.json'; 5 | 6 | export default { 7 | external: Object.keys(pkg.dependencies), 8 | input: 'src/index.js', 9 | output: { 10 | format: 'cjs', 11 | dir: 'lib', 12 | preserveModules: true, 13 | }, 14 | plugins: [resolve(), babel()], 15 | }; 16 | -------------------------------------------------------------------------------- /examples/pirate-ship-app/client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Parrot 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/parrot-server/README.md: -------------------------------------------------------------------------------- 1 |

2 | Parrot-Server 3 |

4 | 5 | parrot-server is a CLI that creates and starts up an express app that uses [parrot-middleware](https://github.com/americanexpress/parrot/blob/main/packages/parrot-middleware) 6 | 7 | ## Example 8 | 9 | ```bash 10 | parrot-server --port 3001 --scenarios /path/to/scenarios.js 11 | # could also use shorthand: 12 | parrot-server -p 3001 -s /path/to/scenarios.js 13 | ``` 14 | -------------------------------------------------------------------------------- /packages/parrot-devtools/__tests__/app/components/__snapshots__/Toolbar.spec.jsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Toolbar should render Toolbar component 1`] = ` 4 |
7 | 12 |
17 | `; 18 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/browser/extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Parrot DevTools", 3 | "version": "3.0.0", 4 | "manifest_version": 2, 5 | "description": "DevTools for Parrot", 6 | "devtools_page": "views/devtools.html", 7 | "background": { 8 | "scripts": ["background.bundle.js"] 9 | }, 10 | "permissions": ["tabs", "http://localhost/*"], 11 | "icons": { 12 | "16": "assets/img/parrot_16x.png", 13 | "48": "assets/img/parrot_48x.png", 14 | "128": "assets/img/parrot_128x.png" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 2 | 3 | on: [pull_request, issues] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/first-interaction@v1 10 | with: 11 | repo-token: ${{ secrets.GITHUB_TOKEN }} 12 | issue-message: 'Thanks for opening your first issue. Pull requests are always welcome!' 13 | pr-message: 'Thank you for your contribution to this repository. We will be reviewing your PR. In the meantime, please sign the CLA and make sure all status checks have passed.' 14 | -------------------------------------------------------------------------------- /examples/pirate-ship-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parrot-example-server", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start:client": "cd client && npm run start", 7 | "start:server": "node server.js", 8 | "start": "concurrently --kill-others-on-fail 'npm run start:client' 'npm run start:server'" 9 | }, 10 | "dependencies": { 11 | "express": "^4.21.1", 12 | "parrot-middleware": "^3.1.0" 13 | }, 14 | "devDependencies": { 15 | "@babel/cli": "^7.22.10", 16 | "concurrently": "^5.3.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/parrot-graphql/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parrot-graphql", 3 | "version": "5.3.0", 4 | "contributors": [ 5 | "Jack Cross ", 6 | "Nathan Force ", 7 | "Jason Schapiro" 8 | ], 9 | "description": "A helper library to create GraphQL Parrot mocks.", 10 | "main": "src/index.js", 11 | "license": "Apache-2.0", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/americanexpress/parrot.git", 15 | "directory": "packages/parrot-graphql" 16 | }, 17 | "dependencies": { 18 | "@graphql-tools/mock": "^8.7.20", 19 | "graphql": "^15.8.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/parrot-devtools/__tests__/app/components/__snapshots__/Settings.spec.jsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Settings should render 1`] = ` 4 | 5 |
8 |

11 | Settings 12 |

13 |
16 | 29 |
30 |
31 |
32 | `; 33 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | module.exports = { 18 | presets: ['amex'], 19 | }; 20 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | require('@babel/polyfill'); 18 | require('whatwg-fetch'); 19 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": ["amex", "plugin:prettier/recommended", "prettier/react"], 4 | "overrides": [ 5 | { 6 | "files": ["**/__tests__/**", "**/__mocks__/**", "*.spec.{js,jsx}", "jest.setup.js"], 7 | "extends": ["amex/test", "plugin:prettier/recommended", "prettier/react"], 8 | "rules": { 9 | "import/no-extraneous-dependencies": 0, 10 | "no-restricted-globals": 0 11 | } 12 | }, 13 | { 14 | "files": ["*.md"], 15 | "rules": { 16 | "no-useless-constructor": 0, 17 | "global-require": 0 18 | } 19 | }, 20 | { 21 | "files": ["packages/parrot-devtools/**"], 22 | "env": { 23 | "webextensions": true 24 | } 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /packages/parrot-core/src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | export { default } from './Parrot'; 16 | export getParams from './utils/getParams'; 17 | -------------------------------------------------------------------------------- /packages/parrot-devtools/README.md: -------------------------------------------------------------------------------- 1 |

2 | Parrot-DevTools 3 |

4 | 5 | 6 | ### Chrome Extension 7 | 8 | - Install [the extension](https://chrome.google.com/webstore/detail/parrot-devtools/jckchajdleibnohnphddbiglgpjpbffn). 9 | - Navigate to the Parrot tab of your devtools panel. 10 | 11 | ### Firefox Add-On 12 | 13 | - Install [the add-on](https://github.com/americanexpress/parrot/blob/main/packages/parrot-devtools/parrot-devtools-extension.zip). 14 | - Navigate to the Parrot tab of your devtools panel. 15 | 16 | ### Standalone 17 | 18 | - Download and unzip the [zipped standalone folder](https://github.com/americanexpress/parrot/blob/main/packages/parrot-devtools/parrot-devtools.zip) 19 | - Open the `index.html` file in any browser. 20 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Mark stale issues and pull requests 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | 7 | jobs: 8 | stale: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/stale@v3 14 | with: 15 | repo-token: ${{ secrets.GITHUB_TOKEN }} 16 | stale-issue-message: 'This issue is stale because it has been open 30 days with no activity.' 17 | stale-pr-message: 'This pull request is stale because it has been open 30 days with no activity.' 18 | stale-issue-label: 'stale-issue' 19 | exempt-issue-labels: 'enhancement,documentation,good-first-issue,question' 20 | stale-pr-label: 'stale-pr' 21 | exempt-pr-labels: 'work-in-progress' 22 | days-before-stale: 30 23 | days-before-close: -1 24 | 25 | -------------------------------------------------------------------------------- /examples/pirate-ship-app/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parrot-example-client", 3 | "version": "1.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.14.0", 7 | "react-dom": "^16.14.0" 8 | }, 9 | "scripts": { 10 | "start": "react-scripts start", 11 | "build": "react-scripts build", 12 | "test": "react-scripts test --env=jsdom", 13 | "eject": "react-scripts eject" 14 | }, 15 | "proxy": "http://localhost:3001", 16 | "devDependencies": { 17 | "react-scripts": "^5.0.1" 18 | }, 19 | "browserslist": { 20 | "production": [ 21 | ">0.2%", 22 | "not dead", 23 | "not op_mini all" 24 | ], 25 | "development": [ 26 | "last 1 chrome version", 27 | "last 1 firefox version", 28 | "last 1 safari version" 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/app/utils/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | export * from './constants'; 16 | export * from './createStore'; 17 | export * from './localStorage'; 18 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/app/utils/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | export const DEFAULT_MIDDLEWARE_URL = 'http://localhost:3002'; 16 | export const PARROT_STATE = 'PARROT_STATE'; 17 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/app/components/styled/Logo.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import styled from 'styled-components'; 16 | 17 | export default styled.img` 18 | height: 42.85px; 19 | `; 20 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/app/hooks/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | export { default as useDevTools } from './useDevTools'; 18 | export { default as useScenarios } from './useScenarios'; 19 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/app/components/styled/Grid.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import styled from 'styled-components'; 16 | 17 | export default styled.div` 18 | height: 100%; 19 | display: grid; 20 | `; 21 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/app/components/styled/Scrollable.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import styled from 'styled-components'; 16 | 17 | export default styled.div` 18 | overflow-y: scroll; 19 | height: 100%; 20 | `; 21 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/app/components/styled/Content.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import styled from 'styled-components'; 16 | 17 | export default styled.div` 18 | width: 100%; 19 | height: calc(100% - 97.85px); 20 | `; 21 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/browser/extension/devtools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | // create our devtool tab/panel in the devtools 16 | chrome.devtools.panels.create('Parrot', 'assets/img/parrot_48x.png', 'views/devtool-panel.html'); 17 | -------------------------------------------------------------------------------- /examples/pirate-ship-app/client/src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import React from 'react'; 16 | import ReactDOM from 'react-dom'; 17 | import App from './App'; 18 | 19 | ReactDOM.render(, document.getElementById('root')); 20 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/app/components/styled/Navigation.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import styled from 'styled-components'; 16 | 17 | export default styled.div` 18 | bottom: 0; 19 | width: 100%; 20 | height: 62.85px; 21 | `; 22 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/app/components/styled/Container.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import styled from 'styled-components'; 16 | 17 | export default styled.div` 18 | position: absolute; 19 | width: 100%; 20 | height: 100%; 21 | `; 22 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | module.exports = { 18 | extends: ['@commitlint/config-conventional'], 19 | rules: { 20 | 'scope-case': [2, 'always', ['pascal-case', 'camel-case', 'kebab-case']], 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /examples/pirate-ship-app/server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | const express = require('express'); 16 | const parrot = require('parrot-middleware'); 17 | const scenarios = require('./scenarios'); 18 | 19 | const app = express(); 20 | app.use(parrot(scenarios)); 21 | app.listen(3001); 22 | -------------------------------------------------------------------------------- /packages/parrot-core/src/utils/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | export { default as normalizeScenarios } from './normalizeScenarios'; 16 | export { default as matchMock } from './matchMock'; 17 | export { default as resolveResponse } from './resolveResponse'; 18 | export { default as logger } from './logger'; 19 | -------------------------------------------------------------------------------- /packages/parrot-devtools/__tests__/app/hooks/index.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import * as hooks from '../../../src/app/hooks'; 18 | 19 | describe('hooks', () => { 20 | it('should consistently export hooks', () => { 21 | expect(Object.entries(hooks)).toMatchSnapshot(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | tests: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | node: ['16.x', '18.x' ] 17 | name: Node ${{ matrix.node }} 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | with: 22 | fetch-depth: 0 23 | persist-credentials: false 24 | ref: ${{ github.event.pull_request.head.sha }} 25 | - run: | 26 | git remote set-branches --add origin main 27 | git fetch 28 | - name: Node Install 29 | with: 30 | node-version: ${{ matrix.node }} 31 | uses: actions/setup-node@v1 32 | - name: Installing Packages 33 | env: 34 | NODE_ENV: development 35 | run: npm ci 36 | - name: Unit Tests 37 | run: npm run test 38 | -------------------------------------------------------------------------------- /packages/parrot-devtools/__tests__/app/index.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import ReactDOM from 'react-dom'; 16 | import '../../src/app'; 17 | 18 | jest.mock('react-dom'); 19 | 20 | describe('Renders DevTools', () => { 21 | it('renders app', () => { 22 | expect(ReactDOM.render).toHaveBeenCalled(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/parrot-core/src/utils/getParams.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import { match } from 'path-to-regexp'; 16 | 17 | export default function getParams(path, route) { 18 | const matchRoute = match(route); 19 | const result = matchRoute(path); 20 | if (!result) { 21 | return {}; 22 | } 23 | return result.params; 24 | } 25 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/app/components/styled/ClearButton.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import styled from 'styled-components'; 16 | 17 | const ClearButton = styled.span` 18 | margin-left: -20px; 19 | cursor: pointer; 20 | height: 16px; 21 | `; 22 | ClearButton.displayName = 'button'; 23 | 24 | export default ClearButton; 25 | -------------------------------------------------------------------------------- /packages/parrot-core/jest.setup.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | const localStorageMock = { 18 | getItem: jest.fn(), 19 | setItem: jest.fn(), 20 | removeItem: jest.fn(), 21 | clear: jest.fn(), 22 | }; 23 | global.localStorage = localStorageMock; 24 | // eslint-disable-next-line no-underscore-dangle 25 | global._localStorage = localStorageMock; 26 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/app/hooks/useDevTools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | 19 | export default function useDevTools() { 20 | const [showSettings, setShowSettings] = React.useState(false); 21 | 22 | return { 23 | showSettings, 24 | toggleSettings: () => setShowSettings(state => !state), 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /packages/parrot-friendly/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parrot-friendly", 3 | "version": "5.3.0", 4 | "contributors": [ 5 | "Jack Cross ", 6 | "Nathan Force ", 7 | "Jason Schapiro" 8 | ], 9 | "description": "BDD syntax for writing Parrot scenarios.", 10 | "files": [ 11 | "lib" 12 | ], 13 | "main": "lib", 14 | "license": "Apache-2.0", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/americanexpress/parrot.git", 18 | "directory": "packages/parrot-friendly" 19 | }, 20 | "scripts": { 21 | "clean": "rimraf lib", 22 | "prebuild": "npm run clean", 23 | "build": "babel src --out-dir lib --copy-files", 24 | "prepublish": "npm run build" 25 | }, 26 | "dependencies": { 27 | "parrot-graphql": "^5.3.0" 28 | }, 29 | "devDependencies": { 30 | "@babel/cli": "^7.22.10", 31 | "@babel/core": "^7.22.10", 32 | "babel-preset-amex": "^3.6.1", 33 | "rimraf": "^3.0.2" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/parrot-fetch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parrot-fetch", 3 | "version": "5.3.0", 4 | "contributors": [ 5 | "Jack Cross ", 6 | "Nathan Force ", 7 | "Jason Schapiro" 8 | ], 9 | "description": "A Fetch mocking implementation of Parrot.", 10 | "files": [ 11 | "lib" 12 | ], 13 | "main": "lib", 14 | "license": "Apache-2.0", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/americanexpress/parrot.git", 18 | "directory": "packages/parrot-fetch" 19 | }, 20 | "scripts": { 21 | "clean": "rimraf lib", 22 | "prebuild": "npm run clean", 23 | "build": "babel src --out-dir lib --copy-files", 24 | "prepublish": "npm run build" 25 | }, 26 | "dependencies": { 27 | "parrot-core": "^5.3.0", 28 | "url-parse": "^1.5.10" 29 | }, 30 | "devDependencies": { 31 | "@babel/cli": "^7.22.10", 32 | "@babel/core": "^7.22.10", 33 | "babel-preset-amex": "^3.6.1", 34 | "rimraf": "^3.0.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/app/components/styled/SearchBar.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import styled from 'styled-components'; 16 | 17 | const SearchBar = styled.input` 18 | border-radius: 3px; 19 | outline: none; 20 | border: 1px solid #2874b7; 21 | padding: 0px 0px 0px 5px; 22 | height: 25px; 23 | width: 50%; 24 | `; 25 | SearchBar.displayName = 'input'; 26 | 27 | export default SearchBar; 28 | -------------------------------------------------------------------------------- /packages/parrot-devtools/__tests__/app/components/__snapshots__/ScenariosDisplay.spec.jsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ScenariosDisplay ScenariosDisplay should render ScenariosDisplay component 1`] = ` 4 | 5 | 18 | 19 | 32 | 33 | 34 | `; 35 | -------------------------------------------------------------------------------- /packages/parrot-devtools/scripts/pack-base.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | /* eslint import/no-extraneous-dependencies: 'off' */ 16 | const fs = require('fs-extra'); 17 | const zipAndRm = require('./zip-and-rm'); 18 | 19 | fs.removeSync('./parrot-devtools'); 20 | fs.ensureDirSync('./parrot-devtools'); 21 | fs.copySync('./dist/base', './parrot-devtools'); 22 | fs.copySync('./src/browser/views/base', './parrot-devtools'); 23 | zipAndRm('./parrot-devtools', './parrot-devtools.zip'); 24 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/browser/extension/background.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | // This is necessary because the chrome.tab API is not exposed to the dev panel. 16 | chrome.runtime.onMessage.addListener(({ tabId }, sender, sendResponse) => { 17 | chrome.tabs.get(tabId, ({ url }) => sendResponse({ url })); 18 | 19 | // returning true tells chrome to keep the message port open until 20 | // the async chrome.tabs above returns and sendResponse is called 21 | return true; 22 | }); 23 | -------------------------------------------------------------------------------- /packages/parrot-middleware/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parrot-middleware", 3 | "version": "5.3.0", 4 | "contributors": [ 5 | "Jack Cross ", 6 | "Nathan Force ", 7 | "Jason Schapiro" 8 | ], 9 | "description": "An Express middleware implementation of Parrot.", 10 | "files": [ 11 | "lib" 12 | ], 13 | "main": "lib", 14 | "license": "Apache-2.0", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/americanexpress/parrot.git", 18 | "directory": "packages/parrot-middleware" 19 | }, 20 | "scripts": { 21 | "clean": "rimraf lib", 22 | "prebuild": "npm run clean", 23 | "build": "rollup -c", 24 | "prepublish": "npm run build" 25 | }, 26 | "dependencies": { 27 | "body-parser": "^1.20.2", 28 | "cookie-parser": "^1.4.7", 29 | "express": "^4.18.2", 30 | "parrot-core": "^5.3.0" 31 | }, 32 | "devDependencies": { 33 | "@rollup/plugin-babel": "^5.3.1", 34 | "@rollup/plugin-node-resolve": "^8.4.0", 35 | "babel-preset-amex": "^3.6.1", 36 | "rimraf": "^3.0.2", 37 | "rollup": "^2.79.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/app/components/styled/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | export { default as Container } from './Container'; 16 | export { default as Content } from './Content'; 17 | export { default as Grid } from './Grid'; 18 | export { default as Logo } from './Logo'; 19 | export { default as Navigation } from './Navigation'; 20 | export { default as Scrollable } from './Scrollable'; 21 | export { default as SearchBar } from './SearchBar'; 22 | export { default as ClearButton } from './ClearButton'; 23 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/app/utils/createStore.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | /* eslint import/prefer-default-export: 'off' */ 16 | import { createStore } from 'redux'; 17 | import reducer from '../ducks'; 18 | import { getLocalStorage, setLocalStorage } from './localStorage'; 19 | 20 | export function createLocalStorageStore() { 21 | const state = getLocalStorage(); 22 | const store = createStore(reducer, state); 23 | store.subscribe(() => setLocalStorage(store.getState())); 24 | return store; 25 | } 26 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | module.exports = { 18 | preset: 'amex-jest-preset-react', 19 | collectCoverageFrom: ['packages/*/src/**/*.{js,jsx}'], 20 | moduleNameMapper: { 21 | '\\.png': 'identity-obj-proxy', 22 | react$: '/node_modules/react', 23 | 'react-dom$': '/node_modules/react-dom', 24 | 'react-test-renderer$': '/node_modules/react-test-renderer', 25 | }, 26 | setupFiles: ['./jest.setup.js', './packages/parrot-core/jest.setup.js'], 27 | }; 28 | -------------------------------------------------------------------------------- /lockFileLint.js: -------------------------------------------------------------------------------- 1 | const { spawnSync } = require('child_process'); 2 | // eslint-disable-next-line import/no-extraneous-dependencies 3 | const glob = require('glob'); 4 | 5 | const lockfileGlob = '**/package-lock.json'; 6 | const ignoreGlob = '**/node_modules/**/package-lock.json'; 7 | 8 | const invalidations = []; 9 | 10 | glob(lockfileGlob, { ignore: ignoreGlob }, (globError, lockFiles) => { 11 | if (globError) { 12 | process.stderr.write(globError); 13 | // eslint-disable-next-line unicorn/no-process-exit 14 | process.exit(1); 15 | } 16 | 17 | lockFiles.forEach(lockPath => { 18 | const { stderr } = spawnSync('./node_modules/.bin/lockfile-lint', [ 19 | '-p', 20 | lockPath, 21 | '-t', 22 | 'npm', 23 | '-a', 24 | 'npm', 25 | '-o', 26 | 'https:', 27 | '-c', 28 | '-i', 29 | ]); 30 | const error = stderr.toString(); 31 | if (error) invalidations.push([lockPath, error].join(':\n\n')); 32 | }); 33 | 34 | if (invalidations.length > 0) { 35 | process.stderr.write(invalidations.join('\n')); 36 | // eslint-disable-next-line unicorn/no-process-exit 37 | process.exit(1); 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /packages/parrot-devtools/scripts/zip-and-rm.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | /* eslint import/no-extraneous-dependencies: 'off' */ 16 | const fs = require('fs-extra'); 17 | const archiver = require('archiver'); 18 | 19 | module.exports = (directory, zipFile) => { 20 | const output = fs.createWriteStream(zipFile); 21 | const archive = archiver('zip'); 22 | 23 | output.on('close', () => { 24 | fs.removeSync(directory); 25 | }); 26 | 27 | archive.pipe(output); 28 | archive.directory(directory, false); 29 | archive.finalize(); 30 | }; 31 | -------------------------------------------------------------------------------- /packages/parrot-devtools/__tests__/browser/extension/devtools.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | global.chrome = { 16 | devtools: { 17 | panels: { 18 | create: jest.fn(), 19 | }, 20 | }, 21 | }; 22 | 23 | require('../../../src/browser/extension/devtools'); 24 | 25 | describe('devtools', () => { 26 | it('should setup tab', () => { 27 | expect(global.chrome.devtools.panels.create).toHaveBeenCalledWith( 28 | 'Parrot', 29 | 'assets/img/parrot_48x.png', 30 | 'views/devtool-panel.html' 31 | ); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/parrot-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parrot-core", 3 | "version": "5.3.0", 4 | "contributors": [ 5 | "Jack Cross ", 6 | "Nathan Force ", 7 | "Jason Schapiro" 8 | ], 9 | "description": "Common Parrot functionality.", 10 | "files": [ 11 | "lib" 12 | ], 13 | "main": "lib", 14 | "license": "Apache-2.0", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/americanexpress/parrot.git", 18 | "directory": "packages/parrot-core" 19 | }, 20 | "scripts": { 21 | "clean": "rimraf lib", 22 | "prebuild": "npm run clean", 23 | "build": "babel src --out-dir lib --copy-files", 24 | "prepublish": "npm run build" 25 | }, 26 | "dependencies": { 27 | "@babel/runtime": "^7.22.10", 28 | "chalk": "^1.1.3", 29 | "lodash": "^4.17.21", 30 | "path-to-regexp": "^8.2.0", 31 | "util-inspect": "^0.1.8" 32 | }, 33 | "devDependencies": { 34 | "@babel/cli": "^7.22.10", 35 | "@babel/core": "^7.22.10", 36 | "@babel/plugin-transform-runtime": "^7.22.10", 37 | "babel-preset-amex": "^3.6.1", 38 | "jest-when": "^3.6.0", 39 | "rimraf": "^3.0.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/app/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import 'whatwg-fetch'; 16 | import React from 'react'; 17 | import ReactDOM from 'react-dom'; 18 | import { Provider } from 'react-redux'; 19 | import DevTools from './components/DevTools'; 20 | import { createLocalStorageStore } from './utils'; 21 | 22 | const store = createLocalStorageStore(); 23 | 24 | ReactDOM.render( 25 | // eslint-disable-next-line react/jsx-filename-extension 26 | 27 | 28 | , 29 | document.querySelector('root') 30 | ); 31 | -------------------------------------------------------------------------------- /packages/parrot-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parrot-server", 3 | "version": "5.3.0", 4 | "contributors": [ 5 | "Andres Escobar ", 6 | "Jack Cross ", 7 | "Nathan Force ", 8 | "Jason Schapiro" 9 | ], 10 | "description": "CLI to get a parrot server up and running", 11 | "files": [ 12 | "lib" 13 | ], 14 | "license": "Apache-2.0", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/americanexpress/parrot.git", 18 | "directory": "packages/parrot-server" 19 | }, 20 | "bin": { 21 | "parrot-server": "./lib/index.js" 22 | }, 23 | "scripts": { 24 | "clean": "rimraf lib", 25 | "prebuild": "npm run clean", 26 | "build": "babel bin --out-dir lib --copy-files", 27 | "prepublish": "npm run build" 28 | }, 29 | "dependencies": { 30 | "express": "^4.18.2", 31 | "parrot-middleware": "^5.3.0", 32 | "yargs": "^11.1.1" 33 | }, 34 | "devDependencies": { 35 | "@babel/cli": "^7.22.10", 36 | "@babel/core": "^7.22.10", 37 | "node-fetch": "^2.6.12", 38 | "pretty-format": "^22.4.3", 39 | "rimraf": "^3.0.2", 40 | "supertest": "^3.4.2", 41 | "wait-port": "^0.2.14" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/parrot-devtools/__tests__/app/utils/createStore.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import { createLocalStorageStore, getLocalStorage, setLocalStorage } from '../../../src/app/utils'; 16 | 17 | jest.mock('../../../src/app/utils/localStorage'); 18 | jest.mock('redux', () => ({ 19 | createStore: () => ({ subscribe: fn => fn(), getState: () => null }), 20 | })); 21 | 22 | describe('createStore', () => { 23 | it('reads and writes localStorage', () => { 24 | createLocalStorageStore(); 25 | expect(getLocalStorage).toHaveBeenCalled(); 26 | expect(setLocalStorage).toHaveBeenCalled(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/app/ducks/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import { DEFAULT_MIDDLEWARE_URL } from '../utils/constants'; 16 | 17 | export const SET_URL = 'SET_URL'; 18 | 19 | const initialState = { 20 | url: DEFAULT_MIDDLEWARE_URL, 21 | }; 22 | 23 | export default function reducer(state = initialState, { type, url }) { 24 | switch (type) { 25 | case SET_URL: { 26 | return { 27 | ...state, 28 | url, 29 | }; 30 | } 31 | default: { 32 | return state; 33 | } 34 | } 35 | } 36 | 37 | export const setUrl = url => ({ 38 | type: SET_URL, 39 | url, 40 | }); 41 | -------------------------------------------------------------------------------- /packages/parrot-server/bin/utils/createServer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | const path = require('path'); 16 | const express = require('express'); 17 | const parrot = require('parrot-middleware'); 18 | 19 | const createServer = pathToScenarios => { 20 | const absolutePathToScenarios = path.isAbsolute(pathToScenarios) 21 | ? pathToScenarios 22 | : path.resolve(pathToScenarios); 23 | const app = express(); 24 | 25 | // eslint-disable-next-line global-require, import/no-dynamic-require 26 | app.use(parrot(require(absolutePathToScenarios))); 27 | 28 | return app; 29 | }; 30 | 31 | module.exports = createServer; 32 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/app/utils/localStorage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import { PARROT_STATE } from './constants'; 16 | 17 | export const getLocalStorage = () => { 18 | try { 19 | const serialized = localStorage.getItem(PARROT_STATE); 20 | if (serialized === null) { 21 | return undefined; 22 | } 23 | return JSON.parse(serialized); 24 | } catch (e) { 25 | return undefined; 26 | } 27 | }; 28 | 29 | export const setLocalStorage = value => { 30 | try { 31 | const serialized = JSON.stringify(value); 32 | localStorage.setItem(PARROT_STATE, serialized); 33 | } catch (e) { 34 | // do nothing 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /packages/parrot-graphql/src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | const { mockServer } = require('@graphql-tools/mock'); 16 | 17 | // eslint-disable-next-line max-params 18 | module.exports = function graphql(path, schema, mocks, preserveResolvers = true) { 19 | const server = mockServer(schema, mocks, preserveResolvers); 20 | return { 21 | request: ({ method }, match) => match({ path }) && (method === 'GET' || method === 'POST'), 22 | response: { 23 | body: ({ method, query: queryString, body }) => { 24 | const { query, variables } = method === 'GET' ? queryString : body; 25 | return server.query(query, variables); 26 | }, 27 | }, 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Manually Publish 2 | 3 | on: workflow_dispatch 4 | jobs: 5 | info: 6 | name: Check commit 7 | runs-on: ubuntu-latest 8 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 9 | steps: 10 | - id: commit 11 | run: echo "message=${{ github.event.head_commit.message }}" >> $GITHUB_OUTPUT 12 | outputs: 13 | commitMsg: ${{ steps.commit.outputs.message }} 14 | publish: 15 | name: Publish 16 | needs: [info] 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | with: 22 | fetch-depth: 0 23 | persist-credentials: false 24 | ref: main 25 | - name: Node Install 26 | uses: actions/setup-node@v1 27 | with: 28 | node-version: '18' 29 | - name: Installing Packages 30 | env: 31 | NODE_ENV: development 32 | run: npm ci 33 | - name: Testing Packages 34 | run: npm test 35 | - name: Releasing Packages 36 | env: 37 | NODE_ENV: production 38 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 39 | GITHUB_TOKEN: ${{ secrets.PA_TOKEN }} 40 | run: |- 41 | echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" >> $HOME/.npmrc 2> /dev/null 42 | npm run lerna:publish 43 | -------------------------------------------------------------------------------- /packages/parrot-devtools/__tests__/browser/extension/background.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | const sendResponse = jest.fn(); 16 | 17 | global.chrome = { 18 | runtime: { 19 | onMessage: { 20 | addListener: jest.fn(cb => cb({}, null, sendResponse)), 21 | }, 22 | }, 23 | tabs: { 24 | get: jest.fn((tabId, cb) => cb({})), 25 | }, 26 | }; 27 | 28 | require('../../../src/browser/extension/background'); 29 | 30 | describe('devtools', () => { 31 | it('should setup tab', () => { 32 | expect(global.chrome.runtime.onMessage.addListener).toHaveBeenCalled(); 33 | expect(global.chrome.tabs.get).toHaveBeenCalled(); 34 | expect(sendResponse).toHaveBeenCalled(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/parrot-devtools/scripts/pack-extension.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | /* eslint import/no-extraneous-dependencies: 'off' */ 16 | const fs = require('fs-extra'); 17 | const zipAndRm = require('./zip-and-rm'); 18 | 19 | fs.removeSync('./parrot-devtools-extension'); 20 | fs.ensureDirSync('./parrot-devtools-extension'); 21 | fs.copySync('./dist/extension', './parrot-devtools-extension'); 22 | fs.copySync('./src/browser/views/extension', './parrot-devtools-extension/views'); 23 | fs.copySync('./src/assets', './parrot-devtools-extension/assets'); 24 | fs.copySync('./src/browser/extension/manifest.json', './parrot-devtools-extension/manifest.json'); 25 | zipAndRm('./parrot-devtools-extension', './parrot-devtools-extension.zip'); 26 | -------------------------------------------------------------------------------- /packages/parrot-fetch/__tests__/index.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import parrotFetch from '../src'; 16 | 17 | describe('parrot-fetch', () => { 18 | it('should mock fetch', () => { 19 | const contextFetch = fetch; 20 | parrotFetch(); 21 | expect(fetch).not.toBe(contextFetch); 22 | }); 23 | it('should mock fetchClient', () => { 24 | const fetchWrapper = { 25 | fetchClient: fetch, 26 | }; 27 | const scenarios = { 28 | one: [ 29 | { 30 | request: () => 'test', 31 | response: () => 'response', 32 | }, 33 | ], 34 | }; 35 | parrotFetch(scenarios, fetchWrapper); 36 | expect(fetch).not.toBe(fetchWrapper.fetchClient); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/parrot-friendly/src/Mock.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | export default class Mock { 16 | constructor(structure) { 17 | this.structure = structure; 18 | } 19 | 20 | query = value => { 21 | this.structure.request.query = value; 22 | return this; 23 | }; 24 | 25 | headers = value => { 26 | this.structure.request.headers = value; 27 | return this; 28 | }; 29 | 30 | response = value => { 31 | this.structure.response.body = value; 32 | return this; 33 | }; 34 | 35 | delay = value => { 36 | this.structure.response.delay = value; 37 | return this; 38 | }; 39 | 40 | status = value => { 41 | this.structure.response.status = value; 42 | return this; 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /lernaDeploy.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | 15 | const { exec } = require('child_process'); 16 | // This regex was obtained from https://semver.org/ test it out here https://regex101.com/r/vkijKf/1/ 17 | const regex = /^chore\(release\): (0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/gm; 18 | const commitMessage = process.argv[2]; // This is the commit message 19 | if (commitMessage != null && regex.test(commitMessage)) { 20 | exec('npm run lerna:publish').stderr.pipe(process.stderr); 21 | } else { 22 | // eslint-disable-next-line no-console 23 | console.log('Lerna Deploy: No valid release commit detected'); 24 | } 25 | -------------------------------------------------------------------------------- /packages/parrot-devtools/__tests__/browser/extension/devtool-panel.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | global.chrome = { 16 | runtime: { 17 | sendMessage: jest.fn((obj, fn) => fn()), 18 | }, 19 | devtools: { 20 | inspectedWindow: { 21 | tabId: 'squawk', 22 | }, 23 | }, 24 | }; 25 | 26 | const ReactDOM = require('react-dom'); 27 | require('../../../src/browser/extension/devtool-panel'); 28 | 29 | jest.mock('react-dom'); 30 | 31 | describe('Renders DevTools', () => { 32 | it('renders app', () => { 33 | expect(ReactDOM.render).toHaveBeenCalled(); 34 | expect(global.chrome.runtime.sendMessage).toHaveBeenCalledWith( 35 | { tabId: 'squawk' }, 36 | expect.any(Function) 37 | ); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /packages/parrot-devtools/__tests__/app/ducks/index.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import reducer, { setUrl, SET_URL } from '../../../src/app/ducks'; 16 | import { DEFAULT_MIDDLEWARE_URL } from '../../../src/app/utils'; 17 | 18 | describe('ducks', () => { 19 | it('should create SET_URL action', () => { 20 | expect(setUrl('squawk')).toMatchObject({ 21 | type: SET_URL, 22 | url: 'squawk', 23 | }); 24 | }); 25 | 26 | it('should return state with URL', () => { 27 | expect(reducer({}, { type: SET_URL, url: 'squawk' })).toMatchObject({ url: 'squawk' }); 28 | }); 29 | 30 | it('should return state untouched', () => { 31 | expect(reducer(undefined, {})).toMatchObject({ url: DEFAULT_MIDDLEWARE_URL }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/browser/extension/devtool-panel.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | // eslint-disable-next-line import/no-extraneous-dependencies 16 | import 'whatwg-fetch'; 17 | import React from 'react'; 18 | import ReactDOM from 'react-dom'; 19 | import { Provider } from 'react-redux'; 20 | import DevTools from '../../app/components/DevTools'; 21 | import { createLocalStorageStore } from '../../app/utils'; 22 | 23 | const store = createLocalStorageStore(); 24 | 25 | chrome.runtime.sendMessage({ tabId: chrome.devtools.inspectedWindow.tabId }, () => { 26 | ReactDOM.render( 27 | // eslint-disable-next-line react/jsx-filename-extension 28 | 29 | 30 | , 31 | document.querySelector('root') 32 | ); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/parrot-devtools/__tests__/app/components/__snapshots__/DevTools.spec.jsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`DevTools component hides settings when false 1`] = ` 4 | 7 | 10 | 11 | 12 | 15 | 18 | 21 | 29 | 30 | or updating the middleware URL in settings. 31 | 32 |

33 | 34 |
35 | `; 36 | 37 | exports[`Scenarios ScenariosMiddleware should loading state 1`] = ` 38 | 39 |
42 | 43 | `; 44 | 45 | exports[`Scenarios ScenariosMiddleware should render scenarios middleware component 1`] = ` 46 | 49 |
    52 |
  • 56 | 64 |
  • 65 |
  • 69 | 77 |
  • 78 |
79 |
80 | `; 81 | -------------------------------------------------------------------------------- /packages/parrot-fetch/src/ParrotFetch.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import Parrot from 'parrot-core'; 16 | import parse from 'url-parse'; 17 | 18 | class ParrotFetch extends Parrot { 19 | constructor(scenarios, contextFetch) { 20 | super(scenarios); 21 | this.contextFetch = contextFetch; 22 | } 23 | 24 | normalizeRequest = (input, { method = 'GET', ...init } = {}) => { 25 | const { pathname: path, query, ...parsed } = parse(input, true); 26 | const headers = new Headers(init.headers); 27 | let { body } = init; 28 | if (headers.get('Content-Type') === 'application/json') { 29 | body = JSON.parse(body); 30 | } 31 | return { 32 | ...init, 33 | ...parsed, 34 | body, 35 | path, 36 | query: Object.keys(query) && query, 37 | method: method && method.toUpperCase(), 38 | }; 39 | }; 40 | 41 | resolver = (input, init) => response => { 42 | if (!response) { 43 | return this.contextFetch(input, init); 44 | } 45 | const { body, status } = response; 46 | const responseBlob = new Blob([JSON.stringify(body)], { 47 | type: 'application/json', 48 | }); 49 | const responseOptions = { 50 | status, 51 | headers: { 52 | 'Content-Type': 'application/json', 53 | }, 54 | }; 55 | return Promise.resolve(new Response(responseBlob, responseOptions)); 56 | }; 57 | } 58 | 59 | export default ParrotFetch; 60 | -------------------------------------------------------------------------------- /packages/parrot-server/__tests__/__snapshots__/createServer.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`createServer uses parrot middleware 1`] = ` 4 | "Array [ 5 | Object { 6 | \\"mocks\\": Array [ 7 | Object { 8 | \\"request\\": Object { 9 | \\"method\\": \\"GET\\", 10 | \\"path\\": \\"/ship_log\\", 11 | }, 12 | \\"response\\": Object { 13 | \\"body\\": Array [ 14 | Object { 15 | \\"captain\\": \\"Captain Hook\\", 16 | \\"name\\": \\"The Jolly Roger\\", 17 | }, 18 | ], 19 | \\"status\\": 200, 20 | }, 21 | }, 22 | ], 23 | \\"name\\": \\"has one ship\\", 24 | }, 25 | Object { 26 | \\"mocks\\": Array [ 27 | Object { 28 | \\"request\\": Object { 29 | \\"method\\": \\"GET\\", 30 | \\"path\\": \\"/ship_log\\", 31 | }, 32 | \\"response\\": Object { 33 | \\"body\\": Array [ 34 | Object { 35 | \\"captain\\": \\"Captain Hook\\", 36 | \\"name\\": \\"The Jolly Roger\\", 37 | }, 38 | Object { 39 | \\"captain\\": \\"Jack Sparrow\\", 40 | \\"name\\": \\"The Black Pearl\\", 41 | }, 42 | Object { 43 | \\"captain\\": \\"Davy Jones\\", 44 | \\"name\\": \\"Flying Dutchman\\", 45 | }, 46 | Object { 47 | \\"captain\\": \\"Captain Ron\\", 48 | \\"name\\": \\"The Wanderer\\", 49 | }, 50 | ], 51 | \\"status\\": 200, 52 | }, 53 | }, 54 | ], 55 | \\"name\\": \\"has more ships\\", 56 | }, 57 | Object { 58 | \\"mocks\\": Array [ 59 | Object { 60 | \\"request\\": Object { 61 | \\"method\\": \\"GET\\", 62 | \\"path\\": \\"/ship_log\\", 63 | }, 64 | \\"response\\": Object { 65 | \\"status\\": 500, 66 | }, 67 | }, 68 | ], 69 | \\"name\\": \\"has a server error\\", 70 | }, 71 | ]" 72 | `; 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parrot", 3 | "version": "4.0.0", 4 | "license": "Apache-2.0", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/americanexpress/parrot.git" 8 | }, 9 | "contributors": [ 10 | "Jack Cross ", 11 | "Nathan Force ", 12 | "Jason Schapiro", 13 | "Jacob Franklin " 14 | ], 15 | "scripts": { 16 | "postinstall": "lerna bootstrap", 17 | "clean": "git clean -d -X", 18 | "test": "jest && npm run test:git-history", 19 | "pretest": "npm run lint", 20 | "lint": "eslint --ext js,jsx,md ./", 21 | "test:git-history": "commitlint --from origin/main --to HEAD", 22 | "test:lockfile": "node lockFileLint.js", 23 | "posttest": "npm run test:lockfile", 24 | "lerna:version": "lerna version", 25 | "lerna:publish": "lerna publish from-package --yes", 26 | "lerna:deploy": "node lernaDeploy.js" 27 | }, 28 | "devDependencies": { 29 | "@babel/core": "^7.22.10", 30 | "@babel/polyfill": "^7.12.1", 31 | "@commitlint/cli": "^17.7.1", 32 | "@commitlint/config-conventional": "^8.3.6", 33 | "@testing-library/react-hooks": "^7.0.2", 34 | "@wojtekmaj/enzyme-adapter-react-17": "^0.6.7", 35 | "amex-jest-preset-react": "^8.1.0", 36 | "babel-jest": "^24.9.0", 37 | "babel-preset-amex": "^3.6.1", 38 | "enzyme": "^3.11.0", 39 | "enzyme-to-json": "^3.6.2", 40 | "eslint": "^6.8.0", 41 | "eslint-config-amex": "^11.2.0", 42 | "eslint-config-prettier": "^6.15.0", 43 | "eslint-plugin-prettier": "^3.4.1", 44 | "glob": "^7.2.3", 45 | "husky": "^3.1.0", 46 | "identity-obj-proxy": "^3.0.0", 47 | "isomorphic-fetch": "^2.2.1", 48 | "jest": "^27.5.1", 49 | "lerna": "^3.22.1", 50 | "lockfile-lint": "^4.12.0", 51 | "prettier": "^1.19.1", 52 | "react": "^17.0.0", 53 | "react-dom": "^17.0.2", 54 | "rimraf": "^3.0.2", 55 | "whatwg-fetch": "^3.6.17" 56 | }, 57 | "husky": { 58 | "hooks": { 59 | "pre-commit": "npm test", 60 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/parrot-core/__tests__/utils/logger.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | global.console.log = jest.fn(); 16 | 17 | const chalk = require('chalk'); 18 | const { logger } = require('../../src/utils'); 19 | const { loggerColors } = require('../../src/utils/logger'); 20 | 21 | describe('logger', () => { 22 | const testLoggerOutput = (logType, logPrefix) => { 23 | const message = `Test ${logPrefix} Message`; 24 | const scenario = 'happyPath'; 25 | const path = '/test'; 26 | logger.setScenario(scenario); 27 | 28 | logger[logType](message, path); 29 | return ( 30 | `[Parrot] ${chalk.underline(path)}` + 31 | ` ${chalk.dim(`(${scenario})`)}\n\t${loggerColors[logType](`${logPrefix}: ${message}`)}` 32 | ); 33 | }; 34 | 35 | it('can set a scenario', () => { 36 | const scenario = 'bigError'; 37 | logger.setScenario(scenario); 38 | expect(logger.scenario).toEqual(scenario); 39 | }); 40 | 41 | it('can log an info message', () => { 42 | const expected = testLoggerOutput('info', 'Info'); 43 | expect(global.console.log).toHaveBeenCalledWith(expected); 44 | }); 45 | 46 | it('can log a warning message', () => { 47 | const expected = testLoggerOutput('warn', 'Warning'); 48 | expect(global.console.log).toHaveBeenCalledWith(expected); 49 | }); 50 | 51 | it('can log an error message', () => { 52 | const expected = testLoggerOutput('error', 'Error'); 53 | expect(global.console.log).toHaveBeenCalledWith(expected); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /packages/parrot-friendly/__tests__/Mock.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import Mock from '../src/Mock'; 16 | 17 | describe('Mock', () => { 18 | it('creates initial mock structure', () => { 19 | const mock = new Mock('ahoy'); 20 | expect(mock.structure).toEqual('ahoy'); 21 | }); 22 | 23 | it('adds query', () => { 24 | const mock = new Mock({ request: {} }); 25 | mock.query('ahoy'); 26 | expect(mock.structure).toEqual({ 27 | request: { 28 | query: 'ahoy', 29 | }, 30 | }); 31 | }); 32 | 33 | it('adds headers', () => { 34 | const mock = new Mock({ request: {} }); 35 | mock.headers('ahoy'); 36 | expect(mock.structure).toEqual({ 37 | request: { 38 | headers: 'ahoy', 39 | }, 40 | }); 41 | }); 42 | 43 | it('adds body', () => { 44 | const mock = new Mock({ response: {} }); 45 | mock.response('ahoy'); 46 | expect(mock.structure).toEqual({ 47 | response: { 48 | body: 'ahoy', 49 | }, 50 | }); 51 | }); 52 | 53 | it('adds delay', () => { 54 | const mock = new Mock({ response: {} }); 55 | mock.delay('ahoy'); 56 | expect(mock.structure).toEqual({ 57 | response: { 58 | delay: 'ahoy', 59 | }, 60 | }); 61 | }); 62 | 63 | it('adds status', () => { 64 | const mock = new Mock({ response: {} }); 65 | mock.status('ahoy'); 66 | expect(mock.structure).toEqual({ 67 | response: { 68 | status: 'ahoy', 69 | }, 70 | }); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | The following guidelines must be followed by all contributors to this repository. Please review them carefully and do not hesitate to ask for help. 4 | 5 | ### Code of Conduct 6 | 7 | * Review and test your code before submitting a pull request. 8 | * Be kind and professional. Avoid assumptions; oversights happen. 9 | * Be clear and concise when documenting code; focus on value. 10 | * Don't commit commented code to the main repo (stash locally, if needed). 11 | 12 | See [our code of conduct](./CODE_OF_CONDUCT.md) for more details. 13 | 14 | ### Opening the PR 15 | 16 | * [Fork the Parrot repository](https://github.com/americanexpress/parrot/fork), open a PR to `main`, and follow the guidelines outlined in this document. 17 | 18 | ### Pull Request Guidelines 19 | 20 | * Keep PRs small, there should be one change per pull request. 21 | 22 | * All pull requests must have descriptions and a link to corresponding issue(s) if applicable. 23 | 24 | * Keep [commit history clean](https://americanexpress.io/on-the-importance-of-commit-messages/). Follow commit message guidelines (see below) and squash commits as needed to keep a clean history. Remember that your git commit history should tell a story that should be easy to follow for anyone in the future. 25 | 26 | * Before making substantial changes or changes to core functionality and/or architecture [open up an issue](https://github.com/americanexpress/parrot/issues/new) to propose and discuss the changes. 27 | 28 | * Be patient. The review process will be thorough. It must be kept in mind that changes to our repos are platform wide and thus are not taken lightly. Be prepared to defend every single line of code in your pull request. Attempting to rush changes in will not work. 29 | 30 | 31 | ### Git Commit Guidelines 32 | 33 | We follow precise rules for git commit message formatting. These rules make it easier to review commit logs and improve contextual understanding of code changes. This also allows us to auto-generate the CHANGELOG from commit messages and automatically version Parrot during releases. 34 | 35 | For more information on the commit message guidelines we follow see [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/). 36 | -------------------------------------------------------------------------------- /packages/parrot-server/__tests__/createServer.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | /* eslint-disable global-require */ 16 | import request from 'supertest'; 17 | import prettyFormat from 'pretty-format'; 18 | 19 | describe('createServer', () => { 20 | beforeEach(() => { 21 | jest.resetModules().clearAllMocks(); 22 | }); 23 | 24 | it('can resolve scenarios file from relative path', () => { 25 | const createServer = require('../bin/utils/createServer'); 26 | 27 | expect(() => createServer('./packages/parrot-server/__fixtures__/scenarios.js')).not.toThrow(); 28 | }); 29 | 30 | it('can resolve scenarios file from absolute path', () => { 31 | const createServer = require('../bin/utils/createServer'); 32 | 33 | expect(() => createServer(require.resolve('../__fixtures__/scenarios.js'))).not.toThrow(); 34 | }); 35 | 36 | it('uses parrot middleware', async () => { 37 | const createServer = require('../bin/utils/createServer'); 38 | 39 | const app = createServer(require.resolve('../__fixtures__/scenarios.js')); 40 | const response = await request(app).get('/parrot/scenarios'); 41 | 42 | expect(response.status).toBe(200); 43 | expect(prettyFormat(response.body)).toMatchSnapshot(); 44 | }); 45 | 46 | it('returns an express application', () => { 47 | const mockExpressApp = { use: jest.fn() }; 48 | jest.doMock('express', () => () => mockExpressApp); 49 | const createServer = require('../bin/utils/createServer'); 50 | 51 | const app = createServer(require.resolve('../__fixtures__/scenarios.js')); 52 | expect(app).toEqual(mockExpressApp); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /packages/parrot-core/src/utils/normalizeScenarios.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | function normalizeMock(mock) { 16 | if (typeof mock !== 'function' && (!mock.request || !mock.response)) { 17 | throw new Error('expected mock to be a function'); 18 | } 19 | 20 | if (typeof mock === 'function') { 21 | return mock; 22 | } 23 | 24 | const { request, response } = mock; 25 | 26 | const normalizedRequest = 27 | typeof request === 'string' ? { path: request, method: 'GET' } : request; 28 | 29 | let normalizedResponse = response; 30 | if (!response.status && !response.body && !response.delay) { 31 | normalizedResponse = { 32 | status: 200, 33 | body: response, 34 | }; 35 | } else if (!response.status) { 36 | normalizedResponse = { 37 | status: 200, 38 | ...response, 39 | }; 40 | } 41 | 42 | return { request: normalizedRequest, response: normalizedResponse }; 43 | } 44 | 45 | export default function normalizeScenarios(scenarios) { 46 | const normalizedContainer = Array.isArray(scenarios) 47 | ? scenarios.reduce((acc, { name, mocks }) => ({ ...acc, [name]: mocks }), {}) 48 | : scenarios; 49 | 50 | return Object.keys(normalizedContainer).reduce( 51 | (acc, name) => ({ 52 | ...acc, 53 | [name]: normalizedContainer[name].map((mock, mockIndex) => { 54 | try { 55 | return normalizeMock(mock); 56 | } catch (err) { 57 | throw new Error( 58 | `Mock ${mockIndex} in scenario '${name}' is not an object with keys request and response or a function.` 59 | ); 60 | } 61 | }), 62 | }), 63 | {} 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /packages/parrot-friendly/src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import parrotGraphql from 'parrot-graphql'; 16 | import Mock from './Mock'; 17 | 18 | let scenarios = {}; 19 | let scenario; 20 | 21 | function createMethod(method) { 22 | return path => { 23 | const initialMock = new Mock({ 24 | request: { 25 | method, 26 | path, 27 | }, 28 | response: {}, 29 | }); 30 | scenario.push(initialMock.structure); 31 | return initialMock; 32 | }; 33 | } 34 | 35 | export const get = createMethod('GET'); 36 | export const head = createMethod('HEAD'); 37 | export const post = createMethod('POST'); 38 | export const put = createMethod('PUT'); 39 | export const del = createMethod('DELETE'); 40 | export const connect = createMethod('CONNECT'); 41 | export const options = createMethod('OPTIONS'); 42 | export const patch = createMethod('PATCH'); 43 | 44 | export function mock(structure) { 45 | const initialMock = new Mock(structure); 46 | scenario.push(initialMock.structure); 47 | return initialMock; 48 | } 49 | 50 | export function request(structure) { 51 | const initialMock = new Mock({ 52 | request: structure, 53 | response: {}, 54 | }); 55 | scenario.push(initialMock.structure); 56 | return initialMock; 57 | } 58 | 59 | export function graphql(path, schema, mocks) { 60 | const initialMock = new Mock(parrotGraphql(path, schema, mocks)); 61 | scenario.push(initialMock.structure); 62 | return initialMock; 63 | } 64 | 65 | export function describe(name, block) { 66 | scenarios = {}; 67 | block(); 68 | return scenarios; 69 | } 70 | 71 | export function it(name, block) { 72 | scenarios[name] = []; 73 | scenario = scenarios[name]; 74 | block(); 75 | } 76 | -------------------------------------------------------------------------------- /packages/parrot-middleware/__tests__/index.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import { Router } from 'express'; 16 | import bodyParser from 'body-parser'; 17 | import ParrotMiddleware from '../src/ParrotMiddleware'; 18 | import parrotMiddleware from '../src'; 19 | 20 | jest.mock('express', () => { 21 | const req = { body: { scenario: 'squawk' } }; 22 | const res = { 23 | sendStatus: jest.fn(), 24 | json: jest.fn(), 25 | }; 26 | const post = jest.fn((path, cb) => cb(req, res)); 27 | const get = jest.fn((path, cb) => cb(req, res)); 28 | return { 29 | Router: () => ({ post, get, res }), 30 | }; 31 | }); 32 | jest.mock('body-parser', () => ({ json: jest.fn() })); 33 | jest.mock( 34 | '../src/ParrotMiddleware', 35 | () => 36 | class Mock { 37 | static getActiveScenario = jest.fn(); 38 | 39 | static setActiveScenario = jest.fn(); 40 | 41 | static getScenarios = jest.fn(); 42 | 43 | getActiveScenario = Mock.getActiveScenario; 44 | 45 | setActiveScenario = Mock.setActiveScenario; 46 | 47 | getScenarios = Mock.getScenarios; 48 | } 49 | ); 50 | 51 | describe('parrot-middleware', () => { 52 | it('should set up the middleware', () => { 53 | const middleware = parrotMiddleware(); 54 | const router = Router(); 55 | 56 | expect(bodyParser.json).toHaveBeenCalled(); 57 | expect(router.post).toHaveBeenCalled(); 58 | expect(router.get).toHaveBeenCalled(); 59 | expect(router.res.sendStatus).toHaveBeenCalled(); 60 | expect(router.res.json).toHaveBeenCalled(); 61 | expect(ParrotMiddleware.getActiveScenario).toHaveBeenCalled(); 62 | expect(ParrotMiddleware.setActiveScenario).toHaveBeenCalled(); 63 | expect(ParrotMiddleware.getScenarios).toHaveBeenCalled(); 64 | expect(middleware).toEqual(expect.any(Array)); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /packages/parrot-core/src/Parrot.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import { normalizeScenarios, matchMock, resolveResponse, logger } from './utils'; 16 | 17 | class Parrot { 18 | constructor(scenarios = {}) { 19 | this.scenarios = normalizeScenarios(scenarios); 20 | [this.activeScenario] = Object.keys(scenarios); 21 | logger.setScenario(this.activeScenario); 22 | this.logger = logger; 23 | } 24 | 25 | getActiveScenario = () => this.activeScenario; 26 | 27 | setActiveScenario = name => { 28 | this.activeScenario = name; 29 | logger.setScenario(name); 30 | }; 31 | 32 | getScenarios = () => 33 | Object.keys(this.scenarios).map(name => ({ name, mocks: this.scenarios[name] })); 34 | 35 | setScenarios = scenarios => { 36 | this.scenarios = normalizeScenarios(scenarios); 37 | }; 38 | 39 | getScenario = name => this.scenarios[name]; 40 | 41 | setScenario = (name, mocks) => { 42 | const scenarios = { [name]: mocks }; 43 | this.scenarios = { 44 | ...this.scenarios, 45 | ...normalizeScenarios(scenarios), 46 | }; 47 | }; 48 | 49 | getMock = (name, index) => this.scenarios[name][index]; 50 | 51 | setMock = (name, index, mock) => { 52 | this.scenarios[name][index] = mock; 53 | }; 54 | 55 | resolve = async (...platformRequest) => { 56 | const normalizedRequest = this.normalizeRequest(...platformRequest); 57 | const resolver = this.resolver(...platformRequest); 58 | const activeScenarioOverride = 59 | this.getActiveScenarioOverride && this.getActiveScenarioOverride(...platformRequest); 60 | const mocks = this.scenarios[activeScenarioOverride || this.activeScenario]; 61 | const mock = await matchMock(normalizedRequest, platformRequest, mocks); 62 | return resolveResponse(normalizedRequest, platformRequest, mock, resolver); 63 | }; 64 | } 65 | 66 | export default Parrot; 67 | -------------------------------------------------------------------------------- /packages/parrot-devtools/__tests__/app/components/Scenarios.spec.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | import { shallow } from 'enzyme'; 19 | 20 | import ScenariosMiddleware from '../../../src/app/components/Scenarios'; 21 | 22 | const scenario = 'api-fetch'; 23 | const setScenario = jest.fn(() => Promise.resolve()); 24 | const scenariosData = { 25 | scenario, 26 | setScenario, 27 | loading: false, 28 | filteredScenarios: [{ name: scenario }, { name: 'scenario-2' }], 29 | loadScenarios: jest.fn(() => Promise.resolve()), 30 | }; 31 | const mockProps = { scenariosData }; 32 | 33 | describe('Scenarios', () => { 34 | beforeEach(() => { 35 | jest.clearAllMocks(); 36 | }); 37 | 38 | describe('ScenariosMiddleware', () => { 39 | it('should render scenarios middleware component', () => { 40 | const result = shallow(); 41 | 42 | expect(result).toMatchSnapshot(); 43 | 44 | result 45 | .find('button') 46 | .first() 47 | .simulate('click'); 48 | 49 | expect(setScenario).toHaveBeenCalledWith(scenario); 50 | }); 51 | 52 | it('should loading state', () => { 53 | const props = { 54 | scenariosData: { 55 | ...scenariosData, 56 | loading: true, 57 | }, 58 | }; 59 | const result = shallow(); 60 | 61 | expect(result).toMatchSnapshot(); 62 | }); 63 | 64 | it('should inform the user of failure', () => { 65 | const props = { 66 | scenariosData: { 67 | ...scenariosData, 68 | loading: false, 69 | scenario: '', 70 | scenarios: [], 71 | }, 72 | }; 73 | const result = shallow(); 74 | 75 | expect(result).toMatchSnapshot(); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /packages/parrot-fetch/__tests__/ParrotFetch.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import ParrotFetch from '../src/ParrotFetch'; 16 | 17 | jest.mock('parrot-core', () => class {}); 18 | 19 | describe('ParrotFetch', () => { 20 | it('should normalize', () => { 21 | const parrotFetch = new ParrotFetch(); 22 | const normalized = parrotFetch.normalizeRequest('http://www.parrot.com/squawk?ahoy=matey'); 23 | expect(normalized).toMatchObject({ 24 | path: '/squawk', 25 | query: { 26 | ahoy: 'matey', 27 | }, 28 | protocol: 'http:', 29 | host: 'www.parrot.com', 30 | }); 31 | }); 32 | 33 | it('should parse the body is content-type is json', () => { 34 | const parrotFetch = new ParrotFetch(); 35 | const normalized = parrotFetch.normalizeRequest('http://www.parrot.com/squawk', { 36 | headers: { 37 | 'content-type': 'application/json', 38 | }, 39 | body: JSON.stringify({ ahoy: 'matey' }), 40 | }); 41 | expect(normalized).toMatchObject({ 42 | path: '/squawk', 43 | body: { 44 | ahoy: 'matey', 45 | }, 46 | protocol: 'http:', 47 | host: 'www.parrot.com', 48 | }); 49 | }); 50 | 51 | it('should resolve to context fetch', () => { 52 | const input = 'http://www.parrot.com'; 53 | const contextFetch = jest.fn(() => 'ahoy'); 54 | const parrotFetch = new ParrotFetch({}, contextFetch); 55 | const resolved = parrotFetch.resolver(input)(); 56 | expect(contextFetch).toHaveBeenCalledWith(input, undefined); 57 | expect(resolved).toBe('ahoy'); 58 | }); 59 | 60 | it('should resolve response', async () => { 61 | const input = 'http://www.parrot.com'; 62 | const contextFetch = jest.fn(); 63 | const parrotFetch = new ParrotFetch({}, contextFetch); 64 | const resolved = parrotFetch.resolver(input)({ body: 'ahoy', status: 204 }); 65 | expect(contextFetch).not.toHaveBeenCalled(); 66 | const data = await resolved; 67 | await expect(data.text()).resolves.toBe('"ahoy"'); 68 | expect(data.status).toBe(204); 69 | expect(Object.fromEntries([...data.headers])).toEqual({ 70 | 'content-type': 'application/json', 71 | }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/app/components/Scenarios.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | import PropTypes from 'prop-types'; 19 | 20 | import { Grid, Scrollable } from './styled'; 21 | 22 | function ScenariosMiddleware({ scenariosData }) { 23 | const { loading, filteredScenarios, scenario, setScenario, loadScenarios } = scenariosData; 24 | 25 | if (loading) { 26 | return ( 27 | 28 |
29 | 30 | ); 31 | } 32 | 33 | if (!filteredScenarios || !scenario) { 34 | return ( 35 | 36 |
37 | 38 |

Failed to Load Scenarios

39 |

40 | Try 41 | 44 | or updating the middleware URL in settings. 45 |

46 |
47 |
48 | ); 49 | } 50 | 51 | return ( 52 | 53 |
    54 | {filteredScenarios.map(({ name }) => ( 55 |
  • 56 | 66 |
  • 67 | ))} 68 |
69 |
70 | ); 71 | } 72 | 73 | ScenariosMiddleware.propTypes = { 74 | scenariosData: PropTypes.shape({ 75 | loading: PropTypes.bool, 76 | filteredScenarios: PropTypes.arrayOf( 77 | PropTypes.shape({ 78 | name: PropTypes.string.isRequired, 79 | }) 80 | ), 81 | scenario: PropTypes.string, 82 | setScenario: PropTypes.func, 83 | loadScenarios: PropTypes.func, 84 | }).isRequired, 85 | }; 86 | 87 | export default ScenariosMiddleware; 88 | -------------------------------------------------------------------------------- /packages/parrot-friendly/__tests__/index.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import { describe as parrotDescribe, it as parrotIt, get, mock, request, graphql } from '../src'; 16 | 17 | jest.mock('parrot-graphql', () => () => 'squawk'); 18 | 19 | describe('Friendly methods', () => { 20 | it('describe calls function passed and returns scenarios', () => { 21 | const block = jest.fn(); 22 | const scenarios = parrotDescribe('squawk', block); 23 | expect(scenarios).toEqual({}); 24 | expect(block).toHaveBeenCalled(); 25 | }); 26 | 27 | it('it calls function passed', () => { 28 | const block = jest.fn(); 29 | parrotIt('squawk', block); 30 | expect(block).toHaveBeenCalled(); 31 | }); 32 | 33 | it('HTTP methods return mock object', () => { 34 | const createdMock = get('/polly/wanna/cracker'); 35 | expect(createdMock).toMatchObject({ 36 | structure: expect.any(Object), 37 | query: expect.any(Function), 38 | headers: expect.any(Function), 39 | response: expect.any(Function), 40 | delay: expect.any(Function), 41 | status: expect.any(Function), 42 | }); 43 | }); 44 | 45 | it('mock returns mock object', () => { 46 | const createdMock = mock('squawk'); 47 | expect(createdMock).toMatchObject({ 48 | structure: 'squawk', 49 | query: expect.any(Function), 50 | headers: expect.any(Function), 51 | response: expect.any(Function), 52 | delay: expect.any(Function), 53 | status: expect.any(Function), 54 | }); 55 | }); 56 | 57 | it('request returns mock object', () => { 58 | const createdMock = request('squawk'); 59 | expect(createdMock).toMatchObject({ 60 | structure: { 61 | request: 'squawk', 62 | response: {}, 63 | }, 64 | query: expect.any(Function), 65 | headers: expect.any(Function), 66 | response: expect.any(Function), 67 | delay: expect.any(Function), 68 | status: expect.any(Function), 69 | }); 70 | }); 71 | 72 | it('graphql returns parrotGraphql function', () => { 73 | const createdMock = graphql(); 74 | expect(createdMock).toMatchObject({ 75 | structure: 'squawk', 76 | query: expect.any(Function), 77 | headers: expect.any(Function), 78 | response: expect.any(Function), 79 | delay: expect.any(Function), 80 | status: expect.any(Function), 81 | }); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /packages/parrot-devtools/src/app/hooks/useScenarios.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | 19 | export default function useScenarios(url, initialScenario) { 20 | const [loading, setLoading] = React.useState(false); 21 | const [scenario, setScenario] = React.useState(''); 22 | const [scenarios, setScenarios] = React.useState([]); 23 | 24 | const methods = React.useMemo( 25 | () => ({ 26 | loadScenarios: async () => { 27 | setLoading(true); 28 | try { 29 | const scenariosResponse = await fetch(`${url}/parrot/scenarios`); 30 | const scenarioResponse = await fetch(`${url}/parrot/scenario`); 31 | // eslint-disable-next-line prettier/prettier 32 | setScenario( await scenarioResponse.json() ); 33 | // eslint-disable-next-line prettier/prettier 34 | setScenarios( await scenariosResponse.json() ); 35 | } catch (e) { 36 | // do nothing 37 | } 38 | setLoading(false); 39 | }, 40 | setScenario: async selectedScenario => { 41 | try { 42 | await fetch(`${url}/parrot/scenario`, { 43 | method: 'POST', 44 | headers: { 45 | 'Content-Type': 'application/json', 46 | }, 47 | body: JSON.stringify({ 48 | scenario: selectedScenario, 49 | }), 50 | }); 51 | 52 | setScenario(selectedScenario); 53 | 54 | if ('chrome' in window) { 55 | chrome.devtools.inspectedWindow.reload(null); 56 | } 57 | } catch (e) { 58 | // do nothing 59 | } 60 | }, 61 | }), 62 | [url] 63 | ); 64 | 65 | React.useLayoutEffect(() => { 66 | if (initialScenario) methods.setScenario(initialScenario); 67 | methods.loadScenarios(); 68 | }, [url, initialScenario]); 69 | 70 | const [filteredScenarios, setFilteredScenarios] = React.useState(scenarios); 71 | const [filterValue, setFilterValue] = React.useState(''); 72 | 73 | React.useEffect(() => { 74 | if (filterValue === '') { 75 | setFilteredScenarios(scenarios); 76 | } else { 77 | setFilteredScenarios( 78 | scenarios.filter(({ name }) => name.toLowerCase().includes(filterValue)) 79 | ); 80 | } 81 | }, [filterValue, scenarios]); 82 | 83 | return { 84 | loading, 85 | scenario, 86 | filteredScenarios, 87 | filterValue, 88 | setFilterValue, 89 | ...methods, 90 | }; 91 | } 92 | -------------------------------------------------------------------------------- /packages/parrot-core/src/utils/matchMock.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import util from 'util'; 16 | import isEqual from 'lodash/isEqual'; 17 | import { match } from 'path-to-regexp'; 18 | import logger from './logger'; 19 | 20 | function matchRequest(normalizedRequest) { 21 | return request => 22 | Object.keys(request).every(property => { 23 | if (property === 'path') { 24 | if (request.path instanceof RegExp) { 25 | return request.path.test(normalizedRequest.path); 26 | } 27 | const matchRoute = match(request.path); 28 | const result = matchRoute(normalizedRequest.path); 29 | return result !== false; 30 | } 31 | if (property === 'headers') { 32 | return Object.keys(request.headers).every( 33 | key => request.headers[key] === normalizedRequest.headers[key] 34 | ); 35 | } 36 | 37 | return isEqual(normalizedRequest[property], request[property]); 38 | }); 39 | } 40 | 41 | export default function matchMock(normalizedRequest, platformRequest, mocks) { 42 | let matchedMock; 43 | 44 | if (!Array.isArray(mocks)) { 45 | throw new TypeError(`mocks is not an array as expected. What was passed: ${mocks}`); 46 | } 47 | 48 | if (mocks.length === 0) { 49 | throw new TypeError('mocks is empty, and likely none are defined for the current scenario.'); 50 | } 51 | 52 | for (let index = 0; index < mocks.length; index += 1) { 53 | const mock = mocks[index]; 54 | if (typeof mock === 'function') { 55 | const response = mock( 56 | normalizedRequest, 57 | matchRequest(normalizedRequest), 58 | ...[].concat(platformRequest) 59 | ); 60 | if (response) { 61 | matchedMock = { response }; 62 | logger.info('Matched mock function.', normalizedRequest.path); 63 | break; 64 | } 65 | } else if ( 66 | typeof mock.request === 'function' && 67 | mock.request(normalizedRequest, matchRequest(normalizedRequest)) 68 | ) { 69 | logger.info('Matched request function.', normalizedRequest.path); 70 | matchedMock = mock; 71 | break; 72 | } else if (typeof mock.request === 'object' && matchRequest(normalizedRequest)(mock.request)) { 73 | logger.info( 74 | `Matched request object: ${util.inspect(mock.request, { 75 | colors: true, 76 | breakLength: Infinity, 77 | })}`, 78 | normalizedRequest.path 79 | ); 80 | matchedMock = mock; 81 | break; 82 | } 83 | } 84 | 85 | return matchedMock; 86 | } 87 | -------------------------------------------------------------------------------- /packages/parrot-core/__tests__/utils/normalizeScenarios.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import { normalizeScenarios } from '../../src/utils'; 16 | 17 | describe('normalizeScenarios', () => { 18 | it('normalizes scenarios array', () => { 19 | const scenarios = [ 20 | { 21 | name: 'default', 22 | mocks: [ 23 | { 24 | request: 'squawk', 25 | response: 'squawk', 26 | }, 27 | ], 28 | }, 29 | ]; 30 | const { 31 | default: [ 32 | { 33 | request: { path }, 34 | response: { body }, 35 | }, 36 | ], 37 | } = normalizeScenarios(scenarios); 38 | expect(path).toEqual(scenarios[0].mocks[0].request); 39 | expect(body).toEqual(scenarios[0].mocks[0].response); 40 | }); 41 | 42 | it('normalizes scenarios object', () => { 43 | const scenarios = { 44 | default: [ 45 | { 46 | request: 'squawk', 47 | response: { 48 | body: 'squawk', 49 | delay: 123, 50 | }, 51 | }, 52 | ], 53 | }; 54 | const { 55 | default: [ 56 | { 57 | request: { path }, 58 | response: { body }, 59 | }, 60 | ], 61 | } = normalizeScenarios(scenarios); 62 | expect(path).toEqual(scenarios.default[0].request); 63 | expect(body).toEqual(scenarios.default[0].response.body); 64 | }); 65 | 66 | it('does not throw an error for correct mock object', () => { 67 | const scenarios = { 68 | default: [ 69 | { 70 | request: { 71 | path: 'squawk', 72 | method: 'GET', 73 | }, 74 | response: { 75 | body: 'squawk', 76 | status: 200, 77 | }, 78 | }, 79 | ], 80 | }; 81 | const { 82 | default: [{ request, response }], 83 | } = normalizeScenarios(scenarios); 84 | expect(request).toEqual(scenarios.default[0].request); 85 | expect(response).toEqual({ ...scenarios.default[0].response, status: 200 }); 86 | }); 87 | 88 | it('throws an error for incorrect mock object', () => { 89 | const scenarios = { 90 | default: [{}], 91 | }; 92 | expect(() => normalizeScenarios(scenarios)).toThrow(); 93 | }); 94 | 95 | it('does not throw an error for mock function', () => { 96 | const scenarios = { default: [() => null] }; 97 | const { default: defaultScenario } = normalizeScenarios(scenarios); 98 | expect(defaultScenario).toEqual(scenarios.default); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /packages/parrot-core/__tests__/utils/matchMock.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import { matchMock, logger } from '../../src/utils'; 16 | 17 | jest.mock('../../src/utils/logger', () => ({ 18 | info: jest.fn(), 19 | })); 20 | 21 | describe('matchMock', () => { 22 | it('calls mock function that does not match', () => { 23 | const mocks = [jest.fn(() => false)]; 24 | matchMock({}, {}, mocks); 25 | expect(mocks[0]).toHaveBeenCalled(); 26 | expect(logger.info).not.toHaveBeenCalled(); 27 | }); 28 | 29 | it('calls mock function that matches', () => { 30 | const mocks = [jest.fn(() => true)]; 31 | matchMock({}, {}, mocks); 32 | expect(logger.info).toHaveBeenCalled(); 33 | }); 34 | 35 | it('calls mock request function that matches', () => { 36 | const mocks = [{ request: jest.fn(() => true) }]; 37 | expect(matchMock({}, {}, mocks)).toEqual(mocks[0]); 38 | }); 39 | 40 | it('does not match mock object', () => { 41 | const mocks = [{ request: { path: '/squawk', headers: 'ahoy', 'Keep-Alive': 'timeout=5' } }]; 42 | const req = { path: '/squawk', headers: 'matey', 'Keep-Alive': 'timeout=5' }; 43 | expect(matchMock(req, {}, mocks)).toBe(undefined); 44 | }); 45 | 46 | it('matches mock object', () => { 47 | const mocks = [{ request: { path: '/squawk', headers: 'ahoy', 'Keep-Alive': 'timeout=5' } }]; 48 | const req = { path: '/squawk', headers: 'ahoy', 'Keep-Alive': 'timeout=5' }; 49 | expect(matchMock(req, {}, mocks)).toEqual(mocks[0]); 50 | }); 51 | 52 | it('matches mock object when path variable', () => { 53 | const mocks = [ 54 | { request: { path: '/squawk/:id', headers: 'ahoy', 'Keep-Alive': 'timeout=5' } }, 55 | ]; 56 | const req = { path: '/squawk/123', headers: 'ahoy', 'Keep-Alive': 'timeout=5' }; 57 | expect(matchMock(req, {}, mocks)).toEqual(mocks[0]); 58 | }); 59 | 60 | it('matches mock object when regex path', () => { 61 | const mocks = [ 62 | { request: { path: /^\/squawk\/\d?/, headers: 'ahoy', 'Keep-Alive': 'timeout=5' } }, 63 | ]; 64 | const req = { path: '/squawk/123', headers: 'ahoy', 'Keep-Alive': 'timeout=5' }; 65 | expect(matchMock(req, {}, mocks)).toEqual(mocks[0]); 66 | }); 67 | 68 | it('throws when mocks is not an array', () => { 69 | const mocks = {}; 70 | expect(() => matchMock({}, {}, mocks)).toThrow( 71 | 'mocks is not an array as expected. What was passed: [object Object]' 72 | ); 73 | }); 74 | 75 | it('throws when mocks is an empty array', () => { 76 | const mocks = []; 77 | expect(() => matchMock({}, {}, mocks)).toThrow( 78 | 'mocks is empty, and likely none are defined for the current scenario.' 79 | ); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /packages/parrot-server/__tests__/index.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import { spawn, spawnSync } from 'child_process'; 16 | import fetch from 'node-fetch'; 17 | import waitPort from 'wait-port'; 18 | 19 | const runningParrotServerProcesses = []; 20 | 21 | jest.setTimeout(10000); 22 | 23 | const executeParrotServerCommand = async parrotServerArgs => { 24 | const { portNumber, pathToScenarios } = parrotServerArgs; 25 | 26 | const args = [require.resolve('../bin/index.js'), '--scenarios', pathToScenarios]; 27 | if (portNumber) { 28 | args.push('--port', portNumber); 29 | } 30 | const parrotServerProcess = spawn('node', args); 31 | 32 | /* eslint-disable no-console */ 33 | parrotServerProcess.stdout.on('data', data => console.log(data.toString())); 34 | parrotServerProcess.stderr.on('data', data => console.error(data.toString())); 35 | /* eslint-enable */ 36 | let isParrotUp; 37 | try { 38 | isParrotUp = await waitPort({ port: portNumber || 3001, timeout: 5000, output: 'silent' }); 39 | } catch (error) { 40 | throw new Error(error); 41 | } 42 | if (isParrotUp) { 43 | runningParrotServerProcesses.push(parrotServerProcess); 44 | return parrotServerProcess; 45 | } 46 | 47 | throw new Error('parrot-server did not start up within 5 seconds'); 48 | }; 49 | 50 | it('defaults to port 3001 if --port is not given', async () => { 51 | const args = { pathToScenarios: require.resolve('../__fixtures__/scenarios.js') }; 52 | await executeParrotServerCommand(args); 53 | 54 | const response = await fetch('http://localhost:3001/parrot/scenarios'); 55 | expect(response.status).toBe(200); 56 | }); 57 | 58 | it('starts server on given port', async () => { 59 | const portNumber = 3005; 60 | const args = { 61 | pathToScenarios: require.resolve('../__fixtures__/scenarios.js'), 62 | portNumber, 63 | }; 64 | 65 | await executeParrotServerCommand(args); 66 | 67 | const response = await fetch(`http://localhost:${portNumber}/parrot/scenarios`); 68 | expect(response.status).toBe(200); 69 | }); 70 | 71 | it('exits with status 1 if something goes wrong while starting the server', () => { 72 | expect.assertions(1); 73 | 74 | const args = [ 75 | require.resolve('../bin/index.js'), 76 | '--scenarios', 77 | '../__fixtures__/not-a-valid-scenarios-file.js', 78 | ]; 79 | 80 | const parrotServerProcess = spawnSync('node', args, { timeout: 5000 }); 81 | 82 | /* eslint-disable no-console */ 83 | console.log(parrotServerProcess.stderr.toString()); 84 | console.log(parrotServerProcess.stdout.toString()); 85 | /* eslint-enable */ 86 | 87 | expect(parrotServerProcess.status).toBe(1); 88 | }); 89 | 90 | afterAll(() => { 91 | runningParrotServerProcesses.forEach(process => process.kill()); 92 | }); 93 | -------------------------------------------------------------------------------- /packages/parrot-core/README.md: -------------------------------------------------------------------------------- 1 |

2 | Parrot-Core 3 |

4 | 5 | parrot-core abstracts the matching, logging, and resolving functionality of Parrot away from each implementation. [parrot-middleware](https://github.com/americanexpress/parrot/blob/main/packages/parrot-middleware) and [parrot-fetch](https://github.com/americanexpress/parrot/blob/main/packages/parrot-fetch) use parrot-core and any new implementations could extend parrot-core in a similar way. 6 | 7 | ## Example Implementation 8 | 9 | ```js 10 | import Parrot from 'parrot-core'; 11 | 12 | class ParrotNew extends Parrot { 13 | constructor(scenarios) { 14 | super(scenarios); 15 | // any constructor logic that is needed 16 | } 17 | 18 | normalizeRequest = request => { 19 | // conform incoming requests to match the scenarios structure 20 | }; 21 | 22 | resolver = request => response => { 23 | // resolve the matched response to the implementation platform 24 | }; 25 | } 26 | 27 | export default ParrotNew; 28 | ``` 29 | 30 | ## Access Methods 31 | 32 | parrot-core also defines several methods that can be used to interact with the scenarios that are passed in. 33 | 34 | ### `getActiveScenario()` 35 | 36 | Returns the name of the currently active scenario. 37 | 38 | ### `setActiveScenario(name)` 39 | 40 | Sets the currently active scenario. 41 | 42 | #### Arguments 43 | 44 | - `name` (_String_): Scenario name. 45 | 46 | ### `getScenarios()` 47 | 48 | Returns an array of scenario objects. 49 | 50 | ### `setScenarios(scenarios)` 51 | 52 | Sets `scenarios` as the array of available scenarios. 53 | 54 | #### Arguments 55 | 56 | - `scenarios` (_Array_ or _Object_): Scenarios descriptor. 57 | 58 | ### `getScenario(name)` 59 | 60 | Returns the scenario object with matching `name`. 61 | 62 | #### Arguments 63 | 64 | - `name` (_String_): Scenario name. 65 | 66 | ### `setScenario(name, mocks)` 67 | 68 | Sets the mocks for scenario with matching `name`. 69 | 70 | #### Arguments 71 | 72 | - `name` (_String_): Scenario name. 73 | - `mocks` (_Array_): Array of mock objects. 74 | 75 | ### `getMock(name, index)` 76 | 77 | Returns the mock at `index` for scenario with matching `name`. 78 | 79 | #### Arguments 80 | 81 | - `name` (_String_): Scenario name. 82 | - `index` (_Number_): Mock index. 83 | 84 | ### `setMock(name, index, mock)` 85 | 86 | Sets the mock at `index` for scenario with matching `name`. 87 | 88 | #### Arguments 89 | 90 | - `name` (_String_): Scenario name. 91 | - `index` (_Number_): Mock index. 92 | - `mock` (_Object_): Mock object. 93 | 94 | ## Utility Methods 95 | 96 | ### `getParams(path, route)` 97 | 98 | Extracts the route parameters from a given `path`, using the specified `route`. 99 | 100 | This is useful when using the manual `match` function. 101 | 102 | #### Arguments 103 | 104 | - `path` (_String_): Requested URL path. 105 | - `route` (_String_): [Express-style](https://expressjs.com/en/guide/routing.html) route with route parameters. 106 | 107 | #### Example Usage 108 | 109 | ```javascript 110 | const { getParams } = require('parrot-core'); 111 | 112 | const getBook = ({ url }, match) => { 113 | const path = '/books/:bookId'; 114 | if (match({ path, method: 'GET' })) { 115 | const { bookId } = getParams(url, path); 116 | const requestedBook = books.find(book => book.bookId === bookId); 117 | if (!requestedBook) { 118 | return { status: 404 }; 119 | } 120 | return { status: 200, body: requestedBook }; 121 | } 122 | return null; 123 | }; 124 | ``` 125 | -------------------------------------------------------------------------------- /packages/parrot-core/__tests__/utils/resolveResponse.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import { resolveResponse } from '../../src/utils'; 16 | 17 | describe('resolveResponse', () => { 18 | expect.assertions(1); 19 | 20 | let resolver; 21 | beforeEach(() => { 22 | resolver = jest.fn(); 23 | }); 24 | 25 | it('handles undefined mock', async () => { 26 | resolveResponse({}, {}, undefined, resolver); 27 | 28 | expect(resolver).toHaveBeenCalled(); 29 | }); 30 | 31 | it('handles mock without request', async () => { 32 | expect.assertions(1); 33 | 34 | const mock = { 35 | response: { 36 | body: 'squawk', 37 | }, 38 | }; 39 | await resolveResponse({}, {}, mock, resolver); 40 | 41 | expect(resolver).toHaveBeenCalledWith({ body: 'squawk' }); 42 | }); 43 | 44 | it('sets params', async () => { 45 | expect.assertions(1); 46 | 47 | const mock = { 48 | request: { 49 | path: '/:ahoy', 50 | }, 51 | response: { 52 | body: jest.fn(req => req), 53 | }, 54 | }; 55 | await resolveResponse({ path: '/squawk' }, {}, mock, resolver); 56 | 57 | expect(mock.response.body).toHaveBeenCalledWith( 58 | { 59 | path: '/squawk', 60 | params: { 61 | ahoy: 'squawk', 62 | }, 63 | }, 64 | {} 65 | ); 66 | }); 67 | 68 | it('does not set params when no path params', async () => { 69 | expect.assertions(1); 70 | 71 | const mock = { 72 | request: { 73 | path: '/squawk', 74 | }, 75 | response: { 76 | body: jest.fn(req => req), 77 | }, 78 | }; 79 | await resolveResponse({ path: '/squawk' }, {}, mock, resolver); 80 | 81 | expect(mock.response.body).toHaveBeenCalledWith( 82 | { 83 | path: '/squawk', 84 | params: {}, 85 | }, 86 | {} 87 | ); 88 | }); 89 | 90 | it('does not set params when no match', async () => { 91 | expect.assertions(1); 92 | 93 | const mock = { 94 | request: { 95 | path: '/:ahoy', 96 | }, 97 | response: { 98 | body: jest.fn(req => req), 99 | }, 100 | }; 101 | await resolveResponse({ path: '/' }, {}, mock, resolver); 102 | 103 | expect(mock.response.body).toHaveBeenCalledWith( 104 | { 105 | path: '/', 106 | params: {}, 107 | }, 108 | {} 109 | ); 110 | }); 111 | 112 | it('does not set params when no path', async () => { 113 | expect.assertions(1); 114 | 115 | const mock = { 116 | request: {}, 117 | response: { 118 | body: jest.fn(req => req), 119 | }, 120 | }; 121 | await resolveResponse({}, {}, mock, resolver); 122 | 123 | expect(mock.response.body).toHaveBeenCalledWith({}, {}); 124 | }); 125 | 126 | it('delays resolving response', async () => { 127 | expect.assertions(1); 128 | 129 | global.setTimeout = jest.fn(fn => fn()); 130 | const mock = { 131 | request: {}, 132 | response: { 133 | body: 'squawk', 134 | delay: 1234, 135 | }, 136 | }; 137 | await resolveResponse({}, {}, mock, resolver); 138 | 139 | expect(global.setTimeout).toHaveBeenCalledWith(expect.any(Function), 1234); 140 | }); 141 | }); 142 | -------------------------------------------------------------------------------- /packages/parrot-friendly/README.md: -------------------------------------------------------------------------------- 1 |

2 | Parrot-Friendly 3 |

4 | 5 | parrot-friendly is a helper library that allows you to write [scenarios](https://github.com/americanexpress/parrot/blob/main/SCENARIOS.md) using a more declarative syntax. Based on the behavior driven development (BDD) syntax of libraries such as [Jasmine](https://jasmine.github.io/) and [Mocha](https://mochajs.org/), parrot-friendly provides `describe`, `it`, and other methods to construct your scenarios object. 6 | 7 | ## Example 8 | 9 | ```js 10 | import { describe, it, get } from 'parrot-friendly'; 11 | 12 | const scenarios = describe('Ship Log', () => { 13 | it('should display the ship log', () => { 14 | get('/ship_log') 15 | .response(require('./mocks/shipLog.json')) 16 | .delay(1200); 17 | }); 18 | 19 | it('should show an error', () => { 20 | get('/ship_log').status(500); 21 | }); 22 | }); 23 | 24 | export default scenarios; 25 | ``` 26 | 27 | ## API 28 | 29 | ### `describe(description, scenarioDefinitions)` 30 | 31 | Returns a scenarios object based on the `scenarioDefinitions` declared. 32 | 33 | #### Arguments 34 | 35 | - `description` (_String_): Scenarios object description. Currently this is not used internally but supported to provide a more common API. 36 | - `scenarioDefinitions` (_Function_): Function that will define scenarios when invoked. 37 | 38 | ### `it(description, mockDefinitions)` 39 | 40 | Adds a scenario with key `description` to the scenarios object. 41 | 42 | #### Arugments 43 | 44 | - `description` (_String_): Scenario description that will be used as a key to identify the scenario. Must be unique to a scenarios object. 45 | - `mockDefinitions`: (_Function_): Function that will define mock objects when invoked. 46 | 47 | ### `mock(mockDefinition)` 48 | 49 | Creates a mock for a HTTP request where `mockDefinition` is the entire mock object. This can be used in place of chaining methods such as `query` and `delay`, or to provide custom mock handling with a function. 50 | 51 | #### Arguments 52 | 53 | - `mockDefinition` (_Object_ or _Function_): Mock object with `request` and `response` keys or mock function. 54 | 55 | ### `request(requestDefinition)` 56 | 57 | Creates a mock for a HTTP request where `requestDefinition` is the entire request object. Can be used in place of chaining request methods such as `query` or to provide a custom matching function. 58 | 59 | #### Arguments 60 | 61 | - `requestDefinition` (_Object_ or _Function_): Request object to be matched against or request function returning true for a match and false for a miss. 62 | 63 | ### `METHOD(path)` 64 | 65 | Creates a mock for a HTTP request where METHOD is one of `get`, `head`, `post`, `put`, `del`, `connect`, `options`, `patch`. 66 | 67 | `del` is used in place of `delete` as `delete` is a JavaScript reserved word. 68 | 69 | #### Arguments 70 | 71 | - `path` (_String_): Path matcher string. May include route params. 72 | 73 | #### Methods 74 | 75 | ##### `.query(query)` 76 | 77 | Matches against the `query` object provided. 78 | 79 | ##### `.headers(headers)` 80 | 81 | Matches against the `headers` object provided 82 | 83 | ##### `.response(resource)` 84 | 85 | Responds with the `resource` provided. 86 | 87 | ##### `.delay(amount)` 88 | 89 | Delays the response for `amount` of milliseconds. 90 | 91 | ##### `.status(code)` 92 | 93 | Responds with a `code` status code. 94 | 95 | ### `graphql(path, schema, mocks)` 96 | 97 | Creates a mock for your GraphQL endpoint. 98 | 99 | #### Arguments 100 | 101 | - `path` (_String_): Path of your GraphQL endpoint. 102 | - `schema` (_String_): GraphQL schema string. 103 | - `mocks` (_Object_): Object describing your [mocking logic](https://www.apollographql.com/docs/graphql-tools/mocking.html#Customizing-mocks) that is passed to [graphql-tools](https://github.com/apollographql/graphql-tools) `mockServer`. 104 | -------------------------------------------------------------------------------- /packages/parrot-graphql/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parrot-graphql", 3 | "version": "5.3.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "parrot-graphql", 9 | "version": "5.0.0-alpha.1", 10 | "license": "Apache-2.0", 11 | "dependencies": { 12 | "@graphql-tools/mock": "^8.7.20", 13 | "graphql": "^15.8.0" 14 | } 15 | }, 16 | "node_modules/@graphql-tools/merge": { 17 | "version": "8.4.2", 18 | "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.4.2.tgz", 19 | "integrity": "sha512-XbrHAaj8yDuINph+sAfuq3QCZ/tKblrTLOpirK0+CAgNlZUCHs0Fa+xtMUURgwCVThLle1AF7svJCxFizygLsw==", 20 | "dependencies": { 21 | "@graphql-tools/utils": "^9.2.1", 22 | "tslib": "^2.4.0" 23 | }, 24 | "peerDependencies": { 25 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" 26 | } 27 | }, 28 | "node_modules/@graphql-tools/mock": { 29 | "version": "8.7.20", 30 | "resolved": "https://registry.npmjs.org/@graphql-tools/mock/-/mock-8.7.20.tgz", 31 | "integrity": "sha512-ljcHSJWjC/ZyzpXd5cfNhPI7YljRVvabKHPzKjEs5ElxWu2cdlLGvyNYepApXDsM/OJG/2xuhGM+9GWu5gEAPQ==", 32 | "dependencies": { 33 | "@graphql-tools/schema": "^9.0.18", 34 | "@graphql-tools/utils": "^9.2.1", 35 | "fast-json-stable-stringify": "^2.1.0", 36 | "tslib": "^2.4.0" 37 | }, 38 | "peerDependencies": { 39 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" 40 | } 41 | }, 42 | "node_modules/@graphql-tools/schema": { 43 | "version": "9.0.19", 44 | "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-9.0.19.tgz", 45 | "integrity": "sha512-oBRPoNBtCkk0zbUsyP4GaIzCt8C0aCI4ycIRUL67KK5pOHljKLBBtGT+Jr6hkzA74C8Gco8bpZPe7aWFjiaK2w==", 46 | "dependencies": { 47 | "@graphql-tools/merge": "^8.4.1", 48 | "@graphql-tools/utils": "^9.2.1", 49 | "tslib": "^2.4.0", 50 | "value-or-promise": "^1.0.12" 51 | }, 52 | "peerDependencies": { 53 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" 54 | } 55 | }, 56 | "node_modules/@graphql-tools/utils": { 57 | "version": "9.2.1", 58 | "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz", 59 | "integrity": "sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==", 60 | "dependencies": { 61 | "@graphql-typed-document-node/core": "^3.1.1", 62 | "tslib": "^2.4.0" 63 | }, 64 | "peerDependencies": { 65 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" 66 | } 67 | }, 68 | "node_modules/@graphql-typed-document-node/core": { 69 | "version": "3.2.0", 70 | "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", 71 | "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", 72 | "peerDependencies": { 73 | "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" 74 | } 75 | }, 76 | "node_modules/fast-json-stable-stringify": { 77 | "version": "2.1.0", 78 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 79 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" 80 | }, 81 | "node_modules/graphql": { 82 | "version": "15.8.0", 83 | "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.8.0.tgz", 84 | "integrity": "sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==", 85 | "engines": { 86 | "node": ">= 10.x" 87 | } 88 | }, 89 | "node_modules/tslib": { 90 | "version": "2.6.1", 91 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", 92 | "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==" 93 | }, 94 | "node_modules/value-or-promise": { 95 | "version": "1.0.12", 96 | "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.12.tgz", 97 | "integrity": "sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q==", 98 | "engines": { 99 | "node": ">=12" 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /packages/parrot-middleware/__tests__/ParrotMiddleware.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import ParrotMiddleware from '../src/ParrotMiddleware'; 16 | 17 | jest.mock( 18 | 'parrot-core', 19 | () => 20 | class { 21 | constructor() { 22 | this.logger = { warn: jest.fn() }; 23 | } 24 | } 25 | ); 26 | 27 | describe('ParrotMiddleware', () => { 28 | it('should normalize', () => { 29 | const parrotFetch = new ParrotMiddleware(); 30 | const normalized = parrotFetch.normalizeRequest({ 31 | path: 'http://www.parrot.com/squawk?ahoy=matey', 32 | }); 33 | expect(normalized).toMatchObject({ 34 | path: 'http://www.parrot.com/squawk?ahoy=matey', 35 | }); 36 | }); 37 | 38 | it('should call next middleware and log a warning', () => { 39 | const req = {}; 40 | const res = { headersSent: false }; 41 | const next = jest.fn(); 42 | const parrotMiddleware = new ParrotMiddleware(); 43 | parrotMiddleware.resolver(req, res, next)(); 44 | expect(next).toHaveBeenCalled(); 45 | expect(parrotMiddleware.logger.warn).toHaveBeenCalledWith( 46 | 'No matching mock found for request', 47 | req.path 48 | ); 49 | }); 50 | 51 | it('should not call next middleware if headers sent', () => { 52 | const req = {}; 53 | const res = { headersSent: true }; 54 | const next = jest.fn(); 55 | const parrotMiddleware = new ParrotMiddleware(); 56 | parrotMiddleware.resolver(req, res, next)(); 57 | expect(next).not.toHaveBeenCalled(); 58 | }); 59 | 60 | it('should send json if response is an object', () => { 61 | const req = {}; 62 | const res = { json: jest.fn(), status: jest.fn() }; 63 | const next = jest.fn(); 64 | const response = { body: {} }; 65 | const parrotMiddleware = new ParrotMiddleware(); 66 | parrotMiddleware.resolver(req, res, next)(response); 67 | expect(res.json).toHaveBeenCalled(); 68 | }); 69 | 70 | it('should send status if there is no body', () => { 71 | const req = {}; 72 | const res = { sendStatus: jest.fn(), status: jest.fn() }; 73 | const next = jest.fn(); 74 | const response = { status: 200 }; 75 | const parrotMiddleware = new ParrotMiddleware(); 76 | parrotMiddleware.resolver(req, res, next)(response); 77 | expect(res.sendStatus).toHaveBeenCalled(); 78 | }); 79 | 80 | it('should send body', () => { 81 | const req = {}; 82 | const res = { send: jest.fn(), status: jest.fn() }; 83 | const next = jest.fn(); 84 | const response = { body: 'squawk' }; 85 | const parrotMiddleware = new ParrotMiddleware(); 86 | parrotMiddleware.resolver(req, res, next)(response); 87 | expect(res.send).toHaveBeenCalled(); 88 | }); 89 | 90 | it('should send body with type', () => { 91 | const req = {}; 92 | const res = { send: jest.fn(), status: jest.fn(), type: jest.fn() }; 93 | const next = jest.fn(); 94 | const response = { body: '', contentType: 'html' }; 95 | const parrotMiddleware = new ParrotMiddleware(); 96 | parrotMiddleware.resolver(req, res, next)(response); 97 | expect(res.type).toHaveBeenCalled(); 98 | expect(res.send).toHaveBeenCalled(); 99 | }); 100 | 101 | it('should return the active scenario override from the cookie', () => { 102 | const testScenarioName = 'Test Scenario Name'; 103 | const req = { 104 | cookies: { 105 | parrotScenarioOverride: testScenarioName, 106 | }, 107 | }; 108 | const parrotMiddleware = new ParrotMiddleware(); 109 | expect(parrotMiddleware.getActiveScenarioOverride(req)).toEqual(testScenarioName); 110 | }); 111 | 112 | it('should return undefined if the active scenario override is not present as a cookie', () => { 113 | const req = { 114 | cookies: {}, 115 | }; 116 | const parrotMiddleware = new ParrotMiddleware(); 117 | expect(parrotMiddleware.getActiveScenarioOverride(req)).toBeUndefined(); 118 | }); 119 | 120 | it('should return undefined if there are no cookies', () => { 121 | const req = {}; 122 | const parrotMiddleware = new ParrotMiddleware(); 123 | expect(parrotMiddleware.getActiveScenarioOverride(req)).toBeUndefined(); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /SCENARIOS.md: -------------------------------------------------------------------------------- 1 | # Scenarios 2 | 3 | Parrot scenarios describe combinations of data that you want to develop against. A scenario is comprised of multiple mocks that each describe how to match against an incoming request and what response to return. 4 | 5 | ## Examples 6 | 7 | Below are some examples of how to configure your Parrot scenarios using different methods. 8 | ### Response as an object 9 | 10 | ```js 11 | const scenarios = { 12 | 'has a ship log': [ 13 | { 14 | request: '/ship_log', // If request is a string, the HTTP method defaults to GET 15 | response: { 16 | body: require('./mocks/shipLog.json'), 17 | delay: 1200, // Optional, defaults to 0 18 | status: 200, // Optional, defaults to 200 19 | }, 20 | }, 21 | ], 22 | }; 23 | ``` 24 | 25 | ### Response as a function 26 | 27 | ```js 28 | const scenarios = { 29 | 'has a ship log': [ 30 | { 31 | request: '/ship_log', 32 | // Whatever is returned by this function will be put in the body of the response 33 | // Status will always be 200 for functions 34 | response: () => { 35 | return require('./mocks/shipLog.json'); 36 | }, 37 | }, 38 | ], 39 | }; 40 | ``` 41 | 42 | ## Functional Scenarios 43 | 44 | ```js 45 | const scenarios = { 46 | 'has one ship': [ 47 | // Instead of an object with request/response, the function will 48 | // handle all requests. Use the `match` function to match routes. 49 | // Use this to set dynamic HTTP status codes. 50 | (req, match) => { 51 | if (match({ path: '/ship_log' })) { 52 | return { 53 | status: 200, 54 | body: require('./mocks/shipLog.json'), 55 | delay: 100, 56 | }; 57 | } 58 | return { 59 | status: 404, 60 | body: { error: 'Resource Not Found' }, 61 | delay: 100, 62 | }; 63 | }, 64 | ], 65 | }; 66 | ``` 67 | 68 | ### GraphQL Example 69 | 70 | ```js 71 | import graphql from 'parrot-graphql'; 72 | import schema from './schema'; // our GraphQL schema 73 | 74 | const scenarios = { 75 | 'has a ship log from GraphQL': [ 76 | graphql('/graphql', schema, () => ({ 77 | ShipLog: () => require('./mocks/shipLog.json'), 78 | })), 79 | ], 80 | }; 81 | ``` 82 | 83 | ## Scenarios API 84 | 85 | The options you can define on the `request` and `response` objects are described below. 86 | 87 | ### Request Options 88 | 89 | | Property | Description | Type | 90 | | --------- | ------------------------------------------------------------ | ------ | 91 | | `path` | Path matcher string. May include route params | String | 92 | | `query` | Match against querystring key/value pairs | Object | 93 | | `method` | HTTP method defaults to GET, explicitly specify to handle others | String | 94 | | `headers` | HTTP headers to match against | Object | 95 | 96 | ### Response Options 97 | 98 | | Property | Description | Type | 99 | | -------- | ------------------------------------------------------------ | -------------------------- | 100 | | `body` | Blob to be returned directly or object to be returned as JSON or function that resolve to a blob or object | String / Object / Function | 101 | | `delay` | Set a response delay in milliseconds | Number | 102 | | `status` | Defaults to 200, but can be set explicitly | Number | 103 | 104 | ## Request, Response, and Mock Functions 105 | 106 | In addition to defining `request`, `response` and `mock` as objects, they can also be defined as functions. 107 | 108 | ### `request(req, match)` 109 | 110 | If `request` is a function, it will be run when determing if an incoming request matches the mock. If a truthy value is returned the mock will match, if a falsy value is returned the mock will not match. 111 | 112 | #### Arguments 113 | 114 | * `req`: (*Object*): The normalized request object with `path`, `method`, `body`, `headers`, and `query` properties. 115 | * `match`: (*Function*): Parrot's internal matching function that can be run against a request object. 116 | 117 | ### `response(req)` 118 | 119 | If `response` is a function, it will be run once the request has matched and the response is being resolved. The value that is returned from the function will be sent as the response. 120 | 121 | #### Arguments 122 | 123 | * `req`: (*Object*): The normalized request object with `path`, `method`, `body`, `headers`, `query`, and `params` properties. 124 | * Additional implementation-specific arguments passed are the Express `req` and `res` objects for parrot-middleware and the `input` and `init` arguments to `fetch` for parrot-fetch. 125 | 126 | ### `mock(req, match)` 127 | 128 | If the entire `mock` is a function, it will be called when determining if an incoming request matches the mock. The value that is returned from the function will be sent as the response. 129 | 130 | #### Arguments 131 | 132 | - `req`: (*Object*): The normalized request object with `path`, `method`, `body`, `headers`, and `query` properties. 133 | - `match`: (*Function*): Parrot's internal matching function that can be run against a request object. 134 | - Additional implementation-specific arguments passed are the Express `req` and `res` objects for parrot-middleware and the `input` and `init` arguments to `fetch` for parrot-fetch. 135 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | [Parrot is now InnerSource](https://github.com/americanexpress/one-app/issues/1393) 4 | 5 |
6 | 7 | Parrot 8 |

9 | 10 | Parrot is a set of tools that allow you to create HTTP mocks and organize them into scenarios in order to develop your app against different sets of data. We have implemented all of Parrot's functionality in JavaScript, but [scenarios](https://github.com/americanexpress/parrot/blob/main/SCENARIOS.md) are a general specification that can be implemented in any language. 11 | 12 | ## 🤹‍ Usage 13 | 14 | Let's walk through a common development workflow using Parrot. 15 | 16 | #### Define your [scenarios](https://github.com/americanexpress/parrot/blob/main/SCENARIOS.md) 17 | 18 | ```js 19 | import { describe, it, get, post, graphql } from 'parrot-friendly'; 20 | import casual from 'casual'; // for generating fake data 21 | import schema from './schema'; // our GraphQL schema 22 | 23 | const scenarios = describe('Ship Log', () => { 24 | it('has a ship log', () => { 25 | // respond with a mock JSON file and add a delay 26 | get('/ship_log') 27 | .response(require('./mocks/shipLog.json')) 28 | .delay(1200); 29 | 30 | // respond with the request body that was sent 31 | post('/ship_log').response(req => req.body); 32 | }); 33 | 34 | it('has a random ship log', () => { 35 | // respond with random data generated by casual 36 | get('/ship_log').response(() => [ 37 | { 38 | port: casual.city, 39 | captain: casual.full_name, 40 | }, 41 | ]); 42 | }); 43 | 44 | it('has a server error', () => { 45 | // respond with a 500 status 46 | get('/ship_log').status(500); 47 | }); 48 | 49 | it('has a ship log from GraphQL', () => { 50 | // respond to GraphQL queries 51 | graphql('/graphql', schema, () => ({ 52 | ShipLog: () => require('./mocks/shipLog.json'), 53 | })); 54 | }); 55 | }); 56 | 57 | export default scenarios; 58 | ``` 59 | 60 | More information about writing scenarios can be found in the [scenarios documentation](https://github.com/americanexpress/parrot/blob/main/SCENARIOS.md). 61 | 62 | #### Add them to your server 63 | 64 | ```js 65 | import express from 'express'; 66 | import parrot from 'parrot-middleware'; 67 | import scenarios from './scenarios'; 68 | 69 | const app = express(); 70 | app.use(parrot(scenarios)); 71 | app.listen(3000); 72 | ``` 73 | 74 | #### Develop with Parrot's [devtools](https://github.com/americanexpress/parrot/blob/main/packages/parrot-devtools) 75 | 76 | ![parrot-devtools](assets/parrot-devtools.gif) 77 | 78 | #### Example API requests 79 | 80 | Fetch current scenario. 81 | 82 | ```sh 83 | $ curl 'http://localhost:3002/parrot/scenario' 84 | ``` 85 | 86 | Fetch all scenarios. 87 | 88 | ```sh 89 | $ curl 'http://localhost:3002/parrot/scenarios' 90 | ``` 91 | 92 | Setting parrot to a new scenario. 93 | 94 | ```sh 95 | $ curl -X POST -H "Content-Type: application/json" -d '{ "scenario": "[scenario name here]" }' 'http://localhost:3002/parrot/scenario' 96 | ``` 97 | 98 | ## 📦 Packages 99 | 100 | Parrot is divided into several packages that can be used together depending on your use case. 101 | 102 | | Name | Description | 103 | | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | 104 | | **[parrot-core](https://github.com/americanexpress/parrot/blob/main/packages/parrot-core)** | Core Parrot functionality that can be extended to new use cases | 105 | | **[parrot-devtools](https://github.com/americanexpress/parrot/blob/main/packages/parrot-devtools)** | Devtools that allow you to switch between Parrot scenarios | 106 | | **[parrot-fetch](https://github.com/americanexpress/parrot/blob/main/packages/parrot-fetch)** | Fetch mocking implementation of Parrot | 107 | | **[parrot-friendly](https://github.com/americanexpress/parrot/blob/main/packages/parrot-friendly)** | Helper library to write your scenarios in BDD style | 108 | | **[parrot-graphql](https://github.com/americanexpress/parrot/blob/main/packages/parrot-graphql)** | Helper library to add GraphQL mocks to your scenarios | 109 | | **[parrot-middleware](https://github.com/americanexpress/parrot/blob/main/packages/parrot-middleware)** | Express middleware implementation of Parrot | 110 | | **[parrot-server](https://github.com/americanexpress/parrot/blob/main/packages/parrot-server)** | CLI to get a parrot server up and running | 111 | 112 | ## 🏆 Contributing 113 | 114 | We welcome Your interest in the American Express Open Source Community on Github. 115 | Any Contributor to any Open Source Project managed by the American Express Open 116 | Source Community must accept and sign an Agreement indicating agreement to the 117 | terms below. Except for the rights granted in this Agreement to American Express 118 | and to recipients of software distributed by American Express, You reserve all 119 | right, title, and interest, if any, in and to Your Contributions. Please [fill out the Agreement](https://cla-assistant.io/americanexpress/parrot). 120 | 121 | Please see our [CONTRIBUTING.md](./CONTRIBUTING.md). 122 | 123 | ## 🗝️ License 124 | 125 | Any contributions made under this project will be governed by the [Apache License 2.0](./LICENSE.md). 126 | 127 | ## 🗣️ Code of Conduct 128 | 129 | This project adheres to the [American Express Community Guidelines](./CODE_OF_CONDUCT.md). 130 | By participating, you are expected to honor these guidelines. 131 | --------------------------------------------------------------------------------