├── .editorconfig ├── .eslintrc ├── .github ├── renovate.json └── workflows │ ├── publish.yaml │ ├── release.yaml │ └── test-and-build.yaml ├── .gitignore ├── .npmignore ├── .prettierrc ├── .versionrc ├── .whitesource ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example-cypress-10 └── todo-example │ ├── .gitignore │ ├── README.md │ ├── cypress.config.js │ ├── cypress │ ├── e2e │ │ ├── todo.cy.js │ │ └── todoGet.cy.js │ ├── fixtures │ │ └── todo.json │ ├── plugins │ │ └── index.js │ └── support │ │ ├── commands.js │ │ └── e2e.js │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt │ └── src │ ├── App.css │ ├── App.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ ├── reportWebVitals.js │ └── setupTests.js ├── example └── todo-example │ ├── .gitignore │ ├── README.md │ ├── cypress.json │ ├── cypress │ ├── fixtures │ │ └── todo.json │ ├── integration │ │ ├── todo.spec.js │ │ └── todoGet.spec.js │ ├── plugins │ │ └── index.js │ └── support │ │ ├── commands.js │ │ └── index.js │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt │ └── src │ ├── App.css │ ├── App.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ ├── reportWebVitals.js │ └── setupTests.js ├── jest.config.js ├── package-lock.json ├── package.json ├── scripts └── ci │ └── build-and-test.sh ├── src ├── constants.ts ├── index.ts ├── plugin.ts ├── types.ts └── utils.ts ├── test └── utils.test.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | end_of_line = lf 10 | # editorconfig-tools is unable to ignore longs strings or urls 11 | max_line_length = off -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "plugins": [ 4 | "@typescript-eslint" 5 | ], 6 | "globals": { 7 | "cy": false, 8 | "Cypress": false, 9 | "assert": false 10 | }, 11 | "extends": "./tsconfig.json" 12 | } -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:best-practices" 5 | ], 6 | "pre-commit": { 7 | "enabled": true 8 | }, 9 | "git-submodules": { 10 | "enabled": true 11 | }, 12 | "lockFileMaintenance": { 13 | "enabled": true 14 | }, 15 | "prHourlyLimit": 0, 16 | "prConcurrentLimit": 0, 17 | "packageRules": [ 18 | { 19 | "matchUpdateTypes": [ 20 | "minor", 21 | "patch", 22 | "pin", 23 | "digest" 24 | ], 25 | "automerge": true 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish to NPM 2 | on: 3 | workflow_run: 4 | workflows: [Release] 5 | types: 6 | - completed 7 | jobs: 8 | publish: 9 | if: ${{ github.event.workflow_run.conclusion == 'success' && !contains(github.event.head_commit.message, 'skip-release') }} 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 13 | - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 14 | with: 15 | node-version: '22.x' 16 | registry-url: 'https://registry.npmjs.org' 17 | - run: npm ci 18 | - run: npm run build 19 | - run: npm publish --access public 20 | env: 21 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | workflow_dispatch: 4 | 5 | jobs: 6 | release: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 10 | with: 11 | token: ${{ secrets.GH_TOKEN }} 12 | fetch-depth: 0 13 | - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 14 | with: 15 | node-version: '22.x' 16 | registry-url: 'https://registry.npmjs.org' 17 | - run: npm ci 18 | - uses: fregante/setup-git-user@024bc0b8e177d7e77203b48dab6fb45666854b35 # v2 19 | - name: Bump version and push changelogs 20 | run: | 21 | npm run release 22 | git push origin main --follow-tags 23 | -------------------------------------------------------------------------------- /.github/workflows/test-and-build.yaml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - '*' 10 | 11 | jobs: 12 | build-and-test: 13 | if: ${{!contains(github.event.head_commit.message, 'chore(release)')}} 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | node-version: [14.x, 16.x] 18 | 19 | steps: 20 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 21 | with: 22 | fetch-depth: 0 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: scripts/ci/build-and-test.sh 28 | env: 29 | NODE_VERSION: ${{ matrix.node-version }} 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | 3 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 4 | 5 | # dependencies 6 | node_modules/ 7 | 8 | # testing 9 | coverage 10 | 11 | # production 12 | dist/ 13 | 14 | # misc 15 | .DS_Store 16 | npm-debug.log* 17 | yarn-debug.log* 18 | yarn-error.log* 19 | 20 | # cypress 21 | lib 22 | pacts/ 23 | logs/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # folders 2 | pacts/ 3 | test/ 4 | example/ 5 | .workflow 6 | # files 7 | .babelrc 8 | .editorconfig -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "none", 3 | "semi": false, 4 | "singleQuote": true, 5 | "printWidth": 120 6 | } -------------------------------------------------------------------------------- /.versionrc: -------------------------------------------------------------------------------- 1 | { 2 | "types": [ 3 | {"type":"feat","section":"Features"}, 4 | {"type":"fix","section":"Bug Fixes"}, 5 | {"type":"test","section":"Tests", "hidden": true}, 6 | {"type":"chore","section":"Chores", "hidden": true}, 7 | {"type":"ci","hidden":true} 8 | ] 9 | } -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "settingsInheritedFrom": "pactflow/whitesource-config@main" 3 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [1.3.0](https://github.com/pactflow/cypress-pact-adapter/compare/v1.2.6...v1.3.0) (2022-09-22) 6 | 7 | 8 | ### Features 9 | 10 | * Add client name/version into pact file ([bf1c86d](https://github.com/pactflow/cypress-pact-adapter/commit/bf1c86d475a48f50265691a14f477f7c5fcebae2)) 11 | 12 | ### [1.2.6](https://github.com/pactflow/cypress-pact-adapter/compare/v1.2.5...v1.2.6) (2022-03-10) 13 | 14 | 15 | ### Bug Fixes 16 | 17 | * missing index.d.ts ([22642b6](https://github.com/pactflow/cypress-pact-adapter/commit/22642b6681ce7446aad68c9409e8ef93ae71f042)) 18 | 19 | ### [1.2.5](https://github.com/pactflow/cypress-pact-adapter/compare/v1.2.4...v1.2.5) (2022-03-10) 20 | 21 | ### [1.2.4](https://github.com/pactflow/cypress-pact-adaptor/compare/v1.2.3...v1.2.4) (2022-03-09) 22 | 23 | ### [1.2.3](https://github.com/pactflow/cypress-pact/compare/v1.2.2...v1.2.3) (2022-03-09) 24 | 25 | 26 | ### Bug Fixes 27 | 28 | * missing auto generated headers when no setupPactHeaderBlocklist is called ([955d215](https://github.com/pactflow/cypress-pact/commit/955d2150c9d11423e1e91ece6581636bbcc1e8e3)) 29 | 30 | ### [1.2.2](https://github.com/pactflow/cypress-pact/compare/v1.2.1...v1.2.2) (2022-03-09) 31 | 32 | ### [1.2.1](https://github.com/pactflow/cypress-pact/compare/v1.2.0...v1.2.1) (2022-03-09) 33 | 34 | ## [1.2.0](https://github.com/pactflow/cypress-pact/compare/v1.1.0...v1.2.0) (2022-03-09) 35 | 36 | 37 | ### Features 38 | 39 | * add blocklist and blocklist api ([d675362](https://github.com/pactflow/cypress-pact/commit/d675362dc394e32fe09b6af004fd62b4d47c3fde)) 40 | * add ignoreDefaultBlocklist ([ea99189](https://github.com/pactflow/cypress-pact/commit/ea9918993e7cd623b879adb1489f962c10af7868)) 41 | 42 | 43 | ### Bug Fixes 44 | 45 | * example 404 issues ([979fcda](https://github.com/pactflow/cypress-pact/commit/979fcda111cf9a1a5a509bcc814f41aa9b42e9ec)) 46 | * update readme and example ([3141fea](https://github.com/pactflow/cypress-pact/commit/3141fea7fe581133e7e692e61c362548c0bca837)) 47 | 48 | ## 1.1.0 (2022-03-01) 49 | 50 | 51 | ### Features 52 | 53 | * add setupPact command ([27ee9d3](https://github.com/pactflow/cypress-pact/commit/27ee9d338cb91b9f94fc0befc2a0b72be083e5bd)) 54 | * add usePactGet and usePactRequest ([e9f0624](https://github.com/pactflow/cypress-pact/commit/e9f0624b0bd01646c861a1dd4b0d816b887d1de9)) 55 | * update status to statusCode ([e330b6e](https://github.com/pactflow/cypress-pact/commit/e330b6e1d6c4caca48dbee6879763f3afba98a6a)) 56 | * usePactWait ([8be151e](https://github.com/pactflow/cypress-pact/commit/8be151e00b3038d3977aa41ae99639064927ccec)) 57 | 58 | 59 | ### Bug Fixes 60 | 61 | * XHRrequestAndResponse type with body and headers ([30fb932](https://github.com/pactflow/cypress-pact/commit/30fb932f693ccd7acb9252ee948b802df750eee4)) 62 | 63 | ### 1.0.1 (2022-03-01) 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 PactFlow 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pact Cypress Adapter 2 | [![Build and test](https://github.com/pactflow/cypress-pact-adapter/actions/workflows/test-and-build.yaml/badge.svg)](https://github.com/pactflow/cypress-pact-adapter/actions/workflows/test-and-build.yaml) [![npm version](https://badge.fury.io/js/@pactflow%2Fpact-cypress-adapter.svg)](https://badge.fury.io/js/@pactflow%2Fpact-cypress-adapter) 3 | 4 | Generate Pact contracts from your existing Cypress tests. 5 | 6 | > Accelerate your entry into contract testing with the Cypress development experience you know and love. — With Pact Cypress Adapter you can get the extra layer of testing safety, easily using existing mocks you’ve created with Cypress. 7 | > 8 | > Read our [blog post](https://pactflow.io/blog/use-cypress-in-contract-testing/) to find out more, otherwise dive-right in. 9 | 10 | ## Installation 11 | **NPM**: 12 | ```bash 13 | npm i -D @pactflow/pact-cypress-adapter 14 | ``` 15 | 16 | **yarn**: 17 | ```bash 18 | yarn add -D @pactflow/pact-cypress-adapter 19 | ``` 20 | 21 | ## Setup for Cypress 10 22 | Setup your cypress plugin at `cypress/plugins/index.js` 23 | 24 | ```js 25 | const pactCypressPlugin = require('@pactflow/pact-cypress-adapter/dist/plugin') 26 | const fs = require('fs') 27 | 28 | module.exports = (on, config) => { 29 | pactCypressPlugin(on, config, fs) 30 | } 31 | ``` 32 | 33 | Finally, update `cypress/support/e2e.js` file to include cypress-pact commands via adding: 34 | ```js 35 | import '@pactflow/pact-cypress-adapter' 36 | ``` 37 | 38 | ## Setup for Cypress 9.x and below 39 | Setup your cypress plugin at `cypress/plugins/index.js` 40 | 41 | ```js 42 | const pactCypressPlugin = require('@pactflow/pact-cypress-adapter/dist/plugin') 43 | const fs = require('fs') 44 | 45 | module.exports = (on, config) => { 46 | pactCypressPlugin(on, config, fs) 47 | } 48 | ``` 49 | 50 | Finally, update `cypress/support/index.js` file to include cypress-pact commands via adding: 51 | ```js 52 | import '@pactflow/pact-cypress-adapter' 53 | ``` 54 | 55 | ## Configuration 56 | By default, this plugin omits most cypress auto-generated HTTP headers. 57 | 58 | ### Add more headers to blocklist for Cypress 10 59 | To exclude other headers in your pact, add them as a list of strings in `cypress.config.{js,ts,mjs,cjs}` under key `env.headersBlocklist`. Eg. in your cypress config file 60 | ```js 61 | { 62 | ...otherCypressConfig, 63 | "env": { 64 | "headersBlocklist": ["ignore-me-globally"] 65 | } 66 | } 67 | ``` 68 | 69 | ### Add more headers to blocklist for Cypress 9.x and below 70 | To exclude other headers in your pact, add them as a list of strings in `cypress.json` under key `env.headersBlocklist`. Eg. in your `cypress.json` 71 | ```js 72 | { 73 | ...otherCypressConfig, 74 | "env": { 75 | "headersBlocklist": ["ignore-me-globally"] 76 | } 77 | } 78 | ``` 79 | 80 | Note: Header blocklist can be set up at test level. Check command [cy.setupPactHeaderBlocklist](/#cy.setupPactHeaderBlocklist([headers])) 81 | 82 | ### Ignore cypress auto-generated header blocklist 83 | To stop cypress auto-generated HTTP headers being omitted by the plugin, set `env.ignoreDefaultBlocklist` in your `cypress.json`. Eg. in your `cypress.json` 84 | ```js 85 | { 86 | ...otherCypressConfig, 87 | "env": { 88 | "headersBlocklist": ["ignore-me-globally"], 89 | "ignoreDefaultBlocklist": true 90 | 91 | } 92 | } 93 | ``` 94 | 95 | ## Commands 96 | ### cy.setupPact(consumerName:string, providerName: string) 97 | Configure your consumer and provider name 98 | 99 | **Example** 100 | ```js 101 | before(() => { 102 | cy.setupPact('ui-consumer', 'api-provider') 103 | }) 104 | ``` 105 | ### cy.usePactWait([alias] | alias) 106 | Listen to aliased `cy.intercept` network call(s), record network request and response to a pact file. 107 | [Usage and example](https://docs.cypress.io/api/commands/intercept) about `cy.intercept` 108 | 109 | **Example** 110 | ```js 111 | before(() => { 112 | cy.setupPact('ui-consumer', 'api-provider') 113 | cy.intercept('GET', '/users').as('getAllUsers') 114 | }) 115 | 116 | //... cypress test 117 | 118 | after(() => { 119 | cy.usePactWait(['getAllUsers']) 120 | }) 121 | 122 | ``` 123 | 124 | ### cy.setupPactHeaderBlocklist([headers]) 125 | Add a list of headers that will be excluded in a pact at test case level 126 | 127 | **Example** 128 | ```js 129 | before(() => { 130 | cy.setupPact('ui-consumer', 'api-provider') 131 | cy.intercept('GET', '/users', headers: {'ignore-me': 'ignore me please'}).as('getAllUsers') 132 | cy.setupPactHeaderBlocklist(['ignore-me']) 133 | }) 134 | 135 | //... cypress test 136 | 137 | after(() => { 138 | cy.usePactWait(['getAllUsers']) 139 | }) 140 | ``` 141 | 142 | ### cy.usePactRequest(option, alias) and cy.usePactGet([alias] | alias) 143 | Use `cy.usePactRequest` to initiate network calls and use `cy.usePactGet` to record network request and response to a pact file. 144 | 145 | Convenience wrapper for `cy.request(options).as(alias)` 146 | 147 | - Accepts a valid Cypress request options argument [Cypress request options argument](https://docs.cypress.io/api/commands/request#Arguments) 148 | 149 | **Example** 150 | ```js 151 | 152 | before(() => { 153 | cy.setupPact('ui-consumer', 'api-provider') 154 | cy.usePactRequest( 155 | { 156 | method: 'GET', 157 | url: '/users', 158 | }, 159 | 'getAllUsers' 160 | ) 161 | }) 162 | 163 | //... cypress test 164 | 165 | after(() => { 166 | cy.usePactGet(['getAllUsers']) 167 | }) 168 | 169 | ``` 170 | 171 | ## Example Project 172 | Check out simple react app example projects at [/example/todo-example](/example/todo-example/). Example contains examples for Cypress 10.x and Cypress 9.x. 173 | -------------------------------------------------------------------------------- /example-cypress-10/todo-example/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /example-cypress-10/todo-example/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!** 35 | 36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. 39 | 40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /example-cypress-10/todo-example/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | video: false, 5 | env: { 6 | headersBlocklist: ['ignore-me-globally'], 7 | ignoreDefaultBlocklist: false, 8 | }, 9 | e2e: { 10 | // We've imported your old cypress plugins here. 11 | // You may want to clean this up later by importing these. 12 | setupNodeEvents(on, config) { 13 | return require('./cypress/plugins/index.js')(on, config) 14 | }, 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /example-cypress-10/todo-example/cypress/e2e/todo.cy.js: -------------------------------------------------------------------------------- 1 | import todosResponse from '../fixtures/todo.json' 2 | describe('example to-do app', () => { 3 | before(() => { 4 | cy.setupPact('ui-consumer', 'todo-api') 5 | cy.intercept( 6 | { 7 | method: 'GET', 8 | url: '**/api/todo', 9 | headers: { 10 | 'x-pactflow': 'blah', 11 | 'ignore-me': 'ignore', 12 | 'ignore-me-globally': 'ignore' 13 | } 14 | }, 15 | { 16 | statusCode: 200, 17 | body: todosResponse, 18 | headers: { 'access-control-allow-origin': '*' } 19 | } 20 | ).as('getTodos') 21 | cy.setupPactHeaderBlocklist(['ignore-me']) 22 | cy.visit('http://localhost:3000/') 23 | }) 24 | 25 | it('shows todo', () => { 26 | cy.contains('clean desk') 27 | }) 28 | 29 | after(() => { 30 | cy.usePactWait('getTodos').its('response.statusCode').should('eq', 200) 31 | }) 32 | }) -------------------------------------------------------------------------------- /example-cypress-10/todo-example/cypress/e2e/todoGet.cy.js: -------------------------------------------------------------------------------- 1 | 2 | describe('example to-do app', () => { 3 | before(() => { 4 | cy.setupPact('ui-consumer', 'todo-api') 5 | cy.usePactRequest( 6 | { 7 | method: 'GET', 8 | url: 'https://jsonplaceholder.typicode.com/todos', 9 | headers: { 10 | 'x-pactflow': 'blah', 11 | 'ignore-me': 'ignore', 12 | 'ignore-me-globally': 'ignore' 13 | } 14 | }, 15 | 'getTodosGet' 16 | ) 17 | cy.setupPactHeaderBlocklist(['ignore-me']) 18 | }) 19 | 20 | it('shows todo', () => { 21 | }) 22 | 23 | after(() => { 24 | cy.usePactGet('getTodosGet') 25 | .its('response.statusCode') 26 | .should('eq', 200) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /example-cypress-10/todo-example/cypress/fixtures/todo.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "content": "clean desk" 4 | }, 5 | { 6 | "content": "make coffee" 7 | } 8 | ] -------------------------------------------------------------------------------- /example-cypress-10/todo-example/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | const pactCypressPlugin = require('@pactflow/pact-cypress-adapter/dist/plugin') 16 | const fs = require('fs') 17 | 18 | module.exports = (on, config) => { 19 | pactCypressPlugin(on, config, fs) 20 | } 21 | -------------------------------------------------------------------------------- /example-cypress-10/todo-example/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add('login', (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /example-cypress-10/todo-example/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | import '@pactflow/pact-cypress-adapter' 19 | 20 | // Alternatively you can use CommonJS syntax: 21 | // require('./commands') 22 | -------------------------------------------------------------------------------- /example-cypress-10/todo-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.2", 7 | "@testing-library/react": "^12.1.3", 8 | "@testing-library/user-event": "^13.5.0", 9 | "react": "^17.0.2", 10 | "react-dom": "^17.0.2", 11 | "react-scripts": "5.0.1", 12 | "web-vitals": "^5.0.0" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject", 19 | "cypress:open": "cypress open", 20 | "cypress:run": "cypress run --headless --browser chrome", 21 | "http-server": "http-server -p 3000 build/ &" 22 | }, 23 | "eslintConfig": { 24 | "extends": [ 25 | "react-app", 26 | "react-app/jest" 27 | ] 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.2%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | }, 41 | "devDependencies": { 42 | "@pactflow/pact-cypress-adapter": "1.3.0", 43 | "cypress": "10.11.0", 44 | "http-server": "14.1.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example-cypress-10/todo-example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pactflow/pact-cypress-adapter/d13dc9f66f2c02fb9bb3cdc79c1d5db0fd0b28c1/example-cypress-10/todo-example/public/favicon.ico -------------------------------------------------------------------------------- /example-cypress-10/todo-example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /example-cypress-10/todo-example/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pactflow/pact-cypress-adapter/d13dc9f66f2c02fb9bb3cdc79c1d5db0fd0b28c1/example-cypress-10/todo-example/public/logo192.png -------------------------------------------------------------------------------- /example-cypress-10/todo-example/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pactflow/pact-cypress-adapter/d13dc9f66f2c02fb9bb3cdc79c1d5db0fd0b28c1/example-cypress-10/todo-example/public/logo512.png -------------------------------------------------------------------------------- /example-cypress-10/todo-example/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /example-cypress-10/todo-example/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /example-cypress-10/todo-example/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /example-cypress-10/todo-example/src/App.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | function App() { 4 | const [todos, setTodos] = useState([]) 5 | useEffect(() => { 6 | async function fetchTodos() { 7 | let response = await fetch('/api/todo', { 8 | headers: { 9 | 'x-pactflow': 'blah', 10 | 'ignore-me': 'ignore', 11 | 'ignore-me-globally': 'ignore' 12 | } 13 | }) 14 | response = await response.json() 15 | setTodos(response) 16 | } 17 | fetchTodos() 18 | }, []) 19 | return ( 20 |
21 | {todos.length === 0 ? ( 22 |

No todos is found

23 | ) : ( 24 | 29 | )} 30 |
31 | ) 32 | } 33 | 34 | export default App 35 | -------------------------------------------------------------------------------- /example-cypress-10/todo-example/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /example-cypress-10/todo-example/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /example-cypress-10/todo-example/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example-cypress-10/todo-example/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /example-cypress-10/todo-example/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /example/todo-example/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /example/todo-example/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!** 35 | 36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. 39 | 40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /example/todo-example/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "video": false, 3 | "env": { 4 | "headersBlocklist": ["ignore-me-globally"], 5 | "ignoreDefaultBlocklist": false 6 | } 7 | } -------------------------------------------------------------------------------- /example/todo-example/cypress/fixtures/todo.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "content": "clean desk" 4 | }, 5 | { 6 | "content": "make coffee" 7 | } 8 | ] -------------------------------------------------------------------------------- /example/todo-example/cypress/integration/todo.spec.js: -------------------------------------------------------------------------------- 1 | import '../../../../dist/index' 2 | import todosResponse from '../fixtures/todo.json' 3 | describe('example to-do app', () => { 4 | before(() => { 5 | cy.setupPact('ui-consumer', 'todo-api') 6 | cy.intercept( 7 | { 8 | method: 'GET', 9 | url: '**/api/todo', 10 | headers: { 11 | 'x-pactflow': 'blah', 12 | 'ignore-me': 'ignore', 13 | 'ignore-me-globally': 'ignore' 14 | } 15 | }, 16 | { 17 | statusCode: 200, 18 | body: todosResponse, 19 | headers: { 'access-control-allow-origin': '*' } 20 | } 21 | ).as('getTodos') 22 | cy.setupPactHeaderBlocklist(['ignore-me']) 23 | cy.visit('http://localhost:3000/') 24 | }) 25 | 26 | it('shows todo', () => { 27 | cy.contains('clean desk') 28 | }) 29 | 30 | after(() => { 31 | cy.usePactWait('getTodos').its('response.statusCode').should('eq', 200) 32 | }) 33 | }) -------------------------------------------------------------------------------- /example/todo-example/cypress/integration/todoGet.spec.js: -------------------------------------------------------------------------------- 1 | import '../../../../dist/index' 2 | 3 | describe('example to-do app', () => { 4 | before(() => { 5 | cy.setupPact('ui-consumer', 'todo-api') 6 | cy.usePactRequest( 7 | { 8 | method: 'GET', 9 | url: 'https://jsonplaceholder.typicode.com/todos', 10 | headers: { 11 | 'x-pactflow': 'blah', 12 | 'ignore-me': 'ignore', 13 | 'ignore-me-globally': 'ignore' 14 | } 15 | }, 16 | 'getTodosGet' 17 | ) 18 | cy.setupPactHeaderBlocklist(['ignore-me']) 19 | }) 20 | 21 | it('shows todo', () => { 22 | }) 23 | 24 | after(() => { 25 | cy.usePactGet('getTodosGet') 26 | .its('response.statusCode') 27 | .should('eq', 200) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /example/todo-example/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | const pactCypressPlugin = require('@pactflow/pact-cypress-adapter/dist/plugin') 16 | const fs = require('fs') 17 | 18 | module.exports = (on, config) => { 19 | pactCypressPlugin(on, config, fs) 20 | } 21 | -------------------------------------------------------------------------------- /example/todo-example/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add('login', (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /example/todo-example/cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | import '@pactflow/pact-cypress-adapter' 19 | 20 | // Alternatively you can use CommonJS syntax: 21 | // require('./commands') 22 | -------------------------------------------------------------------------------- /example/todo-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.2", 7 | "@testing-library/react": "^12.1.3", 8 | "@testing-library/user-event": "^13.5.0", 9 | "react": "^17.0.2", 10 | "react-dom": "^17.0.2", 11 | "react-scripts": "5.0.1", 12 | "web-vitals": "^5.0.0" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject", 19 | "cypress:open": "cypress open", 20 | "cypress:run": "cypress run --headless --browser chrome", 21 | "http-server": "http-server -p 3000 build/ &" 22 | }, 23 | "eslintConfig": { 24 | "extends": [ 25 | "react-app", 26 | "react-app/jest" 27 | ] 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.2%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | }, 41 | "devDependencies": { 42 | "@pactflow/pact-cypress-adapter": "1.3.0", 43 | "cypress": "9.7.0", 44 | "http-server": "14.1.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example/todo-example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pactflow/pact-cypress-adapter/d13dc9f66f2c02fb9bb3cdc79c1d5db0fd0b28c1/example/todo-example/public/favicon.ico -------------------------------------------------------------------------------- /example/todo-example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /example/todo-example/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pactflow/pact-cypress-adapter/d13dc9f66f2c02fb9bb3cdc79c1d5db0fd0b28c1/example/todo-example/public/logo192.png -------------------------------------------------------------------------------- /example/todo-example/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pactflow/pact-cypress-adapter/d13dc9f66f2c02fb9bb3cdc79c1d5db0fd0b28c1/example/todo-example/public/logo512.png -------------------------------------------------------------------------------- /example/todo-example/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /example/todo-example/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /example/todo-example/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /example/todo-example/src/App.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | function App() { 4 | const [todos, setTodos] = useState([]) 5 | useEffect(() => { 6 | async function fetchTodos() { 7 | let response = await fetch('/api/todo', { 8 | headers: { 9 | 'x-pactflow': 'blah', 10 | 'ignore-me': 'ignore', 11 | 'ignore-me-globally': 'ignore' 12 | } 13 | }) 14 | response = await response.json() 15 | setTodos(response) 16 | } 17 | fetchTodos() 18 | }, []) 19 | return ( 20 |
21 | {todos.length === 0 ? ( 22 |

No todos is found

23 | ) : ( 24 |
    25 | {todos.map((todo, i) => { 26 | return
  • {todo.content}
  • 27 | })} 28 |
29 | )} 30 |
31 | ) 32 | } 33 | 34 | export default App 35 | -------------------------------------------------------------------------------- /example/todo-example/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /example/todo-example/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /example/todo-example/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/todo-example/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /example/todo-example/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pactflow/pact-cypress-adapter", 3 | "version": "1.3.0", 4 | "description": "A cypress adapter for pact", 5 | "keywords": [ 6 | "pact", 7 | "cypress", 8 | "contract testing", 9 | "ui" 10 | ], 11 | "main": "./dist/index.js", 12 | "types": "./dist/index.d.ts", 13 | "files": [ 14 | "dist/*.js", 15 | "dist/**/*.js", 16 | "dist/index.d.ts" 17 | ], 18 | "scripts": { 19 | "test": "jest --testPathPattern=test", 20 | "build": "tsc", 21 | "test:example": "cd example/todo-example && npm i && npm run build && npm run http-server && npm run cypress:run", 22 | "release": "standard-version" 23 | }, 24 | "author": "", 25 | "license": "MIT", 26 | "devDependencies": { 27 | "@types/jest": "27.5.2", 28 | "@typescript-eslint/eslint-plugin": "8.32.1", 29 | "cypress": "9.7.0", 30 | "eslint": "8.57.1", 31 | "jest": "27.5.1", 32 | "standard-version": "9.5.0", 33 | "ts-jest": "27.1.5", 34 | "typescript": "4.9.5" 35 | }, 36 | "dependencies": { 37 | "@types/node": "^22.0.0", 38 | "@types/lodash": "^4.14.178", 39 | "lodash": "^4.17.21" 40 | }, 41 | "repository": { 42 | "type": "git", 43 | "url": "git+https://github.com/pactflow/cypress-pact-adapter.git" 44 | }, 45 | "bugs": { 46 | "url": "https://github.com/pactflow/cypress-pact-adapter/issues" 47 | }, 48 | "homepage": "https://github.com/pactflow/cypress-pact-adapter#readme" 49 | } 50 | -------------------------------------------------------------------------------- /scripts/ci/build-and-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | echo "=====Install dependency=====" 5 | npm i 6 | echo "=====Run unit test=====" 7 | npm run test 8 | echo "=====Build project=====" 9 | npm run build 10 | echo "=====Run cypress example=====" 11 | npm run test:example -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const AUTOGEN_HEADER_BLOCKLIST = [ 2 | 'access-control-expose-headers', 3 | 'access-control-allow-credentials', 4 | 'host', 5 | 'proxy-connection', 6 | 'sec-ch-ua', 7 | 'sec-ch-ua-mobile', 8 | 'user-agent', 9 | 'sec-ch-ua-platform', 10 | 'origin', 11 | 'sec-fetch-site', 12 | 'sec-fetch-mode', 13 | 'sec-fetch-dest', 14 | 'referer', 15 | 'accept-encoding', 16 | 'accept-language', 17 | 'date', 18 | 'x-powered-by' 19 | ] -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { AUTOGEN_HEADER_BLOCKLIST } from './constants' 2 | import { AliasType, AnyObject, PactConfigType, XHRRequestAndResponse, RequestOptionType } from 'types' 3 | import { formatAlias, writePact } from './utils' 4 | import { env } from 'process' 5 | 6 | declare global { 7 | // eslint-disable-next-line @typescript-eslint/no-namespace 8 | namespace Cypress { 9 | interface Chainable { 10 | usePactWait: (alias: AliasType) => Chainable 11 | usePactRequest: (option: AnyObject, alias: string) => Chainable 12 | usePactGet: (alias: string, pactConfig: PactConfigType) => Chainable 13 | setupPact: (consumerName: string, providerName: string) => Chainable 14 | setupPactHeaderBlocklist: (headers: string[]) => Chainable 15 | } 16 | } 17 | } 18 | 19 | const pactConfig: PactConfigType = { 20 | consumerName: 'customer', 21 | providerName: 'provider' 22 | } 23 | 24 | const setupPact = (consumerName: string, providerName: string) => { 25 | pactConfig['consumerName'] = consumerName 26 | pactConfig['providerName'] = providerName 27 | } 28 | 29 | const ignoreDefaultBlocklist = Cypress.env('ignoreDefaultBlocklist') || false 30 | const globalBlocklist = Cypress.env('headersBlocklist') || [] 31 | let headersBlocklist: string[] = ignoreDefaultBlocklist 32 | ? globalBlocklist 33 | : [...globalBlocklist, ...AUTOGEN_HEADER_BLOCKLIST] 34 | 35 | const setupPactHeaderBlocklist = (headers: string[]) => { 36 | headersBlocklist = [...headers, ...headersBlocklist] 37 | } 38 | 39 | const usePactWait = (alias: AliasType) => { 40 | const formattedAlias = formatAlias(alias) 41 | // Cypress versions older than 8.2 do not have a currentTest objects 42 | const testCaseTitle = Cypress.currentTest ? Cypress.currentTest.title : '' 43 | //NOTE: spread only works for array containing more than one item 44 | if (formattedAlias.length > 1) { 45 | cy.wait([...formattedAlias]).spread((...intercepts) => { 46 | intercepts.forEach((intercept, index) => { 47 | writePact({ 48 | intercept, 49 | testCaseTitle: `${testCaseTitle}-${formattedAlias[index]}`, 50 | pactConfig, 51 | blocklist: headersBlocklist 52 | }) 53 | }) 54 | }) 55 | } else { 56 | cy.wait(formattedAlias).then((intercept) => { 57 | const flattenIntercept = Array.isArray(intercept) ? intercept[0] : intercept 58 | writePact({ 59 | intercept: flattenIntercept, 60 | testCaseTitle: `${testCaseTitle}`, 61 | pactConfig, 62 | blocklist: headersBlocklist 63 | }) 64 | }) 65 | } 66 | } 67 | 68 | const requestDataMap: AnyObject = {} 69 | 70 | const usePactGet = (alias: string) => { 71 | const formattedAlias = formatAlias(alias) 72 | // Cypress versions older than 8.2 do not have a currentTest objects 73 | const testCaseTitle = Cypress.currentTest ? Cypress.currentTest.title : '' 74 | 75 | formattedAlias.forEach((alias) => { 76 | cy.get(alias).then((response: any) => { 77 | const fullRequestAndResponse = { 78 | request: { 79 | method: requestDataMap[alias].method, 80 | url: requestDataMap[alias].url, 81 | headers: response.requestHeaders, 82 | body: response.requestBody 83 | }, 84 | response: { 85 | body: response.body, 86 | statusCode: response.status, 87 | headers: response.headers, 88 | statusText: response.statusText 89 | } 90 | } as XHRRequestAndResponse 91 | writePact({ 92 | intercept: fullRequestAndResponse, 93 | testCaseTitle: `${testCaseTitle}-${alias}`, 94 | pactConfig, 95 | blocklist: headersBlocklist 96 | }) 97 | }) 98 | }) 99 | } 100 | 101 | const usePactRequest = (option: Partial, alias: string) => { 102 | cy.request(option).as(alias) 103 | // Store request url and method to a global item as cy.request.get() doesn't 104 | // provide related information 105 | requestDataMap[`@${alias}`] = option 106 | } 107 | 108 | Cypress.Commands.add('usePactWait', usePactWait) 109 | Cypress.Commands.add('usePactRequest', usePactRequest) 110 | Cypress.Commands.add('usePactGet', usePactGet) 111 | Cypress.Commands.add('setupPact', setupPact) 112 | Cypress.Commands.add('setupPactHeaderBlocklist', setupPactHeaderBlocklist) 113 | -------------------------------------------------------------------------------- /src/plugin.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {Cypress.PluginConfig} 3 | */ 4 | 5 | // eslint-disable-next-line no-unused-vars 6 | const { readFileAsync } = require('./utils') 7 | module.exports = (on: any, config: any, fs: any) => { 8 | const readFile = (filename: string) => readFileAsync(fs.promises, filename) 9 | const removePactDir = () => { 10 | fs.promises.rm('cypress/pacts', { recursive: true, force: true }).then(() => { 11 | console.log('Clear up pacts') 12 | }) 13 | } 14 | on('before:run', () => { 15 | removePactDir() 16 | }) 17 | on('task', { 18 | readFile 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { Interception } from 'cypress/types/net-stubbing' 2 | 3 | export type AliasType = string | string[] 4 | 5 | export type AnyObject = { 6 | [K in string | number]: any 7 | } 8 | 9 | export type PactConfigType = { 10 | consumerName: string 11 | providerName: string 12 | } 13 | 14 | export type HeaderType = Record | undefined 15 | 16 | type BaseXHR = { 17 | headers: HeaderType 18 | body: any | undefined 19 | } 20 | export type Interaction = { 21 | description: string 22 | providerState: string 23 | request: { 24 | method: string 25 | path: string 26 | query: string 27 | } & BaseXHR 28 | response: { 29 | status: string | number | undefined 30 | } & BaseXHR 31 | } 32 | 33 | export type XHRRequestAndResponse = { 34 | request: 35 | | { 36 | method: string 37 | url: string 38 | } & BaseXHR 39 | response: { 40 | statusCode: string | number | undefined 41 | statusText: string | undefined 42 | } & BaseXHR 43 | } 44 | 45 | type Encodings = 46 | | 'ascii' 47 | | 'base64' 48 | | 'binary' 49 | | 'hex' 50 | | 'latin1' 51 | | 'utf8' 52 | | 'utf-8' 53 | | 'ucs2' 54 | | 'ucs-2' 55 | | 'utf16le' 56 | | 'utf-16le' 57 | | null 58 | 59 | export type RequestOptionType = { 60 | auth: object 61 | body: AnyObject 62 | encoding: Encodings 63 | followRedirect: boolean 64 | form: boolean 65 | gzip: boolean 66 | headers: object 67 | method: string 68 | qs: object 69 | url: string 70 | } 71 | 72 | export type PactFileType = { 73 | intercept: Interception | XHRRequestAndResponse 74 | testCaseTitle: string 75 | pactConfig: PactConfigType 76 | blocklist?: string[], 77 | content?: any 78 | } 79 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { Interception } from 'cypress/types/net-stubbing' 2 | import { uniqBy, reverse, omit } from 'lodash' 3 | import { AliasType, Interaction, PactConfigType, XHRRequestAndResponse, PactFileType, HeaderType } from 'types' 4 | const pjson = require('../package.json') 5 | export const formatAlias = (alias: AliasType) => { 6 | if (Array.isArray(alias)) { 7 | return [...alias].map((a) => `@${a}`) 8 | } 9 | return [`@${alias}`] 10 | } 11 | 12 | const constructFilePath = ({ consumerName, providerName }: PactConfigType) => 13 | `cypress/pacts/${providerName}-${consumerName}.json` 14 | 15 | export const writePact = ({ intercept, testCaseTitle, pactConfig, blocklist }: PactFileType) => { 16 | const filePath = constructFilePath(pactConfig) 17 | cy.task('readFile', filePath) 18 | .then((content) => { 19 | if (content) { 20 | const parsedContent = JSON.parse(content as string) 21 | return constructPactFile({ intercept, testCaseTitle, pactConfig, blocklist, content: parsedContent }) 22 | } else { 23 | return constructPactFile({ intercept, testCaseTitle, pactConfig, blocklist }) 24 | } 25 | }) 26 | .then((data) => { 27 | cy.writeFile(filePath, JSON.stringify(data)) 28 | }) 29 | .then(() => { 30 | return intercept 31 | }) 32 | } 33 | 34 | export const omitHeaders = (headers: HeaderType, blocklist: string[]) => { 35 | return omit(headers, [...blocklist]) 36 | } 37 | 38 | const constructInteraction = ( 39 | intercept: Interception | XHRRequestAndResponse, 40 | testTitle: string, 41 | blocklist: string[] 42 | ): Interaction => { 43 | const path = new URL(intercept.request.url).pathname 44 | const search = new URL(intercept.request.url).search 45 | const query = new URLSearchParams(search).toString() 46 | return { 47 | description: testTitle, 48 | providerState: '', 49 | request: { 50 | method: intercept.request.method, 51 | path: path, 52 | headers: omitHeaders(intercept.request.headers, blocklist), 53 | body: intercept.request.body, 54 | query: query 55 | }, 56 | response: { 57 | status: intercept.response?.statusCode, 58 | headers: omitHeaders(intercept.response?.headers, blocklist), 59 | body: intercept.response?.body 60 | } 61 | } 62 | } 63 | export const constructPactFile = ({ intercept, testCaseTitle, pactConfig, blocklist = [], content }: PactFileType) => { 64 | const pactSkeletonObject = { 65 | consumer: { name: pactConfig.consumerName }, 66 | provider: { name: pactConfig.providerName }, 67 | interactions: [], 68 | metadata: { 69 | pactSpecification: { 70 | version: '2.0.0' 71 | }, 72 | client: { 73 | name: 'pact-cypress-adapter', 74 | version: pjson.version 75 | } 76 | } 77 | } 78 | 79 | if (content) { 80 | const interactions = [...content.interactions, constructInteraction(intercept, testCaseTitle, blocklist)] 81 | const nonDuplicatesInteractions = reverse(uniqBy(reverse(interactions), 'description')) 82 | const data = { 83 | ...pactSkeletonObject, 84 | ...content, 85 | interactions: nonDuplicatesInteractions 86 | } 87 | return data 88 | } 89 | 90 | return { 91 | ...pactSkeletonObject, 92 | interactions: [...pactSkeletonObject.interactions, constructInteraction(intercept, testCaseTitle, blocklist)] 93 | } 94 | } 95 | 96 | const isFileExisted = async (fs: any, filename: string) => !!(await fs.stat(filename).catch((e: any) => false)) 97 | export const readFileAsync = async (fs: any, filename: string) => { 98 | if (await isFileExisted(fs, filename)) { 99 | const data = await fs.readFile(filename, 'utf8') 100 | return data 101 | } 102 | return null 103 | } 104 | -------------------------------------------------------------------------------- /test/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { formatAlias, constructPactFile, readFileAsync, omitHeaders } from '../src/utils' 2 | import { expect } from '@jest/globals' 3 | import { XHRRequestAndResponse } from '../src/types' 4 | const pjson = require('../package.json') 5 | 6 | import { promises, Stats } from 'fs' 7 | 8 | describe('formatAlias', () => { 9 | it('should format array of string in alias format', () => { 10 | expect(formatAlias(['a', 'b'])).toEqual(['@a', '@b']) 11 | }) 12 | 13 | it('should format single string to a formatted array', () => { 14 | expect(formatAlias('a')).toEqual(['@a']) 15 | }) 16 | }) 17 | 18 | describe('constructPactFile', () => { 19 | it('should append intercept to the existing content', () => { 20 | const existingContent = { 21 | consumer: { 22 | name: 'ui-consumer' 23 | }, 24 | provider: { 25 | name: 'todo-api' 26 | }, 27 | interactions: [ 28 | { 29 | description: 'shows todo', 30 | providerState: '', 31 | request: { 32 | method: 'GET', 33 | path: '/api/todo', 34 | body: '', 35 | query: '' 36 | }, 37 | response: { 38 | status: 200, 39 | headers: { 40 | 'access-control-allow-origin': '*', 41 | 'content-type': 'application/json', 42 | 'access-control-expose-headers': '*', 43 | 'access-control-allow-credentials': 'true' 44 | }, 45 | body: [ 46 | { 47 | content: 'clean desk' 48 | }, 49 | { 50 | content: 'make coffee' 51 | } 52 | ] 53 | } 54 | } 55 | ], 56 | metadata: { 57 | pactSpecification: { 58 | version: '2.0.0' 59 | }, 60 | client: { 61 | name: 'pact-cypress-adapter', 62 | version: pjson.version 63 | } 64 | } 65 | } 66 | const newIntercept = { 67 | request: { 68 | method: 'POST', 69 | url: 'https://localhost:3000/create', 70 | body: 'hello' 71 | }, 72 | response: { 73 | statusCode: 201, 74 | statusText: 'Created' 75 | } 76 | } as XHRRequestAndResponse 77 | const result = constructPactFile({ 78 | intercept: newIntercept, 79 | testCaseTitle: 'create todo', 80 | pactConfig: { 81 | consumerName: 'ui-consumer', 82 | providerName: 'todo-api' 83 | }, 84 | blocklist: [], 85 | content: existingContent 86 | }) 87 | expect(result.interactions.length).toBe(2) 88 | expect(result.interactions[1].description).toBe('create todo') 89 | }) 90 | 91 | it('should create a new file when no pact file is found', () => { 92 | const newIntercept = { 93 | request: { 94 | method: 'POST', 95 | url: 'https://localhost:3000/create', 96 | body: 'hello' 97 | }, 98 | response: { 99 | statusCode: 201, 100 | statusText: 'Created' 101 | } 102 | } as XHRRequestAndResponse 103 | const result = constructPactFile({ 104 | intercept: newIntercept, 105 | testCaseTitle: 'create todo', 106 | pactConfig: { 107 | consumerName: 'ui-consumer', 108 | providerName: 'todo-api' 109 | } 110 | }) 111 | expect(result.consumer.name).toBe('ui-consumer') 112 | expect(result.provider.name).toBe('todo-api') 113 | expect(result.interactions.length).toBe(1) 114 | }) 115 | }) 116 | 117 | describe('readFile', () => { 118 | it('should return null when no file is found', async () => { 119 | const mock = jest.spyOn(promises, 'stat') 120 | mock.mockReturnValue( 121 | new Promise((resolve, reject) => { 122 | reject() 123 | }) 124 | ) 125 | const fileContent = await readFileAsync(promises, 'hello') 126 | expect(fileContent).toBeNull() 127 | }) 128 | 129 | it('should return file content', async () => { 130 | const statMock = jest.spyOn(promises, 'stat') 131 | statMock.mockReturnValue( 132 | new Promise((resolve) => { 133 | resolve({} as Stats) 134 | }) 135 | ) 136 | 137 | const readFileMock = jest.spyOn(promises, 'readFile') 138 | readFileMock.mockReturnValue( 139 | new Promise((resolve) => { 140 | resolve('hello') 141 | }) 142 | ) 143 | const fileContent = await readFileAsync(promises, 'hello') 144 | expect(fileContent).toBe('hello') 145 | }) 146 | }) 147 | 148 | describe('omitHeaders', () => { 149 | it('should omit auto-generated headers and header from customised blocklist', () => { 150 | const result = omitHeaders( 151 | { 152 | referer: 'me', 153 | 'x-pactflow': 'lol', 154 | 'ignore-me': 'ignore' 155 | }, 156 | ['ignore-me', 'referer'] 157 | ) 158 | expect(result).toStrictEqual({ 'x-pactflow': 'lol' }) 159 | }) 160 | }) 161 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "baseUrl": "src", 6 | "sourceMap": true, 7 | "noLib": false, 8 | "noImplicitReturns": true, 9 | "noImplicitAny": true, 10 | "noImplicitThis": true, 11 | "resolveJsonModule": true, 12 | "strictNullChecks": true, 13 | "moduleResolution": "node", 14 | "noEmitOnError": true, 15 | "emitDecoratorMetadata": true, 16 | "declaration": true, 17 | "experimentalDecorators": true, 18 | "target": "es5", 19 | "lib": ["es5", "dom"], 20 | "types": ["node", "cypress", "jest"], 21 | "skipLibCheck": true 22 | }, 23 | "include": ["src"], 24 | "exclude": ["./node_modules/**", "dist", "examples"] 25 | } 26 | --------------------------------------------------------------------------------