├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── issue_template.md ├── pull_request_template.md ├── renovate.json └── workflows │ ├── build-and-test.yml │ ├── publish.yml │ ├── smartbear-issue-label-added.yml │ ├── triage.yml │ └── update-ffi.yml ├── .gitignore ├── .mocharc.js ├── .npmignore ├── .prettierignore ├── .snyk ├── CHANGELOG.md ├── CONTRIBUTING.md ├── DEVELOPER.md ├── LICENSE ├── MIGRATION.md ├── README.md ├── RELEASING.md ├── binding.gyp ├── native ├── addon.cc ├── consumer.cc ├── consumer.h ├── ffi.cc ├── ffi.h ├── plugin.cc ├── plugin.h ├── provider.cc └── provider.h ├── package-lock.json ├── package.json ├── package.json.tmpl ├── script ├── README.md ├── ci │ ├── README.md │ ├── build-and-test.sh │ ├── build-opt-dependencies.sh │ ├── check-release-libs.sh │ ├── clean.sh │ ├── lib │ │ ├── README.md │ │ ├── create_npmrc_file.sh │ │ ├── get-version.sh │ │ └── publish.sh │ ├── prebuild-alpine.sh │ ├── prebuild.sh │ ├── release.sh │ └── unpack-and-test.sh ├── create-pr-to-update-pact-ffi.sh ├── dispatch-ffi-released.sh ├── download-libs.sh ├── download-plugins.sh ├── install-plugin-cli.sh ├── lib │ ├── README.md │ ├── download-ffi.sh │ ├── download-file.sh │ ├── export-binary-versions.sh │ └── robust-bash.sh └── trigger-release.sh ├── src ├── consumer │ ├── checkErrors.ts │ ├── index.ts │ ├── internals.ts │ └── types.ts ├── ffi │ ├── index.ts │ ├── node-gyp-build.ts │ └── types.ts ├── index.spec.ts ├── index.ts ├── logger │ ├── crashMessage.ts │ ├── index.ts │ ├── pino.ts │ └── types.ts ├── pact-environment.ts ├── pact.spec.ts ├── pact.ts └── verifier │ ├── argumentMapper │ ├── arguments.ts │ ├── index.ts │ └── types.ts │ ├── index.ts │ ├── nativeVerifier.ts │ ├── types.ts │ ├── validateOptions.spec.ts │ └── validateOptions.ts ├── test ├── consumer.integration.spec.ts ├── integration │ ├── data-utils.ts │ ├── data │ │ ├── get-noauth-provider_they-consumer_anotherclient-latest.json │ │ ├── get-noauth-provider_they-consumer_me-latest.json │ │ ├── get-provider_they-consumer_anotherclient-latest.json │ │ └── get-provider_they-consumer_me-latest.json │ ├── grpc │ │ ├── grpc.json │ │ └── route_guide.proto │ ├── me-they-fail.json │ ├── me-they-multi.json │ ├── me-they-post-regex-success.json │ ├── me-they-post-success.json │ ├── me-they-states.json │ ├── me-they-success.json │ ├── me-they-weird path-success.json │ ├── plugin.proto │ ├── provider-mock.ts │ ├── publish-verification-example weird path-success.json │ ├── publish-verification-example-fail.json │ └── publish-verification-example-success.json ├── matt.consumer.integration.spec.ts ├── matt.provider.integration.spec.ts ├── message.integration.spec.ts ├── monkeypatch.rb ├── plugin-verifier.integration.spec.ts └── verifier.integration.spec.ts ├── ts-node.js ├── tsconfig.build.json └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.d.ts 2 | src/**/*.js 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint", "import", "mocha", "chai-friendly"], 5 | "extends": [ 6 | "eslint:recommended", 7 | "plugin:@typescript-eslint/recommended", 8 | "airbnb-base", 9 | "airbnb-typescript/base", 10 | "prettier" 11 | ], 12 | "settings": { 13 | "import/resolver": { 14 | "typescript": { 15 | "project": ["tsconfig.json"] 16 | } 17 | } 18 | }, 19 | "parserOptions": { 20 | "ecmaVersion": 2018, 21 | "sourceType": "module", 22 | "project": ["tsconfig.json"] 23 | }, 24 | "rules": { 25 | "@typescript-eslint/no-unused-vars": "error", 26 | "@typescript-eslint/no-explicit-any": "error", 27 | "@typescript-eslint/no-shadow": "error", 28 | "@typescript-eslint/explicit-module-boundary-types": "error", 29 | "@typescript-eslint/no-empty-function": [ 30 | "error", 31 | { "allow": ["constructors"] } 32 | ], 33 | "import/prefer-default-export": "off", 34 | "import/no-import-module-exports": "off", 35 | "no-underscore-dangle": "off", 36 | "class-methods-use-this": "off" 37 | }, 38 | "overrides": [ 39 | { 40 | "files": ["**/*.spec.ts", "test/**/*.ts"], 41 | "env": { 42 | "mocha": true 43 | }, 44 | "extends": ["plugin:mocha/recommended"], 45 | "rules": { 46 | "@typescript-eslint/ban-ts-comment": "off", 47 | "@typescript-eslint/dot-notation": [ 48 | "error", 49 | { "allowPattern": "RouteGuide" } 50 | ], 51 | "@typescript-eslint/no-explicit-any": "off", 52 | "@typescript-eslint/no-empty-function": "off", 53 | "@typescript-eslint/no-unused-expressions": "off", 54 | "@typescript-eslint/no-unused-vars": [ 55 | "error", 56 | { "varsIgnorePattern": "unused" } 57 | ], 58 | "chai-friendly/no-unused-expressions": "error", 59 | "@typescript-eslint/no-var-requires": "off", 60 | "global-require": "off", 61 | "import/no-dynamic-require": "off", 62 | "mocha/no-mocha-arrows": "off", 63 | "mocha/no-setup-in-describe": "off", 64 | "no-console": "off", 65 | "no-new": "off" 66 | } 67 | } 68 | ], 69 | "globals": { 70 | "NodeJS": true 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug with pact-js-core 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | _Thank you for reporting a bug! We appreciate it very much. Issues are a big input into the priorities for Pact development_ 11 | 12 | _All italic text in this template is safe to remove before submitting_ 13 | 14 | _Thanks again!_ 15 | 16 | ### Software versions 17 | 18 | _Please provide at least OS and version of pact-js_ 19 | 20 | - **OS**: _e.g. Mac OSX 10.11.5_ 21 | - **Pact Node version**: _e.g. Pact Node v2.6.0_ 22 | - **Node Version**: `node --version` 23 | - **Other Versions**: _Any other versions you think might be relevant (optional)_ 24 | 25 | ### Issue Checklist 26 | 27 | Please confirm the following: 28 | 29 | - [ ] I have upgraded to the latest 30 | - [ ] I have the read the FAQs in the Readme 31 | - [ ] I have triple checked, that there are **no unhandled promises** in my code 32 | - [ ] I have set my log level to debug and attached a log file showing the complete request/response cycle 33 | - [ ] For bonus points and virtual high fives, I have created a reproduceable git repository (see below) to illustrate the problem 34 | 35 | ### Expected behaviour 36 | 37 | _fill in here_ 38 | 39 | ### Actual behaviour 40 | 41 | _fill in here_ 42 | 43 | ### Steps to reproduce 44 | 45 | _How can someone else reproduce this bug?_ 46 | 47 | _For extra bonus points and internet karma, provide a repository, gist or reproducible code snippet so that we can test the problem._ 48 | 49 | _We recommend forking the project and modifying a relevant example [in the examples folder](https://github.com/pact-foundation/pact-js/blob/master/examples)_ 50 | 51 | ### Relevant log files 52 | 53 | _Please ensure you set logging to `DEBUG` and attach any relevant log files here (or link to a gist)._ 54 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Request a new feature (or a modification to an existing feature) 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | _Thank you for making a feature request! We appreciate it very much. GitHub Issues are a big input into the priorities for Pact development_ 11 | 12 | _All italic text in this template is safe to remove before submitting_ 13 | 14 | _Thanks again!_ 15 | 16 | ### Checklist 17 | 18 | _This checklist is optional, but studies show that people who have followed it checklist are really excellent people and we like them_ 19 | 20 | Before making a feature request, I have: 21 | 22 | - [ ] [Searched the issues to check that this feature hasn't been requested before](https://github.com/pact-foundation/pact-js-core/issues?q=is%3Aissue) 23 | - [ ] Checked the documentation to see if it is possible to do what I want already 24 | 25 | ### Feature description 26 | 27 | _Please describe what you would like Pact-js-core to do_ 28 | 29 | ### Use case 30 | 31 | _What is the use case that motivates this feature request?_ 32 | 33 | _Please describe *why* you would like Pact-js-core to have this feature._ 34 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | _Thank you for reporting an issue! We appreciate it very much. Issues are a big input into the priorities for Pact development_ 2 | 3 | _NOTE: If your issue is a how-to question, it is probably better asked in the pact-foundation slack channel, https://slack.pact.io/ _ 4 | 5 | _This is the template for issues that aren't feature requests or bug reports. If your issue is a feature request or bug report, please use the relevant template from https://github.com/pact-foundation/pact-js-core/issues/new/choose - these will cover off the most common questions we ask and speed up a fix_ 6 | 7 | _If you're reading this thinking "Hmmm, it is bug report/feature request, but neither of those templates work for what I want to report", then please feel free to ignore the template in whatever way you think best suits your issue. We trust you, you're awesome_ 8 | 9 | _All italic text in this template is safe to remove before submitting_ 10 | 11 | _Thanks again!_ 12 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | _Please read and then remove this section_ 2 | 3 | Thank you for making a pull request! 4 | 5 | Pact-js-core is built and maintained by developers like you, and we appreciate contributions very much. You are awesome! 6 | 7 | Our changelog is automatically built from our commit history, using conventional changelog. This means we'd like to take care that: 8 | 9 | - commit messages with the prefix `fix:` or `fix(foo):` are suitable to be added to the changelog under "Fixes and improvements" 10 | - commit messages with the prefix `feat:` or `feat(foo):` are suitable to be added to the changelog under "New features" 11 | 12 | If you've made many commits that don't adhere to this style, we recommend squashing 13 | your commits to a new branch before making a PR. Alternatively, we can do a squash 14 | merge, but you'll lose attribution for your change. 15 | 16 | For more information please see CONTRIBUTING.md 17 | 18 | _Everything above can be removed_ 19 | 20 | ### PR Template 21 | 22 | _Please describe what this PR is for, or link the issue that this PR fixes_ 23 | 24 | _You may add as much or as little context as you like here, whatever you think is right_ 25 | 26 | _Thanks again!_ 27 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:best-practices", 5 | ":pinOnlyDevDependencies" 6 | ], 7 | "prHourlyLimit": 0, 8 | "prConcurrentLimit": 5, 9 | "automerge": true 10 | } 11 | -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | workflow_dispatch: 11 | 12 | jobs: 13 | 14 | create_pre_release: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 18 | with: 19 | fetch-depth: 0 20 | - run: GH_CREATE_PRE_RELEASE=true ./script/ci/release.sh 21 | if: github.ref == 'refs/heads/master' && env.ACT != true && runner.os == 'Linux' 22 | env: 23 | GITHUB_TOKEN: ${{ github.token }} 24 | 25 | prebuild: 26 | needs: [create_pre_release] 27 | runs-on: ${{ matrix.os }} 28 | defaults: 29 | run: 30 | shell: bash 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | node-version: [20] 35 | os: [ 36 | macos-13, 37 | ubuntu-latest, 38 | windows-latest 39 | ] 40 | docker: [false] 41 | alpine: [false] 42 | arch: ['amd64'] 43 | include: 44 | - os: ubuntu-24.04-arm 45 | docker: false 46 | alpine: false 47 | arch: arm64 48 | - os: ubuntu-24.04-arm 49 | docker: true 50 | alpine: true 51 | arch: arm64 52 | - os: ubuntu-latest 53 | docker: true 54 | alpine: true 55 | arch: amd64 56 | - os: macos-14 57 | docker: false 58 | alpine: false 59 | arch: arm64 60 | name: Prebuild ${{ matrix.docker == true && matrix.alpine == true && 'linux-musl' || matrix.docker == true && matrix.alpine == false && 'linux' || matrix.os }}-${{ matrix.arch }} 61 | 62 | env: 63 | NODE_VERSION: ${{ matrix.node-version }} 64 | 65 | steps: 66 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 67 | if: github.ref == 'refs/heads/master' 68 | with: 69 | fetch-depth: 0 70 | 71 | - name: Use Node.js ${{ env.NODE_VERSION }} 72 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 73 | if: github.ref == 'refs/heads/master' 74 | with: 75 | node-version: ${{ env.NODE_VERSION }} 76 | 77 | - if: ${{ runner.os == 'Windows' && github.ref == 'refs/heads/master'}} 78 | run: echo "ONLY_DOWNLOAD_PACT_FOR_WINDOWS=true" >> $GITHUB_ENV 79 | 80 | - if: ${{ matrix.docker == true && matrix.alpine == true && github.ref == 'refs/heads/master'}} 81 | name: prebuild linux ${{ matrix.arch }} musl 82 | run: docker run -v $PWD:/home --platform linux/${{ matrix.arch }} --rm node:20-alpine bin/sh -c 'apk add bash && cd /home && bash -c "/home/script/ci/prebuild-alpine.sh" && rm -rf ffi node_modules' 83 | 84 | - if: ${{ matrix.docker == true && matrix.alpine != true && github.ref == 'refs/heads/master' }} 85 | name: prebuild linux ${{ matrix.arch }} 86 | run: docker run -v $PWD:/home --platform linux/${{ matrix.arch }} --rm node:20 bin/bash -c 'cd /home && /home/script/ci/prebuild.sh && rm -rf ffi node_modules' 87 | 88 | - run: sudo chown -R $(id -u):$(id -g) prebuilds 89 | if: ${{ matrix.docker == true && github.ref == 'refs/heads/master' }} 90 | 91 | - run: ./script/ci/prebuild.sh 92 | if: ${{ matrix.docker != true && github.ref == 'refs/heads/master'}} 93 | 94 | - name: Upload prebuild for ${{ runner.os }}-${{ runner.arch }} 95 | uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4 96 | if: github.ref == 'refs/heads/master' 97 | with: 98 | path: prebuilds/*.tar.gz 99 | name: artifact-${{ matrix.docker == true && matrix.alpine == true && 'linux-musl' || matrix.docker == true && matrix.alpine == false && 'linux' || matrix.os }}-${{ matrix.arch }} 100 | 101 | - run: GH_PRE_RELEASE_UPLOAD=true ./script/ci/release.sh 102 | if: github.ref == 'refs/heads/master' && env.ACT != true 103 | env: 104 | GITHUB_TOKEN: ${{ github.token }} 105 | 106 | test: 107 | runs-on: ${{ matrix.os }} 108 | needs: [prebuild] 109 | defaults: 110 | run: 111 | shell: bash 112 | strategy: 113 | fail-fast: false 114 | matrix: 115 | node-version: [16, 18, 20, 22] 116 | os: [macos-14, macos-13, ubuntu-latest, windows-latest, ubuntu-24.04-arm] 117 | docker: [false] 118 | include: 119 | - os: ubuntu-24.04-arm 120 | docker: true 121 | alpine: true 122 | arch: arm64 123 | node-version: 22 124 | - os: ubuntu-latest 125 | docker: true 126 | alpine: true 127 | arch: amd64 128 | node-version: 22 129 | - os: ubuntu-24.04-arm 130 | docker: true 131 | alpine: true 132 | arch: arm64 133 | node-version: 20 134 | - os: ubuntu-latest 135 | docker: true 136 | alpine: true 137 | arch: amd64 138 | node-version: 20 139 | - os: ubuntu-24.04-arm 140 | docker: true 141 | alpine: true 142 | arch: arm64 143 | node-version: 18 144 | - os: ubuntu-latest 145 | docker: true 146 | alpine: true 147 | arch: amd64 148 | node-version: 18 149 | 150 | name: Test ${{ matrix.docker == true && matrix.alpine == true && 'linux-musl' || matrix.docker == true && matrix.alpine == false && 'linux' || matrix.os }}-${{ matrix.arch }}-node-${{ matrix.node-version }} 151 | 152 | env: 153 | NODE_VERSION: ${{ matrix.node-version }} 154 | LOG_LEVEL: debug 155 | 156 | steps: 157 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 158 | with: 159 | fetch-depth: 0 160 | 161 | - name: Download prebuilds 162 | if: github.ref == 'refs/heads/master' 163 | uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 164 | - run: FETCH_ASSETS=true REPO=pact-foundation/pact-js-core ./script/ci/check-release-libs.sh --fetch-assets 165 | if: github.ref != 'refs/heads/master' 166 | env: 167 | GITHUB_TOKEN: ${{ github.token }} 168 | 169 | - name: Use Node.js ${{ env.NODE_VERSION }} 170 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 171 | with: 172 | node-version: ${{ env.NODE_VERSION }} 173 | 174 | - if: runner.os == 'Windows' 175 | run: echo "ONLY_DOWNLOAD_PACT_FOR_WINDOWS=true" >> $GITHUB_ENV 176 | - if: matrix.os == 'macos-14' 177 | run: brew install protobuf 178 | 179 | - run: LOG_LEVEL=debug ./script/ci/unpack-and-test.sh 180 | if: ${{ matrix.docker != true }} 181 | 182 | - if: ${{ matrix.docker == true && matrix.alpine == true && matrix.arch == 'amd64' && matrix.os == 'ubuntu-latest' }} 183 | name: test linux amd64 musl 184 | run: docker run -v $PWD:/home --platform linux/${{ matrix.arch }} --rm node:${{ matrix.node-version }}-alpine bin/sh -c 'apk add jq gettext-envsubst bash curl gcompat file && cd /home && /home/script/ci/unpack-and-test.sh' 185 | 186 | - if: ${{ matrix.docker == true && matrix.alpine == true && matrix.arch == 'arm64' && matrix.os == 'ubuntu-24.04-arm' }} 187 | name: test linux arm64 musl 188 | run: docker run -v $PWD:/home --platform linux/${{ matrix.arch }} --rm node:${{ matrix.node-version }}-alpine bin/sh -c 'apk add jq gettext-envsubst bash curl file protoc protobuf-dev && cd /home && /home/script/ci/unpack-and-test.sh' 189 | 190 | release_dry_run: 191 | runs-on: ubuntu-latest 192 | needs: [create_pre_release, prebuild] 193 | if: github.ref == 'refs/heads/master' 194 | 195 | env: 196 | NODE_VERSION: 20 197 | 198 | steps: 199 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 200 | with: 201 | fetch-depth: 0 202 | 203 | - name: Use Node.js ${{ env.NODE_VERSION }} 204 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 205 | with: 206 | node-version: ${{ env.NODE_VERSION }} 207 | registry-url: 'https://registry.npmjs.org' 208 | 209 | - name: 'release - dry run: ${{ env.DRY_RUN }}' 210 | id: publish 211 | run: script/ci/release.sh 212 | env: 213 | GITHUB_TOKEN: ${{ github.token }} 214 | DRY_RUN: true 215 | 216 | - run: echo "New Release will be v${{ steps.publish.outputs.version }}" 217 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish and release 2 | 3 | on: 4 | workflow_dispatch: 5 | repository_dispatch: 6 | types: 7 | - release-triggered 8 | 9 | jobs: 10 | release: 11 | runs-on: ubuntu-latest 12 | 13 | env: 14 | NODE_VERSION: 20 15 | 16 | steps: 17 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Use Node.js ${{ env.NODE_VERSION }} 22 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 23 | with: 24 | node-version: ${{ env.NODE_VERSION }} 25 | registry-url: 'https://registry.npmjs.org' 26 | env: 27 | NODE_AUTH_TOKEN: ${{secrets.NPM_AUTOMATION_TOKEN}} 28 | - name: "release" 29 | id: publish 30 | run: script/ci/release.sh 31 | env: 32 | GITHUB_TOKEN: ${{ github.token }} 33 | -------------------------------------------------------------------------------- /.github/workflows/smartbear-issue-label-added.yml: -------------------------------------------------------------------------------- 1 | name: SmartBear Supported Issue Label Added 2 | 3 | on: 4 | issues: 5 | types: 6 | - labeled 7 | 8 | jobs: 9 | call-workflow: 10 | uses: pact-foundation/.github/.github/workflows/smartbear-issue-label-added.yml@master 11 | secrets: inherit 12 | -------------------------------------------------------------------------------- /.github/workflows/triage.yml: -------------------------------------------------------------------------------- 1 | name: Triage Issue 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | - labeled 8 | pull_request: 9 | types: 10 | - labeled 11 | 12 | jobs: 13 | call-workflow: 14 | uses: pact-foundation/.github/.github/workflows/triage.yml@master 15 | secrets: inherit 16 | -------------------------------------------------------------------------------- /.github/workflows/update-ffi.yml: -------------------------------------------------------------------------------- 1 | name: Update Pact FFI Library 2 | 3 | on: 4 | repository_dispatch: 5 | types: 6 | - pact-ffi-released 7 | 8 | jobs: 9 | update: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 13 | 14 | - run: | 15 | git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com" 16 | git config --global user.name "${GITHUB_ACTOR}" 17 | git config pull.ff only 18 | 19 | - run: script/create-pr-to-update-pact-ffi.sh ${{ github.event.client_payload.version }} 20 | env: 21 | GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | npm-debug.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | 23 | 24 | # Redis database dump 25 | dump.rdb 26 | 27 | # Dependency directory 28 | node_modules 29 | 30 | # IDE settings 31 | .settings 32 | .idea 33 | *.iml 34 | .vscode 35 | 36 | # packages 37 | *.zip 38 | *.tgz 39 | *.tar.gz 40 | 41 | # Ignore compiled files 42 | src/**/**.d.ts 43 | test/**/**.d.ts 44 | bin/**/**.d.ts 45 | **/*.d.ts 46 | src/**/**.js 47 | test/**/**.js 48 | bin/**/**.js 49 | # *.js 50 | # !test.js 51 | 52 | *.js.map 53 | 54 | # ts-node cache 55 | ts-node-* 56 | 57 | # FFI native bindings 58 | *.so 59 | *.dll* 60 | *.dylib 61 | pact.h 62 | pact-cpp.h 63 | ffi/README.md 64 | # Compiled binary addons (http://nodejs.org/api/addons.html) 65 | build 66 | # Precompiled binary addons 67 | prebuilds 68 | 69 | # Folders created during testing 70 | log 71 | reports 72 | tmp 73 | .tmp 74 | test/__testoutput__ 75 | 76 | # platform-arch specific packages 77 | @pact-foundation/* -------------------------------------------------------------------------------- /.mocharc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | exclude: '**/*.jest.spec.ts', 3 | require: 'ts-node/register', 4 | timeout: '30000', 5 | slow: '5000', 6 | exit: true, 7 | require: 'ts-node.js', 8 | 'check-leaks': true, 9 | }; 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | npm-debug.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Build scripts 11 | script 12 | 13 | # Tests 14 | test 15 | *.spec.ts 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # node-waf configuration 27 | .lock-wscript 28 | 29 | # Compiled binary addons (http://nodejs.org/api/addons.html) 30 | build/Release 31 | 32 | # Redis database dump 33 | dump.rdb 34 | 35 | # Dependency directory 36 | node_modules 37 | 38 | # IDE settings and dev tools 39 | .settings 40 | .idea 41 | *.iml 42 | .eslintignore 43 | .eslintrc 44 | .mocharc.js 45 | .prettierignore 46 | jest.config.js 47 | appveyor.yml 48 | docker-compose.yml 49 | .github 50 | .vscode 51 | ts-node.js 52 | 53 | # npm 54 | package-lock.json 55 | 56 | # packages 57 | *.zip 58 | *.tgz 59 | *.tar.gz 60 | 61 | 62 | # ts-node cache 63 | ts-node-* 64 | 65 | # ignore the TypeScript sources as compiled JS is published 66 | *.ts 67 | 68 | # include TypeScript definitions to go with compiled JS 69 | !*.d.ts 70 | 71 | # Folders created during testing 72 | log 73 | reports 74 | tmp 75 | .tmp 76 | 77 | # Actual tests 78 | test 79 | 80 | # release scripts 81 | script 82 | 83 | ## these arent needed in the final bundle 84 | binding.gyp 85 | native 86 | 87 | .gitattributes 88 | DEVELOPER.md 89 | RELEASING.md 90 | test.js 91 | tsconfig.build.json 92 | tsconfig.json 93 | 94 | # Standalone Binaries - Published as seperate packages 95 | @pact-foundation/ 96 | 97 | # Cross packaging files 98 | Makefile 99 | package.json.tmpl 100 | prebuilds -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.d.ts 2 | *.md -------------------------------------------------------------------------------- /.snyk: -------------------------------------------------------------------------------- 1 | # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. 2 | version: v1.13.5 3 | ignore: {} 4 | # patches apply the minimum changes required to fix a vulnerability 5 | patch: 6 | 'npm:extend:20180424': 7 | - request > extend: 8 | patched: '2019-07-16T10:43:49.177Z' 9 | SNYK-JS-HTTPSPROXYAGENT-469131: 10 | - snyk > proxy-agent > https-proxy-agent: 11 | patched: '2019-10-04T05:36:40.516Z' 12 | - snyk > proxy-agent > pac-proxy-agent > https-proxy-agent: 13 | patched: '2019-10-04T05:36:40.516Z' 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Key Branches 4 | 5 | * `master` - this is the current main version supporting the 10.x.x release line of Pact JS. Most investment will be here, inclruding major new features, enhancements, bug fixes, security patches etc. 6 | * `pact-node` - this is the previous major version supporting the 9.x.x release line of Pact JS. Critical security patches and bug fixes will be provided as a priority. 7 | * 8 | ## Raising issues 9 | 10 | Before raising an issue, make sure you have checked the open and closed issues to see if an answer is provided there. 11 | There may also be an answer to your question on [stackoverflow]. 12 | 13 | Please provide the following information with your issue to enable us to respond as quickly as possible. 14 | 15 | * The relevant versions of the packages you are using. 16 | * The steps to recreate your issue. 17 | * An executable code example where possible. You can fork this repository and use one of the [examples] to quickly recreate your issue. 18 | 19 | ## I want to contribute code but don't know where to start 20 | 21 | If you're not sure where to start, look for the [help wanted](https://github.com/pact-foundation/pact-core/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) 22 | label in our issue tracker. If you have an idea that you think would be great, come and chat to us on [slack] in the `#pact-js` channel, or open a feature request issue. 23 | 24 | ## I'm ready to contribute code 25 | 26 | Awesome! We have some guidelines here that will help your PR be accepted: 27 | 28 | ### Commit messages 29 | 30 | Pact Node uses the [Conventional Changelog](https://github.com/bcoe/conventional-changelog-standard/blob/master/convention.md) 31 | commit message conventions to simplify automation process. Please ensure you follow the guidelines. 32 | 33 | You can take a look at the git history (`git log`) to get the gist of it. 34 | If you have questions, feel free to reach out in `#pact-js` in our [slack 35 | community](https://pact-foundation.slack.com/). 36 | 37 | #### Release notes 38 | 39 | Commit messages with `fix` or `feat` prefixes will appear in the release notes. 40 | These communicate changes that users may want to know about. 41 | 42 | * `feat():` or `feat:` messages appear under "New Features", and trigger minor version bumps. 43 | * `fix():` or `fix:` messages appear under "Fixes and improvements", and trigger patch version bumps. 44 | 45 | If your commit message introduces a breaking change, please include a footer that starts with `BREAKING CHANGE:`. 46 | For more information, please see the [Conventional Changelog](https://github.com/bcoe/conventional-changelog-standard/blob/master/convention.md) 47 | guidelines. 48 | 49 | (Also, if you are committing breaking changes, you may want to check with the other maintainers on slack first). 50 | 51 | Examples of `fix` include bug fixes and dependency bumps that users of pact-js may want to know about. 52 | 53 | Examples of `feat` include new features and substantial modifications to existing features. 54 | 55 | Examples of things that we'd prefer not to appear in the release notes include documentation updates, 56 | modified or new examples, refactorings, new tests, etc. We usually use one of `chore`, `style`, 57 | `refactor`, or `test` as appropriate. 58 | 59 | ## Pull requests 60 | 61 | * Write tests for any changes 62 | * Follow existing code style and conventions 63 | * Separate unrelated changes into multiple pull requests 64 | * For bigger changes, make sure you start a discussion first by creating an issue and explaining the intended change 65 | 66 | [stackoverflow]: https://stackoverflow.com/questions/tagged/pact 67 | -------------------------------------------------------------------------------- /DEVELOPER.md: -------------------------------------------------------------------------------- 1 | # Developer documentation 2 | 3 | Pact-Js-Core uses FFI bindings from the pact-reference project, which are prebuilt for end users, the following steps will show some of the steps required to build and test locally on your machine. 4 | 5 | Do this and you should be 👌👌👌: 6 | 7 | ```sh 8 | bash script/ci/prebuild.sh 9 | supported_platforms=$(./script/ci/build-opt-dependencies.sh determine_platform) 10 | ./script/ci/build-opt-dependencies.sh build 11 | ./script/ci/build-opt-dependencies.sh link 12 | npm run build 13 | npm run test 14 | ``` 15 | 16 | set supported platform to one of these values 17 | 18 | - `linux-x64-glibc` 19 | - `linux-arm64-glibc` 20 | - `linux-x64-musl` 21 | - `linux-arm64-musl` 22 | - `darwin-x64` 23 | - `darwin-arm64` 24 | - `windows-x64` 25 | 26 | _notes_ - 27 | 28 | As a developer, you need to run `bash script/ci/prebuild.sh` to 29 | 30 | - download the FFI libraries to `ffi` folder 31 | - prebuilds the binaries and outputs to `prebuilds` 32 | - cleans up `ffi` and `build` 33 | 34 | For end users, the following is provided as part of the packaging and release step in CI. 35 | 36 | - the `prebuilds` folder containing built `ffi` bindings 37 | - the `binding.gyp` file is removed from the npm package, so `npm install` doesn't attempt to build the `ffi` buildings, that are prebuilt. 38 | 39 | If you have a `binding.gyp` file, and have created `prebuilds` you will want to perform `npm ci` or `npm install` with `--ignore-scripts` set, to avoid building the `ffi` which is prebuilt. 40 | 41 | Alternatively you can run the following, which will not create a prebuild, but instead use `node-gyp` to output the built `ffi` libraries to the `build` folder. This was the previous method, which meant that end users would also perform. 42 | 43 | ``` 44 | bash script/download-libs.sh 45 | npm ci 46 | npm run build 47 | npm run test 48 | ``` 49 | 50 | ## Creating Platform specific packages 51 | 52 | We create cross-platform and architecture binaries which are published individually to NPM, and consumed in this project. 53 | 54 | ### Download prebuilt binaries for all platforms 55 | 56 | ```sh 57 | ./script/ci/build_opt_dependencies.sh libs v15.2.1 58 | ``` 59 | 60 | Tag is optional and defaults to latest 61 | 62 | This will run the following script, which will grab the latest prebuilt binaries from GitHub. 63 | 64 | ```sh 65 | FETCH_ASSETS=true ./script/ci/check-release-libs.sh --fetch-assets -t v15.2.1 66 | ``` 67 | 68 | ### Building all platform specific npm packages 69 | 70 | ```sh 71 | ./script/ci/build_opt_dependencies.sh build 72 | ``` 73 | 74 | ### Building individual platform specific npm package 75 | 76 | Supported platforms are 77 | 78 | - linux-x64-glibc 79 | - linux-arm64-glibc 80 | - linux-x64-musl 81 | - linux-arm64-musl 82 | - darwin-x64 83 | - darwin-arm64 84 | - windows-x64 85 | 86 | You can detect your platform with 87 | 88 | ```sh 89 | ./script/ci/build-opt-dependencies.sh determine_platform 90 | ``` 91 | 92 | You can build with one 93 | 94 | ```sh 95 | supported_platforms=$(./script/ci/build-opt-dependencies.sh determine_platform) ./script/ci/build-opt-dependencies.sh build 96 | ``` 97 | 98 | or all 99 | 100 | ```sh 101 | ./script/ci/build-opt-dependencies.sh build 102 | ``` 103 | 104 | ### Linking arch specific package, for your local build 105 | 106 | Make link will try to link all available packages, for all available platforms, and will link any that apply 107 | 108 | ```sh 109 | ./script/ci/build-opt-dependencies.sh 110 | ``` 111 | 112 | You can scope it with `supported_platforms` 113 | 114 | ```sh 115 | supported_platforms=$(./script/ci/build-opt-dependencies.sh determine_platform) ./script/ci/build-opt-dependencies.sh link 116 | ``` 117 | 118 | ### Publishing packages 119 | 120 | Dry run publishing optional packages, (default) 121 | 122 | ```sh 123 | ./script/ci/build-opt-dependencies.sh publish 124 | ``` 125 | 126 | Publishing packages with `--dry-run` option removed. 127 | 128 | ```sh 129 | PUBLISH=true ./script/ci/build-opt-dependencies.sh publish 130 | ``` 131 | 132 | ### Linux x86_64 Task 133 | 134 | #### Pre Reqs 135 | 136 | 1. x86_64 Machine 137 | 1. ARM64 Mac - If you have Rosetta (MacOS) 138 | 139 | ### CI Locally 140 | 141 | 1. Docker/Podman 142 | 2. Act 143 | 144 | ```sh 145 | act --container-architecture linux/amd64 -W .github/workflows/build-and-test.yml --artifact-server-path tmp 146 | ``` 147 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Pact Foundation 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. 22 | 23 | -------------------------------------------------------------------------------- /MIGRATION.md: -------------------------------------------------------------------------------- 1 | # Pact-core migration guide 2 | 3 | This guide is for migrating code that calls Pact-Core to a new version. 4 | If you are a user of pact rather than a maintainer of tools for pact, you 5 | probably want to be using https://github.com/pact-foundation/pact-js directly 6 | instead of pact-core. There is a corresponding migration guide there. 7 | 8 | # v12.x.x from v11.x.x 9 | 10 | ## Verifier 11 | 12 | * Remove `verbose`. This option has been removed, as it is now implied by `logLevel` 13 | of `DEBUG` or `TRACE`. 14 | * `customProviderHeaders` has been removed. If you need this functionality, use an 15 | appropriate request filter with `requestFilters` instead. 16 | * Remove `logDir` / `format` / `out` options, as they are no longer supported. 17 | 18 | ### Changes you may want to know about: 19 | 20 | * All logging and reporting is now on standard out. This was the default before. If 21 | your ecosystem needs the ability to customise logging and reporting, please let us 22 | know by opening an issue. 23 | * The undocumented option `monkeypatch` has been removed. The use cases for this feature are mostly covered by other options. 24 | 25 | # v11.x.x. from v10.x.x and below 26 | 27 | * Update any calls that recieves a `q.Promise` to simply accept a native ES `Promise`. 28 | * In `VerifierOptions`: replace use of `tags`, `consumerVersionTag` and 29 | `providerVersionTag` with the appropriate `consumerVersionTags` or 30 | `providerVersionTags` option. 31 | 32 | # v10.12.0 (first version as `pact-core`) 33 | 34 | * `@pact-foundation/pact-node` has been renamed to `@pact-foundation/pact-core`. 35 | This better describes what it is for, and will hopefully reduce confusion. See 36 | [this issue](https://github.com/pact-foundation/pact-js-core/issues/224) for 37 | background. -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Releasing 4 | 5 | We use Github Actions for releases. 6 | 7 | ## How a release works 8 | 9 | Releases trigger when the repository recieves the custom repository_dispatch event 10 | `release-triggered`. 11 | 12 | This triggers the `publish.yml` workflow, which in turn 13 | triggers the `release.sh` script in `scripts/ci`. 14 | The workflow will also create a github release with an appropriate changelog. 15 | 16 | Having the release triggered by a custom event is useful for automating 17 | releases in the future (eg for version bumps in pact dependencies). 18 | 19 | ### Release.sh 20 | 21 | This script is not intended to be run locally. Note that it modifies your git 22 | settings. 23 | 24 | The script will: 25 | 26 | - Modify git authorship settings 27 | - Confirm that there would be changes in the changelog after release 28 | - Run Lint 29 | - Run Build 30 | - Run Test 31 | - Commit an appropriate version bump, changelog and tag 32 | - Package and publish to npm 33 | - Push the new commit and tag back to the main branch. 34 | 35 | Should you need to modify the script locally, you will find it uses some 36 | dependencies in `scripts/ci/lib`. 37 | 38 | ## Kicking off a release 39 | 40 | You must be able to create a github access token with `repo` scope to the 41 | pact-js-core repository. 42 | 43 | - Set an environment variable `GITHUB_ACCESS_TOKEN_FOR_PF_RELEASES` to this token. 44 | - Make sure master contains the code you want to release 45 | - Run `scripts/trigger-release.sh` 46 | 47 | Then wait for github to do its magic. It will release the current head of master. 48 | 49 | Note that the release script refuses to publish anything that wouldn't 50 | produce a changelog. Please make sure your commits follow the guidelines in 51 | `CONTRIBUTING.md` 52 | 53 | ## If the release fails 54 | 55 | The publish is the second to last step, so if the release fails, you don't 56 | need to do any rollbacks. 57 | 58 | However, there is a potential for the push to fail _after_ a publish if there 59 | are new commits to master since the release started. This is unlikely with 60 | the current commit frequency, but could still happen. Check the logs to 61 | determine if npm has a version that doesn't exist in the master branch. 62 | 63 | If this has happened, you will need to manually put the release commit in. 64 | 65 | ``` 66 | npm run release # This tags, commits and updates the changelog only 67 | ``` 68 | 69 | Depending on the nature of the new commits to master after the release, you 70 | may need to rebase them on top of the tagged release commit and force push. 71 | 72 | ## Releasing Pact Node Manually 73 | 74 | If any changes needs to be released, let it be dependencies or code, you must have access to push directly to master on the pact-js-core repo, then follow these steps: 75 | 76 | - Run `npm ci --ignore-scripts` to confirm that the dependencies are appropriately configured. 77 | - Run `npm test` first to make sure all tests pass. This will also build and download the appropriate checksums. 78 | - Run `npm run release` to generate the changelog and create a tagged commit 79 | - Run `npm publish --access public --tag latest` to publish to npm. 80 | - Push the commit and the tag to the origin using `git push --follow-tags` 81 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "is_alpine": " 2 | #include "ffi.h" 3 | #include "consumer.h" 4 | #include "provider.h" 5 | #include "plugin.h" 6 | 7 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 8 | exports.Set(Napi::String::New(env, "pactffiVersion"), Napi::Function::New(env, PactffiVersion)); 9 | exports.Set(Napi::String::New(env, "pactffiInit"), Napi::Function::New(env, PactffiInit)); 10 | exports.Set(Napi::String::New(env, "pactffiInitWithLogLevel"), Napi::Function::New(env, PactffiInitWithLogLevel)); 11 | exports.Set(Napi::String::New(env, "pactffiLogToFile"), Napi::Function::New(env, PactffiLogToFile)); 12 | 13 | // Consumer 14 | exports.Set(Napi::String::New(env, "pactffiMockServerMatched"), Napi::Function::New(env, PactffiMockServerMatched)); 15 | exports.Set(Napi::String::New(env, "pactffiMockServerMismatches"), Napi::Function::New(env, PactffiMockServerMismatches)); 16 | exports.Set(Napi::String::New(env, "pactffiCreateMockServerForPact"), Napi::Function::New(env, PactffiCreateMockServerForPact)); 17 | exports.Set(Napi::String::New(env, "pactffiCreateMockServerForTransport"), Napi::Function::New(env, PactffiCreateMockServerForTransport)); 18 | exports.Set(Napi::String::New(env, "pactffiCleanupMockServer"), Napi::Function::New(env, PactffiCleanupMockServer)); 19 | exports.Set(Napi::String::New(env, "pactffiWritePactFile"), Napi::Function::New(env, PactffiWritePactFile)); 20 | exports.Set(Napi::String::New(env, "pactffiWritePactFileByPort"), Napi::Function::New(env, PactffiWritePactFileByPort)); 21 | exports.Set(Napi::String::New(env, "pactffiNewPact"), Napi::Function::New(env, PactffiNewPact)); 22 | exports.Set(Napi::String::New(env, "pactffiNewInteraction"), Napi::Function::New(env, PactffiNewInteraction)); 23 | exports.Set(Napi::String::New(env, "pactffiUponReceiving"), Napi::Function::New(env, PactffiUponReceiving)); 24 | exports.Set(Napi::String::New(env, "pactffiGiven"), Napi::Function::New(env, PactffiGiven)); 25 | exports.Set(Napi::String::New(env, "pactffiGivenWithParam"), Napi::Function::New(env, PactffiGivenWithParam)); 26 | exports.Set(Napi::String::New(env, "pactffiGivenWithParams"), Napi::Function::New(env, PactffiGivenWithParams)); 27 | exports.Set(Napi::String::New(env, "pactffiWithRequest"), Napi::Function::New(env, PactffiWithRequest)); 28 | exports.Set(Napi::String::New(env, "pactffiWithQueryParameter"), Napi::Function::New(env, PactffiWithQueryParameter)); 29 | exports.Set(Napi::String::New(env, "pactffiWithSpecification"), Napi::Function::New(env, PactffiWithSpecification)); 30 | exports.Set(Napi::String::New(env, "pactffiWithPactMetadata"), Napi::Function::New(env, PactffiWithPactMetadata)); 31 | exports.Set(Napi::String::New(env, "pactffiWithHeader"), Napi::Function::New(env, PactffiWithHeader)); 32 | exports.Set(Napi::String::New(env, "pactffiWithBody"), Napi::Function::New(env, PactffiWithBody)); 33 | exports.Set(Napi::String::New(env, "pactffiWithBinaryFile"), Napi::Function::New(env, PactffiWithBinaryFile)); 34 | exports.Set(Napi::String::New(env, "pactffiWithMultipartFile"), Napi::Function::New(env, PactffiWithMultipartFile)); 35 | exports.Set(Napi::String::New(env, "pactffiResponseStatus"), Napi::Function::New(env, PactffiResponseStatus)); 36 | exports.Set(Napi::String::New(env, "pactffiUsingPlugin"), Napi::Function::New(env, PactffiUsingPlugin)); 37 | exports.Set(Napi::String::New(env, "pactffiCleanupPlugins"), Napi::Function::New(env, PactffiCleanupPlugins)); 38 | exports.Set(Napi::String::New(env, "pactffiPluginInteractionContents"), Napi::Function::New(env, PactffiPluginInteractionContents)); 39 | 40 | // exports.Set(Napi::String::New(env, "pactffiNewMessagePact"), Napi::Function::New(env, PactffiNewMessagePact)); 41 | // exports.Set(Napi::String::New(env, "pactffiWriteMessagePactFile"), Napi::Function::New(env, PactffiWriteMessagePactFile)); 42 | // exports.Set(Napi::String::New(env, "pactffiWithMessagePactMetadata"), Napi::Function::New(env, PactffiWithMessagePactMetadata)); 43 | exports.Set(Napi::String::New(env, "pactffiNewAsyncMessage"), Napi::Function::New(env, PactffiNewAsyncMessage)); 44 | exports.Set(Napi::String::New(env, "pactffiNewSyncMessage"), Napi::Function::New(env, PactffiNewSyncMessage)); 45 | // exports.Set(Napi::String::New(env, "pactffiSyncMessageSetDescription"), Napi::Function::New(env, PactffiSyncMessageSetDescription)); 46 | // exports.Set(Napi::String::New(env, "pactffiNewMessage"), Napi::Function::New(env, PactffiNewMessage)); 47 | exports.Set(Napi::String::New(env, "pactffiMessageReify"), Napi::Function::New(env, PactffiMessageReify)); 48 | exports.Set(Napi::String::New(env, "pactffiMessageGiven"), Napi::Function::New(env, PactffiMessageGiven)); 49 | exports.Set(Napi::String::New(env, "pactffiMessageGivenWithParam"), Napi::Function::New(env, PactffiMessageGivenWithParam)); 50 | exports.Set(Napi::String::New(env, "pactffiMessageGivenWithParams"), Napi::Function::New(env, PactffiGivenWithParams)); 51 | exports.Set(Napi::String::New(env, "pactffiMessageWithBinaryContents"), Napi::Function::New(env, PactffiMessageWithBinaryContents)); 52 | exports.Set(Napi::String::New(env, "pactffiMessageWithContents"), Napi::Function::New(env, PactffiMessageWithContents)); 53 | exports.Set(Napi::String::New(env, "pactffiMessageWithMetadata"), Napi::Function::New(env, PactffiMessageWithMetadata)); 54 | exports.Set(Napi::String::New(env, "pactffiMessageExpectsToReceive"), Napi::Function::New(env, PactffiMessageExpectsToReceive)); 55 | 56 | // Provider 57 | exports.Set(Napi::String::New(env, "pactffiVerifierNewForApplication"), Napi::Function::New(env, PactffiVerifierNewForApplication)); 58 | exports.Set(Napi::String::New(env, "pactffiVerifierSetVerificationOptions"), Napi::Function::New(env, PactffiVerifierSetVerificationOptions)); 59 | exports.Set(Napi::String::New(env, "pactffiVerifierSetPublishOptions"), Napi::Function::New(env, PactffiVerifierSetPublishOptions)); 60 | exports.Set(Napi::String::New(env, "pactffiVerifierExecute"), Napi::Function::New(env, PactffiVerifierExecute)); 61 | exports.Set(Napi::String::New(env, "pactffiVerifierShutdown"), Napi::Function::New(env, PactffiVerifierShutdown)); 62 | exports.Set(Napi::String::New(env, "pactffiVerifierSetProviderInfo"), Napi::Function::New(env, PactffiVerifierSetProviderInfo)); 63 | exports.Set(Napi::String::New(env, "pactffiVerifierSetFilterInfo"), Napi::Function::New(env, PactffiVerifierSetFilterInfo)); 64 | exports.Set(Napi::String::New(env, "pactffiVerifierSetProviderState"), Napi::Function::New(env, PactffiVerifierSetProviderState)); 65 | exports.Set(Napi::String::New(env, "pactffiVerifierSetConsumerFilters"), Napi::Function::New(env, PactffiVerifierSetConsumerFilters)); 66 | exports.Set(Napi::String::New(env, "pactffiVerifierSetFailIfNoPactsFound"), Napi::Function::New(env, PactffiVerifierSetFailIfNoPactsFound)); 67 | exports.Set(Napi::String::New(env, "pactffiVerifierAddCustomHeader"), Napi::Function::New(env, PactffiVerifierAddCustomHeader)); 68 | exports.Set(Napi::String::New(env, "pactffiVerifierAddFileSource"), Napi::Function::New(env, PactffiVerifierAddFileSource)); 69 | exports.Set(Napi::String::New(env, "pactffiVerifierAddDirectorySource"), Napi::Function::New(env, PactffiVerifierAddDirectorySource)); 70 | exports.Set(Napi::String::New(env, "pactffiVerifierUrlSource"), Napi::Function::New(env, PactffiVerifierUrlSource)); 71 | exports.Set(Napi::String::New(env, "pactffiVerifierBrokerSourceWithSelectors"), Napi::Function::New(env, PactffiVerifierBrokerSourceWithSelectors)); 72 | exports.Set(Napi::String::New(env, "pactffiVerifierAddProviderTransport"), Napi::Function::New(env, PactffiVerifierAddProviderTransport)); 73 | exports.Set(Napi::String::New(env, "pactffiVerifierSetNoPactsIsError"), Napi::Function::New(env, PactffiVerifierSetNoPactsIsError)); 74 | 75 | return exports; 76 | } 77 | 78 | NODE_API_MODULE(pact, Init) 79 | -------------------------------------------------------------------------------- /native/consumer.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | Napi::Value PactffiUponReceiving(const Napi::CallbackInfo& info); 4 | Napi::Value PactffiWithBinaryFile(const Napi::CallbackInfo& info); 5 | Napi::Value PactffiWithBody(const Napi::CallbackInfo& info); 6 | Napi::Value PactffiWithHeader(const Napi::CallbackInfo& info); 7 | Napi::Value PactffiWithMessagePactMetadata(const Napi::CallbackInfo& info); 8 | Napi::Value PactffiWithMultipartFile(const Napi::CallbackInfo& info); 9 | Napi::Value PactffiWithPactMetadata(const Napi::CallbackInfo& info); 10 | Napi::Value PactffiWithQueryParameter(const Napi::CallbackInfo& info); 11 | Napi::Value PactffiWithRequest(const Napi::CallbackInfo& info); 12 | Napi::Value PactffiWithSpecification(const Napi::CallbackInfo& info); 13 | Napi::Value PactffiWritePactFile(const Napi::CallbackInfo& info); 14 | Napi::Value PactffiWritePactFileByPort(const Napi::CallbackInfo& info); 15 | Napi::Value PactffiCleanupMockServer(const Napi::CallbackInfo& info); 16 | Napi::Value PactffiCreateMockServerForPact(const Napi::CallbackInfo& info); 17 | Napi::Value PactffiCreateMockServer(const Napi::CallbackInfo& info); 18 | Napi::Value PactffiGiven(const Napi::CallbackInfo& info); 19 | Napi::Value PactffiGivenWithParam(const Napi::CallbackInfo& info); 20 | Napi::Value PactffiGivenWithParams(const Napi::CallbackInfo& info); 21 | Napi::Value PactffiMockServerLogs(const Napi::CallbackInfo& info); 22 | Napi::Value PactffiMockServerMatched(const Napi::CallbackInfo& info); 23 | Napi::Value PactffiMockServerMismatches(const Napi::CallbackInfo& info); 24 | Napi::Value PactffiNewAsyncMessage(const Napi::CallbackInfo& info); 25 | Napi::Value PactffiNewInteraction(const Napi::CallbackInfo& info); 26 | Napi::Value PactffiNewPact(const Napi::CallbackInfo& info); 27 | 28 | // Message Pact 29 | Napi::Value PactffiNewAsyncMessage(const Napi::CallbackInfo& info); 30 | Napi::Value PactffiNewSyncMessage(const Napi::CallbackInfo& info); 31 | // Napi::Value PactffiNewMessage(const Napi::CallbackInfo& info); 32 | // Napi::Value PactffiNewSyncMessageInteraction(const Napi::CallbackInfo& info); 33 | // Napi::Value PactffiNewMessageInteraction(const Napi::CallbackInfo& info); 34 | // Napi::Value PactffiNewMessagePact(const Napi::CallbackInfo& info); 35 | Napi::Value PactffiMessageReify(const Napi::CallbackInfo& info); 36 | Napi::Value PactffiMessageGiven(const Napi::CallbackInfo& info); 37 | Napi::Value PactffiMessageGivenWithParam(const Napi::CallbackInfo& info); 38 | Napi::Value PactffiMessageSetDescription(const Napi::CallbackInfo& info); 39 | Napi::Value PactffiMessageWithContents(const Napi::CallbackInfo& info); 40 | Napi::Value PactffiMessageWithBinaryContents(const Napi::CallbackInfo& info); 41 | Napi::Value PactffiMessageWithMetadata(const Napi::CallbackInfo& info); 42 | Napi::Value PactffiMessageExpectsToReceive(const Napi::CallbackInfo& info); 43 | Napi::Value PactffiWriteMessagePactFile(const Napi::CallbackInfo& info); 44 | 45 | // Plugins 46 | Napi::Value PactffiUsingPlugin(const Napi::CallbackInfo& info); 47 | Napi::Value PactffiCleanupPlugins(const Napi::CallbackInfo& info); 48 | Napi::Value PactffiPluginInteractionContents(const Napi::CallbackInfo& info); 49 | 50 | // Unimplemented 51 | Napi::Value PactffiConsumerGetName(const Napi::CallbackInfo& info); 52 | Napi::Value PactffiFreeMessagePactHandle(const Napi::CallbackInfo& info); 53 | Napi::Value PactffiFreePactHandle(const Napi::CallbackInfo& info); 54 | Napi::Value PactffiGenerateDatetimeString(const Napi::CallbackInfo& info); 55 | Napi::Value PactffiGenerateRegexValue(const Napi::CallbackInfo& info); 56 | Napi::Value PactffiGetErrorMessage(const Napi::CallbackInfo& info); 57 | Napi::Value PactffiGetTlsCaCertificate(const Napi::CallbackInfo& info); 58 | Napi::Value PactffiInteractionTestName(const Napi::CallbackInfo& info); 59 | Napi::Value PactffiMatchMessage(const Napi::CallbackInfo& info); 60 | Napi::Value PactffiMessageDelete(const Napi::CallbackInfo& info); 61 | Napi::Value PactffiMessageFindMetadata(const Napi::CallbackInfo& info); 62 | Napi::Value PactffiMessageGetContents(const Napi::CallbackInfo& info); 63 | Napi::Value PactffiMessageGetContentsBin(const Napi::CallbackInfo& info); 64 | Napi::Value PactffiMessageGetContentsLength(const Napi::CallbackInfo& info); 65 | Napi::Value PactffiMessageGetDescription(const Napi::CallbackInfo& info); 66 | Napi::Value PactffiMessageGetMetadataIter(const Napi::CallbackInfo& info); 67 | Napi::Value PactffiMessageGetProviderState(const Napi::CallbackInfo& info); 68 | Napi::Value PactffiMessageGetProviderStateIter(const Napi::CallbackInfo& info); 69 | Napi::Value PactffiMessageInsertMetadata(const Napi::CallbackInfo& info); 70 | Napi::Value PactffiMessageMetadataIterDelete(const Napi::CallbackInfo& info); 71 | Napi::Value PactffiMessageMetadataIterNext(const Napi::CallbackInfo& info); 72 | Napi::Value PactffiMessageMetadataPairDelete(const Napi::CallbackInfo& info); 73 | Napi::Value PactffiMessageNew(const Napi::CallbackInfo& info); 74 | Napi::Value PactffiMessageNewFromBody(const Napi::CallbackInfo& info); 75 | Napi::Value PactffiMessageNewFromJson(const Napi::CallbackInfo& info); 76 | Napi::Value PactffiMessagePactDelete(const Napi::CallbackInfo& info); 77 | Napi::Value PactffiMessagePactFindMetadata(const Napi::CallbackInfo& info); 78 | Napi::Value PactffiMessagePactGetConsumer(const Napi::CallbackInfo& info); 79 | Napi::Value PactffiMessagePactGetMessageIter(const Napi::CallbackInfo& info); 80 | Napi::Value PactffiMessagePactGetMetadataIter(const Napi::CallbackInfo& info); 81 | Napi::Value PactffiMessagePactGetProvider(const Napi::CallbackInfo& info); 82 | Napi::Value PactffiMessagePactMessageIterDelete(const Napi::CallbackInfo& info); 83 | Napi::Value PactffiMessagePactMessageIterNext(const Napi::CallbackInfo& info); 84 | Napi::Value PactffiMessagePactMetadataIterDelete(const Napi::CallbackInfo& info); 85 | Napi::Value PactffiMessagePactMetadataIterNext(const Napi::CallbackInfo& info); 86 | Napi::Value PactffiMessagePactMetadataTripleDelete(const Napi::CallbackInfo& info); 87 | Napi::Value PactffiMessagePactNewFromJson(const Napi::CallbackInfo& info); 88 | Napi::Value PactffiMismatchAnsiDescription(const Napi::CallbackInfo& info); 89 | Napi::Value PactffiMismatchDescription(const Napi::CallbackInfo& info); 90 | Napi::Value PactffiMismatchSummary(const Napi::CallbackInfo& info); 91 | Napi::Value PactffiMismatchToJson(const Napi::CallbackInfo& info); 92 | Napi::Value PactffiMismatchType(const Napi::CallbackInfo& info); 93 | Napi::Value PactffiMismatchesDelete(const Napi::CallbackInfo& info); 94 | Napi::Value PactffiMismatchesGetIter(const Napi::CallbackInfo& info); 95 | Napi::Value PactffiMismatchesIterDelete(const Napi::CallbackInfo& info); 96 | Napi::Value PactffiMismatchesIterNext(const Napi::CallbackInfo& info); 97 | Napi::Value PactffiNewSyncMessageInteraction(const Napi::CallbackInfo& info); 98 | Napi::Value PactffiPactHandleGetMessageIter(const Napi::CallbackInfo& info); 99 | Napi::Value PactffiPactHandleGetSyncMessageIter(const Napi::CallbackInfo& info); 100 | Napi::Value PactffiPactHandleWriteFile(const Napi::CallbackInfo& info); 101 | Napi::Value PactffiPactMessageIterDelete(const Napi::CallbackInfo& info); 102 | Napi::Value PactffiPactMessageIterNext(const Napi::CallbackInfo& info); 103 | Napi::Value PactffiPactSyncMessageIterDelete(const Napi::CallbackInfo& info); 104 | Napi::Value PactffiPactSyncMessageIterNext(const Napi::CallbackInfo& info); 105 | Napi::Value PactffiProviderGetName(const Napi::CallbackInfo& info); 106 | Napi::Value PactffiProviderStateDelete(const Napi::CallbackInfo& info); 107 | Napi::Value PactffiProviderStateGetName(const Napi::CallbackInfo& info); 108 | Napi::Value PactffiProviderStateGetParamIter(const Napi::CallbackInfo& info); 109 | Napi::Value PactffiProviderStateIterDelete(const Napi::CallbackInfo& info); 110 | Napi::Value PactffiProviderStateIterNext(const Napi::CallbackInfo& info); 111 | Napi::Value PactffiProviderStateParamIterDelete(const Napi::CallbackInfo& info); 112 | Napi::Value PactffiProviderStateParamIterNext(const Napi::CallbackInfo& info); 113 | Napi::Value PactffiProviderStateParamPairDelete(const Napi::CallbackInfo& info); 114 | Napi::Value PactffiResponseStatus(const Napi::CallbackInfo& info); 115 | Napi::Value PactffiStringDelete(const Napi::CallbackInfo& info); 116 | Napi::Value PactffiSyncMessageDelete(const Napi::CallbackInfo& info); 117 | Napi::Value PactffiSyncMessageGetDescription(const Napi::CallbackInfo& info); 118 | Napi::Value PactffiSyncMessageGetNumberResponses(const Napi::CallbackInfo& info); 119 | Napi::Value PactffiSyncMessageGetProviderState(const Napi::CallbackInfo& info); 120 | Napi::Value PactffiSyncMessageGetProviderStateIter(const Napi::CallbackInfo& info); 121 | Napi::Value PactffiSyncMessageGetRequestContents(const Napi::CallbackInfo& info); 122 | Napi::Value PactffiSyncMessageGetRequestContentsBin(const Napi::CallbackInfo& info); 123 | Napi::Value PactffiSyncMessageGetRequestContentsLength(const Napi::CallbackInfo& info); 124 | Napi::Value PactffiSyncMessageGetResponseContents(const Napi::CallbackInfo& info); 125 | Napi::Value PactffiSyncMessageGetResponseContentsBin(const Napi::CallbackInfo& info); 126 | Napi::Value PactffiSyncMessageGetResponseContentsLength(const Napi::CallbackInfo& info); 127 | Napi::Value PactffiSyncMessageSetDescription(const Napi::CallbackInfo& info); 128 | Napi::Value PactffiCreateMockServerForTransport(const Napi::CallbackInfo& info); 129 | -------------------------------------------------------------------------------- /native/ffi.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "pact-cpp.h" 3 | 4 | using namespace Napi; 5 | 6 | Napi::Value PactffiVersion(const Napi::CallbackInfo& info) { 7 | const char* version = pactffi_version(); 8 | 9 | return Napi::String::New(info.Env(), version); 10 | } 11 | 12 | Napi::Value PactffiInit(const Napi::CallbackInfo& info) { 13 | Napi::Env env = info.Env(); 14 | 15 | if (info.Length() < 1) { 16 | throw Napi::Error::New(env, "PactffiInit(envVar) received < 1 argument"); 17 | } 18 | 19 | if (!info[0].IsString()) { 20 | throw Napi::Error::New(env, "PactffiInit(envVar) expected a string"); 21 | } 22 | 23 | // Extract log level environment variable 24 | std::string envVar = info[0].As().Utf8Value(); 25 | 26 | // Initialise Pact 27 | pactffi_init_with_log_level(envVar.c_str()); 28 | 29 | return env.Undefined(); 30 | } 31 | 32 | Napi::Value PactffiInitWithLogLevel(const Napi::CallbackInfo& info) { 33 | Napi::Env env = info.Env(); 34 | 35 | if (info.Length() < 1) { 36 | throw Napi::Error::New(env, "PactffiInitWithLogLevel(logLevel) received < 1 argument"); 37 | } 38 | 39 | if (!info[0].IsString()) { 40 | throw Napi::Error::New(env, "PactffiInitWithLogLevel(logLevel) expected a string"); 41 | } 42 | 43 | // Extract log level 44 | std::string logLevel = info[0].As().Utf8Value(); 45 | 46 | // Initialise Pact 47 | pactffi_init_with_log_level(logLevel.c_str()); 48 | 49 | return env.Undefined(); 50 | } 51 | 52 | LevelFilter integerToLevelFilter(Napi::Env &env, uint32_t number) { 53 | LevelFilter logLevel = LevelFilter::LevelFilter_Off; 54 | 55 | switch(number) { 56 | case 0: 57 | logLevel = LevelFilter::LevelFilter_Off; 58 | break; 59 | case 1: 60 | logLevel = LevelFilter::LevelFilter_Error; 61 | break; 62 | case 2: 63 | logLevel = LevelFilter::LevelFilter_Warn; 64 | break; 65 | case 3: 66 | logLevel = LevelFilter::LevelFilter_Info; 67 | break; 68 | case 4: 69 | logLevel = LevelFilter::LevelFilter_Debug; 70 | break; 71 | case 5: 72 | logLevel = LevelFilter::LevelFilter_Trace; 73 | break; 74 | default: 75 | std::string err = "pact-js-core C integration: Unable to parse log level number: "; 76 | err += number; 77 | 78 | throw Napi::Error::New(env, err); 79 | } 80 | 81 | return logLevel; 82 | } 83 | 84 | 85 | Napi::Value PactffiLogToFile(const Napi::CallbackInfo& info) { 86 | // return: int 87 | Napi::Env env = info.Env(); 88 | 89 | if (info.Length() < 2) { 90 | throw Napi::Error::New(env, "PactffiLogToFile(envVar) received < 2 arguments"); 91 | } 92 | 93 | if (!info[0].IsString()) { 94 | throw Napi::Error::New(env, "PactffiLogToFile(envVar) expected a string"); 95 | } 96 | 97 | if (!info[1].IsNumber()) { 98 | throw Napi::Error::New(env, "PactffiLogToFile(envVar) expected a number"); 99 | } 100 | 101 | // Extract log level 102 | std::string fileName = info[0].As().Utf8Value(); 103 | uint32_t levelFilterNumber = info[1].As().Uint32Value(); 104 | LevelFilter levelFilter = integerToLevelFilter(env, levelFilterNumber); 105 | 106 | int res = pactffi_log_to_file(fileName.c_str(), levelFilter); 107 | 108 | return Napi::Number::New(env, res); 109 | } 110 | /* 111 | 112 | Napi::Value Pactffi_log_message(const Napi::CallbackInfo& info) { 113 | // return: void 114 | Napi::Env env = info.Env(); 115 | 116 | if (info.Length() < 3) { 117 | throw Napi::Error::New(env, "PactffiInit(envVar) received < 1 argument"); 118 | } 119 | 120 | if (!info[0].IsString()) { 121 | throw Napi::Error::New(env, "PactffiInit(envVar) expected a string"); 122 | } 123 | 124 | // pactffi_log_message(const char *source, const char *log_level, const char *message); 125 | 126 | pactffi_log_message(const char *source, const char *log_level, const char *message); 127 | 128 | return info.Env().Undefined(); 129 | } 130 | 131 | Napi::Value Pactffi_log_to_stdout(const Napi::CallbackInfo& info) { 132 | // return: int 133 | Napi::Env env = info.Env(); 134 | 135 | if (info.Length() < 1) { 136 | throw Napi::Error::New(env, "PactffiInit(envVar) received < 1 argument"); 137 | } 138 | 139 | if (!info[0].IsString()) { 140 | throw Napi::Error::New(env, "PactffiInit(envVar) expected a string"); 141 | } 142 | 143 | // pactffi_log_to_stdout(LevelFilter level_filter); 144 | 145 | pactffi_log_to_stdout(LevelFilter level_filter); 146 | 147 | return info.Env().Undefined(); 148 | } 149 | 150 | Napi::Value Pactffi_log_to_stderr(const Napi::CallbackInfo& info) { 151 | // return: int 152 | Napi::Env env = info.Env(); 153 | 154 | if (info.Length() < 1) { 155 | throw Napi::Error::New(env, "PactffiInit(envVar) received < 1 argument"); 156 | } 157 | 158 | if (!info[0].IsString()) { 159 | throw Napi::Error::New(env, "PactffiInit(envVar) expected a string"); 160 | } 161 | 162 | // pactffi_log_to_stderr(LevelFilter level_filter); 163 | 164 | pactffi_log_to_stderr(LevelFilter level_filter); 165 | 166 | return info.Env().Undefined(); 167 | } 168 | 169 | Napi::Value Pactffi_log_to_file(const Napi::CallbackInfo& info) { 170 | // return: int 171 | Napi::Env env = info.Env(); 172 | 173 | if (info.Length() < 1) { 174 | throw Napi::Error::New(env, "PactffiInit(envVar) received < 1 argument"); 175 | } 176 | 177 | if (!info[0].IsString()) { 178 | throw Napi::Error::New(env, "PactffiInit(envVar) expected a string"); 179 | } 180 | 181 | // pactffi_log_to_file(const char *file_name, LevelFilter level_filter); 182 | 183 | pactffi_log_to_file(const char *file_name, LevelFilter level_filter); 184 | 185 | return info.Env().Undefined(); 186 | } 187 | 188 | Napi::Value Pactffi_log_to_buffer(const Napi::CallbackInfo& info) { 189 | // return: int 190 | Napi::Env env = info.Env(); 191 | 192 | if (info.Length() < 1) { 193 | throw Napi::Error::New(env, "PactffiInit(envVar) received < 1 argument"); 194 | } 195 | 196 | if (!info[0].IsString()) { 197 | throw Napi::Error::New(env, "PactffiInit(envVar) expected a string"); 198 | } 199 | 200 | // pactffi_log_to_buffer(LevelFilter level_filter); 201 | 202 | pactffi_log_to_buffer(LevelFilter level_filter); 203 | 204 | return info.Env().Undefined(); 205 | } 206 | */ -------------------------------------------------------------------------------- /native/ffi.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | Napi::Value PactffiVersion(const Napi::CallbackInfo& info); 4 | Napi::Value PactffiInit(const Napi::CallbackInfo& info); 5 | Napi::Value PactffiInitWithLogLevel(const Napi::CallbackInfo& info); 6 | 7 | // Unimplemented 8 | Napi::Value PactffiCheckRegex(const Napi::CallbackInfo& info); 9 | Napi::Value PactffiFetchLogBuffer(const Napi::CallbackInfo& info); 10 | Napi::Value PactffiFreeString(const Napi::CallbackInfo& info); 11 | Napi::Value PactffiLogMessage(const Napi::CallbackInfo& info); 12 | Napi::Value PactffiLogToBuffer(const Napi::CallbackInfo& info); 13 | Napi::Value PactffiLogToFile(const Napi::CallbackInfo& info); 14 | Napi::Value PactffiLogToStderr(const Napi::CallbackInfo& info); 15 | Napi::Value PactffiLogToStdout(const Napi::CallbackInfo& info); 16 | Napi::Value PactffiLoggerApply(const Napi::CallbackInfo& info); 17 | Napi::Value PactffiLoggerAttachSink(const Napi::CallbackInfo& info); 18 | Napi::Value PactffiLoggerInit(const Napi::CallbackInfo& info); 19 | -------------------------------------------------------------------------------- /native/plugin.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "pact-cpp.h" 3 | 4 | using namespace Napi; 5 | 6 | // TODO -------------------------------------------------------------------------------- /native/plugin.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Unimplemented 4 | Napi::Value PactffiCleanupPlugins(const Napi::CallbackInfo& info); 5 | Napi::Value PactffiUsingPlugin(const Napi::CallbackInfo& info); 6 | Napi::Value PactffiCleanupPlugins(const Napi::CallbackInfo& info); 7 | Napi::Value PactffiInteractionContents(const Napi::CallbackInfo& info); -------------------------------------------------------------------------------- /native/provider.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | Napi::Value PactffiVerifierNewForApplication(const Napi::CallbackInfo& info); 4 | Napi::Value PactffiVerifierExecute(const Napi::CallbackInfo& info); 5 | Napi::Value PactffiVerifierShutdown(const Napi::CallbackInfo& info); 6 | Napi::Value PactffiVerifierSetProviderInfo(const Napi::CallbackInfo& info); 7 | Napi::Value PactffiVerifierSetFilterInfo(const Napi::CallbackInfo& info); 8 | Napi::Value PactffiVerifierSetProviderState(const Napi::CallbackInfo& info); 9 | Napi::Value PactffiVerifierSetVerificationOptions(const Napi::CallbackInfo& info); 10 | Napi::Value PactffiVerifierSetPublishOptions(const Napi::CallbackInfo& info); 11 | Napi::Value PactffiVerifierSetConsumerFilters(const Napi::CallbackInfo& info); 12 | Napi::Value PactffiVerifierAddCustomHeader(const Napi::CallbackInfo& info); 13 | Napi::Value PactffiVerifierAddFileSource(const Napi::CallbackInfo& info); 14 | Napi::Value PactffiVerifierAddDirectorySource(const Napi::CallbackInfo& info); 15 | Napi::Value PactffiVerifierUrlSource(const Napi::CallbackInfo& info); 16 | Napi::Value PactffiVerifierAddProviderTransport(const Napi::CallbackInfo& info); 17 | Napi::Value PactffiVerifierSetNoPactsIsError(const Napi::CallbackInfo& info); 18 | // Napi::Value PactffiVerifierBrokerSource(const Napi::CallbackInfo& info); 19 | Napi::Value PactffiVerifierBrokerSourceWithSelectors(const Napi::CallbackInfo& info); 20 | Napi::Value PactffiVerifierSetFailIfNoPactsFound(const Napi::CallbackInfo& info); 21 | // Unimplemented 22 | // Napi::Value PactffiVerifierShutdown(const Napi::CallbackInfo& info); 23 | // Napi::Value PactffiVerifierNewForApplication(const Napi::CallbackInfo& info); 24 | // Napi::Value PactffiVerifierNew(const Napi::CallbackInfo& info); 25 | // Napi::Value PactffiVerifierShutdown(const Napi::CallbackInfo& info); 26 | // Napi::Value PactffiVerifierNewForApplication(const Napi::CallbackInfo& info); 27 | // Napi::Value PactffiVerifierShutdown(const Napi::CallbackInfo& info); 28 | // Napi::Value PactffiVerifierSetProviderInfo(const Napi::CallbackInfo& info); 29 | // Napi::Value PactffiVerifierSetFilterInfo(const Napi::CallbackInfo& info); 30 | // Napi::Value PactffiVerifierSetProviderState(const Napi::CallbackInfo& info); 31 | // Napi::Value PactffiVerifierSetVerificationOptions(const Napi::CallbackInfo& info); 32 | // Napi::Value PactffiVerifierSetConsumerFilters(const Napi::CallbackInfo& info); 33 | // Napi::Value PactffiVerifierAddFileSource(const Napi::CallbackInfo& info); 34 | // Napi::Value PactffiVerifierAddDirectorySource(const Napi::CallbackInfo& info); 35 | // Napi::Value PactffiVerifierUrlSource(const Napi::CallbackInfo& info); 36 | // Napi::Value PactffiVerifierBrokerSource(const Napi::CallbackInfo& info); 37 | // Napi::Value PactffiVerifierBrokerSourceWithSelectors(const Napi::CallbackInfo& info); 38 | // Napi::Value PactffiVerifierExecute(const Napi::CallbackInfo& info); 39 | // Napi::Value PactffiVerifierCliArgs(const Napi::CallbackInfo& info); 40 | // Napi::Value PactffiVerifierLogs(const Napi::CallbackInfo& info); 41 | // Napi::Value PactffiVerifierLogsForProvider(const Napi::CallbackInfo& info); 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pact-foundation/pact-core", 3 | "version": "16.0.0", 4 | "description": "Core of @pact-foundation/pact. You almost certainly don't want to depend on this directly.", 5 | "main": "src/index.js", 6 | "homepage": "https://github.com/pact-foundation/pact-js-core#readme", 7 | "types": "src/index.d.ts", 8 | "os": [ 9 | "darwin", 10 | "linux", 11 | "win32" 12 | ], 13 | "cpu": [ 14 | "x64", 15 | "ia32", 16 | "arm64" 17 | ], 18 | "engines": { 19 | "node": ">=16" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git://github.com/pact-foundation/pact-js-core.git" 24 | }, 25 | "keywords": [ 26 | "pact", 27 | "node", 28 | "wrapper", 29 | "mock", 30 | "service", 31 | "provider", 32 | "verifier" 33 | ], 34 | "author": "Matt Fellows (http://www.onegeek.com.au)", 35 | "contributors": [ 36 | "Michel Boudreau (codinghitchhiker.com)" 37 | ], 38 | "license": "MIT", 39 | "bugs": { 40 | "url": "https://github.com/pact-foundation/pact-js-core/issues" 41 | }, 42 | "publishConfig": { 43 | "access": "public" 44 | }, 45 | "dependencies": { 46 | "check-types": "7.4.0", 47 | "detect-libc": "^2.0.3", 48 | "node-gyp-build": "^4.6.0", 49 | "pino": "^8.7.0", 50 | "pino-pretty": "^9.1.1", 51 | "underscore": "1.13.7" 52 | }, 53 | "devDependencies": { 54 | "@grpc/grpc-js": "1.12.4", 55 | "@grpc/proto-loader": "0.7.13", 56 | "@pact-foundation/pact-js-prettier-config": "1.0.0", 57 | "@snyk/protect": "1.1294.2", 58 | "@tsconfig/node14": "14.1.2", 59 | "@types/basic-auth": "1.1.8", 60 | "@types/chai": "4.3.20", 61 | "@types/chai-as-promised": "7.1.8", 62 | "@types/check-types": "7.3.7", 63 | "@types/cors": "2.8.17", 64 | "@types/decompress": "4.2.7", 65 | "@types/express": "4.17.21", 66 | "@types/mocha": "10.0.10", 67 | "@types/needle": "3.3.0", 68 | "@types/node": "22.10.1", 69 | "@types/rimraf": "2.0.5", 70 | "@types/sinon": "17.0.3", 71 | "@types/tar": "6.1.13", 72 | "@types/underscore": "1.13.0", 73 | "@types/unixify": "1.0.2", 74 | "@types/url-join": "4.0.3", 75 | "@typescript-eslint/eslint-plugin": "5.62.0", 76 | "@typescript-eslint/parser": "5.62.0", 77 | "axios": "1.7.9", 78 | "basic-auth": "2.0.1", 79 | "body-parser": "1.20.3", 80 | "chai": "4.5.0", 81 | "chai-as-promised": "7.1.2", 82 | "commit-and-tag-version": "11.3.0", 83 | "cors": "2.8.5", 84 | "cross-env": "5.2.1", 85 | "decamelize": "2.0.0", 86 | "eslint": "8.57.1", 87 | "eslint-config-airbnb-base": "15.0.0", 88 | "eslint-config-airbnb-typescript": "17.1.0", 89 | "eslint-config-prettier": "8.10.0", 90 | "eslint-import-resolver-typescript": "3.7.0", 91 | "eslint-plugin-chai-friendly": "0.7.4", 92 | "eslint-plugin-import": "2.31.0", 93 | "eslint-plugin-mocha": "10.1.0", 94 | "express": "4.21.2", 95 | "form-data": "4.0.1", 96 | "grpc-promise": "1.4.0", 97 | "mocha": "9.2.2", 98 | "node-addon-api": "6.1.0", 99 | "nodemon": "2.0.22", 100 | "prettier": "2.8.8", 101 | "protobufjs": "7.4.0", 102 | "rimraf": "2.7.1", 103 | "sinon": "9.2.4", 104 | "ts-node": "10.9.2", 105 | "typescript": "4.9.5" 106 | }, 107 | "overrides": { 108 | "semver": "7.6.3" 109 | }, 110 | "scripts": { 111 | "clean": "rimraf '{src,test}/**/*.{js,map,d.ts}' 'package.zip' '.tmp' 'tmp'", 112 | "lint": "eslint . --ext .ts --config .eslintrc", 113 | "lint:fix": "npm run lint -- --fix", 114 | "prebuild": "npm run clean", 115 | "download-libs": "npm run clean && bash script/download-libs.sh", 116 | "clean-libs": "rimraf 'ffi'", 117 | "build": "tsc --project tsconfig.build.json", 118 | "prerelease": "npm run snyk-protect", 119 | "release": "commit-and-tag-version", 120 | "test": "cross-env PACT_DO_NOT_TRACK=true mocha \"{src,test}/**/*.spec.ts\"", 121 | "snyk-protect": "snyk-protect", 122 | "format:base": "prettier --parser typescript", 123 | "format:check": "npm run format:base -- --list-different \"{src,test}/**/*.{ts,tsx}\"", 124 | "format:fix": "npm run format:base -- --write \"{src,test}/**/*.{ts,tsx}\"", 125 | "install": "" 126 | }, 127 | "prettier": "@pact-foundation/pact-js-prettier-config", 128 | "commit-and-tag-version": { 129 | "types": [ 130 | { 131 | "type": "feat", 132 | "section": "Features" 133 | }, 134 | { 135 | "type": "fix", 136 | "section": "Fixes and Improvements" 137 | }, 138 | { 139 | "type": "chore", 140 | "hidden": true 141 | }, 142 | { 143 | "type": "docs", 144 | "hidden": true 145 | }, 146 | { 147 | "type": "style", 148 | "hidden": true 149 | }, 150 | { 151 | "type": "refactor", 152 | "section": "Fixes and Improvements" 153 | }, 154 | { 155 | "type": "perf", 156 | "hidden": true 157 | }, 158 | { 159 | "type": "test", 160 | "hidden": true 161 | } 162 | ] 163 | }, 164 | "snyk": true, 165 | "optionalDependencies": { 166 | "@pact-foundation/pact-core-darwin-arm64": "16.0.0", 167 | "@pact-foundation/pact-core-darwin-x64": "16.0.0", 168 | "@pact-foundation/pact-core-linux-arm64-glibc": "16.0.0", 169 | "@pact-foundation/pact-core-linux-arm64-musl": "16.0.0", 170 | "@pact-foundation/pact-core-linux-x64-glibc": "16.0.0", 171 | "@pact-foundation/pact-core-linux-x64-musl": "16.0.0", 172 | "@pact-foundation/pact-core-windows-x64": "16.0.0" 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /package.json.tmpl: -------------------------------------------------------------------------------- 1 | { 2 | "name": "${node_pkg}", 3 | "version": "${pkg_version}", 4 | "description": "Platform/arch specific libpact_ffi binaries for @pact-foundation/pact-core", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/pact-foundation/pact-js-core.git" 9 | }, 10 | "scripts": {}, 11 | "author": "Yousaf Nabi ", 12 | "homepage": "https://github.com/pact-foundation/pact-js-core#readme", 13 | "os": [ 14 | "${node_pkg_os}" 15 | ], 16 | ${libc} 17 | "cpu": [ 18 | "${node_arch}" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /script/README.md: -------------------------------------------------------------------------------- 1 | # script 2 | 3 | This directory contains entry points for scripts intended to be run manually by maintainers + contributors, or from `npm` steps -------------------------------------------------------------------------------- /script/ci/README.md: -------------------------------------------------------------------------------- 1 | # script/CI 2 | 3 | This directory contains entry points for scripts intended to be run by CI 4 | 5 | Each file in here should map to a CI step. -------------------------------------------------------------------------------- /script/ci/build-and-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | set -e # This needs to be here for windows bash, which doesn't read the #! line above 3 | set -u 4 | 5 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)" # Figure out where the script is running 6 | . "$SCRIPT_DIR"/../lib/robust-bash.sh 7 | 8 | if [[ ${SET_NVM:-} == 'true' && "$(uname -s)" == 'Darwin' ]]; then 9 | NVM_DIR=${NVM_DIR:-"$HOME/.nvm"} 10 | . $(brew --prefix nvm)/nvm.sh # Load nvm 11 | nvm install $NODE_VERSION 12 | nvm use $NODE_VERSION 13 | elif [[ ${SET_NVM:-} == 'true' && "$(uname -s)" == 'Linux' ]]; then 14 | NVM_DIR=${NVM_DIR:-"$HOME/.nvm"} 15 | . $NVM_DIR/nvm.sh # Load nvm 16 | nvm install $NODE_VERSION 17 | nvm use $NODE_VERSION 18 | fi 19 | 20 | node --version 21 | npm --version 22 | # Update main package.json optional dependencies versions, with those created earlier 23 | current_platform=$("$SCRIPT_DIR"/build-opt-dependencies.sh determine_platform) 24 | supported_platforms="$current_platform" "$SCRIPT_DIR"/build-opt-dependencies.sh update 25 | # update lockfile post building updated opt deps 26 | npm ci --ignore-scripts || npm i --ignore-scripts 27 | # Link os/arch specific npm package, for running os/arch system 28 | 29 | supported_platforms="$current_platform" "$SCRIPT_DIR"/build-opt-dependencies.sh link 30 | # ensure we test against the linked npm package, not the prebuild 31 | rm -rf prebuilds 32 | npm run format:check 33 | npm run lint 34 | npm run build 35 | npm run test -------------------------------------------------------------------------------- /script/ci/build-opt-dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export bin=@pact-foundation/pact-core 4 | export pkg_version=${pkg_version:-$(cat package.json | jq -r .version)} 5 | 6 | if [ -z "${supported_platforms+x}" ]; then 7 | supported_platforms=("linux-x64-glibc" "linux-arm64-glibc" "linux-x64-musl" "linux-arm64-musl" "darwin-x64" "darwin-arm64" "windows-x64") 8 | fi 9 | 10 | setup_package_vars(){ 11 | unset node_os 12 | unset node_arch 13 | unset node_libc 14 | unset libc 15 | IFS='-' read -r node_os node_arch node_libc <<< "$supported_platform" 16 | export node_os=$node_os 17 | export node_pkg_os=$node_os 18 | export node_arch=$node_arch 19 | export node_libc=$node_libc 20 | export prebuild_package="$node_os-$node_arch" 21 | # we need to overwrite windows as win32 in the package.json 22 | if [ "$node_os" = "windows" ]; then 23 | export node_pkg_os=win32 24 | export prebuild_package="win32-$node_arch" 25 | fi 26 | if [ "$node_libc" = "glibc" ]; then 27 | export libc='"libc": ["glibc"],' 28 | fi 29 | if [ "$node_libc" = "musl" ]; then 30 | export libc='"libc": ["musl"],' 31 | fi 32 | export standalone_package=prebuilds/$prebuild_package 33 | if [ "$node_libc" = "musl" ] || [ "$node_libc" = "glibc" ]; then 34 | export node_pkg=${bin}-$node_os-$node_arch-$node_libc 35 | else 36 | export node_pkg=${bin}-$node_os-$node_arch 37 | fi 38 | } 39 | 40 | clean() { 41 | rm -rf @pact-foundation 42 | npm run clean-libs 43 | } 44 | 45 | libs() { 46 | echo $1 47 | if [ -n "$1" ]; then 48 | tag=$1 49 | else 50 | tag=v${pkg_version} 51 | fi 52 | FETCH_ASSETS=true ./script/ci/check-release-libs.sh --fetch-assets -t $tag 53 | } 54 | 55 | build() { 56 | for supported_platform in "${supported_platforms[@]}"; do 57 | setup_package_vars 58 | echo "Building for $node_os-$node_arch" 59 | echo "Building $node_pkg" 60 | echo "Build $node_libc" 61 | mkdir -p "$node_pkg/prebuilds" 62 | cp -R "$standalone_package" "$node_pkg/prebuilds" 63 | if [ "$node_libc" = "glibc" ]; then 64 | find "$node_pkg/prebuilds" -type f -name '*musl*' -exec rm -f {} + 65 | fi 66 | if [ "$node_libc" = "musl" ]; then 67 | find "$node_pkg/prebuilds" -type f ! -name '*musl*' -exec rm -f {} + 68 | fi 69 | envsubst < package.json.tmpl > "$node_pkg/package.json" 70 | if [ "${PUBLISH:-false}" = true ]; then 71 | (cd $node_pkg && npm publish --access public) 72 | else 73 | (cd $node_pkg && npm publish --access public --dry-run) 74 | fi 75 | done 76 | } 77 | 78 | update() { 79 | for supported_platform in "${supported_platforms[@]}"; do 80 | setup_package_vars 81 | jq '.optionalDependencies."'$node_pkg'" = "'$pkg_version'"' package.json > package-new.json 82 | mv package-new.json package.json 83 | done 84 | } 85 | 86 | publish() { 87 | set -eu 88 | for supported_platform in "${supported_platforms[@]}"; do 89 | setup_package_vars 90 | echo "Building for $node_os-$node_arch" 91 | echo "Building $node_pkg for $node_os-$node_arch" 92 | if [ "${PUBLISH:-false}" = true ]; then 93 | (cd $node_pkg && npm publish --access public) 94 | else 95 | (cd $node_pkg && npm publish --access public --dry-run) 96 | fi 97 | done 98 | } 99 | 100 | link() { 101 | set -eu 102 | for supported_platform in "${supported_platforms[@]}"; do 103 | setup_package_vars 104 | (cd $node_pkg && npm link || echo "cannot link for platform") 105 | npm link $node_pkg || echo "cannot link for platform" 106 | done 107 | } 108 | 109 | determine_platform(){ 110 | case "$(uname -s)" in 111 | Linux) 112 | if [ "$(uname -m)" == "x86_64" ]; then 113 | if [ -f /etc/os-release ] && grep -q 'Alpine' /etc/os-release; then 114 | current_platform="linux-x64-musl" 115 | else 116 | current_platform="linux-x64-glibc" 117 | fi 118 | elif [ "$(uname -m)" == "aarch64" ]; then 119 | if [ -f /etc/os-release ] && grep -q 'Alpine' /etc/os-release; then 120 | current_platform="linux-arm64-musl" 121 | else 122 | export current_platform="linux-arm64-glibc" 123 | fi 124 | fi 125 | ;; 126 | Darwin) 127 | if [ "$(uname -m)" == "x86_64" ]; then 128 | export current_platform="darwin-x64" 129 | elif [ "$(uname -m)" == "arm64" ]; then 130 | export current_platform="darwin-arm64" 131 | fi 132 | ;; 133 | CYGWIN*|MINGW32*|MSYS*|MINGW*) 134 | export current_platform="windows-x64" 135 | ;; 136 | *) 137 | echo "Unsupported platform: $(uname -s)" 138 | exit 1 139 | ;; 140 | esac 141 | if [ -z "$current_platform" ]; then 142 | echo "Error: could not determine current_platform" 143 | exit 1 144 | fi 145 | echo $current_platform 146 | } 147 | 148 | help() { 149 | echo "Pact platform/arch specific dependency builder" 150 | 151 | echo "Usage: $0 {clean|libs|build_opt_deps|update_opt_deps|publish_opt_package|link|determine_platform|help}" 152 | echo 153 | echo "Functions:" 154 | echo " clean - Clean the build environment" 155 | echo " libs - Fetch and check release libraries" 156 | echo " build_opt_deps - Build optional dependencies for supported platforms" 157 | echo " update_opt_deps - Update optional dependencies in package.json" 158 | echo " publish_opt_package - Publish the optional package" 159 | echo " link - Link the package for development" 160 | echo " determine_platform - Determine the current platform" 161 | echo " help - Display this help message" 162 | 163 | echo 164 | echo "Supported platforms:" 165 | for platform in "${supported_platforms[@]}"; do 166 | echo " - $platform" 167 | done 168 | echo 169 | echo "Example to run for the current platform:" 170 | echo ' supported_platforms=$(./script/ci/build_opt_dependencies.sh determine_platform) ./script/ci/build_opt_dependencies.sh link' 171 | } 172 | 173 | if [ $# -eq 0 ]; then 174 | help 175 | exit 1 176 | fi 177 | 178 | "$@" -------------------------------------------------------------------------------- /script/ci/check-release-libs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | # Usage: ./check-release-libs.sh [OPTIONS] 4 | # 5 | # This script checks the release assets of a GitHub repository. 6 | # 7 | # Options: 8 | # -r, --repo The GitHub repository to check (default: pact-foundation/pact-js-core) 9 | # -t, --tag The release tag to check (default: TAG) 10 | # -l, --list-assets List the remote release assets 11 | # -f, --fetch-assets Fetch the remote release assets (will clean local assets) 12 | # -c, --clean-assets Clean the local release assets 13 | # 14 | # Example: 15 | # ./check-release-libs.sh -r myorg/myrepo -t v1.0.0 -l 16 | # 17 | # This will list the remote release assets of the myorg/myrepo repository for the v1.0.0 tag. 18 | 19 | # Parse command line arguments 20 | while [[ $# -gt 0 ]] 21 | do 22 | key="$1" 23 | 24 | case $key in 25 | -r|--repo) 26 | REPO="$2" 27 | shift # past argument 28 | shift # past value 29 | ;; 30 | -t|--tag) 31 | TAG="$2" 32 | shift # past argument 33 | shift # past value 34 | ;; 35 | -l|--list-assets) 36 | LIST_ASSETS=true 37 | shift # past argument 38 | ;; 39 | -f|--fetch-assets) 40 | FETCH_ASSETS=true 41 | shift # past argument 42 | ;; 43 | -c|--clean-assets) 44 | CLEAN_ASSETS=true 45 | shift # past argument 46 | ;; 47 | *) # unknown option 48 | echo "Unknown option: $1" 49 | exit 1 50 | ;; 51 | esac 52 | done 53 | 54 | # Set default values for REPO and TAG if not provided 55 | REPO=${REPO:-pact-foundation/pact-js-core} 56 | TAG=${NEXT_TAG:-${TAG:-latest}} 57 | 58 | echo "Checking release assets" 59 | 60 | if [[ "${CLEAN_ASSETS:-}" = true || "${FETCH_ASSETS:-}" = true ]]; then 61 | echo "Cleaning local release assets" 62 | rm -rf *.tar.gz 63 | rm -rf prebuilds 64 | fi 65 | 66 | if [[ "$TAG" == "" ]]; then 67 | echo "Please provide a release TAG to check" 68 | exit 1 69 | else 70 | GH_TAG_OPTION="$TAG" 71 | if [[ "$TAG" == "latest" ]]; then 72 | GH_TAG_OPTION=$(gh release list --limit 1 --repo pact-foundation/pact-js-core --json tagName --jq '.[].tagName') 73 | fi 74 | 75 | if [[ "${LIST_ASSETS:-}" = true || "${FETCH_ASSETS:-}" = true ]]; then 76 | echo "Listing remote release assets for ${REPO} ${GH_TAG_OPTION}" 77 | gh release view --repo "${REPO}" $GH_TAG_OPTION --json assets | jq '.assets[].name' 78 | fi 79 | 80 | if [ "${FETCH_ASSETS:-}" = true ]; then 81 | echo "Fetching release assets" 82 | gh release download --repo "${REPO}" $GH_TAG_OPTION 83 | fi 84 | 85 | fi 86 | 87 | ERRORS=() 88 | ls *.gz 89 | ls *.gz | xargs -n1 tar -xzf 90 | rm *.tar.gz 91 | ls -1 prebuilds/** 92 | 93 | [[ -f prebuilds/darwin-arm64/libpact_ffi.dylib ]] || ERRORS='prebuilds/darwin-arm64/libpact_ffi.dylib' 94 | [[ -f prebuilds/darwin-arm64/node.napi.node ]] || ERRORS='prebuilds/darwin-arm64/node.napi.node' 95 | 96 | [[ -f prebuilds/darwin-x64/libpact_ffi.dylib ]] || ERRORS='prebuilds/darwin-x64/libpact_ffi.dylib' 97 | [[ -f prebuilds/darwin-x64/node.napi.node ]] || ERRORS='prebuilds/darwin-x64/node.napi.node' 98 | 99 | [[ -f prebuilds/linux-arm64/libpact_ffi.so ]] || ERRORS='prebuilds/linux-arm64/libpact_ffi.so' 100 | [[ -f prebuilds/linux-arm64/node.napi.node ]] || ERRORS='prebuilds/linux-arm64/node.napi.node' 101 | 102 | [[ -f prebuilds/linux-arm64/libpact_ffi_musl.so ]] || ERRORS='prebuilds/linux-arm64/libpact_ffi_musl.so' 103 | [[ -f prebuilds/linux-arm64/node.napi.musl.node ]] || ERRORS='prebuilds/linux-arm64/node.napi.musl.node' 104 | 105 | [[ -f prebuilds/linux-x64/libpact_ffi.so ]] || ERRORS='prebuilds/linux-x64/libpact_ffi.so' 106 | [[ -f prebuilds/linux-x64/node.napi.node ]] || ERRORS='prebuilds/linux-x64/node.napi.node' 107 | 108 | [[ -f prebuilds/linux-x64/libpact_ffi_musl.so ]] || ERRORS='prebuilds/linux-x64/libpact_ffi_musl.so' 109 | [[ -f prebuilds/linux-x64/node.napi.musl.node ]] || ERRORS='prebuilds/linux-x64/node.napi.musl.node' 110 | 111 | [[ -f prebuilds/win32-x64/pact_ffi.dll ]] || ERRORS='prebuilds/win32-x64/pact_ffi.dll' 112 | [[ -f prebuilds/win32-x64/node.napi.node ]] || ERRORS='prebuilds/win32-x64/node.napi.node' 113 | 114 | if [ ! -z "${ERRORS:-}" ]; then 115 | echo "The following files are missing from the release:" 116 | echo $ERRORS 117 | exit 1 118 | else 119 | echo "All release files are present" 120 | fi -------------------------------------------------------------------------------- /script/ci/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | set -e # This needs to be here for windows bash, which doesn't read the #! line above 3 | set -u 4 | 5 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)" # Figure out where the script is running 6 | . "$SCRIPT_DIR"/../lib/robust-bash.sh 7 | 8 | npm run clean 9 | rm -rf node_modules 10 | ls -------------------------------------------------------------------------------- /script/ci/lib/README.md: -------------------------------------------------------------------------------- 1 | # script/CI/lib 2 | 3 | This directory contains scripts that are only used by scripts in the CI directory. -------------------------------------------------------------------------------- /script/ci/lib/create_npmrc_file.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | set -e # Windows bash does not read the #! line above 3 | 4 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)" # Figure out where the script is running 5 | . "$SCRIPT_DIR"/../../lib/robust-bash.sh 6 | 7 | require_env_var NODE_AUTH_TOKEN 8 | 9 | set +x #Don't echo the NPM key 10 | 11 | NPMRC_FILE=.npmrc 12 | echo "@pact-foundation:registry=https://registry.npmjs.org/" > $NPMRC_FILE 13 | echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" >> $NPMRC_FILE 14 | echo "//registry.npmjs.org/:username=pact-foundation" >> $NPMRC_FILE 15 | echo "//registry.npmjs.org/:email=pact-foundation@googlegroups.com" >> $NPMRC_FILE 16 | echo "//registry.npmjs.org/:always-auth=true" >> $NPMRC_FILE 17 | 18 | set -x 19 | -------------------------------------------------------------------------------- /script/ci/lib/get-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | set -eu # Windows bash does not read the #! line above 3 | 4 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)" # Figure out where the script is running 5 | . "$SCRIPT_DIR"/../../lib/robust-bash.sh 6 | 7 | require_binary grep 8 | 9 | VERSION="$(grep '\"version\"' package.json | grep -E -o "([0-9\.]+(-[a-z\.0-9]+)?)")" 10 | echo "$VERSION" -------------------------------------------------------------------------------- /script/ci/lib/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | set -eu # Windows bash does not read the #! line above 3 | 4 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)" # Figure out where the script is running 5 | . "$SCRIPT_DIR"/../../lib/robust-bash.sh 6 | 7 | require_binary npm 8 | 9 | echo "--> Releasing version ${VERSION}" 10 | 11 | echo "--> Releasing artifacts" 12 | echo " Publishing pact-core@${VERSION}..." 13 | if [[ ${DRY_RUN:-} == 'true' ]]; then 14 | echo "publishing in dry run mode" 15 | # Dry-run Publish os/arch specific npm packages 16 | "$SCRIPT_DIR"/../build-opt-dependencies.sh publish 17 | npm publish --access-public --dry-run 18 | else 19 | echo "--> Preparing npmrc file" 20 | "$SCRIPT_DIR"/create_npmrc_file.sh 21 | echo "--> Removing binding.gyp to prevent rebuild. See https://github.com/npm/cli/issues/5234#issuecomment-1291139150" 22 | rm "${SCRIPT_DIR}/../../../binding.gyp" 23 | # Publish os/arch specific npm packages 24 | PUBLISH=true "$SCRIPT_DIR"/../build-opt-dependencies.sh publish 25 | npm publish --access public --tag latest 26 | fi 27 | echo " done!" 28 | -------------------------------------------------------------------------------- /script/ci/prebuild-alpine.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | SCRIPT_DIR="$(cd "$(dirname "${0}")"; pwd)" # Figure out where the script is running 3 | 4 | node --version 5 | npm --version 6 | PREBUILDIFY_VERSION=6.0.1 7 | NODE_VERSION=$(node -p process.version) 8 | PREBUILD_NAME="node.napi" 9 | 10 | apk add bash curl python3 make g++ 11 | 12 | . "${SCRIPT_DIR}/../lib/export-binary-versions.sh" 13 | "${SCRIPT_DIR}/../lib/download-ffi.sh" 14 | rm -rf build node_modules 15 | npm ci --ignore-scripts || npm i --ignore-scripts 16 | export npm_config_target=${NODE_VERSION} 17 | 18 | npx --yes prebuildify@${PREBUILDIFY_VERSION} --napi --libc musl --strip --tag-libc --name ${PREBUILD_NAME} 19 | ls prebuilds/**/* 20 | ARCH=$(uname -m | tr '[:upper:]' '[:lower:]') 21 | case $ARCH in 22 | aarch64) 23 | echo "aarch64" 24 | tar -czf prebuilds/linux-arm64-musl.tar.gz prebuilds/linux-arm64 25 | ;; 26 | x86_64) 27 | tar -czf prebuilds/linux-x64-musl.tar.gz prebuilds/linux-x64 28 | ;; 29 | *) 30 | echo "Unsupported architecture: $ARCH" 31 | exit 1 32 | ;; 33 | esac 34 | rm -rf ffi build -------------------------------------------------------------------------------- /script/ci/prebuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | set -e # This needs to be here for windows bash, which doesn't read the #! line above 3 | set -u 4 | 5 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)" # Figure out where the script is running 6 | . "$SCRIPT_DIR"/../lib/robust-bash.sh 7 | 8 | if [[ ${SET_NVM:-} == 'true' && "$(uname -s)" == 'Darwin' ]]; then 9 | NVM_DIR=${NVM_DIR:-"$HOME/.nvm"} 10 | . $(brew --prefix nvm)/nvm.sh # Load nvm 11 | nvm install $NODE_VERSION 12 | nvm use $NODE_VERSION 13 | elif [[ ${SET_NVM:-} == 'true' && "$(uname -s)" == 'Linux' ]]; then 14 | NVM_DIR=${NVM_DIR:-"$HOME/.nvm"} 15 | . $NVM_DIR/nvm.sh # Load nvm 16 | nvm install $NODE_VERSION 17 | nvm use $NODE_VERSION 18 | fi 19 | 20 | PREBUILDIFY_VERSION=6.0.1 21 | NODE_VERSION=$(node -p process.version) 22 | PREBUILD_NAME="node.napi" 23 | 24 | ## normalise OS and ARCH names 25 | OS=$(uname -s | tr '[:upper:]' '[:lower:]') 26 | ARCH=$(uname -m | tr '[:upper:]' '[:lower:]') 27 | case $OS in 28 | "windows"* | "mingw64"*) 29 | OS=win32 30 | ;; 31 | esac 32 | node --version 33 | npm --version 34 | echo "OS: $OS" 35 | echo "ARCH: $ARCH" 36 | 37 | ./script/download-libs.sh 38 | npm ci --ignore-scripts || npm i --ignore-scripts 39 | export npm_config_target=${NODE_VERSION} 40 | npx --yes prebuildify@${PREBUILDIFY_VERSION} --napi --name ${PREBUILD_NAME} 41 | ls prebuilds/**/* 42 | case $OS in 43 | darwin) 44 | case $ARCH in 45 | arm64) 46 | tar -czf prebuilds/darwin-arm64.tar.gz prebuilds/darwin-arm64 47 | ;; 48 | x86_64) 49 | tar -czf prebuilds/darwin-x64.tar.gz prebuilds/darwin-x64 50 | ;; 51 | *) 52 | echo "Unsupported architecture: $ARCH" 53 | exit 1 54 | ;; 55 | esac 56 | ;; 57 | linux) 58 | echo "Linux" 59 | case $ARCH in 60 | aarch64) 61 | echo "aarch64" 62 | tar -czf prebuilds/linux-arm64.tar.gz prebuilds/linux-arm64 63 | ;; 64 | x86_64) 65 | tar -czf prebuilds/linux-x64.tar.gz prebuilds/linux-x64 66 | ;; 67 | *) 68 | echo "Unsupported architecture: $ARCH" 69 | exit 1 70 | ;; 71 | esac 72 | ;; 73 | win32) 74 | case $ARCH in 75 | arm64) 76 | tar -czf prebuilds/win32-arm64.tar.gz prebuilds/win32-arm64 77 | ;; 78 | x86_64) 79 | tar -czf prebuilds/win32-x64.tar.gz prebuilds/win32-x64 80 | ;; 81 | *) 82 | echo "Unsupported architecture: $ARCH" 83 | exit 1 84 | ;; 85 | esac 86 | ;; 87 | *) 88 | echo "Unsupported OS: $OS" 89 | exit 1 90 | ;; 91 | esac 92 | ls 93 | rm -rf ffi build -------------------------------------------------------------------------------- /script/ci/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | SCRIPT_DIR="$( 3 | cd "$(dirname "${BASH_SOURCE[0]}")" 4 | pwd 5 | )" # Figure out where the script is running 6 | . "$SCRIPT_DIR"/../lib/robust-bash.sh 7 | 8 | if [[ ${DRY_RUN:-} == 'true' && ${CI:-"false"} == "false" ]]; then 9 | echo "running in dry run mode and not in CI" 10 | else 11 | require_env_var CI "This script must be run from CI. If you are running locally, note that it stamps your repo git settings." 12 | if [[ ${GITHUB_ACTIONS:-} == 'true' ]]; then 13 | require_env_var GITHUB_ACTOR 14 | # Setup git for github actions 15 | git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" 16 | git config user.name "${GITHUB_ACTOR}" 17 | fi 18 | fi 19 | REPO=${REPO:-pact-foundation/pact-js-core} 20 | 21 | # It's easier to read the release notes 22 | # from the standard version tool before it runs 23 | RELEASE_NOTES="$(npx -y commit-and-tag-version --dry-run | awk 'BEGIN { flag=0 } /^---$/ { if (flag == 0) { flag=1 } else { flag=2 }; next } flag == 1')" 24 | echo "$RELEASE_NOTES" 25 | NEXT_VERSION=$(npx -y commit-and-tag-version --dry-run | grep 'tagging release' | grep -E -o "([0-9\.]+(-[a-z\.0-9]+)?)") 26 | NEXT_TAG="v${NEXT_VERSION}" 27 | GIT_SHA=$(git rev-parse HEAD) 28 | GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) 29 | 30 | if [ "${GH_CREATE_PRE_RELEASE:-}" = true ]; then 31 | echo "Creating pre-release ${NEXT_TAG}" 32 | if gh release view ${NEXT_TAG} --repo ${REPO}; then 33 | echo "${NEXT_TAG} exists, checking if pre-release" 34 | if gh release view ${NEXT_TAG} --repo ${REPO} --json isPrerelease | jq -e '.isPrerelease == false' >/dev/null; then 35 | echo "${NEXT_TAG} exists, and is not a pre-release, exiting" 36 | exit 1 37 | elif gh release view ${NEXT_TAG} --repo ${REPO} --json isPrerelease | jq -e '.isPrerelease == true' >/dev/null; then 38 | echo "${NEXT_TAG} exists, and is a pre-release, updating" 39 | gh release edit ${NEXT_TAG} --prerelease --draft --repo ${REPO} --title "Release ${NEXT_TAG}" --notes "${RELEASE_NOTES}" --target ${GIT_SHA} 40 | exit 0 41 | fi 42 | else 43 | echo "doesnt exist, lets create" 44 | gh release create ${NEXT_TAG} --prerelease --draft --repo ${REPO} --title "Release ${NEXT_TAG}" --notes "${RELEASE_NOTES}" --target ${GIT_SHA} 45 | exit 0 46 | fi 47 | echo "echo shouldnt get here" 48 | exit 0 49 | elif [ "${GH_PRE_RELEASE_UPLOAD:-}" = true ]; then 50 | echo "Uploading pre-release ${NEXT_TAG}" 51 | gh release upload ${NEXT_TAG} prebuilds/*.tar.gz --repo ${REPO} --clobber 52 | exit 0 53 | fi 54 | 55 | if [[ ${CI:-} == 'true' ]]; then 56 | require_env_var NODE_AUTH_TOKEN 57 | fi 58 | 59 | if [[ ${RUNNER_OS:-} == 'Windows' ]]; then 60 | ONLY_DOWNLOAD_PACT_FOR_WINDOWS=true 61 | fi 62 | 63 | if [ ! -z "${ONLY_DOWNLOAD_PACT_FOR_WINDOWS:-}" ]; then 64 | error "The environment variable ONLY_DOWNLOAD_PACT_FOR_WINDOWS is set" 65 | echo " - you cannot run a release with this variable set" 66 | echo " as only the windows binaries would be included" 67 | echo "*** STOPPING RELEASE PROCESS ***" 68 | exit 1 69 | fi 70 | 71 | FETCH_ASSETS=true ./script/ci/check-release-libs.sh --fetch-assets -t "${NEXT_TAG}" 72 | "$SCRIPT_DIR"/../download-plugins.sh 73 | export pkg_version=$NEXT_VERSION 74 | "$SCRIPT_DIR"/build-opt-dependencies.sh build 75 | "$SCRIPT_DIR"/build-opt-dependencies.sh update 76 | "$SCRIPT_DIR"/build-and-test.sh 77 | 78 | if [[ ${DRY_RUN:-} == 'true' ]]; then 79 | VERSION=$NEXT_VERSION 80 | TAG=$NEXT_TAG 81 | else 82 | # Don't release if there are no changes 83 | if [ "$(echo "$RELEASE_NOTES" | wc -l)" -eq 1 ]; then 84 | error "This release would have no release notes. Does it include changes?" 85 | echo " - You must have at least one fix / feat commit to generate release notes" 86 | echo "*** STOPPING RELEASE PROCESS ***" 87 | exit 1 88 | fi 89 | npm run release 90 | # Emit tag to next step 91 | VERSION="$("$SCRIPT_DIR/lib/get-version.sh")" 92 | TAG="v${VERSION}" 93 | fi 94 | export VERSION 95 | set +eu 96 | # GITHUB_OUPUT is unset if testing DRY_RUN locally 97 | echo "VERSION=$VERSION" >> $GITHUB_OUTPUT 98 | set -eu 99 | "$SCRIPT_DIR"/lib/publish.sh 100 | 101 | # Push the new commit back to the repo. 102 | # and update GH pre-release to released 103 | if [[ ${DRY_RUN:-} == 'true' ]]; then 104 | echo "not pushing tags as in dry run mode" 105 | else 106 | git push --follow-tags 107 | gh release edit ${TAG} --title "Release ${TAG}" --repo ${REPO} --notes "${RELEASE_NOTES}" --draft=false --prerelease=false --target ${GIT_SHA} --latest 108 | fi 109 | -------------------------------------------------------------------------------- /script/ci/unpack-and-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | set -e # This needs to be here for windows bash, which doesn't read the #! line above 3 | set -u 4 | 5 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)" # Figure out where the script is running 6 | . "$SCRIPT_DIR"/../lib/robust-bash.sh 7 | 8 | if [ ! -d "prebuilds" ]; then 9 | ls -1 10 | ls -1 artifact* 11 | mkdir -p prebuilds 12 | mv artifact*/*.tar.gz . 13 | ls *.gz | xargs -n1 tar -xzf 14 | fi 15 | 16 | "$SCRIPT_DIR"/../download-plugins.sh 17 | # Use the determined platform 18 | current_platform=$("$SCRIPT_DIR"/build-opt-dependencies.sh determine_platform) 19 | supported_platforms="$current_platform" "$SCRIPT_DIR"/build-opt-dependencies.sh build 20 | "$SCRIPT_DIR"/build-and-test.sh -------------------------------------------------------------------------------- /script/create-pr-to-update-pact-ffi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | : "${1?Please supply the pact-ffi version to upgrade to}" 6 | 7 | FFI_VERSION=$1 8 | TYPE=${2:-fix} 9 | DASHERISED_VERSION=$(echo "${FFI_VERSION}" | sed 's/\./\-/g') 10 | BRANCH_NAME="chore/upgrade-to-pact-ffi-${DASHERISED_VERSION}" 11 | 12 | git checkout master 13 | git checkout src/ffi/index.ts 14 | git pull origin master 15 | 16 | git checkout -b ${BRANCH_NAME} 17 | 18 | cat src/ffi/index.ts | sed "s/export const PACT_FFI_VERSION.*/export const PACT_FFI_VERSION = '${FFI_VERSION}';/" > tmp-install 19 | mv tmp-install src/ffi/index.ts 20 | 21 | git add src/ffi/index.ts 22 | git commit -m "${TYPE}: update pact-ffi to ${FFI_VERSION}" 23 | git push --set-upstream origin ${BRANCH_NAME} 24 | 25 | gh pr create --title "${TYPE}: update pact-ffi to ${FFI_VERSION}" --fill 26 | 27 | git checkout master 28 | -------------------------------------------------------------------------------- /script/dispatch-ffi-released.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Script to trigger an update of the pact ffi from pact-foundation/pact-reference to listening repos 4 | # Requires a Github API token with repo scope stored in the 5 | # environment variable GITHUB_ACCESS_TOKEN_FOR_PF_RELEASES 6 | 7 | : "${GITHUB_ACCESS_TOKEN_FOR_PF_RELEASES:?Please set environment variable GITHUB_ACCESS_TOKEN_FOR_PF_RELEASES}" 8 | 9 | if [ -n "$1" ]; then 10 | name="\"${1}\"" 11 | else 12 | echo "name not provided as first param" 13 | exit 1 14 | fi 15 | 16 | if [ -n "$2" ]; then 17 | version="\"${2}\"" 18 | else 19 | echo "name not provided as second param" 20 | exit 1 21 | fi 22 | 23 | repository_slug=$(git remote get-url origin | cut -d':' -f2 | sed 's/\.git//') 24 | 25 | output=$(curl -v https://api.github.com/repos/${repository_slug}/dispatches \ 26 | -H 'Accept: application/vnd.github.everest-preview+json' \ 27 | -H "Authorization: Bearer $GITHUB_ACCESS_TOKEN_FOR_PF_RELEASES" \ 28 | -d "{\"event_type\": \"pact-ffi-released\", \"client_payload\": {\"name\": ${name}, \"version\" : ${version}}}" 2>&1) 29 | 30 | if ! echo "${output}" | grep "HTTP\/.* 204" > /dev/null; then 31 | echo "$output" | sed "s/${GITHUB_ACCESS_TOKEN_FOR_PF_RELEASES}/********/g" 32 | echo "Failed to trigger update" 33 | exit 1 34 | else 35 | echo "Update workflow triggered" 36 | fi 37 | 38 | echo "See https://github.com/${repository_slug}/actions?query=workflow%3A%22Update+Pact+FFI+Library%22" -------------------------------------------------------------------------------- /script/download-libs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)" # Figure out where the script is running 3 | 4 | . "${SCRIPT_DIR}/lib/export-binary-versions.sh" 5 | "${SCRIPT_DIR}/lib/download-ffi.sh" 6 | "${SCRIPT_DIR}/download-plugins.sh" -------------------------------------------------------------------------------- /script/download-plugins.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)" # Figure out where the script is running 3 | 4 | . "${SCRIPT_DIR}/lib/export-binary-versions.sh" 5 | "${SCRIPT_DIR}/install-plugin-cli.sh" 6 | $HOME/.pact/bin/pact-plugin-cli install -y https://github.com/mefellows/pact-matt-plugin/releases/tag/$PACT_PLUGIN_MATT_VERSION -------------------------------------------------------------------------------- /script/install-plugin-cli.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | # 3 | # Usage: 4 | # $ curl -fsSL https://raw.githubusercontent.com/pact-foundation/pact-plugins/master/install-cli.sh | bash 5 | # or 6 | # $ wget -q https://raw.githubusercontent.com/pact-foundation/pact-plugins/master/install-cli.sh -O- | bash 7 | # 8 | set -e # Needed for Windows bash, which doesn't read the shebang 9 | 10 | detect_osarch() { 11 | # detect_musl 12 | case $(uname -sm) in 13 | 'Linux x86_64') 14 | os='linux' 15 | arch='x86_64' 16 | ;; 17 | 'Linux aarch64') 18 | os='linux' 19 | arch='aarch64' 20 | ;; 21 | 'Darwin x86' | 'Darwin x86_64') 22 | os='osx' 23 | arch='x86_64' 24 | ;; 25 | 'Darwin arm64') 26 | os='osx' 27 | arch='aarch64' 28 | ;; 29 | CYGWIN*|MINGW32*|MSYS*|MINGW*) 30 | os="windows" 31 | arch='x86_64' 32 | ext='.exe' 33 | ;; 34 | *) 35 | echo "Sorry, you'll need to install the plugin CLI manually." 36 | exit 1 37 | ;; 38 | esac 39 | } 40 | 41 | 42 | VERSION="0.1.2" 43 | detect_osarch 44 | 45 | if [ ! -f ~/.pact/bin/pact-plugin-cli ]; then 46 | echo "--- 🐿 Installing plugins CLI version '${VERSION}' (from tag ${TAG})" 47 | mkdir -p ~/.pact/bin 48 | DOWNLOAD_LOCATION=https://github.com/pact-foundation/pact-plugins/releases/download/pact-plugin-cli-v${VERSION}/pact-plugin-cli-${os}-${arch}${ext}.gz 49 | echo " Downloading from: ${DOWNLOAD_LOCATION}" 50 | curl -L -o ~/.pact/bin/pact-plugin-cli-${os}-${arch}.gz "${DOWNLOAD_LOCATION}" 51 | echo " Downloaded $(file ~/.pact/bin/pact-plugin-cli-${os}-${arch}.gz)" 52 | gunzip -f ~/.pact/bin/pact-plugin-cli-${os}-${arch}.gz 53 | mv ~/.pact/bin/pact-plugin-cli-${os}-${arch} ~/.pact/bin/pact-plugin-cli 54 | chmod +x ~/.pact/bin/pact-plugin-cli 55 | fi -------------------------------------------------------------------------------- /script/lib/README.md: -------------------------------------------------------------------------------- 1 | # script/lib 2 | 3 | This directory contains scripts that are only used by other scripts -------------------------------------------------------------------------------- /script/lib/download-ffi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)" # Figure out where the script is running 3 | . "${LIB_DIR}/robust-bash.sh" 4 | . "${LIB_DIR}/download-file.sh" 5 | 6 | require_binary curl 7 | require_binary gunzip 8 | 9 | require_env_var FFI_VERSION 10 | 11 | BASEURL=https://github.com/pact-foundation/pact-reference/releases/download 12 | FFI_DIR="${LIB_DIR}/../../ffi" 13 | 14 | if [[ $(find "${FFI_DIR}" -name "${FFI_VERSION}*") ]]; then 15 | log "Skipping download of FFI libraries ${FFI_VERSION}, as they exist" 16 | exit 0 17 | fi 18 | 19 | warn "Cleaning ffi directory $FFI_DIR" 20 | rm -rf "${FFI_DIR:?}" 21 | mkdir -p "$FFI_DIR/macos-x86_64" 22 | mkdir -p "$FFI_DIR/linux-x86_64" 23 | mkdir -p "$FFI_DIR/linux-musl-x86_64" 24 | mkdir -p "$FFI_DIR/windows-x86_64" 25 | mkdir -p "$FFI_DIR/macos-aarch64" 26 | mkdir -p "$FFI_DIR/linux-aarch64" 27 | mkdir -p "$FFI_DIR/linux-musl-aarch64" 28 | 29 | function download_ffi_file { 30 | if [ -z "${1:-}" ]; then 31 | error "${FUNCNAME[0]} requires the filename to download" 32 | exit 1 33 | fi 34 | if [ -z "${2:-}" ]; then 35 | error "${FUNCNAME[0]} requires the output filename to download" 36 | exit 1 37 | fi 38 | FFI_FILENAME="$1" 39 | OUTPUT_FILENAME="$2" 40 | 41 | URL="${BASEURL}/libpact_ffi-${FFI_VERSION}/${FFI_FILENAME}" 42 | DOWNLOAD_LOCATION="$FFI_DIR/${OUTPUT_FILENAME}" 43 | 44 | log "Downloading ffi $FFI_VERSION for $FFI_FILENAME" 45 | download_to "$URL" "$DOWNLOAD_LOCATION" 46 | debug_log " ... downloaded to '$DOWNLOAD_LOCATION'" 47 | } 48 | 49 | function download_ffi { 50 | if [ -z "${1:-}" ]; then 51 | error "${FUNCNAME[0]} requires the environment filename suffix" 52 | exit 1 53 | fi 54 | SUFFIX="$1" 55 | PREFIX="${2:-}" 56 | OUTPUT_FILENAME="${3:-}" 57 | 58 | download_ffi_file "${PREFIX}pact_ffi-$SUFFIX" "${OUTPUT_FILENAME}" 59 | debug_log " ... unzipping '$DOWNLOAD_LOCATION'" 60 | gunzip -f "$DOWNLOAD_LOCATION" 61 | } 62 | 63 | if [[ ${RUNNER_OS:-} == 'Windows' ]]; then 64 | ONLY_DOWNLOAD_PACT_FOR_WINDOWS=true 65 | fi 66 | 67 | if [ -z "${ONLY_DOWNLOAD_PACT_FOR_WINDOWS:-}" ]; then 68 | download_ffi "linux-x86_64.so.gz" "lib" "linux-x86_64/libpact_ffi.so.gz" 69 | download_ffi "linux-aarch64.so.gz" "lib" "linux-aarch64/libpact_ffi.so.gz" 70 | download_ffi "linux-x86_64-musl.so.gz" "lib" "linux-musl-x86_64/libpact_ffi_musl.so.gz" 71 | download_ffi "linux-aarch64-musl.so.gz" "lib" "linux-musl-aarch64/libpact_ffi_musl.so.gz" 72 | download_ffi "macos-x86_64.dylib.gz" "lib" "macos-x86_64/libpact_ffi.dylib.gz" 73 | download_ffi "macos-aarch64.dylib.gz" "lib" "macos-aarch64/libpact_ffi.dylib.gz" 74 | else 75 | warn "Skipped download of non-windows FFI libs because ONLY_DOWNLOAD_PACT_FOR_WINDOWS is set" 76 | fi 77 | 78 | download_ffi "windows-x86_64.dll.gz" "" "windows-x86_64/pact_ffi.dll.gz" 79 | download_ffi "windows-x86_64.dll.lib.gz" "" "windows-x86_64/pact_ffi.dll.lib.gz" 80 | 81 | download_ffi_file "pact.h" "pact.h" 82 | download_ffi_file "pact-cpp.h" "pact-cpp.h" 83 | 84 | # Write readme in the ffi folder 85 | cat << EOF > "$FFI_DIR/README.md" 86 | # FFI binaries 87 | 88 | This folder is automatically populated during build by /script/download-ffi.sh 89 | EOF 90 | -------------------------------------------------------------------------------- /script/lib/download-file.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)" # Figure out where the script is running 3 | . "${LIB_DIR}/robust-bash.sh" 4 | 5 | function download_to { 6 | if [ -z "${1:-}" ]; then 7 | error "${FUNCNAME[0]} requires the URL to download from as the first argument" 8 | exit 1 9 | fi 10 | if [ -z "${2:-}" ]; then 11 | error "${FUNCNAME[0]} requires the file to save the download in as the second argument" 12 | exit 1 13 | fi 14 | debug_log "about to download" 15 | URL="$1" 16 | OUTPUT_FILE="$2" 17 | debug_log "doing curl of: '$URL', saving in $OUTPUT_FILE" 18 | 19 | if [[ "$(uname -m)" == "Darwin" ]] || [[ "$(uname -m)" == "Linux" ]]; then 20 | HTTP_CODE="$(curl --silent --output "$OUTPUT_FILE" --write-out "%{http_code}" --location "$URL")" 21 | else 22 | # temp workaround for curl 8.8.x error on windows gha runners 23 | # https://github.com/curl/curl/issues/13845 24 | curl --silent --output "$OUTPUT_FILE" --location "$URL" 25 | if [ $? -ne 0 ]; then 26 | error "Unable to download file at url ${URL}" 27 | exit 1 28 | else 29 | HTTP_CODE=200 30 | fi 31 | fi 32 | debug_log "did curl, http code was '${HTTP_CODE}'" 33 | if [[ "${HTTP_CODE}" -lt 200 || "${HTTP_CODE}" -gt 299 ]] ; then 34 | error "Unable to download file at url ${URL}" 35 | error "Downloaded content follows" 36 | cat "$OUTPUT_FILE" 37 | exit 1 38 | fi 39 | } -------------------------------------------------------------------------------- /script/lib/export-binary-versions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)" # Figure out where the script is running 3 | PROJECT_DIR="${LIB_DIR}"/../../ 4 | 5 | export FFI_VERSION=v$(grep "PACT_FFI_VERSION = '" "$PROJECT_DIR"/src/ffi/index.ts | grep -E -o "'(.*)'" | cut -d"'" -f2) 6 | export PACT_PLUGIN_MATT_VERSION=v0.1.1 -------------------------------------------------------------------------------- /script/lib/robust-bash.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | if [ -z "${LIB_ROBUST_BASH_SH:-}" ]; then 3 | LIB_ROBUST_BASH_SH=included 4 | 5 | function error { 6 | echo "❌ ${1:-}" 7 | } 8 | 9 | function log { 10 | echo "🔵 ${1:-}" 11 | } 12 | 13 | function debug_log { 14 | if [ ! -z "${LIB_ROBUST_BASH_DEBUG:-}" ]; then 15 | echo "🔎 ${1:-}" 16 | fi 17 | } 18 | 19 | function warn { 20 | echo "🟡 ${1:-}" 21 | } 22 | 23 | # Check to see that we have a required binary on the path 24 | # and fail the script if it is not present 25 | function require_binary { 26 | if [ -z "${1:-}" ]; then 27 | error "${FUNCNAME[0]} requires an argument" 28 | exit 1 29 | fi 30 | 31 | if ! [ -x "$(command -v "$1")" ]; then 32 | error "The required executable '$1' is not on the path." 33 | exit 1 34 | fi 35 | } 36 | 37 | # Check to see that we have a required environment variable set, 38 | # and fail the script if it is not set. 39 | # 40 | # Optionally, a second argument can be provided to display 41 | # a helpful message before failing 42 | function require_env_var { 43 | var_name="${1:-}" 44 | if [ -z "${!var_name:-}" ]; then 45 | error "The required environment variable ${var_name} is empty" 46 | if [ ! -z "${2:-}" ]; then 47 | echo " - $2" 48 | fi 49 | exit 1 50 | fi 51 | } 52 | fi -------------------------------------------------------------------------------- /script/trigger-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Script to trigger release of the repository 4 | # Requires a Github API token with repo scope stored in the 5 | # environment variable GITHUB_ACCESS_TOKEN_FOR_PF_RELEASES 6 | # Borrowed from Beth Skurrie's excellent script at 7 | # https://github.com/pact-foundation/pact-ruby/blob/master/script/trigger-release.sh 8 | 9 | : "${GITHUB_ACCESS_TOKEN_FOR_PF_RELEASES:?Please set environment variable GITHUB_ACCESS_TOKEN_FOR_PF_RELEASES}" 10 | 11 | repository_slug=$(git remote get-url $(git remote show) | cut -d':' -f2 | sed 's/\.git//') 12 | 13 | output=$(curl -L -v -X POST https://api.github.com/repos/${repository_slug}/dispatches \ 14 | -H 'Accept: application/vnd.github.everest-preview+json' \ 15 | -H "Authorization: Bearer $GITHUB_ACCESS_TOKEN_FOR_PF_RELEASES" \ 16 | -d "{\"event_type\": \"release-triggered\"}" 2>&1) 17 | 18 | if ! echo "${output}" | grep "HTTP\/2 204" > /dev/null; then 19 | echo "$output" | sed "s/${GITHUB_ACCESS_TOKEN_FOR_PF_RELEASES}/********/g" 20 | echo "Failed to trigger release" 21 | exit 1 22 | else 23 | echo "Release workflow triggered" 24 | fi 25 | -------------------------------------------------------------------------------- /src/consumer/checkErrors.ts: -------------------------------------------------------------------------------- 1 | import logger from '../logger'; 2 | 3 | export const wrapWithCheck = 4 | boolean>( 5 | f: BooleanFunction, 6 | contextMessage: string 7 | ) => 8 | (...args: Parameters): boolean => { 9 | const result = f(...args); 10 | if (!result) { 11 | logger.pactCrash( 12 | `The pact consumer core returned false at '${contextMessage}'. This\nshould only happen if the core methods were invoked out of order` 13 | ); 14 | } 15 | return result; 16 | }; 17 | 18 | type BooleanFunction = T extends (...args: infer A) => boolean 19 | ? (...args: A) => boolean 20 | : never; 21 | 22 | type BooleanFunctions = { 23 | [key in keyof T]: BooleanFunction; 24 | }; 25 | 26 | export const wrapAllWithCheck = >( 27 | o: T 28 | ): BooleanFunctions => 29 | Object.keys(o) 30 | .map((key: string) => ({ 31 | [key]: wrapWithCheck(o[key], key), 32 | })) 33 | .reduce((acc, curr) => ({ ...acc, ...curr }), {}) as T; 34 | -------------------------------------------------------------------------------- /src/consumer/internals.ts: -------------------------------------------------------------------------------- 1 | import { Ffi, FfiPactHandle, FfiWritePactResponse } from '../ffi/types'; 2 | import { logErrorAndThrow, logCrashAndThrow } from '../logger'; 3 | import { MatchingResult, Mismatch } from './types'; 4 | 5 | export const mockServerMismatches = ( 6 | ffi: Ffi, 7 | port: number 8 | ): MatchingResult[] => { 9 | const results: MatchingResult[] = JSON.parse( 10 | ffi.pactffiMockServerMismatches(port) 11 | ); 12 | return results.map((result: MatchingResult) => ({ 13 | ...result, 14 | ...('mismatches' in result 15 | ? { 16 | mismatches: result.mismatches.map((m: string | Mismatch) => 17 | typeof m === 'string' ? JSON.parse(m) : m 18 | ), 19 | } 20 | : {}), 21 | })); 22 | }; 23 | 24 | export const writePact = ( 25 | ffi: Ffi, 26 | pactPtr: FfiPactHandle, 27 | dir: string, 28 | merge = true, 29 | port = 0 30 | ): void => { 31 | let result: FfiWritePactResponse; 32 | 33 | if (port) { 34 | result = ffi.pactffiWritePactFileByPort(port, dir, !merge); 35 | } else { 36 | result = ffi.pactffiWritePactFile(pactPtr, dir, !merge); 37 | } 38 | 39 | switch (result) { 40 | case FfiWritePactResponse['SUCCESS']: 41 | return; 42 | case FfiWritePactResponse['UNABLE_TO_WRITE_PACT_FILE']: 43 | logErrorAndThrow('The pact core was unable to write the pact file'); 44 | break; 45 | case FfiWritePactResponse['GENERAL_PANIC']: 46 | logCrashAndThrow('The pact core panicked while writing the pact file'); 47 | break; 48 | case FfiWritePactResponse['MOCK_SERVER_NOT_FOUND']: 49 | logCrashAndThrow( 50 | 'The pact core was asked to write a pact file from a mock server that appears not to exist' 51 | ); 52 | break; 53 | default: 54 | logCrashAndThrow( 55 | `The pact core returned an unknown error code (${result}) instead of writing the pact` 56 | ); 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /src/ffi/index.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import bindings = require('node-gyp-build'); 3 | import { isNonGlibcLinuxSync } from 'detect-libc'; 4 | import logger, { DEFAULT_LOG_LEVEL } from '../logger'; 5 | import { LogLevel } from '../logger/types'; 6 | import { Ffi, FfiLogLevelFilter } from './types'; 7 | 8 | export const PACT_FFI_VERSION = '0.4.22'; 9 | 10 | /** 11 | * Returns the library path which is located inside `node_modules` 12 | * The naming convention is @pact-foundation/pact-core-${os}-${arch}<-${libc}> 13 | * - "-${libc}" is optional for linux only 14 | * @see https://nodejs.org/api/os.html#osarch 15 | * @see https://nodejs.org/api/os.html#osplatform 16 | * @example "x/xx/node_modules/@pact-foundation/pact-core-darwin-arm64" 17 | */ 18 | function getPlatformArchSpecificPackage() { 19 | const { arch } = process; 20 | let os = process.platform as string; 21 | if (['win32', 'cygwin'].includes(process.platform)) { 22 | os = 'windows'; 23 | } 24 | let platformArchSpecificPackage = `@pact-foundation/pact-core-${os}-${arch}`; 25 | if (os === 'linux') { 26 | platformArchSpecificPackage += isNonGlibcLinuxSync() ? '-musl' : '-glibc'; 27 | } 28 | 29 | const prebuildPackageLocation = process.env['PACT_PREBUILD_PACKAGE_LOCATION']; 30 | if (prebuildPackageLocation) { 31 | platformArchSpecificPackage = path.join( 32 | prebuildPackageLocation, 33 | platformArchSpecificPackage 34 | ); 35 | } 36 | 37 | const packagePath = `${platformArchSpecificPackage}/package.json`; 38 | try { 39 | let resolvedPackagePath = require.resolve(packagePath); 40 | if (os === 'windows') { 41 | resolvedPackagePath = resolvedPackagePath.replace('\\package.json', ''); 42 | } else { 43 | resolvedPackagePath = resolvedPackagePath.replace('/package.json', ''); 44 | } 45 | return resolvedPackagePath; 46 | } catch (e) { 47 | throw new Error( 48 | `Couldn't find npm package ${platformArchSpecificPackage} \n 💡 you can tell Pact where the npm package is located with env var $PACT_PREBUILD_PACKAGE_LOCATION` 49 | ); 50 | } 51 | } 52 | 53 | // supported prebuilds 54 | // darwin-arm64 55 | // darwin-x64 56 | // linux-arm64 57 | // linux-x64 58 | // win32-x64 59 | 60 | const supportedPlatforms = [ 61 | 'darwin-arm64', 62 | 'darwin-x64', 63 | 'linux-arm64', 64 | 'linux-x64', 65 | 'win32-x64', 66 | ]; 67 | const platform = `${process.platform}-${process.arch}`; 68 | 69 | const supportedPlatformsMessage = [ 70 | 'Supported platforms are: ', 71 | ` - ${supportedPlatforms.join('\n - ')}`, 72 | ].join('\n'); 73 | const detectedMessage = `We detected your platform as: \n\n - ${platform}`; 74 | logger.debug(detectedMessage); 75 | if (!supportedPlatforms.includes(platform)) { 76 | logger.warn(supportedPlatformsMessage); 77 | logger.warn(detectedMessage); 78 | logger.error(`Unsupported platform: ${platform}`); 79 | throw new Error(`Unsupported platform: ${platform}`); 80 | } 81 | 82 | const loadPathMessage = (bindingsPath: string) => 83 | `: attempting to load native module from: \n\n - ${path.join( 84 | bindingsPath, 85 | 'prebuilds', 86 | platform 87 | )} ${ 88 | process.env['PACT_PREBUILD_LOCATION'] 89 | ? `\n - source: PACT_PREBUILD_LOCATION \n - You must have a supported prebuild for your platform at this location in the path ${path.join( 90 | process.env['PACT_PREBUILD_LOCATION'], 91 | 'prebuilds', 92 | platform 93 | )}` 94 | : `\n source: pact-js-core binding lookup \n\n - You can override via PACT_PREBUILD_LOCATION\n` 95 | }`; 96 | 97 | const bindingsResolver = (bindingsPath: string | undefined) => 98 | bindings(bindingsPath); 99 | 100 | const bindingPaths = [ 101 | path.resolve(getPlatformArchSpecificPackage()), 102 | process.env['PACT_PREBUILD_LOCATION']?.toString() ?? path.resolve(), 103 | ]; 104 | let ffiLib: Ffi; 105 | 106 | const renderBinaryErrorMessage = (error: unknown) => { 107 | logger.debug(supportedPlatformsMessage); 108 | logger.error(`Failed to find native module for ${platform}: ${error}`); 109 | bindingPaths.forEach((bindingPath) => { 110 | logger.debug( 111 | `We looked for a supported build in this location ${path.join( 112 | bindingPath ?? path.resolve(), 113 | 'prebuilds', 114 | platform 115 | )}` 116 | ); 117 | }); 118 | logger.debug( 119 | `Tip: check there is a prebuild for ${platform} \n 120 | check the path exists\n 121 | Wrong Path?: set the load path with PACT_PREBUILD_LOCATION ensuring that ${path.join( 122 | '$PACT_PREBUILD_LOCATION', 123 | 'prebuilds', 124 | platform 125 | )} exists\n 126 | - Note: You dont need to include the prebuilds/${platform} part of the path, just the parent directory\n 127 | - Let us know: We can add more supported path lookups easily, chat to us on slack or raise an issue on github` 128 | ); 129 | }; 130 | 131 | let ffi: typeof ffiLib; 132 | 133 | const initialiseFfi = (): typeof ffi => { 134 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 135 | // @ts-ignore 136 | if (process.stdout._handle) { 137 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 138 | // @ts-ignore 139 | process.stdout._handle.setBlocking(true); 140 | } 141 | try { 142 | bindingPaths.every((bindingPath, i) => { 143 | try { 144 | logger.debug(`binding path #${i}: ${loadPathMessage(bindingPath)}`); 145 | ffiLib = bindingsResolver(bindingPath); 146 | if (ffiLib.pactffiVersion() === PACT_FFI_VERSION) { 147 | logger.info( 148 | 'pact native library successfully found, and the correct version', 149 | ffiLib.pactffiVersion() 150 | ); 151 | return false; 152 | } 153 | return true; 154 | } catch (error) { 155 | return true; 156 | } 157 | }); 158 | } catch (error) { 159 | renderBinaryErrorMessage(error); 160 | throw new Error( 161 | `Failed to load native module, try setting LOG_LEVEL=debug for more info` 162 | ); 163 | } 164 | return ffiLib; 165 | }; 166 | 167 | export const getFfiLib = ( 168 | logLevel: LogLevel = DEFAULT_LOG_LEVEL, 169 | logFile: string | undefined = undefined 170 | ): typeof ffi => { 171 | if (!ffi) { 172 | logger.trace('Initialising ffi for the first time'); 173 | ffi = initialiseFfi(); 174 | logger.debug( 175 | `Initialising native core at log level '${logLevel}'`, 176 | logFile 177 | ); 178 | if (logFile) { 179 | logger.debug(`writing log file at level ${logLevel} to ${logFile}`); 180 | const res = ffiLib.pactffiLogToFile( 181 | logFile, 182 | FfiLogLevelFilter[logLevel] ?? 3 183 | ); 184 | if (res !== 0) { 185 | logger.warn(`Failed to write log file to ${logFile}, reason: ${res}`); 186 | } 187 | } else { 188 | ffiLib.pactffiInitWithLogLevel(logLevel); 189 | } 190 | } 191 | return ffi; 192 | }; 193 | -------------------------------------------------------------------------------- /src/ffi/node-gyp-build.ts: -------------------------------------------------------------------------------- 1 | declare module 'node-gyp-build'; 2 | -------------------------------------------------------------------------------- /src/index.spec.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import index from './index'; 3 | 4 | const { expect } = chai; 5 | 6 | describe('Index Spec', () => { 7 | it('Typescript import should work', () => { 8 | expect(index).to.be.ok; 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import pact from './pact'; 2 | 3 | // eslint-disable-next-line no-multi-assign 4 | module.exports = exports = pact; 5 | 6 | export default pact; 7 | 8 | export * from './verifier'; 9 | export * from './verifier/types'; 10 | 11 | export * from './logger'; 12 | export * from './logger/types'; 13 | 14 | export * from './consumer'; 15 | export * from './consumer/types'; 16 | 17 | export * from './ffi'; 18 | -------------------------------------------------------------------------------- /src/logger/crashMessage.ts: -------------------------------------------------------------------------------- 1 | export const pactCrashMessage = ( 2 | extraMessage: string 3 | ): string => `!!!!!!!!! PACT CRASHED !!!!!!!!! 4 | 5 | ${extraMessage} 6 | 7 | This is almost certainly a bug in pact-js-core. It would be great if you could 8 | open a bug report at: https://github.com/pact-foundation/pact-js-core/issues 9 | so that we can fix it. 10 | 11 | There is additional debugging information above. If you open a bug report, 12 | please rerun with logLevel: 'debug' set in the VerifierOptions, and include the 13 | full output. 14 | 15 | SECURITY WARNING: Before including your log in the issue tracker, make sure you 16 | have removed sensitive info such as login credentials and urls that you don't want 17 | to share with the world. 18 | 19 | We're sorry about this! 20 | `; 21 | -------------------------------------------------------------------------------- /src/logger/index.ts: -------------------------------------------------------------------------------- 1 | import { pactCrashMessage } from './crashMessage'; 2 | import { createLogger } from './pino'; 3 | import { LogLevel } from './types'; 4 | 5 | // TODO: Replace this hack with https://www.npmjs.com/package/@npmcli/package-json 6 | // TODO: abstract this so it's not repeated in src/verifier/nativeVerifier.ts 7 | // eslint-disable-next-line @typescript-eslint/no-var-requires 8 | const pkg = require('../../package.json'); 9 | 10 | const logContext = `pact-core@${pkg.version}`; 11 | let currentLogLevel: LogLevel = process.env['LOG_LEVEL'] 12 | ? (process.env['LOG_LEVEL'] as LogLevel) 13 | : 'info'; 14 | let logger = createLogger(currentLogLevel); 15 | 16 | export const DEFAULT_LOG_LEVEL: LogLevel = process.env['LOG_LEVEL'] 17 | ? (process.env['LOG_LEVEL'] as LogLevel) 18 | : 'info'; 19 | 20 | export const setLogLevel = (level: LogLevel = 'info'): void => { 21 | currentLogLevel = level; 22 | logger = createLogger(currentLogLevel); 23 | }; 24 | 25 | export const getLogLevel = (): LogLevel => currentLogLevel; 26 | 27 | export const verboseIsImplied = (): boolean => 28 | currentLogLevel === 'trace' || currentLogLevel === 'debug'; 29 | 30 | const addContext = (context: string, message: string) => 31 | `${context}: ${message}`; 32 | 33 | const logFunctions = { 34 | pactCrash: (message: string, context: string = logContext): void => 35 | logger.error(addContext(context, pactCrashMessage(message))), 36 | error: (message: string, context: string = logContext): void => 37 | logger.error(addContext(context, message)), 38 | warn: (message: string, context: string = logContext): void => 39 | logger.warn(addContext(context, message)), 40 | info: (message: string, context: string = logContext): void => 41 | logger.info(addContext(context, message)), 42 | debug: (message: string, context: string = logContext): void => 43 | logger.debug(addContext(context, message)), 44 | trace: (message: string, context: string = logContext): void => 45 | logger.trace(addContext(context, message)), 46 | }; 47 | 48 | export const logErrorAndThrow = (message: string, context?: string): never => { 49 | logger.error(message, context); 50 | throw new Error(message); 51 | }; 52 | 53 | export const logCrashAndThrow = (message: string, context?: string): never => { 54 | logFunctions.pactCrash(message, context); 55 | throw new Error(message); 56 | }; 57 | 58 | export default logFunctions; 59 | -------------------------------------------------------------------------------- /src/logger/pino.ts: -------------------------------------------------------------------------------- 1 | import { pino } from 'pino'; 2 | import pretty from 'pino-pretty'; 3 | import { LogLevel } from './types'; 4 | 5 | export const createLogger = (level: LogLevel): pino.Logger => 6 | pino( 7 | { 8 | level: level.toLowerCase(), 9 | }, 10 | pretty({ sync: true }) 11 | ); 12 | -------------------------------------------------------------------------------- /src/logger/types.ts: -------------------------------------------------------------------------------- 1 | export type LogLevel = 'debug' | 'error' | 'info' | 'trace' | 'warn'; 2 | 3 | export type Logger = { 4 | pactCrash: (message: string, context?: string) => void; 5 | error: (message: string, context?: string) => void; 6 | warn: (message: string, context?: string) => void; 7 | info: (message: string, context?: string) => void; 8 | debug: (message: string, context?: string) => void; 9 | trace: (message: string, context?: string) => void; 10 | }; 11 | -------------------------------------------------------------------------------- /src/pact-environment.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | export class PactEnvironment { 4 | public get cwd(): string { 5 | return path.resolve(__dirname, '..'); 6 | } 7 | 8 | public isWindows(platform: string = process.platform): boolean { 9 | return platform === 'win32'; 10 | } 11 | } 12 | 13 | export default new PactEnvironment(); 14 | -------------------------------------------------------------------------------- /src/pact.spec.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import chaiAsPromised from 'chai-as-promised'; 3 | import pact from './pact'; 4 | 5 | chai.use(chaiAsPromised); 6 | 7 | describe('Pact Spec', () => { 8 | describe('Set Log Level', () => { 9 | let originalLogLevel: any; 10 | // Reset log level after the tests 11 | before(() => { 12 | originalLogLevel = pact.logLevel(); 13 | }); 14 | after(() => pact.logLevel(originalLogLevel)); 15 | 16 | context('when setting a log level', () => { 17 | it("should be able to set log level 'trace'", () => { 18 | pact.logLevel('trace'); 19 | pact.logLevel(); 20 | }); 21 | 22 | it("should be able to set log level 'debug'", () => { 23 | pact.logLevel('debug'); 24 | pact.logLevel(); 25 | }); 26 | 27 | it("should be able to set log level 'info'", () => { 28 | pact.logLevel('info'); 29 | pact.logLevel(); 30 | }); 31 | 32 | it("should be able to set log level 'warn'", () => { 33 | pact.logLevel('warn'); 34 | pact.logLevel(); 35 | }); 36 | 37 | it("should be able to set log level 'error'", () => { 38 | pact.logLevel('error'); 39 | pact.logLevel(); 40 | }); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/pact.ts: -------------------------------------------------------------------------------- 1 | import verifierFactory from './verifier'; 2 | import { VerifierOptions } from './verifier/types'; 3 | import logger, { setLogLevel } from './logger'; 4 | import { LogLevel } from './logger/types'; 5 | 6 | export class Pact { 7 | public logLevel(level?: LogLevel): void { 8 | return setLogLevel(level); 9 | } 10 | 11 | // Run the Pact Verification process 12 | public verifyPacts(options: VerifierOptions): Promise { 13 | logger.info('Verifying Pacts.'); 14 | return verifierFactory(options).verify(); 15 | } 16 | } 17 | 18 | export default new Pact(); 19 | -------------------------------------------------------------------------------- /src/verifier/argumentMapper/index.ts: -------------------------------------------------------------------------------- 1 | import { values, invert } from 'underscore'; 2 | import { FnValidationResult, FnValidationStatus } from './types'; 3 | import logger, { logCrashAndThrow, logErrorAndThrow } from '../../logger'; 4 | import { InternalPactVerifierOptions } from '../types'; 5 | import { ffiFnMapping, orderOfExecution } from './arguments'; 6 | import { Ffi, FfiVerifierHandle } from '../../ffi/types'; 7 | 8 | export const setupVerification = ( 9 | ffi: Ffi, 10 | handle: FfiVerifierHandle, 11 | options: InternalPactVerifierOptions 12 | ): void => { 13 | const order = values(orderOfExecution).sort((a, b) => a - b); 14 | const functionsToCall = invert(orderOfExecution); 15 | 16 | order.forEach((k) => { 17 | const fn = functionsToCall[k]; 18 | const validation: FnValidationResult = ffiFnMapping[fn].validateAndExecute( 19 | ffi, 20 | handle, 21 | options 22 | ); 23 | 24 | switch (validation.status) { 25 | case FnValidationStatus.FAIL: 26 | logErrorAndThrow( 27 | `the required ffi function '${fn}' failed validation with errors: ${validation.messages}` 28 | ); 29 | break; 30 | case FnValidationStatus.IGNORE: 31 | logger.debug( 32 | `the optional ffi function '${fn}' was not executed as it had non-fatal validation errors: ${validation.messages}` 33 | ); 34 | break; 35 | case FnValidationStatus.SUCCESS: 36 | break; 37 | default: 38 | logCrashAndThrow( 39 | `the ffi function '${fn}' returned the following unrecognised validation signal: '${ 40 | (validation as FnValidationResult).status 41 | }'` 42 | ); 43 | } 44 | }); 45 | }; 46 | -------------------------------------------------------------------------------- /src/verifier/argumentMapper/types.ts: -------------------------------------------------------------------------------- 1 | import logger from '../../logger'; 2 | import { Ffi, FfiHandle } from '../../ffi/types'; 3 | 4 | export const deprecatedFunction = (_: unknown, property: string): boolean => { 5 | logger.warn(`${property} is deprecated and no longer has any effect`); 6 | 7 | return true; 8 | }; 9 | 10 | type KeyedObject = { 11 | [key: string]: unknown; 12 | }; 13 | 14 | type FnArgumentMapping = { 15 | validateAndExecute: ( 16 | ffi: Ffi, 17 | handle: FfiHandle, 18 | options: O 19 | ) => FnValidationResult; 20 | }; 21 | 22 | export type FnMapping = { 23 | [Key in keyof T]: FnArgumentMapping; 24 | }; 25 | 26 | export enum FnValidationStatus { 27 | SUCCESS = 0, 28 | IGNORE = 1, 29 | FAIL = 2, 30 | } 31 | 32 | type FnValidationResultSuccess = { 33 | status: FnValidationStatus.SUCCESS; 34 | }; 35 | 36 | type FnValidationResultFail = { 37 | status: FnValidationStatus.FAIL; 38 | messages: string[]; 39 | }; 40 | 41 | type FnValidationResultIgnore = { 42 | status: FnValidationStatus.IGNORE; 43 | messages: string[]; 44 | }; 45 | 46 | export type FnValidationResult = 47 | | FnValidationResultSuccess 48 | | FnValidationResultFail 49 | | FnValidationResultIgnore; 50 | -------------------------------------------------------------------------------- /src/verifier/index.ts: -------------------------------------------------------------------------------- 1 | import logger from '../logger'; 2 | import { VerifierOptions } from './types'; 3 | import { verify } from './nativeVerifier'; 4 | import { validateOptions } from './validateOptions'; 5 | 6 | const applyDefaults = (options: VerifierOptions): VerifierOptions => ({ 7 | timeout: 30000, 8 | logLevel: 'info', 9 | ...options, 10 | }); 11 | 12 | export class Verifier { 13 | public readonly options: VerifierOptions; 14 | 15 | constructor(options: VerifierOptions) { 16 | this.options = validateOptions(applyDefaults(options)); 17 | } 18 | 19 | // Deprecated? 20 | // This might be hard to validate, given the new FFI interface 21 | // and potential extensions 22 | public verify(): Promise { 23 | logger.info('Verifying Pact Files'); 24 | 25 | return verify(this.options); 26 | } 27 | } 28 | 29 | // Creates a new instance of the pact server with the specified option 30 | export default (options: VerifierOptions): Verifier => new Verifier(options); 31 | -------------------------------------------------------------------------------- /src/verifier/nativeVerifier.ts: -------------------------------------------------------------------------------- 1 | import { VerifierOptions } from './types'; 2 | import logger, { setLogLevel } from '../logger'; 3 | import { getFfiLib } from '../ffi'; 4 | import { VERIFY_PROVIDER_RESPONSE } from '../ffi/types'; 5 | 6 | import { setupVerification } from './argumentMapper'; 7 | 8 | // TODO: Replace this hack with https://www.npmjs.com/package/@npmcli/package-json 9 | // TODO: abstract this so it's not repeated in src/logger.ts 10 | // eslint-disable-next-line @typescript-eslint/no-var-requires 11 | const pkg = require('../../package.json'); 12 | 13 | export const verify = (opts: VerifierOptions): Promise => { 14 | if (opts.logLevel) { 15 | setLogLevel(opts.logLevel); 16 | } 17 | const ffi = getFfiLib(opts.logLevel, opts.logFile); 18 | 19 | const handle = ffi.pactffiVerifierNewForApplication( 20 | pkg.name.split('/')[1], 21 | pkg.version 22 | ); 23 | 24 | setupVerification(ffi, handle, opts); 25 | 26 | return new Promise((resolve, reject) => { 27 | ffi.pactffiVerifierExecute(handle, (err: Error, res: number) => { 28 | logger.debug(`shutting down verifier with handle ${handle}`); 29 | 30 | ffi.pactffiVerifierShutdown(handle); 31 | 32 | logger.debug(`response from verifier: ${err}, ${res}`); 33 | if (err) { 34 | if (typeof err === 'string') { 35 | // It might not really be an `Error`, because it comes from native code. 36 | logger.error(err); 37 | } else if (err.message) { 38 | logger.error(err.message); 39 | } 40 | logger.pactCrash( 41 | 'The underlying pact core returned an error through the ffi interface' 42 | ); 43 | reject(err); 44 | } else { 45 | switch (res) { 46 | case VERIFY_PROVIDER_RESPONSE.VERIFICATION_SUCCESSFUL: 47 | logger.info('Verification successful'); 48 | resolve(`finished: ${res}`); 49 | break; 50 | case VERIFY_PROVIDER_RESPONSE.VERIFICATION_FAILED: 51 | logger.error('Verification unsuccessful'); 52 | reject(new Error('Verfication failed')); 53 | break; 54 | case VERIFY_PROVIDER_RESPONSE.INVALID_ARGUMENTS: 55 | logger.pactCrash( 56 | 'The underlying pact core was invoked incorrectly.' 57 | ); 58 | reject(new Error('Verification was unable to run')); 59 | break; 60 | default: 61 | logger.pactCrash( 62 | 'The underlying pact core crashed in an unexpected way.' 63 | ); 64 | reject(new Error('Pact core crashed')); 65 | break; 66 | } 67 | } 68 | }); 69 | }); 70 | }; 71 | -------------------------------------------------------------------------------- /src/verifier/types.ts: -------------------------------------------------------------------------------- 1 | import { LogLevel } from '../logger/types'; 2 | 3 | export interface ConsumerVersionSelector { 4 | tag?: string; 5 | latest?: boolean; 6 | consumer?: string; 7 | deployedOrReleased?: boolean; 8 | deployed?: boolean; 9 | released?: boolean; 10 | environment?: string; 11 | fallbackTag?: string; 12 | branch?: string; 13 | mainBranch?: boolean; 14 | matchingBranch?: boolean; 15 | } 16 | 17 | export type CustomHeaders = { 18 | [header: string]: string; 19 | }; 20 | 21 | export interface Transport { 22 | protocol: string; 23 | port: number; 24 | scheme?: string; 25 | path?: string; 26 | } 27 | 28 | export interface VerifierOptions { 29 | providerBaseUrl: string; 30 | provider?: string; 31 | pactUrls?: string[]; 32 | pactBrokerUrl?: string; 33 | pactBrokerUsername?: string; 34 | pactBrokerPassword?: string; 35 | pactBrokerToken?: string; 36 | consumerVersionTags?: string[]; 37 | providerVersionTags?: string[]; 38 | providerVersionBranch?: string; 39 | providerStatesSetupUrl?: string; 40 | providerStatesSetupTeardown?: boolean; 41 | providerStatesSetupBody?: boolean; 42 | publishVerificationResult?: boolean; 43 | providerVersion?: string; 44 | enablePending?: boolean; 45 | includeWipPactsSince?: string; 46 | consumerVersionSelectors?: ConsumerVersionSelector[]; 47 | timeout?: number; 48 | logLevel?: LogLevel; 49 | logFile?: string; 50 | disableSslVerification?: boolean; 51 | buildUrl?: string; 52 | customProviderHeaders?: CustomHeaders | string[]; 53 | consumerFilters?: string[]; 54 | transports?: Transport[]; 55 | /** 56 | * @deprecated use providerVersionBranch instead 57 | */ 58 | providerBranch?: string; 59 | failIfNoPactsFound?: boolean; 60 | } 61 | 62 | /** These are the deprecated verifier options, removed prior to this verison, 63 | * but it's useful to know what they were so we can potentially map or warn. 64 | */ 65 | type DeprecatedVerifierOptions = { 66 | format?: 'json' | 'xml' | 'progress' | 'RspecJunitFormatter'; 67 | out?: string; 68 | verbose?: boolean; 69 | monkeypatch?: string; 70 | logDir?: string; 71 | }; 72 | 73 | /** Helper type for the mapper to reason about the options we want to be able to handle. 74 | * Not exposed, because we only want to expose the current VerifierOptions to the user 75 | * @internal */ 76 | export type InternalPactVerifierOptions = VerifierOptions & 77 | DeprecatedVerifierOptions; 78 | -------------------------------------------------------------------------------- /src/verifier/validateOptions.ts: -------------------------------------------------------------------------------- 1 | import checkTypes = require('check-types'); 2 | import { pick } from 'underscore'; 3 | import logger, { logErrorAndThrow } from '../logger'; 4 | import { LogLevel } from '../logger/types'; 5 | import { 6 | ConsumerVersionSelector, 7 | InternalPactVerifierOptions, 8 | VerifierOptions, 9 | } from './types'; 10 | 11 | export const deprecatedFunction = 12 | () => 13 | (_: unknown, property: string): boolean => { 14 | logger.warn(`${property} is deprecated and no longer has any effect`); 15 | 16 | return true; 17 | }; 18 | 19 | export const deprecatedBy = 20 | (preferredOption: string) => 21 | () => 22 | (_: unknown, property: string): boolean => { 23 | logger.warn(`${property} is deprecated, use ${preferredOption} instead`); 24 | 25 | return true; 26 | }; 27 | 28 | export const incompatibleWith = 29 | (keys: (keyof InternalPactVerifierOptions)[]) => 30 | (options: InternalPactVerifierOptions) => 31 | (_: unknown, property: string): boolean => { 32 | const incompatibilities = pick(options, keys); 33 | 34 | if (Object.keys(incompatibilities).length > 0) { 35 | logErrorAndThrow( 36 | `${property} is incompatible with the following properties: ${keys.join( 37 | ',' 38 | )}` 39 | ); 40 | return false; 41 | } 42 | 43 | return true; 44 | }; 45 | 46 | export const requires = 47 | (keys: (keyof InternalPactVerifierOptions)[]) => 48 | (options: InternalPactVerifierOptions) => 49 | (_: unknown, property: string): boolean => { 50 | const required = pick(options, keys); 51 | 52 | if (keys.length !== Object.keys(required).length) { 53 | logErrorAndThrow( 54 | `${property} requires the following properties: ${keys.join(',')}` 55 | ); 56 | return false; 57 | } 58 | 59 | return true; 60 | }; 61 | 62 | export const requiresOneOf = 63 | (keys: (keyof InternalPactVerifierOptions)[]) => 64 | (options: InternalPactVerifierOptions) => 65 | (_: unknown, property: string): boolean => { 66 | const required = pick(options, keys); 67 | 68 | if (Object.keys(required).length === 0) { 69 | logErrorAndThrow( 70 | `${property} requires one of the following properties: ${keys.join( 71 | ',' 72 | )}` 73 | ); 74 | return false; 75 | } 76 | 77 | return true; 78 | }; 79 | 80 | type AssertFunction = (a: unknown, property: string) => boolean; 81 | 82 | const assertNonEmptyString = 83 | (): AssertFunction => (a: unknown, property: string) => 84 | checkTypes.assert.nonEmptyString(a as string, property); 85 | 86 | const assertBoolean = (): AssertFunction => (a: unknown, property: string) => 87 | checkTypes.assert.boolean(a as boolean, property); 88 | 89 | const assertPositive = (): AssertFunction => (a: unknown, property: string) => 90 | checkTypes.assert.positive(a as number, property); 91 | 92 | const LogLevels: LogLevel[] = ['debug', 'error', 'info', 'trace', 'warn']; 93 | 94 | const logLevelValidator = 95 | () => 96 | (l: unknown): boolean => { 97 | if (typeof l === 'string') { 98 | if (LogLevels.includes(l.toLowerCase() as LogLevel)) { 99 | return true; 100 | } 101 | } 102 | throw new Error( 103 | `The logLevel '${l}' is not a valid logLevel. The valid options are: ${LogLevels.join( 104 | ', ' 105 | )}` 106 | ); 107 | }; 108 | 109 | const consumerVersionSelectorValidator = 110 | (options: InternalPactVerifierOptions) => (): boolean => { 111 | if ( 112 | options.consumerVersionSelectors && 113 | Array.isArray(options.consumerVersionSelectors) 114 | ) { 115 | const PROPS: Array = [ 116 | 'tag', 117 | 'latest', 118 | 'consumer', 119 | 'deployedOrReleased', 120 | 'deployed', 121 | 'released', 122 | 'environment', 123 | 'fallbackTag', 124 | 'branch', 125 | 'mainBranch', 126 | 'matchingBranch', 127 | ]; 128 | options.consumerVersionSelectors.forEach((selector) => { 129 | if (selector.tag === 'latest') { 130 | logger.warn( 131 | "Using the tag 'latest' is not recommended and probably does not do what you intended." 132 | ); 133 | logger.warn( 134 | ' See https://docs.pact.io/pact_broker/tags/#latest-pacts' 135 | ); 136 | logger.warn(' If you need to specify latest, try:'); 137 | logger.warn(' consumerVersionSelectors: [{ lastest: true }]'); 138 | } 139 | Object.keys(selector).forEach((key) => { 140 | if (!PROPS.includes(key as keyof ConsumerVersionSelector)) { 141 | logger.warn( 142 | `The consumer version selector '${key}' is unknown but will be sent through to the validation. Allowed properties are ${PROPS.join( 143 | ', ' 144 | )})` 145 | ); 146 | } 147 | }); 148 | }); 149 | } 150 | 151 | return true; 152 | }; 153 | 154 | const consumerVersionTagsValidator = 155 | (options: InternalPactVerifierOptions) => (): boolean => { 156 | if (options.consumerVersionTags) { 157 | if ( 158 | !checkTypes.string(options.consumerVersionTags) && 159 | !checkTypes.array.of.string(options.consumerVersionTags) 160 | ) { 161 | throw new Error( 162 | 'consumerVersionTags should be a string or an array of strings' 163 | ); 164 | } 165 | if (options.consumerVersionTags.includes('latest')) { 166 | logger.warn( 167 | "Using the tag 'latest' is not recommended and probably does not do what you intended." 168 | ); 169 | logger.warn( 170 | ' See https://docs.pact.io/pact_broker/tags/#latest-pacts' 171 | ); 172 | logger.warn(' If you need to specify latest, try:'); 173 | logger.warn(' consumerVersionSelectors: [{ lastest: true }]'); 174 | } 175 | } 176 | 177 | return true; 178 | }; 179 | 180 | const customProviderHeadersValidator = 181 | (options: InternalPactVerifierOptions) => (): boolean => { 182 | if (options.customProviderHeaders) { 183 | if (Array.isArray(options.customProviderHeaders)) { 184 | checkTypes.assert.array.of.string(options.customProviderHeaders); 185 | } else { 186 | checkTypes.assert.nonEmptyObject(options.customProviderHeaders); 187 | } 188 | } 189 | 190 | return true; 191 | }; 192 | 193 | export type ArgumentValidationRules = { 194 | [Key in keyof T]-?: ((options: T) => AssertFunction)[]; 195 | }; 196 | 197 | export const validationRules: ArgumentValidationRules = 198 | { 199 | providerBaseUrl: [assertNonEmptyString], 200 | buildUrl: [assertNonEmptyString], 201 | consumerVersionSelectors: [consumerVersionSelectorValidator], 202 | consumerVersionTags: [consumerVersionTagsValidator], 203 | customProviderHeaders: [customProviderHeadersValidator], 204 | disableSslVerification: [assertBoolean], 205 | enablePending: [assertBoolean], 206 | format: [deprecatedFunction], 207 | includeWipPactsSince: [assertNonEmptyString], 208 | provider: [assertNonEmptyString], 209 | pactUrls: [assertNonEmptyString], 210 | pactBrokerUrl: [ 211 | assertNonEmptyString, 212 | requires(['provider']), 213 | requiresOneOf([ 214 | 'pactUrls', 215 | 'consumerVersionSelectors', 216 | 'consumerVersionTags', 217 | ]), 218 | ], 219 | pactBrokerUsername: [ 220 | assertNonEmptyString, 221 | incompatibleWith(['pactBrokerToken']), 222 | requires(['pactBrokerPassword']), 223 | ], 224 | pactBrokerPassword: [ 225 | assertNonEmptyString, 226 | incompatibleWith(['pactBrokerToken']), 227 | requires(['pactBrokerUsername']), 228 | ], 229 | pactBrokerToken: [ 230 | assertNonEmptyString, 231 | incompatibleWith(['pactBrokerUsername', 'pactBrokerPassword']), 232 | ], 233 | providerVersionTags: [assertNonEmptyString], 234 | providerBranch: [ 235 | assertNonEmptyString, 236 | deprecatedBy('providerVersionBranch'), 237 | ], 238 | providerVersionBranch: [assertNonEmptyString], 239 | providerStatesSetupUrl: [assertNonEmptyString], 240 | providerStatesSetupTeardown: [assertBoolean], 241 | providerStatesSetupBody: [assertBoolean], 242 | publishVerificationResult: [assertBoolean, requires(['providerVersion'])], 243 | providerVersion: [assertNonEmptyString], 244 | timeout: [assertPositive], 245 | logLevel: [logLevelValidator], 246 | out: [deprecatedFunction], 247 | verbose: [deprecatedFunction], 248 | monkeypatch: [deprecatedFunction], 249 | logDir: [deprecatedFunction], 250 | logFile: [assertNonEmptyString], 251 | consumerFilters: [assertNonEmptyString], 252 | failIfNoPactsFound: [assertBoolean], 253 | transports: [], 254 | }; 255 | 256 | export const validateOptions = (options: VerifierOptions): VerifierOptions => { 257 | ( 258 | Object.keys(options).concat('providerBaseUrl') as Array< 259 | keyof InternalPactVerifierOptions 260 | > 261 | ).forEach((k) => { 262 | const rules = validationRules[k]; 263 | 264 | // get type of parameter (if an array, we apply the rule to each item of the array instead) 265 | if (Array.isArray(options[k])) { 266 | options[k].forEach((item: unknown) => { 267 | (rules || []).forEach((rule) => { 268 | // rule(item) // If the messages aren't clear, we can do this 269 | rule(options)(item, k); 270 | }); 271 | }); 272 | } else { 273 | (rules || []).forEach((rule) => { 274 | rule(options)(options[k], k); 275 | }); 276 | } 277 | }); 278 | 279 | return options; 280 | }; 281 | -------------------------------------------------------------------------------- /test/integration/data-utils.ts: -------------------------------------------------------------------------------- 1 | import express = require('express'); 2 | import basicAuth = require('basic-auth'); 3 | 4 | export function returnJson( 5 | json: T 6 | ): (req: express.Request, res: express.Response) => express.Response { 7 | return (req, res): express.Response => res.json(json); 8 | } 9 | 10 | export function returnJsonFile( 11 | filename: string 12 | ): (req: express.Request, res: express.Response) => express.Response { 13 | // eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require, global-require 14 | return returnJson(require(filename)); 15 | } 16 | 17 | export function auth( 18 | req: express.Request, 19 | res: express.Response, 20 | next: express.NextFunction 21 | ): express.Response { 22 | const user = basicAuth(req); 23 | if (user && user.name === 'foo' && user.pass === 'bar') { 24 | next(); 25 | } else { 26 | res 27 | .set('WWW-Authenticate', 'Basic realm=Authorization Required') 28 | .sendStatus(401); 29 | } 30 | return res; 31 | } 32 | -------------------------------------------------------------------------------- /test/integration/data/get-noauth-provider_they-consumer_anotherclient-latest.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "anotherclient" 4 | }, 5 | "provider": { 6 | "name": "they" 7 | }, 8 | "interactions": [{ 9 | "description": "Provider state success", 10 | "provider_state": "There is a greeting", 11 | "request": { 12 | "method": "GET", 13 | "path": "/somestate" 14 | }, 15 | "response": { 16 | "status": 200, 17 | "headers": {}, 18 | "body": { 19 | "greeting": "State data!" 20 | } 21 | } 22 | }], 23 | "metadata": { 24 | "pactSpecificationVersion": "2.0.0" 25 | }, 26 | "updatedAt": "2016-05-15T00:09:33+00:00", 27 | "createdAt": "2016-05-15T00:09:06+00:00", 28 | "_links": { 29 | "self": { 30 | "title": "Pact", 31 | "name": "Pact between me (v1.0.0) and they", 32 | "href": "http://pact.onegeek.com.au/pacts/provider/they/consumer/me/version/1.0.0" 33 | }, 34 | "pb:consumer": { 35 | "title": "Consumer", 36 | "name": "anotherclient", 37 | "href": "http://pact.onegeek.com.au/pacticipants/me" 38 | }, 39 | "pb:provider": { 40 | "title": "Provider", 41 | "name": "they", 42 | "href": "http://pact.onegeek.com.au/pacticipants/they" 43 | }, 44 | "pb:latest-pact-version": { 45 | "title": "Pact", 46 | "name": "Latest version of this pact", 47 | "href": "http://pact.onegeek.com.au/pacts/provider/they/consumer/me/latest" 48 | }, 49 | "pb:previous-distinct": { 50 | "title": "Pact", 51 | "name": "Previous distinct version of this pact", 52 | "href": "http://pact.onegeek.com.au/pacts/provider/they/consumer/me/version/1.0.0/previous-distinct" 53 | }, 54 | "pb:diff-previous-distinct": { 55 | "title": "Diff", 56 | "name": "Diff with previous distinct version of this pact", 57 | "href": "http://pact.onegeek.com.au/pacts/provider/they/consumer/me/version/1.0.0/diff/previous-distinct" 58 | }, 59 | "pb:pact-webhooks": { 60 | "title": "Webhooks for the pact between me and they", 61 | "href": "http://pact.onegeek.com.au/webhooks/provider/they/consumer/me" 62 | }, 63 | "pb:tag-prod-version": { 64 | "title": "Tag this version as 'production'", 65 | "href": "http://pact.onegeek.com.au/pacticipants/me/versions/1.0.0/tags/prod" 66 | }, 67 | "pb:tag-version": { 68 | "title": "Tag version", 69 | "href": "http://pact.onegeek.com.au/pacticipants/me/versions/1.0.0/tags/{tag}" 70 | }, 71 | "curies": [{ 72 | "name": "pb", 73 | "href": "http://pact.onegeek.com.au/doc/{rel}", 74 | "templated": true 75 | }] 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /test/integration/data/get-noauth-provider_they-consumer_me-latest.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "me" 4 | }, 5 | "provider": { 6 | "name": "they" 7 | }, 8 | "interactions": [{ 9 | "description": "Provider state success", 10 | "provider_state": "There is a greeting", 11 | "request": { 12 | "method": "GET", 13 | "path": "/somestate" 14 | }, 15 | "response": { 16 | "status": 200, 17 | "headers": {}, 18 | "body": { 19 | "greeting": "State data!" 20 | } 21 | } 22 | }], 23 | "metadata": { 24 | "pactSpecificationVersion": "2.0.0" 25 | }, 26 | "updatedAt": "2016-05-15T00:09:33+00:00", 27 | "createdAt": "2016-05-15T00:09:06+00:00", 28 | "_links": { 29 | "self": { 30 | "title": "Pact", 31 | "name": "Pact between me (v1.0.0) and they", 32 | "href": "http://pact.onegeek.com.au/pacts/provider/they/consumer/me/version/1.0.0" 33 | }, 34 | "pb:consumer": { 35 | "title": "Consumer", 36 | "name": "me", 37 | "href": "http://pact.onegeek.com.au/pacticipants/me" 38 | }, 39 | "pb:provider": { 40 | "title": "Provider", 41 | "name": "they", 42 | "href": "http://pact.onegeek.com.au/pacticipants/they" 43 | }, 44 | "pb:latest-pact-version": { 45 | "title": "Pact", 46 | "name": "Latest version of this pact", 47 | "href": "http://pact.onegeek.com.au/pacts/provider/they/consumer/me/latest" 48 | }, 49 | "pb:previous-distinct": { 50 | "title": "Pact", 51 | "name": "Previous distinct version of this pact", 52 | "href": "http://pact.onegeek.com.au/pacts/provider/they/consumer/me/version/1.0.0/previous-distinct" 53 | }, 54 | "pb:diff-previous-distinct": { 55 | "title": "Diff", 56 | "name": "Diff with previous distinct version of this pact", 57 | "href": "http://pact.onegeek.com.au/pacts/provider/they/consumer/me/version/1.0.0/diff/previous-distinct" 58 | }, 59 | "pb:pact-webhooks": { 60 | "title": "Webhooks for the pact between me and they", 61 | "href": "http://pact.onegeek.com.au/webhooks/provider/they/consumer/me" 62 | }, 63 | "pb:tag-prod-version": { 64 | "title": "Tag this version as 'production'", 65 | "href": "http://pact.onegeek.com.au/pacticipants/me/versions/1.0.0/tags/prod" 66 | }, 67 | "pb:tag-version": { 68 | "title": "Tag version", 69 | "href": "http://pact.onegeek.com.au/pacticipants/me/versions/1.0.0/tags/{tag}" 70 | }, 71 | "curies": [{ 72 | "name": "pb", 73 | "href": "http://pact.onegeek.com.au/doc/{rel}", 74 | "templated": true 75 | }] 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /test/integration/data/get-provider_they-consumer_anotherclient-latest.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "anotherclient" 4 | }, 5 | "provider": { 6 | "name": "they" 7 | }, 8 | "interactions": [{ 9 | "description": "Provider state success", 10 | "provider_state": "There is a greeting", 11 | "request": { 12 | "method": "GET", 13 | "path": "/somestate" 14 | }, 15 | "response": { 16 | "status": 200, 17 | "headers": {}, 18 | "body": { 19 | "greeting": "State data!" 20 | } 21 | } 22 | }], 23 | "metadata": { 24 | "pactSpecificationVersion": "2.0.0" 25 | }, 26 | "updatedAt": "2016-05-15T00:09:33+00:00", 27 | "createdAt": "2016-05-15T00:09:06+00:00", 28 | "_links": { 29 | "self": { 30 | "title": "Pact", 31 | "name": "Pact between me (v1.0.0) and they", 32 | "href": "http://pact.onegeek.com.au/pacts/provider/they/consumer/me/version/1.0.0" 33 | }, 34 | "pb:consumer": { 35 | "title": "Consumer", 36 | "name": "anotherclient", 37 | "href": "http://pact.onegeek.com.au/pacticipants/me" 38 | }, 39 | "pb:provider": { 40 | "title": "Provider", 41 | "name": "they", 42 | "href": "http://pact.onegeek.com.au/pacticipants/they" 43 | }, 44 | "pb:latest-pact-version": { 45 | "title": "Pact", 46 | "name": "Latest version of this pact", 47 | "href": "http://pact.onegeek.com.au/pacts/provider/they/consumer/me/latest" 48 | }, 49 | "pb:previous-distinct": { 50 | "title": "Pact", 51 | "name": "Previous distinct version of this pact", 52 | "href": "http://pact.onegeek.com.au/pacts/provider/they/consumer/me/version/1.0.0/previous-distinct" 53 | }, 54 | "pb:diff-previous-distinct": { 55 | "title": "Diff", 56 | "name": "Diff with previous distinct version of this pact", 57 | "href": "http://pact.onegeek.com.au/pacts/provider/they/consumer/me/version/1.0.0/diff/previous-distinct" 58 | }, 59 | "pb:pact-webhooks": { 60 | "title": "Webhooks for the pact between me and they", 61 | "href": "http://pact.onegeek.com.au/webhooks/provider/they/consumer/me" 62 | }, 63 | "pb:tag-prod-version": { 64 | "title": "Tag this version as 'production'", 65 | "href": "http://pact.onegeek.com.au/pacticipants/me/versions/1.0.0/tags/prod" 66 | }, 67 | "pb:tag-version": { 68 | "title": "Tag version", 69 | "href": "http://pact.onegeek.com.au/pacticipants/me/versions/1.0.0/tags/{tag}" 70 | }, 71 | "curies": [{ 72 | "name": "pb", 73 | "href": "http://pact.onegeek.com.au/doc/{rel}", 74 | "templated": true 75 | }] 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /test/integration/data/get-provider_they-consumer_me-latest.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "me" 4 | }, 5 | "provider": { 6 | "name": "they" 7 | }, 8 | "interactions": [{ 9 | "description": "Provider state success", 10 | "provider_state": "There is a greeting", 11 | "request": { 12 | "method": "GET", 13 | "path": "/somestate" 14 | }, 15 | "response": { 16 | "status": 200, 17 | "headers": {}, 18 | "body": { 19 | "greeting": "State data!" 20 | } 21 | } 22 | }], 23 | "metadata": { 24 | "pactSpecificationVersion": "2.0.0" 25 | }, 26 | "updatedAt": "2016-05-15T00:09:33+00:00", 27 | "createdAt": "2016-05-15T00:09:06+00:00", 28 | "_links": { 29 | "self": { 30 | "title": "Pact", 31 | "name": "Pact between me (v1.0.0) and they", 32 | "href": "http://pact.onegeek.com.au/pacts/provider/they/consumer/me/version/1.0.0" 33 | }, 34 | "pb:consumer": { 35 | "title": "Consumer", 36 | "name": "me", 37 | "href": "http://pact.onegeek.com.au/pacticipants/me" 38 | }, 39 | "pb:provider": { 40 | "title": "Provider", 41 | "name": "they", 42 | "href": "http://pact.onegeek.com.au/pacticipants/they" 43 | }, 44 | "pb:latest-pact-version": { 45 | "title": "Pact", 46 | "name": "Latest version of this pact", 47 | "href": "http://pact.onegeek.com.au/pacts/provider/they/consumer/me/latest" 48 | }, 49 | "pb:previous-distinct": { 50 | "title": "Pact", 51 | "name": "Previous distinct version of this pact", 52 | "href": "http://pact.onegeek.com.au/pacts/provider/they/consumer/me/version/1.0.0/previous-distinct" 53 | }, 54 | "pb:diff-previous-distinct": { 55 | "title": "Diff", 56 | "name": "Diff with previous distinct version of this pact", 57 | "href": "http://pact.onegeek.com.au/pacts/provider/they/consumer/me/version/1.0.0/diff/previous-distinct" 58 | }, 59 | "pb:pact-webhooks": { 60 | "title": "Webhooks for the pact between me and they", 61 | "href": "http://pact.onegeek.com.au/webhooks/provider/they/consumer/me" 62 | }, 63 | "pb:tag-prod-version": { 64 | "title": "Tag this version as 'production'", 65 | "href": "http://pact.onegeek.com.au/pacticipants/me/versions/1.0.0/tags/prod" 66 | }, 67 | "pb:tag-version": { 68 | "title": "Tag version", 69 | "href": "http://pact.onegeek.com.au/pacticipants/me/versions/1.0.0/tags/{tag}" 70 | }, 71 | "curies": [{ 72 | "name": "pb", 73 | "href": "http://pact.onegeek.com.au/doc/{rel}", 74 | "templated": true 75 | }] 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /test/integration/grpc/grpc.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "grpcconsumer" 4 | }, 5 | "interactions": [ 6 | { 7 | "description": "A request to do a foo", 8 | "pending": false, 9 | "request": { 10 | "body": { 11 | "content": { 12 | }, 13 | "contentType": "application/json", 14 | "encoded": false 15 | }, 16 | "headers": { 17 | "Authorization": [ 18 | "Bearer 1234" 19 | ], 20 | "Content-Type": [ 21 | "application/json" 22 | ] 23 | }, 24 | "method": "POST", 25 | "path": "/foobar" 26 | }, 27 | "response": { 28 | "body": { 29 | "content": {}, 30 | "contentType": "application/json", 31 | "encoded": false 32 | }, 33 | "headers": { 34 | "Content-Type": [ 35 | "application/json" 36 | ] 37 | }, 38 | "status": 200 39 | }, 40 | "transport": "http", 41 | "type": "Synchronous/HTTP" 42 | }, 43 | { 44 | "description": "Route guide - GetFeature", 45 | "interactionMarkup": { 46 | "markup": "```protobuf\nmessage Feature {\n string name = 1;\n message .routeguide.Point location = 2;\n}\n```\n", 47 | "markupType": "COMMON_MARK" 48 | }, 49 | "pending": false, 50 | "pluginConfiguration": { 51 | "protobuf": { 52 | "descriptorKey": "32f7898819c9f3ece72c5f9de784d705", 53 | "service": "RouteGuide/GetFeature" 54 | } 55 | }, 56 | "request": { 57 | "contents": { 58 | "content": "CLQBEMgB", 59 | "contentType": "application/protobuf;message=Point", 60 | "contentTypeHint": "BINARY", 61 | "encoded": "base64" 62 | }, 63 | "matchingRules": { 64 | "body": { 65 | "$.latitude": { 66 | "combine": "AND", 67 | "matchers": [ 68 | { 69 | "match": "number" 70 | } 71 | ] 72 | }, 73 | "$.longitude": { 74 | "combine": "AND", 75 | "matchers": [ 76 | { 77 | "match": "number" 78 | } 79 | ] 80 | } 81 | } 82 | } 83 | }, 84 | "response": [ 85 | { 86 | "contents": { 87 | "content": "CghCaWcgVHJlZRIGCLQBEMgB", 88 | "contentType": "application/protobuf;message=Feature", 89 | "contentTypeHint": "BINARY", 90 | "encoded": "base64" 91 | }, 92 | "matchingRules": { 93 | "body": { 94 | "$.location.latitude": { 95 | "combine": "AND", 96 | "matchers": [ 97 | { 98 | "match": "number" 99 | } 100 | ] 101 | }, 102 | "$.location.longitude": { 103 | "combine": "AND", 104 | "matchers": [ 105 | { 106 | "match": "number" 107 | } 108 | ] 109 | }, 110 | "$.name": { 111 | "combine": "AND", 112 | "matchers": [ 113 | { 114 | "match": "type" 115 | } 116 | ] 117 | } 118 | } 119 | } 120 | } 121 | ], 122 | "transport": "grpc", 123 | "type": "Synchronous/Messages" 124 | } 125 | ], 126 | "metadata": { 127 | "pactRust": { 128 | "ffi": "0.3.9", 129 | "mockserver": "0.9.4", 130 | "models": "0.4.4" 131 | }, 132 | "pactSpecification": { 133 | "version": "4.0" 134 | }, 135 | "plugins": [ 136 | { 137 | "configuration": { 138 | "32f7898819c9f3ece72c5f9de784d705": { 139 | "protoDescriptors": "CukGChFyb3V0ZV9ndWlkZS5wcm90bxIKcm91dGVndWlkZSJBCgVQb2ludBIaCghsYXRpdHVkZRgBIAEoBVIIbGF0aXR1ZGUSHAoJbG9uZ2l0dWRlGAIgASgFUglsb25naXR1ZGUiUQoJUmVjdGFuZ2xlEiEKAmxvGAEgASgLMhEucm91dGVndWlkZS5Qb2ludFICbG8SIQoCaGkYAiABKAsyES5yb3V0ZWd1aWRlLlBvaW50UgJoaSJMCgdGZWF0dXJlEhIKBG5hbWUYASABKAlSBG5hbWUSLQoIbG9jYXRpb24YAiABKAsyES5yb3V0ZWd1aWRlLlBvaW50Ughsb2NhdGlvbiJUCglSb3V0ZU5vdGUSLQoIbG9jYXRpb24YASABKAsyES5yb3V0ZWd1aWRlLlBvaW50Ughsb2NhdGlvbhIYCgdtZXNzYWdlGAIgASgJUgdtZXNzYWdlIpMBCgxSb3V0ZVN1bW1hcnkSHwoLcG9pbnRfY291bnQYASABKAVSCnBvaW50Q291bnQSIwoNZmVhdHVyZV9jb3VudBgCIAEoBVIMZmVhdHVyZUNvdW50EhoKCGRpc3RhbmNlGAMgASgFUghkaXN0YW5jZRIhCgxlbGFwc2VkX3RpbWUYBCABKAVSC2VsYXBzZWRUaW1lMoUCCgpSb3V0ZUd1aWRlEjYKCkdldEZlYXR1cmUSES5yb3V0ZWd1aWRlLlBvaW50GhMucm91dGVndWlkZS5GZWF0dXJlIgASPgoMTGlzdEZlYXR1cmVzEhUucm91dGVndWlkZS5SZWN0YW5nbGUaEy5yb3V0ZWd1aWRlLkZlYXR1cmUiADABEj4KC1JlY29yZFJvdXRlEhEucm91dGVndWlkZS5Qb2ludBoYLnJvdXRlZ3VpZGUuUm91dGVTdW1tYXJ5IgAoARI/CglSb3V0ZUNoYXQSFS5yb3V0ZWd1aWRlLlJvdXRlTm90ZRoVLnJvdXRlZ3VpZGUuUm91dGVOb3RlIgAoATABQmgKG2lvLmdycGMuZXhhbXBsZXMucm91dGVndWlkZUIPUm91dGVHdWlkZVByb3RvUAFaNmdvb2dsZS5nb2xhbmcub3JnL2dycGMvZXhhbXBsZXMvcm91dGVfZ3VpZGUvcm91dGVndWlkZWIGcHJvdG8z", 140 | "protoFile": "// Copyright 2015 gRPC authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nsyntax = \"proto3\";\n\noption go_package = \"google.golang.org/grpc/examples/route_guide/routeguide\";\noption java_multiple_files = true;\noption java_package = \"io.grpc.examples.routeguide\";\noption java_outer_classname = \"RouteGuideProto\";\n\npackage routeguide;\n\n// Interface exported by the server.\nservice RouteGuide {\n // A simple RPC.\n //\n // Obtains the feature at a given position.\n //\n // A feature with an empty name is returned if there's no feature at the given\n // position.\n rpc GetFeature(Point) returns (Feature) {}\n\n // A server-to-client streaming RPC.\n //\n // Obtains the Features available within the given Rectangle. Results are\n // streamed rather than returned at once (e.g. in a response message with a\n // repeated field), as the rectangle may cover a large area and contain a\n // huge number of features.\n rpc ListFeatures(Rectangle) returns (stream Feature) {}\n\n // A client-to-server streaming RPC.\n //\n // Accepts a stream of Points on a route being traversed, returning a\n // RouteSummary when traversal is completed.\n rpc RecordRoute(stream Point) returns (RouteSummary) {}\n\n // A Bidirectional streaming RPC.\n //\n // Accepts a stream of RouteNotes sent while a route is being traversed,\n // while receiving other RouteNotes (e.g. from other users).\n rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}\n}\n\n// Points are represented as latitude-longitude pairs in the E7 representation\n// (degrees multiplied by 10**7 and rounded to the nearest integer).\n// Latitudes should be in the range +/- 90 degrees and longitude should be in\n// the range +/- 180 degrees (inclusive).\nmessage Point {\n int32 latitude = 1;\n int32 longitude = 2;\n}\n\n// A latitude-longitude rectangle, represented as two diagonally opposite\n// points \"lo\" and \"hi\".\nmessage Rectangle {\n // One corner of the rectangle.\n Point lo = 1;\n\n // The other corner of the rectangle.\n Point hi = 2;\n}\n\n// A feature names something at a given point.\n//\n// If a feature could not be named, the name is empty.\nmessage Feature {\n // The name of the feature.\n string name = 1;\n\n // The point where the feature is detected.\n Point location = 2;\n}\n\n// A RouteNote is a message sent while at a given point.\nmessage RouteNote {\n // The location from which the message is sent.\n Point location = 1;\n\n // The message to be sent.\n string message = 2;\n}\n\n// A RouteSummary is received in response to a RecordRoute rpc.\n//\n// It contains the number of individual points received, the number of\n// detected features, and the total distance covered as the cumulative sum of\n// the distance between each point.\nmessage RouteSummary {\n // The number of points received.\n int32 point_count = 1;\n\n // The number of known features passed while traversing the route.\n int32 feature_count = 2;\n\n // The distance covered in metres.\n int32 distance = 3;\n\n // The duration of the traversal in seconds.\n int32 elapsed_time = 4;\n}\n" 141 | } 142 | }, 143 | "name": "protobuf", 144 | "version": "0.3.15" 145 | } 146 | ] 147 | }, 148 | "provider": { 149 | "name": "grpcprovider" 150 | } 151 | } -------------------------------------------------------------------------------- /test/integration/grpc/route_guide.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 gRPC authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // 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 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | option go_package = "google.golang.org/grpc/examples/route_guide/routeguide"; 18 | option java_multiple_files = true; 19 | option java_package = "io.grpc.examples.routeguide"; 20 | option java_outer_classname = "RouteGuideProto"; 21 | 22 | package routeguide; 23 | 24 | // Interface exported by the server. 25 | service RouteGuide { 26 | // A simple RPC. 27 | // 28 | // Obtains the feature at a given position. 29 | // 30 | // A feature with an empty name is returned if there's no feature at the given 31 | // position. 32 | rpc GetFeature(Point) returns (Feature) {} 33 | 34 | // A server-to-client streaming RPC. 35 | // 36 | // Obtains the Features available within the given Rectangle. Results are 37 | // streamed rather than returned at once (e.g. in a response message with a 38 | // repeated field), as the rectangle may cover a large area and contain a 39 | // huge number of features. 40 | rpc ListFeatures(Rectangle) returns (stream Feature) {} 41 | 42 | // A client-to-server streaming RPC. 43 | // 44 | // Accepts a stream of Points on a route being traversed, returning a 45 | // RouteSummary when traversal is completed. 46 | rpc RecordRoute(stream Point) returns (RouteSummary) {} 47 | 48 | // A Bidirectional streaming RPC. 49 | // 50 | // Accepts a stream of RouteNotes sent while a route is being traversed, 51 | // while receiving other RouteNotes (e.g. from other users). 52 | rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} 53 | } 54 | 55 | // Points are represented as latitude-longitude pairs in the E7 representation 56 | // (degrees multiplied by 10**7 and rounded to the nearest integer). 57 | // Latitudes should be in the range +/- 90 degrees and longitude should be in 58 | // the range +/- 180 degrees (inclusive). 59 | message Point { 60 | int32 latitude = 1; 61 | int32 longitude = 2; 62 | } 63 | 64 | // A latitude-longitude rectangle, represented as two diagonally opposite 65 | // points "lo" and "hi". 66 | message Rectangle { 67 | // One corner of the rectangle. 68 | Point lo = 1; 69 | 70 | // The other corner of the rectangle. 71 | Point hi = 2; 72 | } 73 | 74 | // A feature names something at a given point. 75 | // 76 | // If a feature could not be named, the name is empty. 77 | message Feature { 78 | // The name of the feature. 79 | string name = 1; 80 | 81 | // The point where the feature is detected. 82 | Point location = 2; 83 | } 84 | 85 | // A RouteNote is a message sent while at a given point. 86 | message RouteNote { 87 | // The location from which the message is sent. 88 | Point location = 1; 89 | 90 | // The message to be sent. 91 | string message = 2; 92 | } 93 | 94 | // A RouteSummary is received in response to a RecordRoute rpc. 95 | // 96 | // It contains the number of individual points received, the number of 97 | // detected features, and the total distance covered as the cumulative sum of 98 | // the distance between each point. 99 | message RouteSummary { 100 | // The number of points received. 101 | int32 point_count = 1; 102 | 103 | // The number of known features passed while traversing the route. 104 | int32 feature_count = 2; 105 | 106 | // The distance covered in metres. 107 | int32 distance = 3; 108 | 109 | // The duration of the traversal in seconds. 110 | int32 elapsed_time = 4; 111 | } 112 | -------------------------------------------------------------------------------- /test/integration/me-they-fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "me" 4 | }, 5 | "provider": { 6 | "name": "they" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "Greeting fail", 11 | "request": { 12 | "method": "GET", 13 | "path": "/fail" 14 | }, 15 | "response": { 16 | "status": 201, 17 | "headers": { 18 | }, 19 | "body": { 20 | "greeting": "Oh yes!" 21 | } 22 | } 23 | } 24 | ], 25 | "metadata": { 26 | "pactSpecificationVersion": "2.0.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/integration/me-they-multi.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "anotherclient" 4 | }, 5 | "provider": { 6 | "name": "they" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "Provider state success", 11 | "provider_state": "There is a greeting", 12 | "request": { 13 | "method": "GET", 14 | "path": "/somestate" 15 | }, 16 | "response": { 17 | "status": 200, 18 | "headers": { 19 | }, 20 | "body": { 21 | "greeting": "State data!" 22 | } 23 | } 24 | }, 25 | { 26 | "description": "Greeting", 27 | "request": { 28 | "method": "GET", 29 | "path": "/" 30 | }, 31 | "response": { 32 | "status": 200, 33 | "headers": { 34 | }, 35 | "body": { 36 | "greeting": "Hello" 37 | } 38 | } 39 | } 40 | ], 41 | "metadata": { 42 | "pactSpecificationVersion": "2.0.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/integration/me-they-post-regex-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "me" 4 | }, 5 | "provider": { 6 | "name": "they" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "Greeting", 11 | "request": { 12 | "method": "POST", 13 | "path": "/", 14 | "headers": { 15 | "Accept": "application/json", 16 | "Content-Type": "application/json; charset=utf-8" 17 | }, 18 | "body": { 19 | "name": "James" 20 | } 21 | }, 22 | "response": { 23 | "status": 200, 24 | "headers": { 25 | }, 26 | "body": { 27 | "greeting": "Hello James" 28 | }, 29 | "matchingRules": { 30 | "$.body.greeting": { 31 | "regex": "Hello [a-zA-Z]+" 32 | } 33 | } 34 | } 35 | } 36 | ], 37 | "metadata": { 38 | "pactSpecificationVersion": "2.0.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/integration/me-they-post-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "me" 4 | }, 5 | "provider": { 6 | "name": "they" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "Greeting", 11 | "request": { 12 | "method": "POST", 13 | "path": "/", 14 | "headers": { 15 | "Accept": "application/json", 16 | "Content-Type": "application/json; charset=utf-8" 17 | }, 18 | "body": { 19 | "name": "James" 20 | } 21 | }, 22 | "response": { 23 | "status": 200, 24 | "headers": { 25 | }, 26 | "body": { 27 | "greeting": "Hello James" 28 | } 29 | } 30 | } 31 | ], 32 | "metadata": { 33 | "pactSpecificationVersion": "2.0.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/integration/me-they-states.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "me" 4 | }, 5 | "provider": { 6 | "name": "they" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "Provider state success", 11 | "provider_state": "There is a greeting", 12 | "request": { 13 | "method": "GET", 14 | "path": "/somestate" 15 | }, 16 | "response": { 17 | "status": 200, 18 | "headers": { 19 | }, 20 | "body": { 21 | "greeting": "State data!" 22 | } 23 | } 24 | } 25 | ], 26 | "metadata": { 27 | "pactSpecificationVersion": "2.0.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/integration/me-they-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "me" 4 | }, 5 | "provider": { 6 | "name": "they" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "Greeting", 11 | "request": { 12 | "method": "GET", 13 | "path": "/" 14 | }, 15 | "response": { 16 | "status": 200, 17 | "headers": { 18 | }, 19 | "body": { 20 | "greeting": "Hello" 21 | } 22 | } 23 | } 24 | ], 25 | "metadata": { 26 | "pactSpecificationVersion": "2.0.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/integration/me-they-weird path-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "me" 4 | }, 5 | "provider": { 6 | "name": "they" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "Greeting", 11 | "request": { 12 | "method": "GET", 13 | "path": "/" 14 | }, 15 | "response": { 16 | "status": 200, 17 | "headers": { 18 | }, 19 | "body": { 20 | "greeting": "Hello" 21 | } 22 | } 23 | } 24 | ], 25 | "metadata": { 26 | "pactSpecificationVersion": "2.0.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/integration/provider-mock.ts: -------------------------------------------------------------------------------- 1 | import express = require('express'); 2 | import * as http from 'http'; 3 | import cors = require('cors'); 4 | import bodyParser = require('body-parser'); 5 | import { returnJson, returnJsonFile, auth } from './data-utils'; 6 | 7 | export default (port: number): Promise => { 8 | const server: express.Express = express(); 9 | server.use(cors()); 10 | server.use(bodyParser.json()); 11 | server.use( 12 | bodyParser.urlencoded({ 13 | extended: true, 14 | }) 15 | ); 16 | 17 | let stateData = ''; 18 | 19 | server.get('/', returnJson({ greeting: 'Hello' })); 20 | 21 | server.get('/fail', returnJson({ greeting: 'Oh noes!' })); 22 | 23 | server.get( 24 | '/provider-states', 25 | returnJson({ 26 | me: ['There is a greeting'], 27 | anotherclient: ['There is a greeting'], 28 | }) 29 | ); 30 | 31 | server.post( 32 | '/provider-state', 33 | (req: express.Request, res: express.Response) => { 34 | stateData = 'State data!'; 35 | return res.json({ 36 | greeting: stateData, 37 | }); 38 | } 39 | ); 40 | 41 | server.get('/somestate', (req: express.Request, res: express.Response) => 42 | res.json({ 43 | greeting: stateData, 44 | }) 45 | ); 46 | 47 | server.post('/', (req: express.Request, res: express.Response) => 48 | res.json({ 49 | greeting: `Hello ${req.body.name}`, 50 | }) 51 | ); 52 | 53 | server.get( 54 | '/contract/:name', 55 | (req: express.Request, res: express.Response) => { 56 | const fileName = req.params['name']; 57 | res.sendFile( 58 | fileName, 59 | { 60 | root: __dirname, 61 | dotfiles: 'deny', 62 | headers: { 63 | 'x-timestamp': Date.now(), 64 | 'x-sent': true, 65 | }, 66 | }, 67 | (err) => { 68 | if (err) { 69 | console.log(err); 70 | res.status(500).end(); 71 | } else { 72 | console.log('Sent:', fileName); 73 | } 74 | } 75 | ); 76 | } 77 | ); 78 | 79 | // Verification result 80 | server.post( 81 | '/pacts/provider/:provider/consumer/:consumer/pact-version/:version/verification-results', 82 | returnJsonFile('./data/get-provider_they-consumer_me-latest.json') 83 | ); 84 | server.get( 85 | '/pacts/provider/they/consumer/me/latest', 86 | auth, 87 | returnJsonFile('./data/get-provider_they-consumer_me-latest.json') 88 | ); 89 | server.get( 90 | '/pacts/provider/they/consumer/anotherclient/latest', 91 | auth, 92 | returnJsonFile( 93 | './data/get-provider_they-consumer_anotherclient-latest.json' 94 | ) 95 | ); 96 | server.get( 97 | '/noauth/pacts/provider/they/consumer/me/latest', 98 | returnJsonFile('./data/get-noauth-provider_they-consumer_me-latest.json') 99 | ); 100 | server.get( 101 | '/noauth/pacts/provider/they/consumer/anotherclient/latest', 102 | returnJsonFile( 103 | './data/get-noauth-provider_they-consumer_anotherclient-latest.json' 104 | ) 105 | ); 106 | 107 | let s: http.Server; 108 | return new Promise((resolve) => { 109 | s = server.listen(port, () => resolve()); 110 | }).then(() => s); 111 | }; 112 | -------------------------------------------------------------------------------- /test/integration/publish-verification-example weird path-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "me" 4 | }, 5 | "provider": { 6 | "name": "they" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "Greeting", 11 | "request": { 12 | "method": "GET", 13 | "path": "/" 14 | }, 15 | "response": { 16 | "status": 200, 17 | "headers": {}, 18 | "body": { 19 | "greeting": "Hello" 20 | } 21 | } 22 | } 23 | ], 24 | "metadata": { 25 | "pactSpecificationVersion": "2.0.0" 26 | }, 27 | "createdAt": "2017-05-08T22:56:48+00:00", 28 | "_links": { 29 | "self": { 30 | "title": "Pact", 31 | "name": "Pact between me (v1.0.0) and they", 32 | "href": "http://localhost:9123/pacts/provider/they/consumer/me/version/1.0.0" 33 | }, 34 | "pb:consumer": { 35 | "title": "Consumer", 36 | "name": "me", 37 | "href": "http://localhost:9123/pacticipants/me" 38 | }, 39 | "pb:provider": { 40 | "title": "Provider", 41 | "name": "they", 42 | "href": "http://localhost:9123/pacticipants/they" 43 | }, 44 | "pb:latest-pact-version": { 45 | "title": "Pact", 46 | "name": "Latest version of this pact", 47 | "href": "http://localhost:9123/pacts/provider/they/consumer/me/latest" 48 | }, 49 | "pb:previous-distinct": { 50 | "title": "Pact", 51 | "name": "Previous distinct version of this pact", 52 | "href": "http://localhost:9123/pacts/provider/they/consumer/me/version/1.0.0/previous-distinct" 53 | }, 54 | "pb:diff-previous-distinct": { 55 | "title": "Diff", 56 | "name": "Diff with previous distinct version of this pact", 57 | "href": "http://localhost:9123/pacts/provider/they/consumer/me/version/1.0.0/diff/previous-distinct" 58 | }, 59 | "pb:pact-webhooks": { 60 | "title": "Webhooks for the pact between me and they", 61 | "href": "http://localhost:9123/webhooks/provider/they/consumer/me" 62 | }, 63 | "pb:tag-prod-version": { 64 | "title": "Tag this version as 'production'", 65 | "href": "http://localhost:9123/pacticipants/me/versions/1.0.0/tags/prod" 66 | }, 67 | "pb:tag-version": { 68 | "title": "Tag version", 69 | "href": "http://localhost:9123/pacticipants/me/versions/1.0.0/tags/{tag}" 70 | }, 71 | "pb:publish-verification-results": { 72 | "title": "Publish verification results", 73 | "href": "http://localhost:9123/pacts/provider/they/consumer/me/pact-version/618bea2e1221b48d59f4330e3788cdd8d196a8de/verification-results" 74 | }, 75 | "curies": [ 76 | { 77 | "name": "pb", 78 | "href": "http://localhost:9123/doc/{rel}", 79 | "templated": true 80 | } 81 | ] 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /test/integration/publish-verification-example-fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "me" 4 | }, 5 | "provider": { 6 | "name": "they" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "Greeting", 11 | "request": { 12 | "method": "GET", 13 | "path": "/" 14 | }, 15 | "response": { 16 | "status": 200, 17 | "headers": {}, 18 | "body": { 19 | "greeting": "Hello" 20 | } 21 | } 22 | } 23 | ], 24 | "metadata": { 25 | "pactSpecificationVersion": "2.0.0" 26 | }, 27 | "createdAt": "2017-05-08T22:56:48+00:00", 28 | "_links": { 29 | "self": { 30 | "title": "Pact", 31 | "name": "Pact between me (v1.0.0) and they", 32 | "href": "http://localhost:9123/pacts/provider/they/consumer/me/version/1.0.0" 33 | }, 34 | "pb:consumer": { 35 | "title": "Consumer", 36 | "name": "me", 37 | "href": "http://localhost:9123/pacticipants/me" 38 | }, 39 | "pb:provider": { 40 | "title": "Provider", 41 | "name": "they", 42 | "href": "http://localhost:9123/pacticipants/they" 43 | }, 44 | "pb:latest-pact-version": { 45 | "title": "Pact", 46 | "name": "Latest version of this pact", 47 | "href": "http://localhost:9123/pacts/provider/they/consumer/me/latest" 48 | }, 49 | "pb:previous-distinct": { 50 | "title": "Pact", 51 | "name": "Previous distinct version of this pact", 52 | "href": "http://localhost:9123/pacts/provider/they/consumer/me/version/1.0.0/previous-distinct" 53 | }, 54 | "pb:diff-previous-distinct": { 55 | "title": "Diff", 56 | "name": "Diff with previous distinct version of this pact", 57 | "href": "http://localhost:9123/pacts/provider/they/consumer/me/version/1.0.0/diff/previous-distinct" 58 | }, 59 | "pb:pact-webhooks": { 60 | "title": "Webhooks for the pact between me and they", 61 | "href": "http://localhost:9123/webhooks/provider/they/consumer/me" 62 | }, 63 | "pb:tag-prod-version": { 64 | "title": "Tag this version as 'production'", 65 | "href": "http://localhost:9123/pacticipants/me/versions/1.0.0/tags/prod" 66 | }, 67 | "pb:tag-version": { 68 | "title": "Tag version", 69 | "href": "http://localhost:9123/pacticipants/me/versions/1.0.0/tags/{tag}" 70 | }, 71 | "pb:publish-verification-results": { 72 | "title": "Publish verification results", 73 | "href": "http://localhost:9123/pacts/provider/they/consumer/me/pact-version/404notfound/verification-results" 74 | }, 75 | "curies": [ 76 | { 77 | "name": "pb", 78 | "href": "http://localhost:9123/doc/{rel}", 79 | "templated": true 80 | } 81 | ] 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /test/integration/publish-verification-example-success.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "me" 4 | }, 5 | "provider": { 6 | "name": "they" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "Greeting", 11 | "request": { 12 | "method": "GET", 13 | "path": "/" 14 | }, 15 | "response": { 16 | "status": 200, 17 | "headers": {}, 18 | "body": { 19 | "greeting": "Hello" 20 | } 21 | } 22 | } 23 | ], 24 | "metadata": { 25 | "pactSpecificationVersion": "2.0.0" 26 | }, 27 | "createdAt": "2017-05-08T22:56:48+00:00", 28 | "_links": { 29 | "self": { 30 | "title": "Pact", 31 | "name": "Pact between me (v1.0.0) and they", 32 | "href": "http://localhost:9123/pacts/provider/they/consumer/me/version/1.0.0" 33 | }, 34 | "pb:consumer": { 35 | "title": "Consumer", 36 | "name": "me", 37 | "href": "http://localhost:9123/pacticipants/me" 38 | }, 39 | "pb:provider": { 40 | "title": "Provider", 41 | "name": "they", 42 | "href": "http://localhost:9123/pacticipants/they" 43 | }, 44 | "pb:latest-pact-version": { 45 | "title": "Pact", 46 | "name": "Latest version of this pact", 47 | "href": "http://localhost:9123/pacts/provider/they/consumer/me/latest" 48 | }, 49 | "pb:previous-distinct": { 50 | "title": "Pact", 51 | "name": "Previous distinct version of this pact", 52 | "href": "http://localhost:9123/pacts/provider/they/consumer/me/version/1.0.0/previous-distinct" 53 | }, 54 | "pb:diff-previous-distinct": { 55 | "title": "Diff", 56 | "name": "Diff with previous distinct version of this pact", 57 | "href": "http://localhost:9123/pacts/provider/they/consumer/me/version/1.0.0/diff/previous-distinct" 58 | }, 59 | "pb:pact-webhooks": { 60 | "title": "Webhooks for the pact between me and they", 61 | "href": "http://localhost:9123/webhooks/provider/they/consumer/me" 62 | }, 63 | "pb:tag-prod-version": { 64 | "title": "Tag this version as 'production'", 65 | "href": "http://localhost:9123/pacticipants/me/versions/1.0.0/tags/prod" 66 | }, 67 | "pb:tag-version": { 68 | "title": "Tag version", 69 | "href": "http://localhost:9123/pacticipants/me/versions/1.0.0/tags/{tag}" 70 | }, 71 | "pb:publish-verification-results": { 72 | "title": "Publish verification results", 73 | "href": "http://localhost:9123/pacts/provider/they/consumer/me/pact-version/618bea2e1221b48d59f4330e3788cdd8d196a8de/verification-results" 74 | }, 75 | "curies": [ 76 | { 77 | "name": "pb", 78 | "href": "http://localhost:9123/doc/{rel}", 79 | "templated": true 80 | } 81 | ] 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /test/matt.consumer.integration.spec.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import path = require('path'); 3 | import chai = require('chai'); 4 | import net = require('net'); 5 | import chaiAsPromised = require('chai-as-promised'); 6 | import { 7 | ConsumerMessagePact, 8 | ConsumerPact, 9 | makeConsumerMessagePact, 10 | makeConsumerPact, 11 | } from '../src'; 12 | import { FfiSpecificationVersion } from '../src/ffi/types'; 13 | 14 | chai.use(chaiAsPromised); 15 | const { expect } = chai; 16 | 17 | const parseMattMessage = (raw: string): string => 18 | raw.replace(/(MATT)+/g, '').trim(); 19 | const generateMattMessage = (raw: string): string => `MATT${raw}MATT`; 20 | 21 | const sendMattMessageTCP = ( 22 | message: string, 23 | host: string, 24 | port: number 25 | ): Promise => { 26 | const socket = net.connect({ 27 | port, 28 | host, 29 | }); 30 | 31 | const res = socket.write(`${generateMattMessage(message)}\n`); 32 | 33 | if (!res) { 34 | throw Error('unable to connect to host'); 35 | } 36 | 37 | return new Promise((resolve) => { 38 | socket.on('data', (data) => { 39 | resolve(parseMattMessage(data.toString())); 40 | socket.destroy(); 41 | }); 42 | }); 43 | }; 44 | 45 | const skipPluginTests = process.env['SKIP_PLUGIN_TESTS'] === 'true'; 46 | (skipPluginTests ? describe.skip : describe)('MATT protocol test', () => { 47 | let provider: ConsumerPact; 48 | let tcpProvider: ConsumerMessagePact; 49 | let port: number; 50 | const HOST = '127.0.0.1'; 51 | 52 | describe('HTTP test', () => { 53 | beforeEach(() => { 54 | const mattRequest = `{"request": {"body": "hello"}}`; 55 | const mattResponse = `{"response":{"body":"world"}}`; 56 | 57 | provider = makeConsumerPact( 58 | 'matt-consumer', 59 | 'matt-provider', 60 | FfiSpecificationVersion['SPECIFICATION_VERSION_V4'] 61 | ); 62 | provider.addPlugin('matt', '0.1.1'); 63 | 64 | const interaction = provider.newInteraction(''); 65 | interaction.uponReceiving('A request to communicate via MATT'); 66 | interaction.withRequest('POST', '/matt'); 67 | interaction.withPluginRequestInteractionContents( 68 | 'application/matt', 69 | mattRequest 70 | ); 71 | interaction.withStatus(200); 72 | interaction.withPluginResponseInteractionContents( 73 | 'application/matt', 74 | mattResponse 75 | ); 76 | 77 | port = provider.createMockServer(HOST); 78 | }); 79 | 80 | afterEach(() => { 81 | provider.cleanupPlugins(); 82 | provider.cleanupMockServer(port); 83 | }); 84 | 85 | it('returns a valid MATT message over HTTP', () => 86 | axios 87 | .request({ 88 | baseURL: `http://${HOST}:${port}`, 89 | headers: { 90 | 'content-type': 'application/matt', 91 | Accept: 'application/matt', 92 | }, 93 | data: generateMattMessage('hello'), 94 | method: 'POST', 95 | url: '/matt', 96 | }) 97 | .then((res) => { 98 | expect(parseMattMessage(res.data)).to.eq('world'); 99 | }) 100 | .then(() => { 101 | expect(provider.mockServerMatchedSuccessfully(port)).to.be.true; 102 | }) 103 | .then(() => { 104 | // You don't have to call this, it's just here to check it works 105 | const mismatches = provider.mockServerMismatches(port); 106 | expect(mismatches).to.have.length(0); 107 | }) 108 | .then(() => { 109 | provider.writePactFile(path.join(__dirname, '__testoutput__')); 110 | })); 111 | }); 112 | 113 | describe('TCP Messages', () => { 114 | beforeEach(() => { 115 | tcpProvider = makeConsumerMessagePact( 116 | 'matt-tcp-consumer', 117 | 'matt-tcp-provider', 118 | FfiSpecificationVersion['SPECIFICATION_VERSION_V4'] 119 | ); 120 | }); 121 | describe('with MATT protocol', () => { 122 | afterEach(() => { 123 | tcpProvider.writePactFileForPluginServer( 124 | port, 125 | path.join(__dirname, '__testoutput__') 126 | ); 127 | tcpProvider.cleanupPlugins(); 128 | // tcpProvider.cleanupMockServer(port) 129 | }); 130 | 131 | beforeEach(() => { 132 | const mattMessage = `{"request": {"body": "hellotcp"}, "response":{"body":"tcpworld"}}`; 133 | tcpProvider.addPlugin('matt', '0.1.1'); 134 | 135 | const message = tcpProvider.newSynchronousMessage('a MATT message'); 136 | message.withPluginRequestResponseInteractionContents( 137 | 'application/matt', 138 | mattMessage 139 | ); 140 | 141 | // TODO: this seems not to be written to the pact file 142 | port = tcpProvider.pactffiCreateMockServerForTransport( 143 | HOST, 144 | 'matt', 145 | '' 146 | ); 147 | }); 148 | 149 | it('generates a pact with success', async () => { 150 | const message = await sendMattMessageTCP('hellotcp', HOST, port); 151 | expect(message).to.eq('tcpworld'); 152 | 153 | const res = tcpProvider.mockServerMatchedSuccessfully(port); 154 | expect(res).to.eq(true); 155 | 156 | const mismatches = tcpProvider.mockServerMismatches(port); 157 | expect(mismatches.length).to.eq(0); 158 | }); 159 | }); 160 | }); 161 | }); 162 | -------------------------------------------------------------------------------- /test/matt.provider.integration.spec.ts: -------------------------------------------------------------------------------- 1 | import path = require('path'); 2 | import net = require('net'); 3 | import chai = require('chai'); 4 | import chaiAsPromised = require('chai-as-promised'); 5 | import express = require('express'); 6 | import * as http from 'http'; 7 | import { setLogLevel } from '../src/logger'; 8 | import verifier from '../src/verifier'; 9 | 10 | chai.use(chaiAsPromised); 11 | 12 | const parseMattMessage = (raw: string): string => 13 | raw.replace(/(MATT)+/g, '').trim(); 14 | const generateMattMessage = (raw: string): string => `MATT${raw}MATT`; 15 | 16 | const startHTTPServer = (host: string, port: number): Promise => { 17 | const server: express.Express = express(); 18 | 19 | server.post('/matt', (req, res) => { 20 | res.setHeader('content-type', 'application/matt'); 21 | res.send(generateMattMessage('world')); 22 | }); 23 | 24 | let s: http.Server; 25 | return new Promise((resolve) => { 26 | s = server.listen(port, host, () => resolve()); 27 | }).then(() => s); 28 | }; 29 | 30 | const startTCPServer = (host: string, port: number) => { 31 | const server = net.createServer(); 32 | 33 | server.on('connection', (sock) => { 34 | sock.on('data', (data) => { 35 | const msg = parseMattMessage(data.toString()); 36 | 37 | if (msg === 'hellotcp') { 38 | sock.write(generateMattMessage('tcpworld')); 39 | } else { 40 | sock.write(generateMattMessage('message not understood')); 41 | } 42 | sock.write('\n'); 43 | sock.destroy(); 44 | }); 45 | }); 46 | 47 | return new Promise((resolve) => { 48 | server.listen(port, host); 49 | 50 | server.on('listening', () => { 51 | resolve(null); 52 | }); 53 | }); 54 | }; 55 | 56 | const skipPluginTests = process.env['SKIP_PLUGIN_TESTS'] === 'true'; 57 | (skipPluginTests ? describe.skip : describe)('MATT protocol test', () => { 58 | setLogLevel('info'); 59 | 60 | describe('HTTP and TCP Provider', () => { 61 | const HOST = '127.0.0.1'; 62 | const HTTP_PORT = 8888; 63 | const TCP_PORT = 8889; 64 | beforeEach(async () => { 65 | await startHTTPServer(HOST, HTTP_PORT); 66 | await startTCPServer(HOST, TCP_PORT); 67 | }); 68 | 69 | it('returns a valid MATT message over HTTP and TCP', () => 70 | verifier({ 71 | providerBaseUrl: 'http://localhost:8888', 72 | transports: [ 73 | { 74 | port: TCP_PORT, 75 | protocol: 'matt', 76 | scheme: 'tcp', 77 | }, 78 | ], 79 | pactUrls: [ 80 | path.join( 81 | __dirname, 82 | '__testoutput__', 83 | 'matt-consumer-matt-provider.json' 84 | ), 85 | path.join( 86 | __dirname, 87 | '__testoutput__', 88 | 'matt-tcp-consumer-matt-tcp-provider.json' 89 | ), 90 | ], 91 | }).verify()); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /test/message.integration.spec.ts: -------------------------------------------------------------------------------- 1 | import chai = require('chai'); 2 | import path = require('path'); 3 | import chaiAsPromised = require('chai-as-promised'); 4 | import * as rimraf from 'rimraf'; 5 | import zlib = require('zlib'); 6 | import { load } from '@grpc/proto-loader'; 7 | import * as grpc from '@grpc/grpc-js'; 8 | 9 | import { ConsumerMessagePact, makeConsumerMessagePact } from '../src'; 10 | import { FfiSpecificationVersion } from '../src/ffi/types'; 11 | import { setLogLevel } from '../src/logger'; 12 | 13 | chai.use(chaiAsPromised); 14 | const { expect } = chai; 15 | 16 | const getFeature = async (address: string, protoFile: string) => { 17 | const def = await load(protoFile); 18 | const { routeguide } = grpc.loadPackageDefinition(def); 19 | 20 | const client = new routeguide['RouteGuide']( 21 | address, 22 | grpc.credentials.createInsecure() 23 | ); 24 | 25 | return new Promise((resolve, reject) => { 26 | client.GetFeature( 27 | { 28 | latitude: 180, 29 | longitude: 200, 30 | }, 31 | (e: Error, feature: any) => { 32 | if (e) { 33 | reject(e); 34 | } else { 35 | resolve(feature); 36 | } 37 | } 38 | ); 39 | }); 40 | }; 41 | 42 | describe('FFI integration test for the Message Consumer API', () => { 43 | setLogLevel('error'); 44 | 45 | let pact: ConsumerMessagePact; 46 | const secret = 'this is an encoded string'; 47 | const bytes: Buffer = zlib.gzipSync(secret); 48 | 49 | before(() => { 50 | rimraf.sync(path.join(__dirname, '__testoutput__', 'message-consumer*')); 51 | }); 52 | 53 | beforeEach(() => { 54 | pact = makeConsumerMessagePact( 55 | 'message-consumer', 56 | 'message-provider', 57 | FfiSpecificationVersion['SPECIFICATION_VERSION_V4'] 58 | ); 59 | }); 60 | 61 | describe('Asynchronous Messages', () => { 62 | describe('with JSON data', () => { 63 | it('generates a pact with success', () => { 64 | pact.addMetadata('pact-node', 'meta-key', 'meta-val'); 65 | const message = pact.newAsynchronousMessage(''); 66 | message.expectsToReceive('a product event'); 67 | message.given('some state'); 68 | message.givenWithParam('some state 2', 'state2 key', 'state2 val'); 69 | message.withContents( 70 | JSON.stringify({ foo: 'bar' }), 71 | 'application/json' 72 | ); 73 | message.withMetadata('meta-key', 'meta-val'); 74 | 75 | const reified = message.reifyMessage(); 76 | 77 | expect(JSON.parse(reified).contents.content).to.have.property( 78 | 'foo', 79 | 'bar' 80 | ); 81 | 82 | pact.writePactFile(path.join(__dirname, '__testoutput__')); 83 | }); 84 | }); 85 | 86 | describe('with binary data', () => { 87 | it('generates a pact with success', () => { 88 | const message = pact.newAsynchronousMessage(''); 89 | message.expectsToReceive('a binary event'); 90 | message.given('some state'); 91 | message.givenWithParam('some state 2', 'state2 key', 'state2 val'); 92 | message.withBinaryContents(bytes, 'application/gzip'); 93 | message.withMetadata('meta-key', 'meta-val'); 94 | 95 | const reified = message.reifyMessage(); 96 | const { contents } = JSON.parse(reified); 97 | 98 | // Check the base64 encoded contents can be decoded, unzipped and equals the secret 99 | const buf = Buffer.from(contents.content, 'base64'); 100 | const deflated = zlib.gunzipSync(buf).toString('utf8'); 101 | expect(deflated).to.equal(secret); 102 | 103 | pact.writePactFile(path.join(__dirname, '__testoutput__')); 104 | }); 105 | }); 106 | }); 107 | 108 | describe('Synchronous Messages', () => { 109 | describe('with JSON data', () => { 110 | it('generates a pact with success', () => { 111 | pact.addMetadata('pact-node', 'meta-key', 'meta-val'); 112 | const message = pact.newSynchronousMessage('A synchronous message'); 113 | message.given('some state'); 114 | message.givenWithParam('some state 2', 'state2 key', 'state2 val'); 115 | message.withRequestContents( 116 | JSON.stringify({ foo: 'bar' }), 117 | 'application/json' 118 | ); 119 | message.withResponseContents( 120 | JSON.stringify({ foo: 'bar' }), 121 | 'application/json' 122 | ); 123 | message.withMetadata('meta-key', 'meta-val'); 124 | 125 | // const reified = message.reifyMessage(); 126 | 127 | // expect(JSON.parse(reified).contents).to.have.property('foo', 'bar'); 128 | 129 | pact.writePactFile(path.join(__dirname, '__testoutput__')); 130 | }); 131 | }); 132 | 133 | const skipPluginTests = process.env['SKIP_PLUGIN_TESTS'] === 'true'; 134 | (skipPluginTests ? describe.skip : describe)( 135 | 'with plugin contents (gRPC)', 136 | () => { 137 | let protoFile = `${__dirname}/integration/grpc/route_guide.proto`; 138 | if (process.platform === 'win32') { 139 | const escapedProtoFile = protoFile.replace(/\\/g, '\\\\'); 140 | protoFile = escapedProtoFile; 141 | } 142 | 143 | let port: number; 144 | 145 | afterEach(() => { 146 | pact.cleanupPlugins(); 147 | }); 148 | 149 | beforeEach(() => { 150 | const grpcInteraction = `{ 151 | "pact:proto": "${protoFile}", 152 | "pact:proto-service": "RouteGuide/GetFeature", 153 | "pact:content-type": "application/protobuf", 154 | "request": { 155 | "latitude": "matching(number, 180)", 156 | "longitude": "matching(number, 200)" 157 | }, 158 | "response": { 159 | "name": "matching(type, 'Big Tree')", 160 | "location": { 161 | "latitude": "matching(number, 180)", 162 | "longitude": "matching(number, 200)" 163 | } 164 | } 165 | }`; 166 | 167 | pact.addMetadata('pact-node', 'meta-key', 'meta-val'); 168 | pact.addPlugin('protobuf', '0.3.15'); 169 | 170 | const message = pact.newSynchronousMessage('a grpc test 1'); 171 | message.given('some state 1'); 172 | message.withPluginRequestResponseInteractionContents( 173 | 'application/protobuf', 174 | grpcInteraction 175 | ); 176 | message.withMetadata('meta-key 1', 'meta-val 2'); 177 | 178 | port = pact.pactffiCreateMockServerForTransport( 179 | '127.0.0.1', 180 | 'grpc', 181 | '' 182 | ); 183 | }); 184 | 185 | it('generates a pact with success', async () => { 186 | const feature: any = await getFeature(`127.0.0.1:${port}`, protoFile); 187 | expect(feature.name).to.eq('Big Tree'); 188 | 189 | const res = pact.mockServerMatchedSuccessfully(port); 190 | expect(res).to.eq(true); 191 | 192 | const mismatches = pact.mockServerMismatches(port); 193 | expect(mismatches.length).to.eq(0); 194 | 195 | pact.writePactFile(path.join(__dirname, '__testoutput__')); 196 | }); 197 | } 198 | ); 199 | }); 200 | }); 201 | -------------------------------------------------------------------------------- /test/monkeypatch.rb: -------------------------------------------------------------------------------- 1 | def Filelock(lockname, options = {}, &block) 2 | puts "Opening file without filelock" 3 | File.open(lockname, File::RDWR|File::CREAT, 0644) do |file| 4 | Timeout::timeout(options.fetch(:timeout, 60), Filelock::ExecTimeout) { yield file } 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/plugin-verifier.integration.spec.ts: -------------------------------------------------------------------------------- 1 | import chai = require('chai'); 2 | import chaiAsPromised = require('chai-as-promised'); 3 | import { loadSync } from '@grpc/proto-loader'; 4 | import * as grpc from '@grpc/grpc-js'; 5 | import express = require('express'); 6 | import * as http from 'http'; 7 | import cors = require('cors'); 8 | import bodyParser = require('body-parser'); 9 | import { returnJson } from './integration/data-utils'; 10 | import verifierFactory from '../src/verifier'; 11 | 12 | const { expect } = chai; 13 | chai.use(chaiAsPromised); 14 | 15 | const HTTP_PORT = 50051; 16 | const GRPC_PORT = 50052; 17 | 18 | const getGRPCServer = () => { 19 | const PROTO_PATH = `${__dirname}/integration/grpc/route_guide.proto`; 20 | 21 | const options = { 22 | keepCase: true, 23 | longs: String, 24 | enums: String, 25 | defaults: true, 26 | oneofs: true, 27 | }; 28 | const packageDefinition = loadSync(PROTO_PATH, options); 29 | const { routeguide } = grpc.loadPackageDefinition(packageDefinition); 30 | 31 | const server = new grpc.Server(); 32 | 33 | server.addService(routeguide['RouteGuide'].service, { 34 | getFeature: (_: unknown, callback: any) => { 35 | callback(null, { 36 | name: 'A place', 37 | latitude: 200, 38 | longitude: 180, 39 | }); 40 | }, 41 | }); 42 | 43 | return server; 44 | }; 45 | 46 | const startGRPCServer = (server: any, port: number) => { 47 | server.bindAsync( 48 | `127.0.0.1:${port}`, 49 | grpc.ServerCredentials.createInsecure(), 50 | (_: unknown, grpcPort: number) => { 51 | console.log(`Server running at http://127.0.0.1:${grpcPort}`); 52 | server.start(); 53 | } 54 | ); 55 | }; 56 | 57 | const startHTTPServer = (port: number): Promise => { 58 | const server: express.Express = express(); 59 | server.use(cors()); 60 | server.use(bodyParser.json()); 61 | server.use( 62 | bodyParser.urlencoded({ 63 | extended: true, 64 | }) 65 | ); 66 | 67 | // Dummy server to respond to state changes etc. 68 | server.all('/*', returnJson({})); 69 | 70 | let s: http.Server; 71 | return new Promise((resolve) => { 72 | s = server.listen(port, () => resolve()); 73 | }).then(() => s); 74 | }; 75 | 76 | const getFeature = async (address: string, protoFile: string) => { 77 | const def = loadSync(protoFile); 78 | const { routeguide } = grpc.loadPackageDefinition(def); 79 | 80 | const client = new routeguide['RouteGuide']( 81 | address, 82 | grpc.credentials.createInsecure() 83 | ); 84 | 85 | return new Promise((resolve, reject) => { 86 | client.GetFeature( 87 | { 88 | latitude: 180, 89 | longitude: 200, 90 | }, 91 | (e: Error, feature: any) => { 92 | if (e) { 93 | reject(e); 94 | } else { 95 | resolve(feature); 96 | } 97 | } 98 | ); 99 | }); 100 | }; 101 | 102 | const skipPluginTests = process.env['SKIP_PLUGIN_TESTS'] === 'true'; 103 | (skipPluginTests ? describe.skip : describe)( 104 | 'Plugin Verifier Integration Spec', 105 | () => { 106 | context('plugin tests', () => { 107 | describe('grpc interaction', () => { 108 | before(async () => { 109 | const server = getGRPCServer(); 110 | startGRPCServer(server, GRPC_PORT); 111 | await startHTTPServer(HTTP_PORT); 112 | }); 113 | 114 | it('should verify the gRPC interactions', async () => { 115 | await verifierFactory({ 116 | providerBaseUrl: `http://127.0.0.1:${HTTP_PORT}`, 117 | transports: [ 118 | { 119 | port: GRPC_PORT, 120 | protocol: 'grpc', 121 | }, 122 | ], 123 | logLevel: 'debug', 124 | pactUrls: [`${__dirname}/integration/grpc/grpc.json`], 125 | }).verify(); 126 | 127 | expect('').to.eq(''); 128 | }); 129 | 130 | it('runs the grpc client', async () => { 131 | const protoFile = `${__dirname}/integration/grpc/route_guide.proto`; 132 | const feature = await getFeature(`127.0.0.1:${GRPC_PORT}`, protoFile); 133 | 134 | console.log(feature); 135 | }); 136 | }); 137 | }); 138 | } 139 | ); 140 | -------------------------------------------------------------------------------- /ts-node.js: -------------------------------------------------------------------------------- 1 | require('ts-node').register({ 2 | project: 'tsconfig.json', 3 | }); 4 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["**/__mocks__", "**/*.spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node14/tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["node", "mocha"], 5 | "module": "commonjs", 6 | "removeComments": true, 7 | "sourceMap": true, 8 | "moduleResolution": "node", 9 | "suppressImplicitAnyIndexErrors": true, 10 | "declaration": true, 11 | "noImplicitReturns": true, 12 | "noPropertyAccessFromIndexSignature": true, 13 | "noImplicitAny": true, 14 | "noImplicitThis": true, 15 | "skipLibCheck": true, 16 | "strictNullChecks": true, 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "stripInternal": true, 20 | "esModuleInterop": true 21 | }, 22 | "include": ["**/*.ts"], 23 | "exclude": ["node_modules"] 24 | } 25 | --------------------------------------------------------------------------------