├── .github └── workflows │ ├── release_gem.yml │ ├── smartbear-issue-label-added.yml │ └── test.yml ├── .gitignore ├── .rspec ├── Appraisals ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── RELEASING.md ├── ROADMAP.md ├── Rakefile ├── bin └── pact ├── config.ru ├── documentation ├── README.md ├── configuration.md ├── diff_formatter_embedded.png ├── diff_formatter_list.png ├── diff_formatter_unix.png └── pact_two_parts.png ├── example ├── animal-service │ ├── Gemfile │ ├── Gemfile.lock │ ├── Rakefile │ ├── config.ru │ ├── db │ │ └── animal_db.sqlite3 │ ├── lib │ │ └── animal_service │ │ │ ├── animal_repository.rb │ │ │ ├── api.rb │ │ │ └── db.rb │ └── spec │ │ └── service_consumers │ │ ├── pact_helper.rb │ │ └── provider_states_for_zoo_app.rb ├── zoo-app │ ├── Gemfile │ ├── Gemfile.lock │ ├── Rakefile │ ├── doc │ │ └── pacts │ │ │ └── markdown │ │ │ ├── README.md │ │ │ └── Zoo App - Animal Service.md │ ├── lib │ │ └── zoo_app │ │ │ ├── animal_service_client.rb │ │ │ ├── models │ │ │ └── alligator.rb │ │ │ └── version.rb │ └── spec │ │ ├── pacts │ │ └── zoo_app-animal_service.json │ │ ├── service_providers │ │ ├── animal_service_client_spec.rb │ │ └── pact_helper.rb │ │ └── spec_helper.rb └── zoo_app-animal_service.png ├── lib ├── pact.rb ├── pact │ ├── cli.rb │ ├── cli │ │ ├── generate_pact_docs.rb │ │ ├── run_pact_verification.rb │ │ └── spec_criteria.rb │ ├── consumer.rb │ ├── consumer │ │ ├── configuration.rb │ │ ├── configuration │ │ │ ├── configuration_extensions.rb │ │ │ ├── dsl.rb │ │ │ ├── mock_service.rb │ │ │ ├── service_consumer.rb │ │ │ └── service_provider.rb │ │ ├── consumer_contract_builder.rb │ │ ├── consumer_contract_builders.rb │ │ ├── interaction_builder.rb │ │ ├── rspec.rb │ │ ├── spec_hooks.rb │ │ └── world.rb │ ├── doc │ │ ├── README.md │ │ ├── doc_file.rb │ │ ├── generate.rb │ │ ├── generator.rb │ │ ├── interaction_view_model.rb │ │ ├── markdown │ │ │ ├── consumer_contract_renderer.rb │ │ │ ├── generator.rb │ │ │ ├── index_renderer.rb │ │ │ ├── interaction.erb │ │ │ └── interaction_renderer.rb │ │ └── sort_interactions.rb │ ├── hal │ │ ├── authorization_header_redactor.rb │ │ ├── entity.rb │ │ ├── http_client.rb │ │ ├── link.rb │ │ └── non_json_entity.rb │ ├── hash_refinements.rb │ ├── pact_broker.rb │ ├── pact_broker │ │ ├── fetch_pact_uris_for_verification.rb │ │ ├── fetch_pacts.rb │ │ ├── notices.rb │ │ └── pact_selection_description.rb │ ├── project_root.rb │ ├── provider.rb │ ├── provider │ │ ├── configuration.rb │ │ ├── configuration │ │ │ ├── configuration_extension.rb │ │ │ ├── dsl.rb │ │ │ ├── message_provider_dsl.rb │ │ │ ├── pact_verification.rb │ │ │ ├── pact_verification_from_broker.rb │ │ │ ├── service_provider_config.rb │ │ │ └── service_provider_dsl.rb │ │ ├── context.rb │ │ ├── help │ │ │ ├── console_text.rb │ │ │ ├── content.rb │ │ │ ├── pact_diff.rb │ │ │ ├── prompt_text.rb │ │ │ └── write.rb │ │ ├── matchers │ │ │ └── messages.rb │ │ ├── pact_helper_locator.rb │ │ ├── pact_source.rb │ │ ├── pact_spec_runner.rb │ │ ├── pact_uri.rb │ │ ├── pact_verification.rb │ │ ├── print_missing_provider_states.rb │ │ ├── request.rb │ │ ├── rspec.rb │ │ ├── rspec │ │ │ ├── backtrace_formatter.rb │ │ │ ├── calculate_exit_code.rb │ │ │ ├── custom_options_file │ │ │ ├── formatter_rspec_2.rb │ │ │ ├── formatter_rspec_3.rb │ │ │ ├── json_formatter.rb │ │ │ ├── matchers.rb │ │ │ └── pact_broker_formatter.rb │ │ ├── state │ │ │ ├── provider_state.rb │ │ │ ├── provider_state_configured_modules.rb │ │ │ ├── provider_state_manager.rb │ │ │ ├── provider_state_proxy.rb │ │ │ ├── set_up.rb │ │ │ └── tear_down.rb │ │ ├── test_methods.rb │ │ ├── verification_report.rb │ │ ├── verification_results │ │ │ ├── create.rb │ │ │ ├── publish.rb │ │ │ ├── publish_all.rb │ │ │ └── verification_result.rb │ │ └── world.rb │ ├── retry.rb │ ├── tasks.rb │ ├── tasks │ │ ├── task_helper.rb │ │ └── verification_task.rb │ ├── templates │ │ ├── help.erb │ │ └── provider_state.erb │ ├── utils │ │ ├── metrics.rb │ │ └── string.rb │ └── version.rb └── tasks │ └── pact.rake ├── pact.gemspec ├── renovate.json ├── scratchpad.rb ├── script ├── release.sh └── trigger-release.sh ├── spec ├── features │ ├── consumer_with_file_upload_spec.rb │ ├── consumption_spec.rb │ ├── foo_bar_spec.rb │ ├── production_spec.rb │ └── provider_states │ │ └── zebras.rb ├── fixtures │ └── certificates │ │ ├── ca_cert.pem │ │ ├── ca_cert.srl │ │ ├── ca_key.pem │ │ ├── client_cert.pem │ │ ├── key.pem │ │ ├── server.csr │ │ ├── unsigned_cert.pem │ │ └── unsigned_key.pem ├── integration │ ├── cli_docs_spec.rb │ ├── cli_spec.rb │ ├── consumer_async_request_spec.rb │ ├── consumer_more_than_one_matching_interaction_spec.rb │ ├── consumer_no_matching_interaction_spec.rb │ ├── consumer_with_a_provider_state_spec.rb │ ├── consumer_with_form_hash_spec.rb │ ├── consumer_with_form_spec.rb │ ├── consumer_with_pact_term_header.rb │ ├── consumer_with_pact_term_in_path.rb │ ├── consumer_with_params_hash_spec.rb │ ├── consumer_with_v2_matching.rb │ ├── executing_verify_from_wrapper_language_spec.rb │ ├── pact │ │ ├── consumer_configuration_spec.rb │ │ └── provider_configuration_spec.rb │ └── publish_verification_spec.rb ├── lib │ └── pact │ │ ├── cli │ │ └── spec_criteria_spec.rb │ │ ├── cli_spec.rb │ │ ├── configuration_spec.rb │ │ ├── consumer │ │ ├── configuration_spec.rb │ │ ├── consumer_contract_builder_spec.rb │ │ ├── interaction_builder_spec.rb │ │ └── service_consumer_spec.rb │ │ ├── doc │ │ ├── generator_spec.rb │ │ ├── interaction_view_model_spec.rb │ │ └── markdown │ │ │ ├── consumer_contract_renderer_spec.rb │ │ │ └── index_renderer_spec.rb │ │ ├── hal │ │ ├── authorization_header_redactor_spec.rb │ │ ├── entity_spec.rb │ │ ├── http_client_spec.rb │ │ └── link_spec.rb │ │ ├── pact_broker │ │ ├── fetch_pact_uris_for_verification_spec.rb │ │ ├── fetch_pacts_spec.rb │ │ ├── notices_spec.rb │ │ └── pact_selection_description_spec.rb │ │ ├── pact_broker_spec.rb │ │ ├── provider │ │ ├── configuration │ │ │ ├── configuration_extension_spec.rb │ │ │ ├── message_provider_dsl_spec.rb │ │ │ ├── pact_verification_from_broker_spec.rb │ │ │ ├── pact_verification_spec.rb │ │ │ ├── service_provider_config_spec.rb │ │ │ └── service_provider_dsl_spec.rb │ │ ├── configuration_spec.rb │ │ ├── generators_spec.rb │ │ ├── help │ │ │ ├── console_text_spec.rb │ │ │ ├── content_spec.rb │ │ │ ├── prompt_text_spec.rb │ │ │ └── write_spec.rb │ │ ├── matchers │ │ │ └── messages_spec.rb │ │ ├── pact_helper_locator_spec.rb │ │ ├── pact_spec_runner_spec.rb │ │ ├── pact_uri_spec.rb │ │ ├── print_missing_provider_states_spec.rb │ │ ├── request_spec.rb │ │ ├── rspec │ │ │ ├── calculate_exit_code_spec.rb │ │ │ ├── formatter_rspec_2_spec.rb │ │ │ └── formatter_rspec_3_spec.rb │ │ ├── rspec_spec.rb │ │ ├── state │ │ │ ├── provider_state_manager_spec.rb │ │ │ ├── provider_state_proxy_spec.rb │ │ │ └── provider_state_spec.rb │ │ ├── verification_results │ │ │ ├── create_spec.rb │ │ │ └── publish_spec.rb │ │ └── world_spec.rb │ │ ├── tasks │ │ ├── task_helper_spec.rb │ │ └── verification_task_spec.rb │ │ └── utils │ │ └── metrics_spec.rb ├── pact_specification │ └── compliance-1.0.0.rb ├── service_providers │ ├── helper.rb │ ├── pact_ruby_fetch_pacts_for_verification_test.rb │ └── pact_ruby_fetch_pacts_test.rb ├── spec_helper.rb ├── standalone │ ├── consumer_fail_test.rb │ └── consumer_pass_test.rb └── support │ ├── a_consumer-a_producer.json │ ├── a_consumer-a_provider.json │ ├── active_support_if_configured.rb │ ├── app_for_config_ru.rb │ ├── bar_fail_pact_helper.rb │ ├── bar_pact_helper.rb │ ├── case-insensitive-response-header-matching.json │ ├── case-insensitive-response-header-matching.rb │ ├── cli.rb │ ├── consumer_contract_template.json │ ├── docs │ └── a_consumer-a_provider.json │ ├── factories.rb │ ├── foo-bar-message.json │ ├── generated_index.md │ ├── generated_markdown.md │ ├── interaction_view_model.json │ ├── interaction_view_model_with_terms.json │ ├── markdown_pact.json │ ├── markdown_pact_with_markdown_chars_in_names.json │ ├── message_spec_helper.rb │ ├── missing_provider_states_output.txt │ ├── options.json │ ├── options_app.rb │ ├── pact_helper.rb │ ├── pact_helper_for_provider_state_params_test.rb │ ├── provider_states_params_test.json │ ├── response_body_term.json │ ├── response_body_term_app.rb │ ├── shared_examples_for_request.rb │ ├── spec_support.rb │ ├── ssl_server.rb │ ├── stubbing.json │ ├── stubbing_using_allow.rb │ ├── term-v2.json │ ├── term.json │ ├── test_app_fail.json │ ├── test_app_pass.json │ ├── test_app_with_right_content_type_differ.json │ ├── text.txt │ └── warning_silencer.rb └── tasks ├── foo-bar.rake ├── message-test.rake ├── pact-test.rake ├── release.rake └── spec.rake /.github/workflows/release_gem.yml: -------------------------------------------------------------------------------- 1 | name: Release gem 2 | 3 | on: 4 | repository_dispatch: 5 | types: 6 | - release-triggered 7 | workflow_dispatch: 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: ruby/setup-ruby@v1 15 | with: 16 | ruby-version: '3.2' 17 | - run: | 18 | gem install bundler -v 2.4 19 | bundle install 20 | - name: Test 21 | run: bundle exec rake 22 | 23 | release: 24 | needs: test 25 | runs-on: ubuntu-latest 26 | outputs: 27 | gem_name: ${{ steps.release-gem.outputs.gem_name }} 28 | version: ${{ steps.release-gem.outputs.version }} 29 | increment: ${{ steps.release-gem.outputs.increment }} 30 | steps: 31 | - uses: actions/checkout@v4 32 | with: 33 | fetch-depth: 0 34 | - id: release-gem 35 | uses: pact-foundation/release-gem@v1 36 | env: 37 | GEM_HOST_API_KEY: '${{ secrets.RUBYGEMS_API_KEY }}' 38 | GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' 39 | INCREMENT: '${{ github.event.client_payload.increment }}' 40 | 41 | notify-gem-released: 42 | needs: release 43 | strategy: 44 | matrix: 45 | repository: [pact-foundation/pact-ruby-cli, pact-foundation/pact-ruby-standalone] 46 | runs-on: ubuntu-latest 47 | steps: 48 | - name: Notify ${{ matrix.repository }} of gem release 49 | uses: peter-evans/repository-dispatch@v3 50 | with: 51 | token: ${{ secrets.GHTOKENFORPACTCLIRELEASE }} 52 | repository: ${{ matrix.repository }} 53 | event-type: gem-released 54 | client-payload: | 55 | { 56 | "name": "${{ needs.release.outputs.gem_name }}", 57 | "version": "${{ needs.release.outputs.version }}", 58 | "increment": "${{ needs.release.outputs.increment }}" 59 | } 60 | -------------------------------------------------------------------------------- /.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/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | workflow_dispatch: 11 | 12 | jobs: 13 | test: 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | ruby_version: ["2.7", "3.0", "3.1", "3.2", "3.3"] 19 | os: ["ubuntu-latest","windows-latest","macos-latest"] 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: ruby/setup-ruby@v1 23 | with: 24 | ruby-version: ${{ matrix.ruby_version }} 25 | bundler-cache: true 26 | - run: "bundle exec rake" 27 | - run: "bundle install && bundle exec rake spec" 28 | if: matrix.ruby_version > '3.0' 29 | working-directory: example/zoo-app 30 | - run: "bundle install && bundle exec rake pact:verify" 31 | if: matrix.os != 'windows-latest' && matrix.ruby_version > '3.0' 32 | working-directory: example/animal-service 33 | test-with-rack-2: 34 | runs-on: ${{ matrix.os }} 35 | strategy: 36 | fail-fast: false 37 | matrix: 38 | ruby_version: ["3.2"] 39 | os: ["ubuntu-latest","windows-latest","macos-latest"] 40 | steps: 41 | - uses: actions/checkout@v4 42 | - uses: ruby/setup-ruby@v1 43 | with: 44 | ruby-version: ${{ matrix.ruby_version }} 45 | bundler-cache: true 46 | - run: "bundle exec appraisal install" 47 | - run: "bundle exec appraisal rack-2 rake" 48 | test-with-active-support: 49 | runs-on: ${{ matrix.os }} 50 | strategy: 51 | fail-fast: false 52 | matrix: 53 | ruby_version: ["2.7", "3.0", "3.1", "3.2"] 54 | os: ["ubuntu-latest","windows-latest","macos-latest"] 55 | defaults: 56 | run: 57 | shell: bash 58 | steps: 59 | - uses: actions/checkout@v4 60 | - uses: ruby/setup-ruby@v1 61 | with: 62 | ruby-version: ${{ matrix.ruby_version }} 63 | bundler-cache: true 64 | - run: "bundle exec appraisal install" 65 | - run: "bundle exec appraisal activesupport rake spec_with_active_support" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | InstalledFiles 7 | _yardoc 8 | coverage 9 | lib/bundler/man 10 | pkg 11 | rdoc 12 | spec/reports 13 | test/tmp 14 | test/version_tmp 15 | tmp 16 | *.swp 17 | .bin 18 | tags 19 | .rbenv-version 20 | spec/pacts 21 | .rvmrc 22 | *.iml 23 | .rakeTasks 24 | *~ 25 | .ruby-gemset 26 | .ruby-version 27 | log 28 | .idea 29 | reports 30 | Gemfile.lock 31 | *.gemfile.lock 32 | gemfiles/*.gemfile.lock 33 | reports/pacts 34 | spec/examples.txt 35 | *bethtest* 36 | .DS_Store -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format progress 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | appraise "rack-2" do 2 | gem "rack", "~> 2.0" 3 | 4 | group :test do 5 | remove_gem "rackup" 6 | end 7 | end 8 | 9 | appraise "activesupport" do 10 | gem "activesupport", "~> 5.1" 11 | end 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Raising issues 2 | 3 | Please provide the following information with your issue to enable us to respond as quickly as possible. 4 | 5 | * The relevant versions of the gems you are using. 6 | * The steps to recreate your issue. 7 | * The full stacktrace if there is an exception. 8 | * An executable code example where possible. You can fork and modify the [pact-ruby-e2e-example] codebase to quickly recreate your issue. 9 | 10 | # Contributing 11 | 12 | 1. Fork it 13 | 2. Create your feature branch (`git checkout -b feat/my-new-feature`) 14 | 3. Commit your changes. **Please use the conventional changelog format for [semantic commit messages](http://karma-runner.github.io/1.0/dev/git-commit-msg.html)** (`git commit -am 'feat(some new feat): add a thing'`) 15 | 4. Push to the branch (`git push origin feat/my-new-feature`) 16 | 5. Create new Pull Request 17 | 18 | All pull requests must have tests that cover the relevant behavioural changes and should conform to the existing code conventions. 19 | 20 | [pact-ruby-e2e-example]: https://github.com/pact-foundation/pact-ruby-e2e-example 21 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in pact.gemspec 4 | gemspec 5 | 6 | # If rspec-mocks is not locked, spec/lib/pact/consumer/configuration_spec.rb fails on Ruby 3.0 7 | # Should raise an issue, but no time right now. 8 | # # received :register_mock_service_for with unexpected arguments 9 | # expected: ("Mock Provider", "http://localhost:1234", {:find_available_port=>false, :pact_specification_version=>"1"}) 10 | # got: ("Mock Provider", "http://localhost:1234", {:find_available_port=>false, :pact_specification_version=>"1"}) 11 | # Diff: 12 | 13 | gem "rspec-mocks", "3.10.2" 14 | gem "appraisal", "~> 2.5" 15 | 16 | if ENV['X_PACT_DEVELOPMENT'] 17 | gem "pact-support", path: '../pact-support' 18 | gem "pact-mock_service", path: '../pact-mock_service' 19 | gem "pry-byebug" 20 | end 21 | 22 | group :local_development do 23 | gem "pry-byebug" 24 | end 25 | 26 | group :test do 27 | gem 'faraday', '~>2.0', '<3.0' 28 | gem 'faraday-retry', '~>2.0' 29 | gem 'rackup', '~> 2.1' 30 | end 31 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 REA Group Ltd 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Release process 2 | 3 | * Unset X_PACT_DEVELOPMENT and bundle update if it was set. 4 | * Ensure all tests are passing and everything is committed. 5 | * Check status of https://travis-ci.org/pact-foundation/pact-ruby 6 | * Run `script/release.sh [major|minor|patch]` (defaults to minor) 7 | * Announce new version on @pact_up twitter account. 8 | * Update any relevant wiki pages or documentation. 9 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | ## Short term 4 | * Provide more flexible matching (eg the keys should match, and the classes of the values should match, but the values of each key do not need to be equal). This is to make the pact verification less brittle. This is a WIP. See details [here](https://github.com/bethesque/pact-specification/tree/version-2) 5 | 6 | ## Long term 7 | 8 | * Add XML support 9 | * Create a test matrix to ensure compatibility with implementations in other languages 10 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "rubygems" 2 | require "bundler/setup" 3 | require "bundler/gem_tasks" 4 | require 'rspec/core/rake_task' 5 | 6 | Dir.glob('./lib/tasks/**/*.rake').each { |task| load task } 7 | Dir.glob('./tasks/**/*.rake').each { |task| load task } 8 | 9 | task :default => [:spec, 'spec:provider', 'pact:tests:all'] 10 | 11 | -------------------------------------------------------------------------------- /bin/pact: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'pact/cli' 4 | Pact::CLI.start 5 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | require './spec/support/app_for_config_ru' 2 | 3 | run AppForConfigRu -------------------------------------------------------------------------------- /documentation/README.md: -------------------------------------------------------------------------------- 1 | ### Pact Documentation 2 | 3 | * Step by step instructions for getting started with pacts can be found in the project [README.md](/README.md#usage) 4 | * [Terminology](https://github.com/pact-foundation/pact-ruby/wiki/Terminology) 5 | * [Configuration](configuration.md) 6 | * [Provider States](https://github.com/pact-foundation/pact-ruby/wiki/Provider-states) 7 | * [Verifying pacts](https://github.com/pact-foundation/pact-ruby/wiki/Verifying-pacts) 8 | * [Frequently asked questions](https://github.com/pact-foundation/pact-ruby/wiki/FAQ) 9 | * [Rarely asked questions](https://github.com/pact-foundation/pact-ruby/wiki/RAQ) 10 | * [Best practices](https://github.com/pact-foundation/pact-ruby/wiki/Best-practices) 11 | * [Troubleshooting](https://github.com/pact-foundation/pact-ruby/wiki/Troubleshooting) 12 | * [Testing with pact diagram](https://github.com/pact-foundation/pact-ruby/wiki/Testing%20with%20pact.png) 13 | * [Development workflow](https://github.com/pact-foundation/pact-ruby/wiki/Development-workflow) 14 | -------------------------------------------------------------------------------- /documentation/diff_formatter_embedded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pact-foundation/pact-ruby/789596fc2b41bd83376e25c777ae4ffe26270f15/documentation/diff_formatter_embedded.png -------------------------------------------------------------------------------- /documentation/diff_formatter_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pact-foundation/pact-ruby/789596fc2b41bd83376e25c777ae4ffe26270f15/documentation/diff_formatter_list.png -------------------------------------------------------------------------------- /documentation/diff_formatter_unix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pact-foundation/pact-ruby/789596fc2b41bd83376e25c777ae4ffe26270f15/documentation/diff_formatter_unix.png -------------------------------------------------------------------------------- /documentation/pact_two_parts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pact-foundation/pact-ruby/789596fc2b41bd83376e25c777ae4ffe26270f15/documentation/pact_two_parts.png -------------------------------------------------------------------------------- /example/animal-service/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | group :development, :test do 4 | gem 'rspec' 5 | gem 'pact', path: '../../' 6 | gem 'pry' 7 | end 8 | 9 | gem 'rake' 10 | gem 'rack', '>= 3.1.12' 11 | gem 'sqlite3' 12 | gem 'sequel' 13 | gem 'sinatra', '>= 4.1.0' 14 | -------------------------------------------------------------------------------- /example/animal-service/Rakefile: -------------------------------------------------------------------------------- 1 | $: << File.join(File.dirname(__FILE__), "lib") 2 | 3 | require 'pact/tasks' 4 | 5 | task :default => 'pact:verify' -------------------------------------------------------------------------------- /example/animal-service/config.ru: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/lib/animal_service/api' 2 | 3 | run AnimalService::Api 4 | -------------------------------------------------------------------------------- /example/animal-service/db/animal_db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pact-foundation/pact-ruby/789596fc2b41bd83376e25c777ae4ffe26270f15/example/animal-service/db/animal_db.sqlite3 -------------------------------------------------------------------------------- /example/animal-service/lib/animal_service/animal_repository.rb: -------------------------------------------------------------------------------- 1 | require 'sequel' 2 | require_relative 'db' 3 | 4 | module AnimalService 5 | class AnimalRepository 6 | 7 | def self.find_alligator_by_name name 8 | DATABASE[:animals].where(name: name).single_record 9 | end 10 | 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /example/animal-service/lib/animal_service/api.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | require_relative 'animal_repository' 3 | require 'json' 4 | 5 | module AnimalService 6 | 7 | class Api < Sinatra::Base 8 | 9 | set :raise_errors, false 10 | set :show_exceptions, false 11 | 12 | error do 13 | e = env['sinatra.error'] 14 | content_type :json, :charset => 'utf-8' 15 | status 500 16 | {error: e.message, backtrace: e.backtrace}.to_json 17 | end 18 | 19 | get '/alligators/:name' do 20 | if (alligator = AnimalRepository.find_alligator_by_name(params[:name])) 21 | content_type :json, :charset => 'utf-8' 22 | alligator.to_json 23 | else 24 | status 404 25 | end 26 | end 27 | 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /example/animal-service/lib/animal_service/db.rb: -------------------------------------------------------------------------------- 1 | require 'sequel' 2 | 3 | module AnimalService 4 | DATABASE ||= Sequel.connect(adapter: 'sqlite', database: './db/animal_db.sqlite3') 5 | end -------------------------------------------------------------------------------- /example/animal-service/spec/service_consumers/pact_helper.rb: -------------------------------------------------------------------------------- 1 | require 'pact/provider/rspec' 2 | 3 | require "./spec/service_consumers/provider_states_for_zoo_app" 4 | 5 | Pact.service_provider 'Animal Service' do 6 | 7 | honours_pact_with "Zoo App" do 8 | pact_uri '../zoo-app/spec/pacts/zoo_app-animal_service.json' 9 | end 10 | 11 | ## For pact contracts from a Pact Broker 12 | 13 | # honours_pacts_from_pact_broker do 14 | # pact_broker_base_url 'http://localhost:9292' 15 | # # fail_if_no_pacts_found false # defaults to true 16 | # end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /example/animal-service/spec/service_consumers/provider_states_for_zoo_app.rb: -------------------------------------------------------------------------------- 1 | require 'sequel' 2 | require 'animal_service/db' 3 | require 'animal_service/animal_repository' 4 | 5 | Pact.provider_states_for "Zoo App" do 6 | 7 | set_up do 8 | AnimalService::DATABASE[:animals].truncate 9 | end 10 | 11 | provider_state "there is an alligator named Mary" do 12 | set_up do 13 | AnimalService::DATABASE[:animals].insert(name: 'Mary') 14 | end 15 | end 16 | 17 | provider_state "there is not an alligator named Mary" do 18 | no_op 19 | end 20 | 21 | provider_state "an error occurs retrieving an alligator" do 22 | set_up do 23 | allow(AnimalService::AnimalRepository).to receive(:find_alligator_by_name).and_raise("Argh!!!") 24 | end 25 | end 26 | end -------------------------------------------------------------------------------- /example/zoo-app/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | group :development, :test do 4 | gem 'rspec' 5 | gem 'pact', path: '../../' 6 | gem 'pact_broker-client' 7 | gem 'pry' 8 | end 9 | 10 | gem 'rake' 11 | 12 | gem 'rack', '>= 3.1.5' 13 | gem 'httparty', '>= 0.21.0' -------------------------------------------------------------------------------- /example/zoo-app/Rakefile: -------------------------------------------------------------------------------- 1 | require 'rspec/core/rake_task' 2 | require 'pact_broker/client/tasks' 3 | 4 | $: << './lib' 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | PactBroker::Client::PublicationTask.new do | task | 9 | require 'zoo_app/version' 10 | task.consumer_version = ZooApp::VERSION 11 | task.pact_broker_base_url = "http://localhost:9292" 12 | end 13 | 14 | task :default => :spec -------------------------------------------------------------------------------- /example/zoo-app/doc/pacts/markdown/README.md: -------------------------------------------------------------------------------- 1 | ### Pacts for Zoo App 2 | 3 | * [Animal Service](Zoo%20App%20-%20Animal%20Service.md) 4 | -------------------------------------------------------------------------------- /example/zoo-app/doc/pacts/markdown/Zoo App - Animal Service.md: -------------------------------------------------------------------------------- 1 | ### A pact between Zoo App and Animal Service 2 | 3 | #### Requests from Zoo App to Animal Service 4 | 5 | * [A request for an alligator](#a_request_for_an_alligator_given_there_is_an_alligator_named_Mary) given there is an alligator named Mary 6 | 7 | * [A request for an alligator](#a_request_for_an_alligator_given_there_is_not_an_alligator_named_Mary) given there is not an alligator named Mary 8 | 9 | * [A request for an alligator](#a_request_for_an_alligator_given_an_error_occurs_retrieving_an_alligator) given an error occurs retrieving an alligator 10 | 11 | #### Interactions 12 | 13 | 14 | Given **there is an alligator named Mary**, upon receiving **a request for an alligator** from Zoo App, with 15 | ```json 16 | { 17 | "method": "get", 18 | "path": "/alligators/Mary", 19 | "headers": { 20 | "Accept": "application/json" 21 | } 22 | } 23 | ``` 24 | Animal Service will respond with: 25 | ```json 26 | { 27 | "status": 200, 28 | "headers": { 29 | "Content-Type": "application/json;charset=utf-8" 30 | }, 31 | "body": { 32 | "name": "Mary" 33 | } 34 | } 35 | ``` 36 | 37 | Given **there is not an alligator named Mary**, upon receiving **a request for an alligator** from Zoo App, with 38 | ```json 39 | { 40 | "method": "get", 41 | "path": "/alligators/Mary", 42 | "headers": { 43 | "Accept": "application/json" 44 | } 45 | } 46 | ``` 47 | Animal Service will respond with: 48 | ```json 49 | { 50 | "status": 404 51 | } 52 | ``` 53 | 54 | Given **an error occurs retrieving an alligator**, upon receiving **a request for an alligator** from Zoo App, with 55 | ```json 56 | { 57 | "method": "get", 58 | "path": "/alligators/Mary", 59 | "headers": { 60 | "Accept": "application/json" 61 | } 62 | } 63 | ``` 64 | Animal Service will respond with: 65 | ```json 66 | { 67 | "status": 500, 68 | "headers": { 69 | "Content-Type": "application/json;charset=utf-8" 70 | }, 71 | "body": { 72 | "error": "Argh!!!" 73 | } 74 | } 75 | ``` 76 | -------------------------------------------------------------------------------- /example/zoo-app/lib/zoo_app/animal_service_client.rb: -------------------------------------------------------------------------------- 1 | require 'httparty' 2 | require 'zoo_app/models/alligator' 3 | 4 | module ZooApp 5 | class AnimalServiceClient 6 | 7 | include HTTParty 8 | base_uri 'animal-service.com' 9 | 10 | def self.find_alligator_by_name name 11 | response = get("/alligators/#{name}", :headers => {'Accept' => 'application/json'}) 12 | when_successful(response) do 13 | ZooApp::Animals::Alligator.new(parse_body(response)) 14 | end 15 | end 16 | 17 | def self.when_successful response 18 | if response.success? 19 | yield 20 | elsif response.code == 404 21 | nil 22 | else 23 | raise response.body 24 | end 25 | end 26 | 27 | def self.parse_body response 28 | JSON.parse(response.body, {:symbolize_names => true}) 29 | end 30 | end 31 | end -------------------------------------------------------------------------------- /example/zoo-app/lib/zoo_app/models/alligator.rb: -------------------------------------------------------------------------------- 1 | module ZooApp 2 | module Animals 3 | class Alligator 4 | 5 | attr_reader :name 6 | 7 | def initialize attributes 8 | @name = attributes[:name] 9 | end 10 | 11 | def == other 12 | other.is_a?(Alligator) && other.name == self.name 13 | end 14 | 15 | end 16 | end 17 | end -------------------------------------------------------------------------------- /example/zoo-app/lib/zoo_app/version.rb: -------------------------------------------------------------------------------- 1 | module ZooApp 2 | VERSION = '1.0.0' 3 | end -------------------------------------------------------------------------------- /example/zoo-app/spec/pacts/zoo_app-animal_service.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "Zoo App" 4 | }, 5 | "provider": { 6 | "name": "Animal Service" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "a request for an alligator", 11 | "providerState": "there is an alligator named Mary", 12 | "request": { 13 | "method": "get", 14 | "path": "/alligators/Mary", 15 | "headers": { 16 | "Accept": "application/json" 17 | } 18 | }, 19 | "response": { 20 | "status": 200, 21 | "headers": { 22 | "Content-Type": "application/json;charset=utf-8" 23 | }, 24 | "body": { 25 | "name": "Mary" 26 | } 27 | } 28 | }, 29 | { 30 | "description": "a request for an alligator", 31 | "providerState": "there is not an alligator named Mary", 32 | "request": { 33 | "method": "get", 34 | "path": "/alligators/Mary", 35 | "headers": { 36 | "Accept": "application/json" 37 | } 38 | }, 39 | "response": { 40 | "status": 404, 41 | "headers": { 42 | } 43 | } 44 | }, 45 | { 46 | "description": "a request for an alligator", 47 | "providerState": "an error occurs retrieving an alligator", 48 | "request": { 49 | "method": "get", 50 | "path": "/alligators/Mary", 51 | "headers": { 52 | "Accept": "application/json" 53 | } 54 | }, 55 | "response": { 56 | "status": 500, 57 | "headers": { 58 | "Content-Type": "application/json;charset=utf-8" 59 | }, 60 | "body": { 61 | "error": "Argh!!!" 62 | } 63 | } 64 | } 65 | ], 66 | "metadata": { 67 | "pactSpecification": { 68 | "version": "2.0.0" 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /example/zoo-app/spec/service_providers/animal_service_client_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative 'pact_helper' 2 | require 'zoo_app/animal_service_client' 3 | 4 | module ZooApp 5 | describe AnimalServiceClient, :pact => true do 6 | 7 | before do 8 | AnimalServiceClient.base_uri animal_service.mock_service_base_url 9 | end 10 | 11 | describe ".find_alligator_by_name" do 12 | context "when an alligator by the given name exists" do 13 | 14 | before do 15 | animal_service.given("there is an alligator named Mary"). 16 | upon_receiving("a request for an alligator").with( 17 | method: :get, 18 | path: '/alligators/Mary', 19 | headers: {'Accept' => 'application/json'} ). 20 | will_respond_with( 21 | status: 200, 22 | headers: {'Content-Type' => 'application/json;charset=utf-8'}, 23 | body: {name: 'Mary'} 24 | ) 25 | end 26 | 27 | it "returns the alligator" do 28 | expect(AnimalServiceClient.find_alligator_by_name("Mary")).to eq ZooApp::Animals::Alligator.new(name: 'Mary') 29 | end 30 | 31 | end 32 | 33 | context "when an alligator by the given name does not exist" do 34 | 35 | before do 36 | animal_service.given("there is not an alligator named Mary"). 37 | upon_receiving("a request for an alligator").with( 38 | method: :get, 39 | path: '/alligators/Mary', 40 | headers: {'Accept' => 'application/json'} ). 41 | will_respond_with(status: 404) 42 | end 43 | 44 | it "returns nil" do 45 | expect(AnimalServiceClient.find_alligator_by_name("Mary")).to be_nil 46 | end 47 | 48 | end 49 | 50 | context "when an error occurs retrieving the alligator" do 51 | 52 | before do 53 | animal_service.given("an error occurs retrieving an alligator"). 54 | upon_receiving("a request for an alligator").with( 55 | method: :get, 56 | path: '/alligators/Mary', 57 | headers: {'Accept' => 'application/json'}). 58 | will_respond_with( 59 | status: 500, 60 | headers: { 'Content-Type' => 'application/json;charset=utf-8'}, 61 | body: {error: 'Argh!!!'}) 62 | end 63 | 64 | it "raises an error" do 65 | expect{ AnimalServiceClient.find_alligator_by_name("Mary") }.to raise_error /Argh/ 66 | end 67 | 68 | end 69 | end 70 | end 71 | end -------------------------------------------------------------------------------- /example/zoo-app/spec/service_providers/pact_helper.rb: -------------------------------------------------------------------------------- 1 | require_relative '../spec_helper' 2 | require 'pact/consumer/rspec' 3 | 4 | Pact.configure do | config | 5 | config.doc_generator = :markdown 6 | end 7 | 8 | Pact.service_consumer 'Zoo App' do 9 | has_pact_with "Animal Service" do 10 | mock_service :animal_service do 11 | port 8888 12 | pact_specification_version "2.0.0" 13 | end 14 | end 15 | end 16 | 17 | -------------------------------------------------------------------------------- /example/zoo-app/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $: << File.expand_path("../../lib", __FILE__) 2 | 3 | RSpec.configure do | config | 4 | config.color = true 5 | config.formatter = :documentation 6 | end -------------------------------------------------------------------------------- /example/zoo_app-animal_service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pact-foundation/pact-ruby/789596fc2b41bd83376e25c777ae4ffe26270f15/example/zoo_app-animal_service.png -------------------------------------------------------------------------------- /lib/pact.rb: -------------------------------------------------------------------------------- 1 | require 'pact/support' 2 | require 'pact/version' 3 | require 'pact/configuration' 4 | require 'pact/consumer' 5 | require 'pact/provider' 6 | require 'pact/consumer_contract' 7 | -------------------------------------------------------------------------------- /lib/pact/cli.rb: -------------------------------------------------------------------------------- 1 | require 'thor' 2 | require 'pact/consumer/configuration' 3 | require 'pact/provider/configuration' 4 | 5 | module Pact 6 | class CLI < Thor 7 | def self.exit_on_failure? # Thor 1.0 deprecation guard 8 | false 9 | end 10 | 11 | desc 'verify', "Verify a pact" 12 | method_option :pact_helper, aliases: "-h", desc: "Pact helper file", :required => true 13 | method_option :pact_uri, aliases: "-p", desc: "Pact URI" 14 | method_option :ignore_failures, type: :boolean, default: false, desc: "Process will always exit with exit code 0", hide: true 15 | method_option :pact_broker_username, aliases: "-u", desc: "Pact broker user name" 16 | method_option :pact_broker_password, aliases: "-w", desc: "Pact broker password" 17 | method_option :pact_broker_token, aliases: "-k", desc: "Pact broker token" 18 | method_option :backtrace, aliases: "-b", desc: "Show full backtrace", :default => false, :type => :boolean 19 | method_option :verbose, aliases: "-v", desc: "Show verbose HTTP logging", :default => false, :type => :boolean 20 | method_option :interactions_replay_order, aliases: "-o", 21 | desc: "Interactions replay order: randomised or recorded (default)", 22 | default: Pact.configuration.interactions_replay_order 23 | method_option :description, aliases: "-d", desc: "Interaction description filter" 24 | method_option :provider_state, aliases: "-s", desc: "Provider state filter" 25 | method_option :interaction_index, type: :numeric, desc: "Index filter" 26 | method_option :pact_broker_interaction_id, desc: "Pact Broker interaction ID filter" 27 | method_option :format, aliases: "-f", banner: "FORMATTER", desc: "RSpec formatter. Defaults to custom Pact formatter. [j]son may also be used." 28 | method_option :out, aliases: "-o", banner: "FILE", desc: "Write output to a file instead of $stdout." 29 | 30 | def verify 31 | require 'pact/cli/run_pact_verification' 32 | Cli::RunPactVerification.call(options) 33 | end 34 | 35 | desc 'docs', "Generate Pact documentation in markdown" 36 | method_option :pact_dir, desc: "Directory containing the pacts", default: Pact.configuration.pact_dir 37 | method_option :doc_dir, desc: "Documentation directory", default: Pact.configuration.doc_dir 38 | 39 | def docs 40 | require 'pact/cli/generate_pact_docs' 41 | require 'pact/doc/generator' 42 | Pact::Doc::Generate.call(options[:pact_dir], options[:doc_dir], [Pact::Doc::Markdown::Generator]) 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/pact/cli/generate_pact_docs.rb: -------------------------------------------------------------------------------- 1 | require 'pact/doc/doc_file' 2 | require 'pact/doc/generate' 3 | require 'pact/doc/markdown/generator' 4 | require 'pact/consumer' 5 | -------------------------------------------------------------------------------- /lib/pact/cli/spec_criteria.rb: -------------------------------------------------------------------------------- 1 | module Pact 2 | module Cli 3 | class SpecCriteria 4 | 5 | def self.call options 6 | criteria = {} 7 | 8 | criteria[:description] = Regexp.new(options[:description]) if options[:description] 9 | criteria[:_id] = options[:pact_broker_interaction_id] if options[:pact_broker_interaction_id] 10 | criteria[:index] = options[:interaction_index] if options[:interaction_index] 11 | 12 | provider_state = options[:provider_state] 13 | 14 | if provider_state 15 | if provider_state.length == 0 16 | criteria[:provider_state] = nil #Allow PACT_PROVIDER_STATE="" to mean no provider state 17 | else 18 | criteria[:provider_state] = Regexp.new(provider_state) 19 | end 20 | end 21 | 22 | criteria 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/pact/consumer.rb: -------------------------------------------------------------------------------- 1 | require 'pact/consumer_contract' 2 | require 'pact/consumer/configuration' 3 | require 'pact/consumer/consumer_contract_builder' 4 | require 'pact/consumer/consumer_contract_builders' 5 | require 'pact/consumer/interaction_builder' 6 | require 'pact/term' 7 | require 'pact/something_like' 8 | -------------------------------------------------------------------------------- /lib/pact/consumer/configuration.rb: -------------------------------------------------------------------------------- 1 | require 'pact/configuration' 2 | require 'pact/consumer/consumer_contract_builders' 3 | require 'pact/consumer/consumer_contract_builder' 4 | require 'pact/consumer/configuration/service_consumer' 5 | require 'pact/consumer/configuration/service_provider' 6 | require 'pact/consumer/configuration/dsl' 7 | require 'pact/consumer/configuration/configuration_extensions' 8 | 9 | Pact.send(:extend, Pact::Consumer::DSL) 10 | Pact::Configuration.send(:include, Pact::Consumer::Configuration::ConfigurationExtensions) -------------------------------------------------------------------------------- /lib/pact/consumer/configuration/dsl.rb: -------------------------------------------------------------------------------- 1 | require 'pact/consumer/configuration/service_consumer' 2 | 3 | module Pact 4 | module Consumer 5 | module DSL 6 | def service_consumer name, &block 7 | Configuration::ServiceConsumer.build(name, &block) 8 | end 9 | end 10 | end 11 | end -------------------------------------------------------------------------------- /lib/pact/consumer/configuration/service_consumer.rb: -------------------------------------------------------------------------------- 1 | require 'pact/shared/dsl' 2 | 3 | module Pact 4 | module Consumer 5 | module Configuration 6 | class ServiceConsumer 7 | 8 | extend Pact::DSL 9 | 10 | attr_accessor :app, :port, :name 11 | 12 | def initialize name 13 | @name = name 14 | @app = nil 15 | @port = nil 16 | end 17 | 18 | dsl do 19 | def app app 20 | self.app = app 21 | end 22 | 23 | def port port 24 | self.port = port 25 | end 26 | 27 | def has_pact_with service_provider_name, &block 28 | ServiceProvider.build(service_provider_name, name, &block) 29 | end 30 | end 31 | 32 | def finalize 33 | validate 34 | register_consumer_app if @app 35 | end 36 | 37 | private 38 | 39 | def validate 40 | raise "Please provide a consumer name" unless (name && !name.empty?) 41 | raise "Please provide a port for the consumer app" if app && !port 42 | end 43 | 44 | 45 | def register_consumer_app 46 | Pact::MockService::AppManager.instance.register app, port 47 | end 48 | end 49 | end 50 | end 51 | end -------------------------------------------------------------------------------- /lib/pact/consumer/configuration/service_provider.rb: -------------------------------------------------------------------------------- 1 | require 'pact/shared/dsl' 2 | require 'pact/consumer/configuration/mock_service' 3 | 4 | module Pact 5 | module Consumer 6 | module Configuration 7 | class ServiceProvider 8 | 9 | extend Pact::DSL 10 | 11 | attr_accessor :service, :consumer_name, :name 12 | 13 | def initialize name, consumer_name 14 | @name = name 15 | @service = nil 16 | @consumer_name = consumer_name 17 | end 18 | 19 | dsl do 20 | def mock_service name, &block 21 | self.service = MockService.build(name, consumer_name, self.name, &block) 22 | end 23 | end 24 | 25 | def finalize 26 | validate 27 | end 28 | 29 | private 30 | 31 | def validate 32 | raise "Please configure a service for #{name}" unless service 33 | end 34 | 35 | end 36 | 37 | end 38 | end 39 | 40 | end -------------------------------------------------------------------------------- /lib/pact/consumer/consumer_contract_builders.rb: -------------------------------------------------------------------------------- 1 | module Pact 2 | module Consumer 3 | module ConsumerContractBuilders 4 | # Placeholder for mock service methods which will be dynamically created 5 | # from the Pact configuration. 6 | 7 | # To be included in RSpec 8 | end 9 | end 10 | end -------------------------------------------------------------------------------- /lib/pact/consumer/interaction_builder.rb: -------------------------------------------------------------------------------- 1 | require 'net/http' 2 | require 'pact/reification' 3 | require 'pact/consumer_contract/interaction' 4 | 5 | module Pact 6 | module Consumer 7 | class InteractionBuilder 8 | 9 | attr_reader :interaction 10 | 11 | def initialize &block 12 | @interaction = Interaction.new 13 | @callback = block 14 | end 15 | 16 | def without_writing_to_pact 17 | interaction.metadata ||= {} 18 | interaction.metadata[:write_to_pact] = false 19 | self 20 | end 21 | 22 | def upon_receiving description 23 | @interaction.description = description 24 | self 25 | end 26 | 27 | def given provider_state 28 | @interaction.provider_state = provider_state.nil? ? nil : provider_state.to_s 29 | self 30 | end 31 | 32 | def with(request_details) 33 | interaction.request = Pact::Request::Expected.from_hash(request_details) 34 | self 35 | end 36 | 37 | def will_respond_with(response) 38 | interaction.response = Pact::Response.new(response) 39 | @callback.call interaction 40 | self 41 | end 42 | 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/pact/consumer/rspec.rb: -------------------------------------------------------------------------------- 1 | require 'pact/consumer' 2 | require 'pact/consumer/spec_hooks' 3 | require 'pact/rspec' 4 | require 'pact/helpers' 5 | 6 | module Pact 7 | module Consumer 8 | module RSpec 9 | include Pact::Consumer::ConsumerContractBuilders 10 | include Pact::Helpers 11 | end 12 | end 13 | end 14 | 15 | hooks = Pact::Consumer::SpecHooks.new 16 | 17 | RSpec.configure do |config| 18 | config.include Pact::Consumer::RSpec, :pact => true 19 | 20 | config.before :all, :pact => true do 21 | hooks.before_all 22 | end 23 | 24 | config.before :each, :pact => true do | example | 25 | hooks.before_each Pact::RSpec.full_description(example) 26 | end 27 | 28 | config.after :each, :pact => true do | example | 29 | hooks.after_each Pact::RSpec.full_description(example) 30 | end 31 | 32 | config.after :suite do 33 | hooks.after_suite 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/pact/consumer/spec_hooks.rb: -------------------------------------------------------------------------------- 1 | require 'pact/doc/generate' 2 | require 'pact/consumer/world' 3 | require 'pact/mock_service/app_manager' 4 | require 'pact/mock_service/client' 5 | 6 | module Pact 7 | module Consumer 8 | class SpecHooks 9 | 10 | def before_all 11 | Pact::MockService::AppManager.instance.spawn_all 12 | FileUtils.mkdir_p Pact.configuration.pact_dir 13 | end 14 | 15 | def before_each example_description 16 | Pact.consumer_world.register_pact_example_ran 17 | Pact.configuration.logger.info "Clearing all expectations" 18 | Pact::MockService::AppManager.instance.urls_of_mock_services.each do | url | 19 | Pact::MockService::Client.clear_interactions url, example_description 20 | end 21 | end 22 | 23 | def after_each example_description 24 | Pact.configuration.logger.info "Verifying interactions for #{example_description}" 25 | Pact.configuration.provider_verifications.each do | provider_verification | 26 | provider_verification.call example_description 27 | end 28 | end 29 | 30 | def after_suite 31 | if Pact.consumer_world.any_pact_examples_ran? 32 | Pact.consumer_world.consumer_contract_builders.each(&:write_pact) 33 | Pact::Doc::Generate.call 34 | Pact::MockService::AppManager.instance.kill_all 35 | Pact::MockService::AppManager.instance.clear_all 36 | end 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/pact/consumer/world.rb: -------------------------------------------------------------------------------- 1 | module Pact 2 | 3 | def self.consumer_world 4 | @consumer_world ||= Pact::Consumer::World.new 5 | end 6 | 7 | # internal api, for testing only 8 | def self.clear_consumer_world 9 | @consumer_world = nil 10 | end 11 | 12 | module Consumer 13 | class World 14 | 15 | def initialize 16 | @any_pact_examples_ran = false 17 | end 18 | 19 | def consumer_contract_builders 20 | @consumer_contract_builders ||= [] 21 | end 22 | 23 | def add_consumer_contract_builder consumer_contract_builder 24 | consumer_contract_builders << consumer_contract_builder 25 | end 26 | 27 | def register_pact_example_ran 28 | @any_pact_examples_ran = true 29 | end 30 | 31 | def any_pact_examples_ran? 32 | @any_pact_examples_ran 33 | end 34 | 35 | end 36 | end 37 | end -------------------------------------------------------------------------------- /lib/pact/doc/README.md: -------------------------------------------------------------------------------- 1 | # How to roll your own Doc Generator 2 | 3 | 1. Create a ConsumerContractRenderer that responds to `call` and accepts a `ConsumerContract` (this is the name for the domain model of a "pact"). This should return a String. For an example, see the [Markdown::ConsumerContractRenderer][consumer_contract_renderer]. 4 | 2. Create an IndexRenderer. This allows you to create an index file for your docs. It should respond to `call` and accept the String name of the consumer, and a hash of Hash of `pact title => file_name`, and return a String. For an example, see the [Markdown::IndexRenderer][index_renderer]. 5 | 3. Create a Generator. This is responsible for the overall file generating and writing process. Copy the [Markdown::Generator][generator] and configure it with your own ConsumerContractRenderer, IndexRenderer and file details. 6 | 7 | If you would like to generate HTML documentation, see how the [HTMLPactRenderer][html_pact_renderer] in the Pact Broker does it. 8 | 9 | [consumer_contract_renderer]: https://github.com/pact-foundation/pact-ruby/blob/master/lib/pact/doc/markdown/consumer_contract_renderer.rb 10 | [index_renderer]: https://github.com/pact-foundation/pact-ruby/blob/master/lib/pact/doc/markdown/index_renderer.rb 11 | [generator]: https://github.com/pact-foundation/pact-ruby/blob/master/lib/pact/doc/markdown/generator.rb 12 | [html_pact_renderer]: https://github.com/pact-foundation/pact_broker/blob/master/lib/pact_broker/api/renderers/html_pact_renderer.rb 13 | 14 | -------------------------------------------------------------------------------- /lib/pact/doc/doc_file.rb: -------------------------------------------------------------------------------- 1 | module Pact 2 | module Doc 3 | 4 | class DocFile 5 | 6 | def initialize consumer_contract, dir, consumer_contract_renderer, file_extension 7 | @dir = dir 8 | @consumer_contract = consumer_contract 9 | @consumer_contract_renderer = consumer_contract_renderer 10 | @file_extension = file_extension 11 | end 12 | 13 | def write 14 | File.open(path, "w") { |io| io << doc_file_contents } 15 | end 16 | 17 | def title 18 | consumer_contract.provider.name 19 | end 20 | 21 | def name 22 | "#{consumer_contract.consumer.name} - #{consumer_contract.provider.name}#{file_extension}" 23 | end 24 | 25 | private 26 | 27 | attr_reader :dir, :consumer_contract, :consumer_contract_renderer, :file_extension 28 | 29 | 30 | def path 31 | File.join(dir, name) 32 | end 33 | 34 | def doc_file_contents 35 | consumer_contract_renderer.call(consumer_contract) 36 | end 37 | 38 | end 39 | end 40 | end -------------------------------------------------------------------------------- /lib/pact/doc/generate.rb: -------------------------------------------------------------------------------- 1 | module Pact 2 | module Doc 3 | class Generate 4 | 5 | def self.call pact_dir = Pact.configuration.pact_dir, doc_dir = Pact.configuration.doc_dir, doc_generators = Pact.configuration.doc_generators 6 | doc_generators.each{| doc_generator| doc_generator.call pact_dir, doc_dir } 7 | end 8 | 9 | end 10 | end 11 | end -------------------------------------------------------------------------------- /lib/pact/doc/generator.rb: -------------------------------------------------------------------------------- 1 | require 'pact/doc/doc_file' 2 | require 'fileutils' 3 | 4 | module Pact 5 | module Doc 6 | 7 | class Generator 8 | 9 | def initialize pact_dir, doc_dir, options 10 | @doc_dir = doc_dir 11 | @pact_dir = pact_dir 12 | @consumer_contract_renderer = options[:consumer_contract_renderer] 13 | @doc_type = options[:doc_type] 14 | @file_extension = options[:file_extension] 15 | @index_renderer = options[:index_renderer] 16 | @index_name = options[:index_name] 17 | @after = options.fetch(:after, lambda{|pact_dir, target_dir, consumer_contracts| }) 18 | end 19 | 20 | def call 21 | ensure_target_dir_exists_and_is_clean 22 | write_index if consumer_contracts.any? 23 | write_doc_files 24 | perform_after_hook 25 | end 26 | 27 | private 28 | 29 | attr_reader :doc_dir, :pact_dir, :consumer_contract_renderer, :doc_type, :file_extension, :index_renderer, :after 30 | 31 | def write_index 32 | File.open(index_file_path, "w") { |io| io << index_file_contents } 33 | end 34 | 35 | def index_file_path 36 | File.join(target_dir, "#{@index_name}#{file_extension}") 37 | end 38 | 39 | def index_file_contents 40 | index_renderer.call(consumer_contracts.first.consumer.name, index_data) 41 | end 42 | 43 | def index_data 44 | doc_files.each_with_object({}) do | doc_file, data | 45 | data[doc_file.title] = doc_file.name 46 | end 47 | end 48 | 49 | def write_doc_files 50 | doc_files.each(&:write) 51 | end 52 | 53 | def doc_files 54 | consumer_contracts.collect do | consumer_contract | 55 | DocFile.new(consumer_contract, target_dir, consumer_contract_renderer, file_extension) 56 | end 57 | end 58 | 59 | def consumer_contracts 60 | @consumer_contracts ||= begin 61 | Dir.glob("#{pact_dir}/**").collect do |file| 62 | Pact::ConsumerContract.from_uri file 63 | end 64 | end 65 | end 66 | 67 | def perform_after_hook 68 | after.call(pact_dir, target_dir, consumer_contracts) 69 | end 70 | 71 | def ensure_target_dir_exists_and_is_clean 72 | FileUtils.rm_rf target_dir 73 | FileUtils.mkdir_p target_dir 74 | end 75 | 76 | def target_dir 77 | File.join(doc_dir, doc_type) 78 | end 79 | 80 | end 81 | end 82 | end -------------------------------------------------------------------------------- /lib/pact/doc/markdown/consumer_contract_renderer.rb: -------------------------------------------------------------------------------- 1 | require 'pact/doc/markdown/interaction_renderer' 2 | require 'pact/doc/sort_interactions' 3 | 4 | module Pact 5 | module Doc 6 | module Markdown 7 | class ConsumerContractRenderer 8 | 9 | def initialize consumer_contract 10 | @consumer_contract = consumer_contract 11 | end 12 | 13 | def self.call consumer_contract 14 | new(consumer_contract).call 15 | end 16 | 17 | def call 18 | title + summaries_title + summaries.join + interactions_title + full_interactions.join 19 | end 20 | 21 | private 22 | 23 | attr_reader :consumer_contract 24 | 25 | def title 26 | "### A pact between #{consumer_name} and #{provider_name}\n\n" 27 | end 28 | 29 | def interaction_renderers 30 | @interaction_renderers ||= sorted_interactions.collect{|interaction| InteractionRenderer.new interaction, @consumer_contract} 31 | end 32 | 33 | def summaries_title 34 | "#### Requests from #{consumer_name} to #{provider_name}\n\n" 35 | end 36 | 37 | def interactions_title 38 | "#### Interactions\n\n" 39 | end 40 | 41 | def summaries 42 | interaction_renderers.collect(&:render_summary) 43 | end 44 | 45 | def full_interactions 46 | interaction_renderers.collect(&:render_full_interaction) 47 | end 48 | 49 | def sorted_interactions 50 | SortInteractions.call(consumer_contract.interactions) 51 | end 52 | 53 | def consumer_name 54 | markdown_escape consumer_contract.consumer.name 55 | end 56 | 57 | def provider_name 58 | markdown_escape consumer_contract.provider.name 59 | end 60 | 61 | def markdown_escape string 62 | string.gsub('*','\*').gsub('_','\_') 63 | end 64 | 65 | end 66 | end 67 | end 68 | end -------------------------------------------------------------------------------- /lib/pact/doc/markdown/generator.rb: -------------------------------------------------------------------------------- 1 | require 'pact/doc/generator' 2 | require 'pact/doc/markdown/consumer_contract_renderer' 3 | require 'pact/doc/markdown/index_renderer' 4 | 5 | module Pact 6 | module Doc 7 | module Markdown 8 | class Generator < Pact::Doc::Generator 9 | 10 | def initialize pact_dir, doc_dir 11 | super(pact_dir, doc_dir, 12 | consumer_contract_renderer: ConsumerContractRenderer, 13 | doc_type: 'markdown', 14 | file_extension: '.md', 15 | index_renderer: IndexRenderer, 16 | index_name: 'README') 17 | end 18 | 19 | def self.call pact_dir, doc_dir 20 | new(pact_dir, doc_dir).call 21 | end 22 | 23 | end 24 | end 25 | end 26 | end -------------------------------------------------------------------------------- /lib/pact/doc/markdown/index_renderer.rb: -------------------------------------------------------------------------------- 1 | require 'erb' 2 | 3 | module Pact 4 | module Doc 5 | module Markdown 6 | class IndexRenderer 7 | 8 | attr_reader :consumer_name 9 | attr_reader :docs # Hash of pact title => file_name 10 | 11 | def initialize consumer_name, docs 12 | @consumer_name = consumer_name 13 | @docs = docs 14 | end 15 | 16 | def self.call consumer_name, docs 17 | new(consumer_name, docs).call 18 | end 19 | 20 | def call 21 | title + "\n\n" + table_of_contents + "\n" 22 | end 23 | 24 | private 25 | 26 | def table_of_contents 27 | docs.collect do | title, file_name | 28 | item title, file_name 29 | end.join("\n") 30 | end 31 | 32 | def title 33 | "### Pacts for #{consumer_name}" 34 | end 35 | 36 | def item title, file_name 37 | "* [#{title}](#{ERB::Util.url_encode(file_name)})" 38 | end 39 | 40 | end 41 | end 42 | end 43 | end -------------------------------------------------------------------------------- /lib/pact/doc/markdown/interaction.erb: -------------------------------------------------------------------------------- 1 | 2 | <%= if interaction.has_provider_state? 3 | "Given **#{interaction.provider_state}**, upon receiving" 4 | else 5 | "Upon receiving" 6 | end 7 | %> **<%= interaction.description %>** from <%= interaction.consumer_name %>, with 8 | ```json 9 | <%= interaction.request %> 10 | ``` 11 | <%= interaction.provider_name %> will respond with: 12 | ```json 13 | <%= interaction.response %> 14 | ``` 15 | -------------------------------------------------------------------------------- /lib/pact/doc/markdown/interaction_renderer.rb: -------------------------------------------------------------------------------- 1 | require 'erb' 2 | require 'pact/doc/interaction_view_model' 3 | 4 | module Pact 5 | module Doc 6 | module Markdown 7 | class InteractionRenderer 8 | 9 | attr_reader :interaction 10 | 11 | def initialize interaction, pact 12 | @interaction = InteractionViewModel.new(interaction, pact) 13 | end 14 | 15 | def render_summary 16 | suffix = interaction.has_provider_state? ? " given #{interaction.provider_state}" : "" 17 | "* [#{interaction.description(true)}](##{interaction.id})#{suffix}\n\n" 18 | end 19 | 20 | def render_full_interaction 21 | render('/interaction.erb') 22 | end 23 | 24 | def render template_file 25 | ERB.new(template_string(template_file)).result(binding) 26 | end 27 | 28 | # The template file is written with only ASCII range characters, so we 29 | # can read as UTF-8. But rendered strings must have same encoding as 30 | # script encoding because it will joined to strings which are produced by 31 | # string literal. 32 | def template_string(template_file) 33 | File.read(template_contents(template_file), external_encoding: Encoding::UTF_8).force_encoding(__ENCODING__) 34 | end 35 | 36 | def template_contents(template_file) 37 | File.dirname(__FILE__) + template_file 38 | end 39 | 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/pact/doc/sort_interactions.rb: -------------------------------------------------------------------------------- 1 | module Pact 2 | module Doc 3 | class SortInteractions 4 | 5 | def self.call interactions 6 | interactions.sort_by { |interaction| sortable_id(interaction) } 7 | end 8 | 9 | private 10 | 11 | def self.sortable_id interaction 12 | "#{interaction.description.downcase} #{interaction.response.status} #{(interaction.provider_state || '').downcase}" 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/pact/hal/authorization_header_redactor.rb: -------------------------------------------------------------------------------- 1 | require 'delegate' 2 | 3 | module Pact 4 | module Hal 5 | class AuthorizationHeaderRedactor < SimpleDelegator 6 | def puts(*args) 7 | __getobj__().puts(*redact_args(args)) 8 | end 9 | 10 | def print(*args) 11 | __getobj__().puts(*redact_args(args)) 12 | end 13 | 14 | def <<(*args) 15 | __getobj__().send(:<<, *redact_args(args)) 16 | end 17 | 18 | private 19 | 20 | attr_reader :redactions 21 | 22 | def redact_args(args) 23 | args.collect{ | s| redact(s) } 24 | end 25 | 26 | def redact(string) 27 | return string unless string.is_a?(String) 28 | string.gsub(/Authorization: .*\\r\\n/, "Authorization: [redacted]\\r\\n") 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/pact/hal/non_json_entity.rb: -------------------------------------------------------------------------------- 1 | module Pact 2 | module Hal 3 | class NonJsonEntity 4 | def initialize(href, body, http_client, response = nil) 5 | @href = href 6 | @body = body 7 | @client = http_client 8 | @response = response 9 | end 10 | 11 | def success? 12 | true 13 | end 14 | 15 | def response 16 | @response 17 | end 18 | 19 | def body 20 | @body 21 | end 22 | 23 | def assert_success! 24 | self 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/pact/hash_refinements.rb: -------------------------------------------------------------------------------- 1 | module Pact 2 | module HashRefinements 3 | refine Hash do 4 | def compact 5 | h = {} 6 | each do |key, value| 7 | h[key] = value unless value == nil 8 | end 9 | h 10 | end unless Hash.method_defined? :compact 11 | 12 | def compact! 13 | reject! {|_key, value| value == nil} 14 | end unless Hash.method_defined? :compact! 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/pact/pact_broker.rb: -------------------------------------------------------------------------------- 1 | require 'pact/pact_broker/fetch_pacts' 2 | require 'pact/pact_broker/fetch_pact_uris_for_verification' 3 | require 'pact/provider/pact_uri' 4 | 5 | # 6 | # @public Used by Pact Provider Verifier 7 | # 8 | module Pact 9 | module PactBroker 10 | extend self 11 | 12 | # Keep for backwards compatibility with pact-provider-verifier < 1.23.1 13 | def fetch_pact_uris *args 14 | Pact::PactBroker::FetchPacts.call(*args).collect(&:uri) 15 | end 16 | 17 | def fetch_pact_uris_for_verification *args 18 | Pact::PactBroker::FetchPactURIsForVerification.call(*args) 19 | end 20 | 21 | def build_pact_uri(*args) 22 | Pact::Provider::PactURI.new(*args) 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/pact/pact_broker/notices.rb: -------------------------------------------------------------------------------- 1 | module Pact 2 | module PactBroker 3 | class Notices < Array 4 | def before_verification_notices 5 | select { | notice | notice[:when].nil? || notice[:when].start_with?('before_verification') } 6 | end 7 | 8 | def before_verification_notices_text 9 | before_verification_notices.collect{ | notice | notice[:text] } 10 | end 11 | 12 | def after_verification_notices(success, published) 13 | select { | notice | notice[:when] == "after_verification:success_#{success}_published_#{published}" || notice[:when] == "after_verification" } 14 | .collect do | notice | 15 | notice.merge(:when => simplify_notice_when(notice[:when])) 16 | end 17 | end 18 | 19 | def after_verification_notices_text(success, published) 20 | after_verification_notices(success, published).collect{ | notice | notice[:text] } 21 | end 22 | 23 | def all_notices(success, published) 24 | before_verification_notices + after_verification_notices(success, published) 25 | end 26 | 27 | private 28 | 29 | def simplify_notice_when(when_key) 30 | when_key.split(":").first 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/pact/project_root.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | 3 | module Pact 4 | def self.project_root 5 | @project_root ||= Pathname.new(File.expand_path('../../../',__FILE__)).freeze 6 | end 7 | end -------------------------------------------------------------------------------- /lib/pact/provider.rb: -------------------------------------------------------------------------------- 1 | require 'pact/configuration' 2 | require 'pact/provider/configuration' 3 | require 'pact/provider/world' 4 | -------------------------------------------------------------------------------- /lib/pact/provider/configuration.rb: -------------------------------------------------------------------------------- 1 | require 'pact/provider/configuration/dsl' 2 | require 'pact/provider/configuration/configuration_extension' 3 | require 'pact/provider/state/provider_state' 4 | 5 | Pact.send(:extend, Pact::Provider::DSL) 6 | Pact.send(:extend, Pact::Provider::State::DSL) 7 | Pact::Configuration.send(:include, Pact::Provider::Configuration::ConfigurationExtension) -------------------------------------------------------------------------------- /lib/pact/provider/configuration/configuration_extension.rb: -------------------------------------------------------------------------------- 1 | require 'pact/provider/state/provider_state' 2 | require 'pact/provider/state/provider_state_configured_modules' 3 | require 'pact/provider/state/set_up' 4 | require 'pact/provider/state/tear_down' 5 | 6 | module Pact 7 | 8 | module Provider 9 | 10 | module Configuration 11 | 12 | module ConfigurationExtension 13 | 14 | attr_accessor :provider_application_version 15 | 16 | def provider= provider 17 | @provider = provider 18 | end 19 | 20 | def provider 21 | if defined? @provider 22 | @provider 23 | else 24 | raise "Please configure your provider. See the Provider section in the README for examples." 25 | end 26 | end 27 | 28 | def config_ru_path 29 | @config_ru_path ||= './config.ru' 30 | end 31 | 32 | def config_ru_path= config_ru_path 33 | @config_ru_path = config_ru_path 34 | end 35 | 36 | def interactions_replay_order 37 | @interactions_replay_order ||= :recorded #or :random 38 | end 39 | 40 | def interactions_replay_order= interactions_replay_order 41 | @interactions_replay_order = interactions_replay_order.to_sym 42 | end 43 | 44 | def provider_state_set_up 45 | @provider_state_set_up ||= Pact::Provider::State::SetUp 46 | end 47 | 48 | def provider_state_set_up= provider_state_set_up 49 | @provider_state_set_up = provider_state_set_up 50 | end 51 | 52 | def provider_state_tear_down 53 | @provider_state_tear_down ||= Pact::Provider::State::TearDown 54 | end 55 | 56 | def provider_state_tear_down= provider_state_tear_down 57 | @provider_state_tear_down = provider_state_tear_down 58 | end 59 | 60 | def include mod 61 | Pact::Provider::State::ProviderStateConfiguredModules.instance_eval do 62 | include mod 63 | end 64 | end 65 | 66 | end 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/pact/provider/configuration/dsl.rb: -------------------------------------------------------------------------------- 1 | require 'pact/provider/configuration/service_provider_dsl' 2 | require 'pact/provider/configuration/message_provider_dsl' 3 | 4 | module Pact 5 | 6 | module Provider 7 | 8 | module DSL 9 | def service_provider name, &block 10 | Configuration::ServiceProviderDSL.build(name, &block) 11 | end 12 | 13 | def message_provider name, &block 14 | Configuration::MessageProviderDSL.build(name, &block) 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/pact/provider/configuration/message_provider_dsl.rb: -------------------------------------------------------------------------------- 1 | require 'pact/provider/configuration/service_provider_dsl' 2 | 3 | module Pact 4 | module Provider 5 | module Configuration 6 | class MessageProviderDSL < ServiceProviderDSL 7 | class RackToMessageAdapter 8 | def initialize(message_builder) 9 | @message_builder = message_builder 10 | end 11 | 12 | def call(env) 13 | request_body_json = JSON.parse(env['rack.input'].read) 14 | contents = @message_builder.call(request_body_json['description']) 15 | [200, {"Content-Type" => "application/json"}, [{ contents: contents }.to_json]] 16 | end 17 | end 18 | 19 | def initialize name 20 | super 21 | @mapper_block = lambda { |args| } 22 | end 23 | 24 | dsl do 25 | def app &block 26 | self.app_block = block 27 | end 28 | 29 | def app_version application_version 30 | self.application_version = application_version 31 | end 32 | 33 | def app_version_tags tags 34 | self.tags = tags 35 | end 36 | 37 | def app_version_branch branch 38 | self.branch = branch 39 | end 40 | 41 | def publish_verification_results publish_verification_results 42 | self.publish_verification_results = publish_verification_results 43 | Pact::RSpec.with_rspec_2 do 44 | Pact.configuration.error_stream.puts "WARN: Publishing of verification results is currently not supported with rspec 2. If you would like this functionality, please feel free to submit a PR!" 45 | end 46 | end 47 | 48 | def honours_pact_with consumer_name, options = {}, &block 49 | create_pact_verification consumer_name, options, &block 50 | end 51 | 52 | def honours_pacts_from_pact_broker &block 53 | create_pact_verification_from_broker(&block) 54 | end 55 | 56 | def builder &block 57 | self.app_block = lambda { RackToMessageAdapter.new(block) } 58 | end 59 | end 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/pact/provider/configuration/pact_verification.rb: -------------------------------------------------------------------------------- 1 | require 'pact/provider/pact_verification' 2 | require 'pact/provider/pact_uri' 3 | require 'pact/shared/dsl' 4 | require 'pact/provider/world' 5 | 6 | module Pact 7 | module Provider 8 | 9 | module Configuration 10 | 11 | class PactVerification 12 | 13 | extend Pact::DSL 14 | 15 | attr_accessor :consumer_name, :pact_uri, :ref 16 | 17 | def initialize consumer_name, options = {} 18 | @consumer_name = consumer_name 19 | @ref = options.fetch(:ref, :head) 20 | @pact_uri = nil 21 | end 22 | 23 | dsl do 24 | def pact_uri pact_uri, options = {} 25 | self.pact_uri = ::Pact::Provider::PactURI.new(pact_uri, options) if pact_uri 26 | end 27 | end 28 | 29 | def finalize 30 | validate 31 | create_pact_verification 32 | end 33 | 34 | private 35 | 36 | def create_pact_verification 37 | verification = Pact::Provider::PactVerification.new(consumer_name, pact_uri, ref) 38 | Pact.provider_world.add_pact_verification verification 39 | end 40 | 41 | def validate 42 | raise "Please provide a pact_uri for the verification" unless pact_uri 43 | end 44 | 45 | end 46 | end 47 | end 48 | end -------------------------------------------------------------------------------- /lib/pact/provider/configuration/service_provider_config.rb: -------------------------------------------------------------------------------- 1 | module Pact 2 | module Provider 3 | module Configuration 4 | class ServiceProviderConfig 5 | 6 | attr_accessor :application_version 7 | attr_reader :branch, :build_url 8 | 9 | def initialize application_version, branch, tags, publish_verification_results, build_url, &app_block 10 | @application_version = application_version 11 | @branch = branch 12 | @tags = [*tags] 13 | @publish_verification_results = publish_verification_results 14 | @app_block = app_block 15 | @build_url = build_url 16 | end 17 | 18 | def app 19 | @app_block.call 20 | end 21 | 22 | def publish_verification_results? 23 | @publish_verification_results 24 | end 25 | 26 | def tags 27 | @tags 28 | end 29 | end 30 | end 31 | end 32 | end -------------------------------------------------------------------------------- /lib/pact/provider/context.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pact-foundation/pact-ruby/789596fc2b41bd83376e25c777ae4ffe26270f15/lib/pact/provider/context.rb -------------------------------------------------------------------------------- /lib/pact/provider/help/console_text.rb: -------------------------------------------------------------------------------- 1 | require 'pact/provider/help/content' 2 | require 'fileutils' 3 | require 'pact/consumer/configuration' 4 | require 'pact/provider/help/write' 5 | require 'rainbow' 6 | 7 | module Pact 8 | module Provider 9 | module Help 10 | class ConsoleText 11 | 12 | def self.call reports_dir = Pact.configuration.reports_dir, options = {color: true} 13 | new(reports_dir || Pact.configuration.reports_dir, options).call 14 | end 15 | 16 | def initialize reports_dir, options 17 | @reports_dir = File.expand_path(reports_dir) 18 | @options = options 19 | end 20 | 21 | def call 22 | begin 23 | options[:color] ? ColorizeMarkdown.(help_text) : help_text 24 | rescue Errno::ENOENT 25 | options[:color] ? error_text_coloured : error_text_plain 26 | end 27 | end 28 | 29 | private 30 | 31 | attr_reader :reports_dir, :options 32 | 33 | def help_text 34 | File.read(help_file_path) 35 | end 36 | 37 | def help_file_path 38 | File.join(reports_dir, Write::HELP_FILE_NAME) 39 | end 40 | 41 | def error_text_plain 42 | "Sorry, could not find help file at #{help_file_path}. Please ensure you have run `rake pact:verify`.\n" + 43 | "If this does not fix the problem, please raise a github issues for this bug." 44 | end 45 | 46 | def error_text_coloured 47 | Rainbow(error_text_plain).red 48 | end 49 | 50 | class ColorizeMarkdown 51 | 52 | def self.call markdown 53 | markdown.split("\n").collect do | line | 54 | if line.start_with?("# ") 55 | yellow_underling line.gsub(/^# /, '') 56 | elsif line.start_with?("* ") 57 | green("* ") + line.gsub(/^\* /, '') 58 | else 59 | line 60 | end 61 | end.join("\n") 62 | end 63 | 64 | def self.yellow_underling string 65 | Rainbow(string).yellow.underline 66 | end 67 | 68 | def self.green string 69 | Rainbow(string).green 70 | end 71 | 72 | end 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/pact/provider/help/content.rb: -------------------------------------------------------------------------------- 1 | require 'pact/provider/help/pact_diff' 2 | 3 | module Pact 4 | module Provider 5 | module Help 6 | class Content 7 | 8 | def initialize pact_sources 9 | @pact_sources = pact_sources 10 | end 11 | 12 | def text 13 | help_text + "\n\n" + pact_diffs 14 | end 15 | 16 | private 17 | 18 | attr_reader :pact_sources 19 | 20 | def help_text 21 | temp_dir = Pact.configuration.tmp_dir 22 | log_path = Pact.configuration.log_path 23 | ERB.new(template_string).result(binding) 24 | end 25 | 26 | def template_string 27 | File.read(File.expand_path( '../../../templates/help.erb', __FILE__)) 28 | end 29 | 30 | def pact_diffs 31 | pact_sources.collect do | pact_json | 32 | PactDiff.call(pact_json) 33 | end.compact.join("\n") 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/pact/provider/help/pact_diff.rb: -------------------------------------------------------------------------------- 1 | require 'pact/hal/entity' 2 | 3 | module Pact 4 | module Provider 5 | module Help 6 | class PactDiff 7 | class PrintPactDiffError < StandardError; end 8 | 9 | attr_reader :pact_source, :output 10 | 11 | def initialize pact_source 12 | @pact_source = pact_source 13 | end 14 | 15 | def self.call pact_source 16 | new(pact_source).call 17 | end 18 | 19 | def call 20 | begin 21 | header + "\n" + get_diff 22 | rescue PrintPactDiffError => e 23 | return e.message 24 | end 25 | end 26 | 27 | private 28 | 29 | def header 30 | "The following changes have been made since the previous distinct version of this pact, and may be responsible for verification failure:\n" 31 | end 32 | 33 | def get_diff 34 | begin 35 | pact_source.hal_entity._link!("pb:diff-previous-distinct").get!(nil, "Accept" => "text/plain").body 36 | rescue StandardError => e 37 | raise PrintPactDiffError.new("Tried to retrieve diff with previous pact, but received error #{e.class} #{e.message}.") 38 | end 39 | end 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/pact/provider/help/prompt_text.rb: -------------------------------------------------------------------------------- 1 | require 'pact/consumer/configuration' 2 | require 'rainbow' 3 | require 'pathname' 4 | 5 | module Pact 6 | module Provider 7 | module Help 8 | class PromptText 9 | 10 | def self.call reports_dir = Pact.configuration.reports_dir, options = {color: Pact.configuration.color_enabled} 11 | new(reports_dir, options).call 12 | end 13 | 14 | def initialize reports_dir, options 15 | @reports_dir = File.expand_path(reports_dir) 16 | @options = options 17 | end 18 | 19 | def call 20 | options[:color] ? prompt_text_colored : prompt_text_plain 21 | end 22 | 23 | private 24 | 25 | attr_reader :reports_dir, :options 26 | 27 | def prompt_text_plain 28 | "For assistance debugging failures, run `bundle exec rake pact:verify:help#{rake_args}`\n" 29 | end 30 | 31 | def prompt_text_colored 32 | Rainbow(prompt_text_plain).yellow 33 | end 34 | 35 | def rake_args 36 | if reports_dir == Pact.configuration.default_reports_dir 37 | '' 38 | else 39 | "[#{relative_reports_dir}]" 40 | end 41 | end 42 | 43 | def relative_reports_dir 44 | Pathname.new(reports_dir).relative_path_from(Pathname.new(Dir.pwd)) 45 | end 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/pact/provider/help/write.rb: -------------------------------------------------------------------------------- 1 | require 'pact/provider/help/content' 2 | require 'fileutils' 3 | require 'pact/consumer/configuration' 4 | 5 | module Pact 6 | module Provider 7 | module Help 8 | class Write 9 | 10 | HELP_FILE_NAME = 'help.md' 11 | 12 | def self.call pact_sources, reports_dir = Pact.configuration.reports_dir 13 | new(pact_sources, reports_dir).call 14 | end 15 | 16 | def initialize pact_sources, reports_dir 17 | @pact_sources = pact_sources 18 | @reports_dir = File.expand_path(reports_dir) 19 | end 20 | 21 | def call 22 | clean_reports_dir 23 | write 24 | rescue StandardError => e 25 | Pact.configuration.error_stream.puts("ERROR: Error generating help output - #{e.class} #{e.message} \n" + e.backtrace.join("\n")) 26 | end 27 | 28 | private 29 | 30 | attr_reader :reports_dir, :pact_sources 31 | 32 | def clean_reports_dir 33 | raise "Cleaning report dir #{reports_dir} would delete project!" if reports_dir_contains_pwd 34 | FileUtils.rm_rf reports_dir 35 | FileUtils.mkdir_p reports_dir 36 | end 37 | 38 | def reports_dir_contains_pwd 39 | Dir.pwd.start_with?(reports_dir) 40 | end 41 | 42 | def write 43 | File.open(help_path, "w") { |file| file << help_text } 44 | end 45 | 46 | def help_path 47 | File.join(reports_dir, 'help.md') 48 | end 49 | 50 | def help_text 51 | Content.new(pact_sources).text 52 | end 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/pact/provider/matchers/messages.rb: -------------------------------------------------------------------------------- 1 | require 'rainbow' 2 | require 'pact/term' 3 | 4 | module Pact 5 | module Matchers 6 | module Messages 7 | 8 | def match_term_failure_message diff, actual, diff_formatter, color_enabled 9 | actual_string = String === actual ? actual : actual.to_json 10 | maybe_coloured_string = color_enabled ? Rainbow(actual_string).white : actual_string 11 | message = "Actual: #{maybe_coloured_string}\n\n" 12 | formatted_diff = diff_formatter.call(diff) 13 | message + colorize_if_enabled(formatted_diff, color_enabled) 14 | end 15 | 16 | def match_header_failure_message header_name, expected, actual 17 | "Expected header \"#{header_name}\" to #{expected_desc(expected)}, but was #{actual_desc(actual)}" 18 | end 19 | 20 | def expected_desc_for_it expected 21 | case expected 22 | when NilClass then "is nil" 23 | when Regexp 24 | "matches #{expected.inspect}" 25 | when Pact::Term 26 | "matches #{expected.matcher.inspect}" 27 | when Pact::SomethingLike 28 | "is an instance of #{Pact::Reification.from_term(expected).class}" 29 | else 30 | "equals #{expected.inspect}" 31 | end 32 | end 33 | 34 | private 35 | 36 | def colorize_if_enabled formatted_diff, color_enabled 37 | if color_enabled 38 | # RSpec wraps each line in the failure message with failure_color, turning it red. 39 | # To ensure the lines in the diff that should be white, stay white, put an 40 | # ANSI reset at the start of each line. 41 | formatted_diff.split("\n").collect{ |line|"\e[0m#{line}" }.join("\n") 42 | else 43 | formatted_diff 44 | end 45 | end 46 | 47 | def expected_desc expected 48 | case expected 49 | when NilClass then "be nil" 50 | when Regexp 51 | "match #{expected.inspect}" 52 | when Pact::Term 53 | "match #{expected.matcher.inspect}" 54 | when Pact::SomethingLike 55 | "be an instance of #{Pact::Reification.from_term(expected).class}" 56 | else 57 | "equal #{expected.inspect}" 58 | end 59 | end 60 | 61 | def actual_desc actual 62 | actual.nil? ? 'nil' : '"' + actual + '"' 63 | end 64 | end 65 | end 66 | end -------------------------------------------------------------------------------- /lib/pact/provider/pact_helper_locator.rb: -------------------------------------------------------------------------------- 1 | module Pact 2 | module Provider 3 | module PactHelperLocater 4 | PACT_HELPER_FILE_PATTERNS = [ 5 | "spec/**/*service*consumer*/pact_helper.rb", 6 | "spec/**/*consumer*/pact_helper.rb", 7 | "spec/**/pact_helper.rb", 8 | "test/**/*service*consumer*/pact_helper.rb", 9 | "test/**/*consumer*/pact_helper.rb", 10 | "test/**/pact_helper.rb", 11 | "**/pact_helper.rb" 12 | ] 13 | 14 | NO_PACT_HELPER_FOUND_MSG = "Please create a pact_helper.rb file that can be found using one of the following patterns: #{PACT_HELPER_FILE_PATTERNS.join(", ")}" 15 | 16 | def self.pact_helper_path 17 | pact_helper_search_results = [] 18 | PACT_HELPER_FILE_PATTERNS.find { | pattern | (pact_helper_search_results.concat(Dir.glob(pattern))).any? } 19 | raise NO_PACT_HELPER_FOUND_MSG if pact_helper_search_results.empty? 20 | File.join(Dir.pwd, pact_helper_search_results[0]) 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pact/provider/pact_source.rb: -------------------------------------------------------------------------------- 1 | require 'pact/consumer_contract/pact_file' 2 | require 'pact/hal/http_client' 3 | require 'pact/hal/entity' 4 | require 'pact/consumer_contract' 5 | 6 | module Pact 7 | module Provider 8 | class PactSource 9 | 10 | attr_reader :uri # PactURI class 11 | 12 | def initialize uri 13 | @uri = uri 14 | end 15 | 16 | def pact_json 17 | @pact_json ||= Pact::PactFile.read(uri.uri, uri.options) 18 | end 19 | 20 | def pact_hash 21 | @pact_hash ||= JSON.load(pact_json, nil, { max_nesting: 50 }) 22 | end 23 | 24 | def pending? 25 | uri.metadata[:pending] 26 | end 27 | 28 | def consumer_contract 29 | @consumer_contract ||= Pact::ConsumerContract.from_json(pact_json) 30 | end 31 | 32 | def hal_entity 33 | http_client_keys = [:username, :password, :token] 34 | http_client_options = uri.options.reject{ |k, _| !http_client_keys.include?(k) } 35 | http_client = Pact::Hal::HttpClient.new(http_client_options) 36 | Pact::Hal::Entity.new(uri, pact_hash, http_client) 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/pact/provider/pact_uri.rb: -------------------------------------------------------------------------------- 1 | module Pact 2 | module Provider 3 | class PactURI 4 | attr_reader :uri, :options, :metadata 5 | 6 | def initialize(uri, options = nil, metadata = nil) 7 | @uri = uri 8 | @options = options || {} 9 | @metadata = metadata || {} # make sure it's not nil if nil is passed in 10 | end 11 | 12 | def == other 13 | other.is_a?(PactURI) && 14 | uri == other.uri && 15 | options == other.options && 16 | metadata == other.metadata 17 | end 18 | 19 | def basic_auth? 20 | !!username && !!password 21 | end 22 | 23 | def username 24 | options[:username] 25 | end 26 | 27 | def password 28 | options[:password] 29 | end 30 | 31 | def to_s 32 | if basic_auth? && http_or_https_uri? 33 | begin 34 | URI(@uri).tap { |x| x.userinfo="#{username}:*****"}.to_s 35 | rescue URI::InvalidComponentError 36 | URI(@uri).tap { |x| x.userinfo="*****:*****"}.to_s 37 | end 38 | elsif personal_access_token? && http_or_https_uri? 39 | URI(@uri).tap { |x| x.userinfo="*****"}.to_s 40 | else 41 | uri 42 | end 43 | end 44 | 45 | private def personal_access_token? 46 | !!username && !password 47 | end 48 | 49 | private def http_or_https_uri? 50 | uri.start_with?('http://', 'https://') 51 | end 52 | 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/pact/provider/pact_verification.rb: -------------------------------------------------------------------------------- 1 | module Pact::Provider 2 | class PactVerification 3 | attr_reader :consumer_name, :uri, :ref 4 | def initialize consumer_name, uri, ref 5 | @consumer_name = consumer_name 6 | @uri = uri 7 | @ref = ref 8 | end 9 | 10 | def == other 11 | other.is_a?(PactVerification) && 12 | consumer_name == other.consumer_name && 13 | uri == other.uri && 14 | ref == other.ref 15 | end 16 | end 17 | end -------------------------------------------------------------------------------- /lib/pact/provider/print_missing_provider_states.rb: -------------------------------------------------------------------------------- 1 | require 'rainbow' 2 | 3 | module Pact 4 | module Provider 5 | class PrintMissingProviderStates 6 | 7 | # Hash of consumer names to array of names of missing provider states 8 | def self.call missing_provider_states, output 9 | if missing_provider_states.any? 10 | output.puts colorize(text(missing_provider_states)) 11 | end 12 | end 13 | 14 | def self.colorize string 15 | lines = string.split("\n") 16 | first_line = Rainbow(lines[0]).cyan.underline 17 | other_lines = Rainbow(lines[1..-1].join("\n")).cyan 18 | first_line + "\n" + other_lines 19 | end 20 | 21 | def self.text missing_provider_states 22 | create_provider_states_for(missing_provider_states) 23 | end 24 | 25 | def self.create_provider_states_for consumers 26 | ERB.new(template_string).result(binding) 27 | end 28 | 29 | def self.template_string 30 | File.read(File.expand_path( '../../templates/provider_state.erb', __FILE__)) 31 | end 32 | 33 | end 34 | end 35 | end -------------------------------------------------------------------------------- /lib/pact/provider/request.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'pact/reification' 3 | require 'pact/shared/null_expectation' 4 | require 'pact/generators' 5 | 6 | module Pact 7 | module Provider 8 | module Request 9 | class Replayable 10 | 11 | # See https://github.com/rack/rack/blob/e7d741c6282ca4cf4e01506f5681e6e6b14c0b32/SPEC#L87-89 12 | NO_HTTP_PREFIX = ["CONTENT-TYPE", "CONTENT-LENGTH"] 13 | 14 | def initialize expected_request, state_params = nil 15 | @expected_request = expected_request 16 | @state_params = state_params 17 | end 18 | 19 | def method 20 | expected_request.method 21 | end 22 | 23 | def path 24 | Pact::Generators.apply_generators(expected_request, "path", expected_request.full_path, @state_params) 25 | end 26 | 27 | def body 28 | case expected_request.body 29 | when String then expected_request.body 30 | when NullExpectation then '' 31 | else 32 | Pact::Generators.apply_generators(expected_request, "body", reified_body, @state_params) 33 | end 34 | end 35 | 36 | def headers 37 | request_headers = {} 38 | # https://github.com/pact-foundation/pact-ruby/pull/327 39 | request_headers.merge!('HOST' => 'localhost') if defined?(Sinatra) 40 | return request_headers if expected_request.headers.is_a?(Pact::NullExpectation) 41 | 42 | expected_request.headers.each do |key, value| 43 | request_headers[key] = Pact::Reification.from_term(value) 44 | end 45 | 46 | request_headers = Pact::Generators.apply_generators(expected_request, "header", request_headers, @state_params) 47 | request_headers.map{ |key,value| [rack_request_header_for(key), value]}.to_h 48 | end 49 | 50 | private 51 | 52 | attr_reader :expected_request 53 | 54 | def reified_body 55 | rb = Pact::Reification.from_term(expected_request.body) 56 | if rb.is_a?(String) 57 | rb 58 | else 59 | JSON.dump(rb) 60 | end 61 | end 62 | 63 | def rack_request_header_for header 64 | with_http_prefix(header.to_s.upcase).tr('-', '_') 65 | end 66 | 67 | def rack_request_value_for value 68 | Array(value).join("\n") 69 | end 70 | 71 | def with_http_prefix header 72 | NO_HTTP_PREFIX.include?(header) ? header : "HTTP_#{header}" 73 | end 74 | end 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/pact/provider/rspec/backtrace_formatter.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Core 3 | 4 | # RSpec 3 has a hardwired @system_exclusion_patterns which removes everything matching /bin\// 5 | # This causes *all* the backtrace lines to be cleaned, as rake pact:verify now shells out 6 | # to the executable `pact verify ...` 7 | # which then causes *all* the lines to be included as the BacktraceFormatter will 8 | # include all lines of the backtrace if all lines were filtered out. 9 | # This monkey patch only shows lines including bin/pact and removes the 10 | # "show all lines if no lines would otherwise be shown" logic. 11 | 12 | class BacktraceFormatter 13 | 14 | 15 | def format_backtrace(backtrace, options = {}) 16 | return backtrace if options[:full_backtrace] 17 | backtrace.map { |l| backtrace_line(l) }.compact 18 | end 19 | 20 | def backtrace_line(line) 21 | relative_path(line) unless exclude?(line) 22 | rescue SecurityError 23 | nil 24 | end 25 | 26 | def exclude?(line) 27 | return false if @full_backtrace 28 | relative_line = relative_path(line) 29 | return true unless /bin\/pact/ =~ relative_line 30 | end 31 | 32 | # Copied from Metadata so a refactor can't break this overridden class 33 | def relative_path(line) 34 | line = line.sub(File.expand_path("."), ".") 35 | line = line.sub(/\A([^:]+:\d+)$/, '\\1') 36 | return nil if line == '-e:1' 37 | line 38 | rescue SecurityError 39 | nil 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/pact/provider/rspec/calculate_exit_code.rb: -------------------------------------------------------------------------------- 1 | module Pact 2 | module Provider 3 | module RSpec 4 | module CalculateExitCode 5 | def self.call(pact_sources, failed_examples) 6 | any_non_pending_failures = pact_sources.any? do |pact_source| 7 | if pact_source.pending? 8 | nil 9 | else 10 | failed_examples.select { |e| e.metadata[:pact_source] == pact_source }.any? 11 | end 12 | end 13 | any_non_pending_failures ? 1 : 0 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/pact/provider/rspec/custom_options_file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pact-foundation/pact-ruby/789596fc2b41bd83376e25c777ae4ffe26270f15/lib/pact/provider/rspec/custom_options_file -------------------------------------------------------------------------------- /lib/pact/provider/rspec/matchers.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'pact/matchers' 3 | require 'pact/provider/matchers/messages' 4 | require 'pact/rspec' 5 | require 'pact/shared/json_differ' 6 | 7 | module Pact 8 | module RSpec 9 | module Matchers 10 | module RSpec2Delegator 11 | # For backwards compatibility with rspec-2 12 | def method_missing(method, *args, &block) 13 | if method_name == :failure_message_for_should 14 | failure_message method, *args, &block 15 | else 16 | super 17 | end 18 | end 19 | end 20 | 21 | class MatchTerm 22 | include Pact::Matchers::Messages 23 | include RSpec2Delegator 24 | 25 | def initialize expected, differ, diff_formatter, example 26 | @expected = expected 27 | @differ = differ 28 | @diff_formatter = diff_formatter 29 | @example = example 30 | end 31 | 32 | def matches? actual 33 | @actual = actual 34 | @difference = @differ.call(@expected, @actual) 35 | unless @difference.empty? 36 | Pact::RSpec.with_rspec_3 do 37 | @example.metadata[:pact_diff] = @difference 38 | end 39 | Pact::RSpec.with_rspec_2 do 40 | @example.example.metadata[:pact_diff] = @difference 41 | end 42 | end 43 | @difference.empty? 44 | end 45 | 46 | def failure_message 47 | match_term_failure_message @difference, @actual, @diff_formatter, Pact::RSpec.color_enabled? 48 | end 49 | end 50 | 51 | def match_term expected, options, example 52 | MatchTerm.new(expected, options.fetch(:with), options.fetch(:diff_formatter), example) 53 | end 54 | 55 | class MatchHeader 56 | include Pact::Matchers 57 | include Pact::Matchers::Messages 58 | include RSpec2Delegator 59 | 60 | def initialize header_name, expected 61 | @header_name = header_name 62 | @expected = expected 63 | end 64 | 65 | def matches? actual 66 | @actual = actual 67 | diff(@expected, @actual).empty? 68 | end 69 | 70 | def failure_message 71 | match_header_failure_message @header_name, @expected, @actual 72 | end 73 | end 74 | 75 | def match_header header_name, expected 76 | MatchHeader.new(header_name, expected) 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/pact/provider/state/provider_state_configured_modules.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/mocks' 2 | 3 | module Pact 4 | module Provider 5 | module State 6 | module ProviderStateConfiguredModules 7 | 8 | include ::RSpec::Mocks::ExampleMethods 9 | 10 | # Placeholder for modules configured using config.include 11 | 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/pact/provider/state/provider_state_manager.rb: -------------------------------------------------------------------------------- 1 | module Pact 2 | module Provider::State 3 | class ProviderStateManager 4 | 5 | attr_reader :provider_state_name, :params, :consumer 6 | 7 | def initialize provider_state_name, params, consumer 8 | @provider_state_name = provider_state_name 9 | @params = params 10 | @consumer = consumer 11 | end 12 | 13 | def set_up_provider_state 14 | get_global_base_provider_state.set_up(params) 15 | get_consumer_base_provider_state.set_up(params) 16 | if provider_state_name 17 | get_provider_state.set_up(params) 18 | end 19 | end 20 | 21 | def tear_down_provider_state 22 | if provider_state_name 23 | get_provider_state.tear_down(params) 24 | end 25 | get_consumer_base_provider_state.tear_down(params) 26 | get_global_base_provider_state.tear_down(params) 27 | end 28 | 29 | def get_provider_state 30 | Pact.provider_world.provider_states.get(provider_state_name, :for => consumer) 31 | end 32 | 33 | def get_consumer_base_provider_state 34 | Pact.provider_world.provider_states.get_base(:for => consumer) 35 | end 36 | 37 | def get_global_base_provider_state 38 | Pact.provider_world.provider_states.get_base 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/pact/provider/state/provider_state_proxy.rb: -------------------------------------------------------------------------------- 1 | module Pact 2 | module Provider::State 3 | class ProviderStateProxy 4 | 5 | attr_reader :missing_provider_states 6 | 7 | def initialize 8 | @missing_provider_states = {} 9 | end 10 | 11 | def get name, options = {} 12 | unless provider_state = ProviderStates.get(name, options) 13 | register_missing_provider_state name, options[:for] 14 | raise error_message name, options[:for] 15 | end 16 | provider_state 17 | end 18 | 19 | def get_base options = {} 20 | ProviderStates.get_base options 21 | end 22 | 23 | private 24 | 25 | def error_message name, consumer 26 | "Could not find provider state \"#{name}\" for consumer #{consumer}" 27 | end 28 | 29 | def register_missing_provider_state name, consumer 30 | missing_states_for(consumer) << name unless missing_states_for(consumer).include?(name) 31 | end 32 | 33 | def missing_states_for consumer 34 | @missing_provider_states[consumer] ||= [] 35 | end 36 | 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/pact/provider/state/set_up.rb: -------------------------------------------------------------------------------- 1 | require 'pact/provider/state/provider_state_manager' 2 | 3 | module Pact 4 | module Provider 5 | module State 6 | class SetUp 7 | def self.call provider_state_name, consumer, options = {} 8 | State::ProviderStateManager.new(provider_state_name, options[:params], consumer).set_up_provider_state 9 | end 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/pact/provider/state/tear_down.rb: -------------------------------------------------------------------------------- 1 | require 'pact/provider/state/provider_state_manager' 2 | 3 | module Pact 4 | module Provider 5 | module State 6 | class TearDown 7 | def self.call provider_state_name, consumer, options = {} 8 | State::ProviderStateManager.new(provider_state_name, options[:params], consumer).tear_down_provider_state 9 | end 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/pact/provider/verification_report.rb: -------------------------------------------------------------------------------- 1 | require 'pact/consumer_contract' 2 | 3 | module Pact::Provider 4 | class VerificationReport 5 | 6 | include Pact::FileName 7 | 8 | def initialize (options) 9 | @consumer = options[:consumer] 10 | @provider = options[:provider] 11 | @result = options[:result] 12 | @output = options[:output] 13 | end 14 | 15 | def to_hash 16 | { 17 | :consumer => @consumer, 18 | :provider => @provider, 19 | :result => @result, 20 | :output => @output 21 | } 22 | end 23 | 24 | def as_json options = {} 25 | to_hash 26 | end 27 | 28 | def to_json(options = {}) 29 | as_json.to_json(options) 30 | end 31 | 32 | def report_file_name 33 | file_name("#{@consumer[:name]}_#{@consumer[:ref]}", "#{@provider[:name]}_#{@provider[:ref]}") 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/pact/provider/verification_results/publish_all.rb: -------------------------------------------------------------------------------- 1 | require'pact/provider/verification_results/create' 2 | require'pact/provider/verification_results/publish' 3 | 4 | module Pact 5 | module Provider 6 | module VerificationResults 7 | class PublishAll 8 | 9 | def self.call pact_sources, test_results_hash, options = {} 10 | new(pact_sources, test_results_hash, options).call 11 | end 12 | 13 | def initialize pact_sources, test_results_hash, options = {} 14 | @pact_sources = pact_sources 15 | @test_results_hash = test_results_hash 16 | @options = options 17 | end 18 | 19 | def call 20 | verification_results.collect do | (pact_source, verification_result) | 21 | published = false 22 | begin 23 | published = Publish.call(pact_source, verification_result, { verbose: options[:verbose] }) 24 | ensure 25 | print_after_verification_notices(pact_source, verification_result, published) 26 | end 27 | end 28 | end 29 | 30 | private 31 | 32 | def verification_results 33 | pact_sources.collect do | pact_source | 34 | [pact_source, Create.call(pact_source, test_results_hash)] 35 | end 36 | end 37 | 38 | def print_after_verification_notices(pact_source, verification_result, published) 39 | if pact_source.uri.metadata[:notices] 40 | pact_source.uri.metadata[:notices].after_verification_notices_text(verification_result.success, published).each do | text | 41 | Pact.configuration.output_stream.puts "DEBUG: #{text}" 42 | end 43 | end 44 | end 45 | 46 | attr_reader :pact_sources, :test_results_hash, :options 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/pact/provider/verification_results/verification_result.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | module Pact 4 | module Provider 5 | module VerificationResults 6 | class VerificationResult 7 | attr_reader :success, :provider_application_version, :test_results_hash 8 | 9 | def initialize publishable, success, provider_application_version, test_results_hash, build_url 10 | @publishable = publishable 11 | @success = success 12 | @provider_application_version = provider_application_version 13 | @test_results_hash = test_results_hash 14 | @build_url = build_url 15 | end 16 | 17 | def publishable? 18 | @publishable 19 | end 20 | 21 | def provider_application_version_set? 22 | !!provider_application_version 23 | end 24 | 25 | def to_json(options = {}) 26 | { 27 | success: success, 28 | providerApplicationVersion: provider_application_version, 29 | testResults: test_results_hash, 30 | buildUrl: @build_url 31 | }.to_json(options) 32 | end 33 | 34 | def to_s 35 | "[success: #{success}, providerApplicationVersion: #{provider_application_version}]" 36 | end 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/pact/provider/world.rb: -------------------------------------------------------------------------------- 1 | require 'pact/provider/state/provider_state_proxy' 2 | 3 | module Pact 4 | 5 | def self.provider_world 6 | @world ||= Pact::Provider::World.new 7 | end 8 | 9 | # internal api, for testing only 10 | def self.clear_provider_world 11 | @world = nil 12 | end 13 | 14 | module Provider 15 | class World 16 | 17 | attr_accessor :pact_sources, :failed_examples, :verbose 18 | 19 | def provider_states 20 | @provider_states_proxy ||= Pact::Provider::State::ProviderStateProxy.new 21 | end 22 | 23 | def add_pact_verification verification 24 | pact_verifications << verification 25 | end 26 | 27 | def pact_verifications 28 | @pact_verifications ||= [] 29 | end 30 | 31 | def pact_urls 32 | (pact_verifications.collect(&:uri) + pact_uris_from_pact_uri_sources).compact 33 | end 34 | 35 | def add_pact_uri_source pact_uri_source 36 | pact_uri_sources << pact_uri_source 37 | end 38 | 39 | private 40 | 41 | def pact_uri_sources 42 | @pact_uri_sources ||= [] 43 | end 44 | 45 | def pact_uris_from_pact_uri_sources 46 | pact_uri_sources.collect(&:call).flatten 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/pact/retry.rb: -------------------------------------------------------------------------------- 1 | require 'pact/errors' 2 | 3 | module Pact 4 | class Retry 5 | class RescuableError 6 | UNRESCUEABLE = [Pact::Error] 7 | 8 | def self.===(e) 9 | case e 10 | when *UNRESCUEABLE then 11 | false 12 | else 13 | true 14 | end 15 | end 16 | end 17 | 18 | def self.until_true options = {} 19 | max_tries = options.fetch(:times, 3) 20 | tries = 0 21 | while true 22 | begin 23 | return yield 24 | rescue RescuableError => e 25 | tries += 1 26 | $stderr.puts "Error making request - #{e.class} #{e.message} #{e.backtrace.find {|l| l.include?('pact_provider')}}, attempt #{tries} of #{max_tries}" 27 | raise e if max_tries == tries 28 | sleep options 29 | end 30 | end 31 | end 32 | 33 | def self.sleep options 34 | Kernel.sleep options.fetch(:sleep, 5) 35 | end 36 | end 37 | end -------------------------------------------------------------------------------- /lib/pact/tasks.rb: -------------------------------------------------------------------------------- 1 | load File.expand_path('../tasks/pact.rake', File.dirname(__FILE__)) 2 | require 'pact/tasks/verification_task' 3 | -------------------------------------------------------------------------------- /lib/pact/templates/help.erb: -------------------------------------------------------------------------------- 1 | # For assistance debugging failures 2 | 3 | * The pact files have been stored locally in the following temp directory: 4 | <%= temp_dir %> 5 | 6 | * The requests and responses are logged in the following log file: 7 | <%= log_path %> 8 | 9 | * Add BACKTRACE=true to the `rake pact:verify` command to see the full backtrace 10 | 11 | * If the diff output is confusing, try using another diff formatter. 12 | The options are :unix, :embedded and :list 13 | 14 | Pact.configure do | config | 15 | config.diff_formatter = :embedded 16 | end 17 | 18 | See https://github.com/pact-foundation/pact-ruby/blob/master/documentation/configuration.md#diff_formatter for examples and more information. 19 | 20 | * Check out https://github.com/pact-foundation/pact-ruby/wiki/Troubleshooting 21 | 22 | * Ask a question on stackoverflow and tag it `pact-ruby` 23 | -------------------------------------------------------------------------------- /lib/pact/templates/provider_state.erb: -------------------------------------------------------------------------------- 1 | Could not find one or more provider states. 2 | Have you required the provider states file for this consumer in your pact_helper.rb? 3 | If you have not yet defined these states, here is a template: 4 | <% consumers.keys.each do | consumer_name | %> 5 | Pact.provider_states_for "<%= consumer_name %>" do 6 | <% consumers[consumer_name].each do | provider_state | %> 7 | provider_state "<%= provider_state %>" do 8 | set_up do 9 | # Your set up code goes here 10 | end 11 | end 12 | <% end %> 13 | end 14 | <% end %> 15 | -------------------------------------------------------------------------------- /lib/pact/utils/string.rb: -------------------------------------------------------------------------------- 1 | # Can't use refinements because of Travelling Ruby 2 | 3 | module Pact 4 | module Utils 5 | module String 6 | 7 | extend self 8 | 9 | # ripped from rubyworks/facets, thank you 10 | def camelcase(string, *separators) 11 | case separators.first 12 | when Symbol, TrueClass, FalseClass, NilClass 13 | first_letter = separators.shift 14 | end 15 | 16 | separators = ['_', '\s'] if separators.empty? 17 | 18 | str = string.dup 19 | 20 | separators.each do |s| 21 | str = str.gsub(/(?:#{s}+)([a-z])/){ $1.upcase } 22 | end 23 | 24 | case first_letter 25 | when :upper, true 26 | str = str.gsub(/(\A|\s)([a-z])/){ $1 + $2.upcase } 27 | when :lower, false 28 | str = str.gsub(/(\A|\s)([A-Z])/){ $1 + $2.downcase } 29 | end 30 | 31 | str 32 | end 33 | end 34 | end 35 | end -------------------------------------------------------------------------------- /lib/pact/version.rb: -------------------------------------------------------------------------------- 1 | # Remember to bump pact-provider-proxy when this changes major version 2 | module Pact 3 | VERSION = "1.66.1" 4 | end 5 | -------------------------------------------------------------------------------- /lib/tasks/pact.rake: -------------------------------------------------------------------------------- 1 | 2 | namespace :pact do 3 | 4 | desc "Verifies the pact files configured in the pact_helper.rb against this service provider." 5 | task :verify do 6 | 7 | require 'pact/tasks/task_helper' 8 | 9 | include Pact::TaskHelper 10 | 11 | handle_verification_failure do 12 | execute_pact_verify 13 | end 14 | end 15 | 16 | desc "Verifies the pact at the given URI against this service provider." 17 | task 'verify:at', :pact_uri do | t, args | 18 | require 'rainbow' 19 | require 'pact/tasks/task_helper' 20 | 21 | include Pact::TaskHelper 22 | 23 | abort(Rainbow("Please provide a pact URI. eg. rake pact:verify:at[../my-consumer/spec/pacts/my_consumer-my_provider.json]").red) unless args[:pact_uri] 24 | handle_verification_failure do 25 | execute_pact_verify args[:pact_uri] 26 | end 27 | end 28 | 29 | desc "Get help debugging pact:verify failures." 30 | task 'verify:help', :reports_dir do | t, args | 31 | require 'pact/provider/help/console_text' 32 | puts Pact::Provider::Help::ConsoleText.(args[:reports_dir]) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /scratchpad.rb: -------------------------------------------------------------------------------- 1 | Ideas for expectation DSL 2 | 3 | Unfortunately RSpec has already stolen the method "example". 4 | Could use: 5 | eg 6 | ex 7 | 8 | my_provider. 9 | given("a thing exists"). 10 | upon_receiving("a request for a thing").with({:method => 'get', :path => '/thing'}) 11 | will_respond_with({:body => { 12 | name: ex("Fred"), 13 | age: eg 29, 14 | mobile: eg("0415 134 234", /\d{4} \d{3} \d{3}/), 15 | dob: eg("1983-02-28", match: /\d\d\d\d-\d\d-\d\d/), 16 | driver_licence_number: eg(12345678, size: 8), 17 | children: eg([{name: 'Mary'}]) 18 | }}) 19 | 20 | eg([ {name: 'Mary'} ]) should match any array where every element has a name String 21 | eg([ {name: example('Mary', size: 4) } ]) should match any array where every element has a 4 letter name 22 | 23 | eg("Fred") could return Pact::Term.new(:matcher => /.+/, :generate => 'Fred') 24 | eg(29) could return Pact::Term.new(:matcher => /\d+/, :generate => 29) 25 | 26 | Need a way to specify a literal empty hash, rather than a hash that matches anything as {} currently does. 27 | 28 | {:something => literal({}) } 29 | {:something => actual({}) } 30 | {:something => empty_hash } 31 | 32 | # Slightly unintuitive behaviour: {} matches any hash, but [] only matches an empty array (or does now we've changed the code). Should [] match any array? How do we then specify an empty array? 33 | 34 | {:something => literal([]) } 35 | {:something => actual([]) } 36 | {:something => empty_array } 37 | 38 | 39 | Pact.build do 40 | { 41 | status: 200, 42 | headers: exactly({ 43 | 44 | }), 45 | body: including({ 46 | age: literal('12'), 47 | phoneNumber: example("0415 134 234", /\d{4} \d{3} \d{3}/), 48 | favouriteColors: 49 | }) 50 | } 51 | 52 | end 53 | -------------------------------------------------------------------------------- /script/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | unset X_PACT_DEVELOPMENT 4 | bundle exec bump ${1:-minor} --no-commit 5 | bundle exec rake generate_changelog 6 | git add CHANGELOG.md lib/pact/version.rb 7 | git commit -m "chore(release): version $(ruby -r ./lib/pact/version.rb -e "puts Pact::VERSION")" && git push 8 | bundle exec rake tag_for_release 9 | echo "Releasing from https://travis-ci.org/pact-foundation/pact-ruby" 10 | -------------------------------------------------------------------------------- /script/trigger-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -x 3 | # Script to trigger release of gem via the pact-foundation/release-gem action 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 | increment="\"${1}\"" 11 | else 12 | increment="null" 13 | fi 14 | 15 | repository_slug=$(git remote get-url $(git remote show) | cut -d':' -f2 | sed 's/\.git//') 16 | 17 | output=$(curl -v https://api.github.com/repos/${repository_slug}/dispatches \ 18 | -H 'Accept: application/vnd.github.everest-preview+json' \ 19 | -H "Authorization: Bearer $GITHUB_ACCESS_TOKEN_FOR_PF_RELEASES" \ 20 | -d "{\"event_type\": \"release-triggered\", \"client_payload\": {\"increment\": ${increment}}}" 2>&1) 21 | 22 | if ! echo "${output}" | grep "HTTP\/.* 204" > /dev/null; then 23 | echo "$output" | sed "s/${GITHUB_ACCESS_TOKEN_FOR_PF_RELEASES}/********/g" 24 | echo "Failed to trigger release" 25 | exit 1 26 | else 27 | echo "Release workflow triggered" 28 | fi 29 | 30 | echo "See https://github.com/${repository_slug}/actions?query=workflow%3A%22Release+gem%22" 31 | -------------------------------------------------------------------------------- /spec/features/foo_bar_spec.rb: -------------------------------------------------------------------------------- 1 | require 'net/http' 2 | require 'pact/consumer' 3 | require 'pact/consumer/rspec' 4 | load 'pact/consumer/world.rb' 5 | 6 | # Use this test along with `rake pact:verify:foobar` to debug end to end issues. 7 | # The Bar app is in spec/support/bar_pact_helper.rb 8 | 9 | describe "Bar", :pact => true do 10 | 11 | it "can retrieve a thing" do 12 | 13 | Pact.clear_configuration 14 | Pact.clear_consumer_world 15 | 16 | Pact.service_consumer "Foo" do 17 | has_pact_with "Bar" do 18 | mock_service :bar_service do 19 | pact_specification_version "2" 20 | host "127.0.0.1" 21 | port 4638 22 | end 23 | end 24 | end 25 | 26 | bar_service. 27 | upon_receiving("a retrieve thing request").with({ 28 | method: :get, 29 | path: '/thing' 30 | }). 31 | will_respond_with({ 32 | status: 200, 33 | headers: { 'Content-Type' => 'application/json' }, 34 | body: { 35 | name: "Thing 1" 36 | } 37 | }) 38 | 39 | bar_response = Net::HTTP.get_response(URI('http://localhost:4638/thing')) 40 | 41 | expect(bar_response.code).to eql '200' 42 | expect(JSON.parse(bar_response.body)).to eq "name" => "Thing 1" 43 | 44 | puts bar_service.write_pact 45 | end 46 | 47 | end -------------------------------------------------------------------------------- /spec/features/provider_states/zebras.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'fileutils' 3 | 4 | Pact.provider_states_for 'the-wild-beast-store' do 5 | 6 | provider_state :the_zebras_are_here do 7 | set_up do 8 | FileUtils.mkdir_p 'tmp' 9 | some_data = [{'name' => 'Jason'},{'name' => 'Sarah'}] 10 | File.open("tmp/a_mock_database.json", "w") { |file| file << some_data.to_json } 11 | end 12 | 13 | tear_down do 14 | FileUtils.rm_rf("tmp/a_mock_database.json") 15 | end 16 | end 17 | end 18 | 19 | Pact.provider_state "some other zebras are here" do 20 | set_up do 21 | some_data = [{'name' => 'Mark'},{'name' => 'Gertrude'}] 22 | File.open("tmp/a_mock_database.json", "w") { |file| file << some_data.to_json } 23 | end 24 | 25 | tear_down do 26 | FileUtils.rm_rf("tmp/a_mock_database.json") 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/fixtures/certificates/ca_cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDATCCAemgAwIBAgIUWfQF2Mh+eFd3q+cSVgekpaMTh9MwDQYJKoZIhvcNAQEL 3 | BQAwDzENMAsGA1UEAwwETXlDQTAgFw0yMzA5MTkxMTA2MjZaGA8yMTIzMDgyNjEx 4 | MDYyNlowDzENMAsGA1UEAwwETXlDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC 5 | AQoCggEBAK9Qha2OdeFrSCUqiYRUBngNLn8PRGDaKPWmjd+3WOWJNM1RNgFfGpKY 6 | nxYJp4J6eW7aeQ6o94Q+QOZp+Yxm6thrtvjRbcEafAore4EwC4tjXvoFoy+mKwzm 7 | njlJw+ha3TsMAqD3GGDLF7uDnmliURRo8TOmJ++Mwss9Uhb5p9LArjWXa3sV8da+ 8 | gsxP2aTgBZfznUhNKDGUfezYa5UEbHQ869rA1PAqL3tOC2M5LTX08C2PlzzLOF5S 9 | gBzicV1PPDkmkbxKmFV+D8LmkwWNsRhrzZ6TIxYoXIRhziS7JuYOGU7G0+6ZKpIP 10 | mo7WXSoSrd7GL5PQJzlHKCsTckd4so0CAwEAAaNTMFEwHQYDVR0OBBYEFCeovNXs 11 | r1mcbprFaLyll+LrBJmQMB8GA1UdIwQYMBaAFCeovNXsr1mcbprFaLyll+LrBJmQ 12 | MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAHOsbZ0iDiKiRU8Q 13 | hIAav056dboPjTK19Q736DUD6oCbTbvecfxMv/wu9LmYGW5jt/DWP6s+jDYhcPpj 14 | c3U03pPKCnvsG5z60ZgmNSqzyVAVPW17UVdw/ZnkKK/SFxYgYQaF/1g6opS2Zana 15 | 4aBGypqqGoD4KE+DAnRjuuCUpiz3zXwGd86auajY6soMlLNnVXteVa/whW6IZ84x 16 | w4LISeMGUr+MXw9ye4WhcZYKZ4vwJdUYst2PA0pDuGwBDbGnrYloGm2BSpaHXUUo 17 | XrwKFFkIxcK63IpAhoceTJpyfjI1BSmItfjEwToOUu6xDBsHLNiH6BKstSxk0DfX 18 | 01PHz2I= 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /spec/fixtures/certificates/ca_cert.srl: -------------------------------------------------------------------------------- 1 | 494F82D5FE5055D2C9C64941C421085B59521071 2 | -------------------------------------------------------------------------------- /spec/fixtures/certificates/ca_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCvUIWtjnXha0gl 3 | KomEVAZ4DS5/D0Rg2ij1po3ft1jliTTNUTYBXxqSmJ8WCaeCenlu2nkOqPeEPkDm 4 | afmMZurYa7b40W3BGnwKK3uBMAuLY176BaMvpisM5p45ScPoWt07DAKg9xhgyxe7 5 | g55pYlEUaPEzpifvjMLLPVIW+afSwK41l2t7FfHWvoLMT9mk4AWX851ITSgxlH3s 6 | 2GuVBGx0POvawNTwKi97TgtjOS019PAtj5c8yzheUoAc4nFdTzw5JpG8SphVfg/C 7 | 5pMFjbEYa82ekyMWKFyEYc4kuybmDhlOxtPumSqSD5qO1l0qEq3exi+T0Cc5Rygr 8 | E3JHeLKNAgMBAAECggEAF2EHQqWB24V2rIYnVT9DUZUobdyiWMF0aYtEK4uuzjAQ 9 | RjpzQkGQMJvWc0DnAW5wbTOzUHIrTTZkFJYYp6boiziUwPUPduCfnqznySBCxIbZ 10 | mUFRNBSBHzT4mq6B8qV+D9bChFFkrdvHlsOu8gzLaouyxsQnWo8MlxU0B55UHrWc 11 | nqIsPKVBeBtiF7c7eyZtpKmYgmWN8hnPzTZ2rtCL/BS3p2+/O+fFJKuul58Yo4t6 12 | bmMCPN5C6HxNhB6ADHm3lPVU3ap5g3a/4UHqVJ8c2SGKfAx6C1PgbajxiA74qMLS 13 | YOhMXzc3jSLmakqvSmVhQFJhFt7drbbGtx4oD3+XPQKBgQDj1k7O2A0yJRQPtvQJ 14 | A1m+H5fmynMnH6XuQuO8WzqCsDsE786EAG6AzY562SMEQrQ0zgpFx0A9ZmECNaOZ 15 | 28OnzcA5xGKQh5dD0ou9lvRHXEavu7fYCrAG+wlQTo1eRHUDOAN4pQPoZ9r3bz1M 16 | tnGtG3rak4KemAsoX8aSy59ZswKBgQDE/C+eu012vzjyr2J1W0Gdms7fh5CWzMp8 17 | hCHk+kmLCY4DHIaUv0tT3IXGKebRH+PZObE3zZ5Hx2QXPjFQWsyTkd9D2tRIWHaZ 18 | ZpKPBLxYJJuBc3YWZM1qC2ZcRyvv1NgtNUFpB5xOGIUL3/QsfcOE25kC7Z21aN+e 19 | uXSi3CkivwKBgGFHSZLLcKbuaehjx0Jp6dFhj+v8mLolqyVV7gKoOQ0/zZNICLcX 20 | sBbSrXkKaQcSq/q31m8Aqg8NPXJCEL5KtPlawi5oCWWIXy+YIA4s+9PUNGIoFlDq 21 | D0qLuOhPAdE0DXn4WpMScd6zKSzolBXC+DpfN09IGEc6x9jPO+vFgR49AoGABPiw 22 | YvsrK1IMJ+PRQlD5SPb9PZr4RTYJ7jaPfG3sqTumf+Gaa+qgBg/MuIGaN7DsWTEh 23 | jdz8n6cimYuSRwrjmt3VmqrNLL4+0ARMsptV/Yt++TdmxY3puUFsZevN6hGfGxT6 24 | /6GXikkIIpKWYQETjCjWpcJFdqyc6C6aCPoxd5UCgYEA1B4AdDgxhZgXhz24sKM7 25 | aX2aY4glBsEZ7dxaqpqvwmsSshvdfudjuFxo5jjMKV2C9JmwrCGML9O6MvSP03n8 26 | B3R543JqKqWLTaSROHkcoil+LdIV9w7jrMBildOCHSDXwuM8Pl7YObIdKMq4pVwe 27 | 87n9/ZihlrKGaZ8utMrrGmc= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /spec/fixtures/certificates/client_cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICrDCCAZQCFElPgtX+UFXSycZJQcQhCFtZUhBxMA0GCSqGSIb3DQEBCwUAMA8x 3 | DTALBgNVBAMMBE15Q0EwIBcNMjMwOTE5MTEwNzM1WhgPMjEyMzA4MjYxMTA3MzVa 4 | MBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC 5 | AQoCggEBAJj6DF+bu0DhXmkBaC2+CkoqNlsO+LzW9bZnNCQk0Jw99fgCGTLifU3N 6 | eyAhKgHs+V3G/9ULbMrxYMSQ/psrrXpS7FM9xtA0WZ0VAg7Oi4WEi+wueE0R1GmO 7 | NMuCVT2JCYd5uDh8+mrWoVqb9L4xIsy0kaV0Nnl+NX1zDvHXUHzfo3T3roaxRbd6 8 | N92qNPzrj8TviwbapT0bo4GKwTCOO1ewPFGCjsWEeLZ4p2UfbOzW/zjIBEUD8Kqg 9 | FOht48y9J6XG3Tb61/7neT0xj6E7cn6hGSzuiIM/oZbtuUt72VDgbLbOrS02oHTz 10 | YmC9tVL35Qvfgzrqw0DEv7zpm/3iG0sCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEA 11 | nndLK/t6+dmoAwg4K7pdo8xqUEDnUx8K7sU2whZvLEUM+mO+jWOe3USHjR3aXYnU 12 | OjNhN90/TAy5wlIK6U2C36nHyZJUeScxuiaVwErwayE+GgwYmw9R7HVofgcVfTve 13 | IpjyrT7mDOCMYjkHgZv1dSHQTcc6uclaw7SgywEEjxjCNSJCN+WPjxCdcuno0td8 14 | i7F6FL7FeOiP1mtQrTo42Tq+knerUc55CbTW4anbQfL+6TFEVCPJKduLHFieGB0k 15 | BFilUR3JD2t8/f4fIilQ6FrMZpUzKcLbgW9cjts8mxq0zNV+z6lISgKbdxZFQp+2 16 | fvyYdnoNLP0YeRI6j9x1pg== 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /spec/fixtures/certificates/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCY+gxfm7tA4V5p 3 | AWgtvgpKKjZbDvi81vW2ZzQkJNCcPfX4Ahky4n1NzXsgISoB7Pldxv/VC2zK8WDE 4 | kP6bK616UuxTPcbQNFmdFQIOzouFhIvsLnhNEdRpjjTLglU9iQmHebg4fPpq1qFa 5 | m/S+MSLMtJGldDZ5fjV9cw7x11B836N0966GsUW3ejfdqjT864/E74sG2qU9G6OB 6 | isEwjjtXsDxRgo7FhHi2eKdlH2zs1v84yARFA/CqoBTobePMvSelxt02+tf+53k9 7 | MY+hO3J+oRks7oiDP6GW7blLe9lQ4Gy2zq0tNqB082JgvbVS9+UL34M66sNAxL+8 8 | 6Zv94htLAgMBAAECggEAF2l9Z0yANgfH2S478XQ6Qut+8iSycMQ9SrM0yatQufjJ 9 | ojFABgefwb6G733j3fOUnoOMN+DNv6l9c9f0/26J2ETEomC8ArVgWagTboyx0bdd 10 | asIZ60GlTppS/ipuPUKx0KgSR6Lo+FzsyN9Bb7I5bzbba4UDqUhli1OGoACh8tpS 11 | pyhD58C0nWBCYUjgkB2ilVoguQnnTvYC0VDbGOWK1P8bw0to810mkKTyv7ztifW2 12 | lHUwTe8vbQk7jY52+crvtgVZWNaXEdma3ivDSDHUjK3WLmPw9MFgVSMVYFLDZUQN 13 | Btd7PyBSkjeHOzoS5b9l4qnjn2vhObpjrT5PZT6TMQKBgQDI+xrzk351kyqezHuZ 14 | Bqo5CqEN3BHvwKALh3DA3uxHVaLqOo/yALv86yHgue/9ksaxxDwufAnVvcg5eEEh 15 | XIsZrfKBIaNV5umqJAkbCbx4hVCX9mE45THv3Nc7XhiHuZZpUHb1i7qcab1lly5Y 16 | 7lFoCd5dCQJUoBf3/9Bw76OjRwKBgQDC2sba56V0dahFxTE3bOIRR2HIYWNfPv0a 17 | 7ejiNSHVHGTLrEfnya5ZcerT0j6QNA2IQcKw5ovPKn2xgjGlfPWgtBAz55r+lfU2 18 | +/6CRf8v6tu9FdPs7RDHxBuicOGQlQGSAH2+tfcY9ZCB8wcdGYB3v5ko0OFsFdZY 19 | +fJOIt4h3QKBgByFJanzADsHC0FFmzR38afujjQ9Sn5PQ2bfbWyxNa5ZxKigbtTU 20 | rdiSNViCij/dmDyZsECYcXzXVZZyLivhygt217bjYx5JilcOjgw8MXaY1Hr8B4ff 21 | Xlq/Z/uQusJn36RKOtdVYMHZb3r/HSCZkQvGeruRD7eakEwtDRM5rmr5AoGAZFt9 22 | s90/ED5RDq5DbQJ9ZNzY9fWC0tmETsxd97PZ2wMmvufamPz8+UB86+ALLQZCOf10 23 | otv7AhYmarhdjZhQghZ7ieAtqhXeGBWtvbcDedCCoF6PqiVnURwmB4IQCwFTr7jl 24 | CsZ5n7dKWEOtVEWALyzVW3pJv/t3TJhfPfMjaVkCgYAxmC4/jmBCLmQZ3eWbmZHx 25 | X7N2qAI7Cu2JVi1Wut4WnBgFNynYH+kt67LZSQ9Jf9lHDnlBe5gOTvF5/8UeoTMv 26 | MGI4R4WJ6ezWV12ugbmKAvzHB/SiJ9U0ph78ibejCxW3gomuDzY1T+xF56kCKXJ0 27 | uPaEN0rPMT6wMEegJHaE8A== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /spec/fixtures/certificates/server.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICWTCCAUECAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B 3 | AQEFAAOCAQ8AMIIBCgKCAQEAmPoMX5u7QOFeaQFoLb4KSio2Ww74vNb1tmc0JCTQ 4 | nD31+AIZMuJ9Tc17ICEqAez5Xcb/1QtsyvFgxJD+myutelLsUz3G0DRZnRUCDs6L 5 | hYSL7C54TRHUaY40y4JVPYkJh3m4OHz6atahWpv0vjEizLSRpXQ2eX41fXMO8ddQ 6 | fN+jdPeuhrFFt3o33ao0/OuPxO+LBtqlPRujgYrBMI47V7A8UYKOxYR4tninZR9s 7 | 7Nb/OMgERQPwqqAU6G3jzL0npcbdNvrX/ud5PTGPoTtyfqEZLO6Igz+hlu25S3vZ 8 | UOBsts6tLTagdPNiYL21UvflC9+DOurDQMS/vOmb/eIbSwIDAQABoAAwDQYJKoZI 9 | hvcNAQELBQADggEBAEz74PiDtYCL1XiZV4On0l5jRjBrKTVEAnjEtWgygy9V6U1d 10 | BYE3AxwsdTUygl/cS2i3g8U2yZGQ1ZAh/qHq0sHB6TDePLmNSEiksP7KOJwXU9vO 11 | /pCS9qbOYcWucLlQpnHxySpUlcxFWmrl33pMaNCzxxLN1q3eRbNmxoxACI/+vZsX 12 | M6sm2fhhw6yZkU7D04BDgSwsddW8ApDqbtwbndyv/ZL13xjG9yow8noSF7uxGQnn 13 | UnVFMGVGp3I6M/E3VFIwRvUYA1MJeqh9tLIEItlGmqkrQmxOnMvXKzJnQ9nK1KBq 14 | 2gaBXdvbabkXKAHnV0tYbDmZXvTO+7Ci7wgapNU= 15 | -----END CERTIFICATE REQUEST----- 16 | -------------------------------------------------------------------------------- /spec/fixtures/certificates/unsigned_cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDCzCCAfOgAwIBAgIUN2/oKOttkdOretzyqc+Zv8IqpT8wDQYJKoZIhvcNAQEL 3 | BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTIzMDkxOTExMjEzM1oYDzIxMjMw 4 | ODI2MTEyMTMzWjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB 5 | AQUAA4IBDwAwggEKAoIBAQD30tqrKrHa0p1RsGlDc7lUUS/ZF2/7ZtNWe0gRuuum 6 | 6l/X5H8F+Ay1cO8DirGx3s/LPpj7DwvjjKo2eE+wcO2v/R5S+uPL4Bm0o+bPGwZP 7 | vw+XMMgBZUsNSMER6DUliP5bHQ/8TCXWpfP3rLJ9QitOAX/rD9bVrOs3g3I0uf2A 8 | RZ0O40//5q9fiXRC3PAfPbX7XdyI9Mr3duwmAW+nK2Gbd98ut27PkO0Fze27Xtk2 9 | EdIh3u5pajK/ub8rf5vyfk+c/6pcN9kMakPtlgIR/eqzTkfRWyIpMoFn/X8VumUQ 10 | X4ylj1SfSs+K47GBjrqknEh1BYlblW8WKg5cUjx/r/b/AgMBAAGjUzBRMB0GA1Ud 11 | DgQWBBQUEefafoC0qDhzThhEBMMwr/C5FTAfBgNVHSMEGDAWgBQUEefafoC0qDhz 12 | ThhEBMMwr/C5FTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQDn 13 | dfkjZtgSdbsEPhUMfUlhZWXqxtLDQBoxM7xF+i5WC6w3yHpN/teA8SqA8CYiPb9d 14 | 5rNfnmJLP4PeyfTu6Pc0EJpQsmK19i9z0FPrA7bqPIzgF4U4R1eQ5mvTzlNoGkp3 15 | 1gnjDdwtTq0RFfuvHKm5EqECKX+hBEJKMiviEH/mGqQuoycpKifZ5WRTQonnWjGe 16 | BVkhdn4Psp83EWdnD/yQbo1XEbYRtsaPM4Dozr6uKbeq9Zbu+xDO9Uw4mTE/WSfb 17 | t4AXqOLDRafOP9w3twlFH2ZQxqpSaqXo8z1RkS9jtCm69JcDnsePKqkhesToMZAz 18 | 2cylIQmuuNIRGLmCRsVK 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /spec/fixtures/certificates/unsigned_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD30tqrKrHa0p1R 3 | sGlDc7lUUS/ZF2/7ZtNWe0gRuuum6l/X5H8F+Ay1cO8DirGx3s/LPpj7DwvjjKo2 4 | eE+wcO2v/R5S+uPL4Bm0o+bPGwZPvw+XMMgBZUsNSMER6DUliP5bHQ/8TCXWpfP3 5 | rLJ9QitOAX/rD9bVrOs3g3I0uf2ARZ0O40//5q9fiXRC3PAfPbX7XdyI9Mr3duwm 6 | AW+nK2Gbd98ut27PkO0Fze27Xtk2EdIh3u5pajK/ub8rf5vyfk+c/6pcN9kMakPt 7 | lgIR/eqzTkfRWyIpMoFn/X8VumUQX4ylj1SfSs+K47GBjrqknEh1BYlblW8WKg5c 8 | Ujx/r/b/AgMBAAECggEAR4aYzBwndvOgqioTR3+H9tjzyWFlVZbo2iX8t/lN+D/e 9 | 562wJ6Xe7SMqKMiH3sFjEdMATj2afdNkcRIqVc9SGqAgd2yoAHiukp9Xh2DSYoPP 10 | WSCgKR72GWBtMODnLe0rFFr/+R51MU12a35xiYtmej4ekFZi+ArPXJdYh/VCQBnG 11 | BGF+EnUJqCAOXLz9zG3FoYVBWu071vEnpBtfblbHYfY2/o5CSoORkxcput6XDHxO 12 | 7pOXN7IRt7DJZ0goda3OwZQ9suKyTLcOxa8cA+DteVP6cvh4u2l+ZMxUIYK5B1eh 13 | VAmJkIbcbAaz/SxyO9E2gWraz+pu6ArOGY/0krcBmQKBgQD9BlVtx7QTVzu+nLzF 14 | 2++cB0LTsTD9T9rlMfIuQMBIOywmsivyvUDr9SjOPqICQUIRufHKqCgHs4TH7Ifs 15 | 4AxyUEwQMG4xuYh3nU5eZlpUEjzUWbbe7o0NjhaJ4ZlUBvzHBcgptUlKTwvwamMs 16 | pnzQxWlFXFuh+pxPPdSXZVmWJQKBgQD6vN0pO/xc3bAqHSCavX95NBjUhFvkAsoo 17 | T8tfv2qoAN8RhI2/N8prix6tJk3AdhzdLmmMktv3MBDXd3cgLgmXQTYHIijWXPlF 18 | /WXWmZXK0E9fiDjfXI9eB7E237fYGOaSobOhLOLoHcuL0kndps67QP2BhtXhYB88 19 | 1We7LoJVUwKBgQCnF1qtH5d0ukPTEdC73Q0z/buM7tPKRMTqXHxxPQN9782tVDYf 20 | nAlWiVTENqpoUM4fxKq/SSL+SvfhyvrMW/z8NLi2bDUpEzviufg58N+v60dOeFyC 21 | hgiSLgYGUfweeGrPx6qymGxo7SCWSLtrjhqZB/UIAADnTAeTcOKGhECQHQKBgAlM 22 | A29J+BuBZMzK87CJIjbeRaVrmvSjXdeMzd+o+01ratn9bjwO14SRTfvhlbRzLLLO 23 | y78YmutZbuZuWY5p5pUjJ9uv2o/INr3vnV0NqM4yVx8Vr/YoOnCkHGAKf4iVs8bw 24 | E/b/8RHmOOvgSjjbvIKY8E1jMH8Az2e0CfqYyOBdAoGAOlhTefyBGgAWFHqH/l4p 25 | ThbWupIMsw1ZXlArwBnTfsUFuz0Yq7B+0tqrV8lhS3P4/0jI2yWnzhluDk62clwz 26 | Xg187V85Ylagshsjv60mP5qBEF4N7Nf5fP2w6+GjMU+YiHEBsgGGt+2jPgKeCGQW 27 | IlV3ym59oL+wGyN9OK3z+aw= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /spec/integration/cli_docs_spec.rb: -------------------------------------------------------------------------------- 1 | require 'open3' 2 | require 'support/cli' 3 | require 'fileutils' 4 | 5 | describe "running the pact docs CLI", skip_windows: true do 6 | 7 | include Pact::Support::CLI 8 | 9 | before do 10 | FileUtils.rm_rf "tmp/docs" 11 | end 12 | 13 | let(:command) { "bundle exec bin/pact docs --pact-dir spec/support/docs --doc-dir tmp/docs" } 14 | 15 | it "writes some docs" do 16 | execute_command command 17 | expect(Dir.glob("tmp/docs/**/*").size).to_not eq 0 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/integration/consumer_async_request_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pact/consumer' 3 | require 'pact/consumer/rspec' 4 | load 'pact/consumer/world.rb' 5 | 6 | describe "A service consumer side of a pact", :pact => true do 7 | 8 | context "with an asynchronous interaction with provider" do 9 | before do 10 | Pact.clear_configuration 11 | 12 | Pact.service_consumer "Consumer" do 13 | has_pact_with "Zebra Service" do 14 | mock_service :zebra_service do 15 | verify true 16 | port 1239 17 | end 18 | end 19 | end 20 | end 21 | 22 | it "goes like this" do 23 | zebra_service. 24 | given(:the_zebras_are_here). 25 | upon_receiving("a retrieve Mallory request"). 26 | with({ 27 | method: :get, 28 | path: '/mallory' 29 | }). 30 | will_respond_with({status: 200}) 31 | 32 | async_interaction { Net::HTTP.get_response(URI('http://localhost:1239/mallory')) } 33 | 34 | zebra_service.wait_for_interactions wait_max_seconds: 1, poll_interval: 0.1 35 | end 36 | 37 | def async_interaction 38 | Thread.new do 39 | sleep 0.2 40 | yield 41 | end 42 | end 43 | 44 | end 45 | 46 | end 47 | -------------------------------------------------------------------------------- /spec/integration/consumer_more_than_one_matching_interaction_spec.rb: -------------------------------------------------------------------------------- 1 | require 'pact/consumer' 2 | require 'pact/consumer/rspec' 3 | load 'pact/consumer/world.rb' 4 | 5 | describe "A service consumer side of a pact", :pact => true do 6 | 7 | context "with more than one matching interaction found" do 8 | let(:expected_response) do 9 | {"message"=>"Multiple interaction found for GET /path", "matching_interactions"=>[{"description"=>"a request", "request"=>{"method"=>"get", "path"=>"/path", "body"=>{"a"=>"some body"}, "headers"=>{"Content-Type"=>"application/json"}}}, {"description"=>"an identical request", "request"=>{"method"=>"get", "path"=>"/path", "body"=>{"a"=>"some body"}, "headers"=>{"Content-Type"=>"application/json"}}}]} 10 | end 11 | 12 | it "returns an error" do 13 | Pact.clear_configuration 14 | Pact.clear_consumer_world 15 | 16 | Pact.service_consumer "Consumer" do 17 | has_pact_with "Mary Service" do 18 | mock_service :mary_service do 19 | verify false 20 | port 1237 21 | end 22 | end 23 | end 24 | 25 | mary_service 26 | .given("something") 27 | .upon_receiving("a request") 28 | .with(method: 'get', path: '/path', body: {a: 'some body'}, headers: {'Content-Type' => 'application/json'}) 29 | .will_respond_with(status: 200) 30 | 31 | 32 | mary_service 33 | .upon_receiving("an identical request") 34 | .with(method: 'get', path: '/path', body: {a: 'some body'}, headers: {'Content-Type' => 'application/json'}) 35 | .will_respond_with(status: 200) 36 | 37 | uri = URI('http://localhost:1237/path') 38 | post_req = Net::HTTP::Get.new(uri.path) 39 | post_req['Content-Type'] = "application/json" 40 | post_req.body = {a: "some body"}.to_json 41 | response = Net::HTTP.start(uri.hostname, uri.port) do |http| 42 | http.request post_req 43 | end 44 | 45 | expect(JSON.load(response.body)).to eq expected_response 46 | end 47 | 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/integration/consumer_no_matching_interaction_spec.rb: -------------------------------------------------------------------------------- 1 | require 'pact/consumer' 2 | require 'pact/consumer/rspec' 3 | load 'pact/consumer/world.rb' 4 | 5 | describe "A service consumer side of a pact", :pact => true do 6 | context "with no matching interaction found" do 7 | 8 | let(:expected_response) do 9 | { 10 | "message"=>"No interaction found for GET /path", 11 | "interaction_diffs"=>[{ 12 | "description" => "a request that will not be properly matched", 13 | "provider_state" => "something", 14 | "body"=>{ 15 | "a"=>{ 16 | "EXPECTED"=>"some body", 17 | "ACTUAL"=>"not matching body" 18 | } 19 | } 20 | }] 21 | } 22 | end 23 | 24 | it "returns an error" do 25 | Pact.clear_configuration 26 | 27 | Pact.service_consumer "Consumer" do 28 | has_pact_with "Mary Service" do 29 | mock_service :mary_service do 30 | verify false 31 | port 1236 32 | end 33 | end 34 | end 35 | 36 | mary_service 37 | .given("something") 38 | .upon_receiving("a request that will not be properly matched") 39 | .with(method: 'get', path: '/path', body: {a: 'some body'}, headers: {'Content-Type' => 'application/json'}) 40 | .will_respond_with(status: 200) 41 | 42 | uri = URI('http://localhost:1236/path') 43 | post_req = Net::HTTP::Get.new(uri.path) 44 | post_req['Content-Type'] = "application/json" 45 | post_req.body = {a: "not matching body"}.to_json 46 | response = Net::HTTP.start(uri.hostname, uri.port) do |http| 47 | http.request post_req 48 | end 49 | 50 | expect(JSON.load(response.body)).to eq expected_response 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /spec/integration/consumer_with_a_provider_state_spec.rb: -------------------------------------------------------------------------------- 1 | require 'pact/consumer' 2 | require 'pact/consumer/rspec' 3 | load 'pact/consumer/world.rb' 4 | require 'faraday' 5 | 6 | describe "A service consumer side of a pact", :pact => true do 7 | context "with a provider state" do 8 | before do 9 | Pact.clear_configuration 10 | 11 | Pact.service_consumer "Consumer" do 12 | has_pact_with "Zebra Service" do 13 | mock_service :zebra_service do 14 | verify false 15 | port 1235 16 | end 17 | end 18 | end 19 | end 20 | 21 | let(:body) { 'That is some good Mallory.' } 22 | let(:zebra_header) { '*.zebra.com' } 23 | 24 | it "goes like this" do 25 | zebra_service. 26 | given(:the_zebras_are_here). 27 | upon_receiving("a retrieve Mallory request").with( 28 | method: :get, 29 | path: '/mallory', 30 | headers: {'Accept' => 'text/html'} 31 | ). 32 | will_respond_with( 33 | status: 200, 34 | headers: { 35 | 'Content-Type' => 'text/html', 36 | 'Zebra-Origin' => term(/\*/, zebra_header) 37 | }, 38 | body: term(/Mallory/, body) 39 | ) 40 | 41 | response = Faraday.get(zebra_service.mock_service_base_url + "/mallory", nil, {'Accept' => 'text/html'}) 42 | expect(response.body).to eq body 43 | expect(response.headers['Zebra-Origin']).to eq zebra_header 44 | 45 | interactions = Pact::ConsumerContract.from_json(zebra_service.write_pact).interactions 46 | expect(interactions.first.provider_state).to eq("the_zebras_are_here") 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/integration/consumer_with_form_hash_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'net/http' 3 | require 'pact/consumer' 4 | require 'pact/consumer/rspec' 5 | require 'faraday' 6 | load 'pact/consumer/world.rb' 7 | 8 | describe "A service consumer side of a pact", :pact => true do 9 | 10 | let(:body) { 'That is some good Mallory.' } 11 | 12 | context 'submitting a form specified as a Hash' do 13 | 14 | before :all do 15 | Pact.clear_configuration 16 | 17 | Pact.service_consumer "Consumer" do 18 | has_pact_with "Zebra Service" do 19 | mock_service :zebra_service_4 do 20 | port 1245 21 | end 22 | end 23 | end 24 | end 25 | 26 | before do 27 | 28 | zebra_service_4. 29 | given("the zebras like using forms"). 30 | upon_receiving("a create Mallory request").with({ 31 | method: :post, 32 | path: '/mallory', 33 | headers: {'Content-Type' => 'application/x-www-form-urlencoded'}, 34 | body: { 35 | param1: term('woger', /w/), 36 | param2: 'penguin' 37 | } 38 | }). 39 | will_respond_with({ 40 | status: 200 41 | }) 42 | 43 | end 44 | 45 | let(:url) { zebra_service_4.mock_service_base_url + "/mallory" } 46 | let(:response) { Faraday.post url, param2: 'penguin', param1: 'wiffle' } 47 | let(:pact_json) { response; zebra_service_4.write_pact } 48 | 49 | it "matches form data" do 50 | expect(response.status).to eq 200 51 | end 52 | 53 | it "does not include any Pact::Terms" do 54 | expect(pact_json).to_not include "Pact::Term" 55 | end 56 | 57 | it "includes the reified form" do 58 | expect(pact_json).to include "param1=woger" 59 | end 60 | 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/integration/consumer_with_form_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'net/http' 3 | require 'pact/consumer' 4 | require 'pact/consumer/rspec' 5 | require 'faraday' 6 | load 'pact/consumer/world.rb' 7 | 8 | describe "A service consumer side of a pact", :pact => true do 9 | 10 | let(:body) { 'That is some good Mallory.' } 11 | 12 | context 'submitting a form' do 13 | 14 | before :all do 15 | Pact.clear_configuration 16 | 17 | Pact.service_consumer "Consumer" do 18 | has_pact_with "Zebra Service" do 19 | mock_service :zebra_service_3 do 20 | port 1243 21 | end 22 | end 23 | end 24 | end 25 | 26 | before do 27 | 28 | zebra_service_3. 29 | given("the zebras like using forms"). 30 | upon_receiving("a create Mallory request").with({ 31 | method: :post, 32 | path: '/mallory', 33 | headers: {'Content-Type' => 'application/x-www-form-urlencoded'}, 34 | body: "param1=wiffle¶m2=penguin" 35 | }). 36 | will_respond_with({ 37 | status: 200 38 | }) 39 | 40 | end 41 | 42 | let(:url) { zebra_service_3.mock_service_base_url + "/mallory" } 43 | 44 | it "matches form data" do 45 | response = Faraday.post url, param2: 'penguin', param1: 'wiffle' 46 | expect(response.status).to eq 200 47 | end 48 | 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/integration/consumer_with_pact_term_header.rb: -------------------------------------------------------------------------------- 1 | require 'pact/consumer' 2 | require 'pact/consumer/rspec' 3 | load 'pact/consumer/world.rb' 4 | require 'faraday' 5 | 6 | describe "A service consumer side of a pact", :pact => true do 7 | context "when the header is a Pact::Term" do 8 | before do 9 | Pact.clear_configuration 10 | 11 | Pact.service_consumer "Consumer" do 12 | has_pact_with "Zebra Service" do 13 | mock_service :another_zebra_service_for_term_header do 14 | port 4445 15 | end 16 | end 17 | end 18 | end 19 | 20 | let(:body) { 'That is some good Mallory.' } 21 | 22 | it "matches using the term" do 23 | another_zebra_service_for_term_header. 24 | upon_receiving("a request to save an alligator").with( 25 | method: :put, 26 | path: '/alligators/John', 27 | headers: { 28 | 'Content-Type' => term(/json/, 'application/json') 29 | } 30 | ). 31 | will_respond_with(status: 200) 32 | 33 | response = Faraday.put(another_zebra_service_for_term_header.mock_service_base_url + "/alligators/John", nil, {'Content-Type' => 'foo/json'}) 34 | expect(response.status).to eq 200 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/integration/consumer_with_pact_term_in_path.rb: -------------------------------------------------------------------------------- 1 | require 'pact/consumer' 2 | require 'pact/consumer/rspec' 3 | load 'pact/consumer/world.rb' 4 | require 'faraday' 5 | 6 | describe "A service consumer side of a pact", :pact => true do 7 | context "with a provider state" do 8 | before do 9 | Pact.clear_configuration 10 | 11 | Pact.service_consumer "Consumer" do 12 | has_pact_with "Zebra Service" do 13 | mock_service :another_zebra_service do 14 | port 4444 15 | end 16 | end 17 | end 18 | end 19 | 20 | let(:body) { 'That is some good Mallory.' } 21 | 22 | it "goes like this" do 23 | another_zebra_service. 24 | upon_receiving("a request for an alligator").with( 25 | method: :get, 26 | path: term(/alligators\/.*/, '/alligators/Mary'), 27 | ). 28 | will_respond_with(status: 200) 29 | 30 | response = Faraday.get(another_zebra_service.mock_service_base_url + "/alligators/John") 31 | expect(response.status).to eq 200 32 | 33 | interactions = Pact::ConsumerContract.from_json(another_zebra_service.write_pact).interactions 34 | expect(interactions.first.request.path).to eq('/alligators/Mary') 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/integration/consumer_with_v2_matching.rb: -------------------------------------------------------------------------------- 1 | require 'pact/consumer' 2 | require 'pact/consumer/rspec' 3 | load 'pact/consumer/world.rb' 4 | require 'faraday' 5 | 6 | describe "When the pact_specification_version is set to 2", :pact => true do 7 | 8 | before do 9 | Pact.clear_configuration 10 | 11 | Pact.service_consumer "Consumer" do 12 | has_pact_with "Zebra Service" do 13 | mock_service :zebra_service do 14 | verify false 15 | port 1235 16 | pact_specification_version '2' 17 | end 18 | end 19 | end 20 | end 21 | 22 | let(:body) { 'That is some good Mallory.' } 23 | 24 | it "writes the pact with v2 matching rules" do 25 | zebra_service. 26 | given(:the_zebras_are_here). 27 | upon_receiving("a retrieve Mallory request").with( 28 | method: :get, 29 | path: '/mallory', 30 | headers: {'Accept' => 'text/html'} 31 | ). 32 | will_respond_with( 33 | status: 200, 34 | headers: { 35 | 'Content-Type' => 'text/html' 36 | }, 37 | body: term(/Mallory/, body) 38 | ) 39 | 40 | response = Faraday.get(zebra_service.mock_service_base_url + "/mallory", nil, {'Accept' => 'text/html'}) 41 | expect(response.body).to eq body 42 | pact_hash = JSON.parse(zebra_service.write_pact) 43 | expect(pact_hash['interactions'][0]['response']['matchingRules']).to be_instance_of(Hash) 44 | end 45 | 46 | end 47 | -------------------------------------------------------------------------------- /spec/integration/executing_verify_from_wrapper_language_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "executing pact verify", skip_windows: true do 2 | let(:command) { "bundle exec rake pact:verify:test_app:fail > /dev/null" } 3 | let(:reports_dir) { 'tmp/spec_reports' } # The config for this is in spec/support/pact_helper.rb 4 | 5 | before do 6 | FileUtils.rm_rf reports_dir 7 | end 8 | 9 | after do 10 | FileUtils.rm_rf reports_dir 11 | end 12 | 13 | context "from ruby" do 14 | it "creates a reports dir" do 15 | system({}, command) 16 | expect(File.exist?(reports_dir)).to be true 17 | end 18 | end 19 | 20 | context "with a wrapper language" do 21 | it "does not create a reports dir" do 22 | system({'PACT_EXECUTING_LANGUAGE' => 'foo'}, command) 23 | 24 | expect(File.exist?(reports_dir)).to be false 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/integration/pact/consumer_configuration_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pact/configuration' 3 | require 'pact/consumer/configuration' 4 | 5 | describe "consumer side" do 6 | describe "configure" do 7 | 8 | class TestHelper 9 | include Pact::Consumer::ConsumerContractBuilders 10 | end 11 | 12 | let(:application) { double("App")} 13 | let(:world) { Pact::Consumer::World.new } 14 | 15 | before do 16 | Pact.clear_configuration 17 | Pact::MockService::AppManager.instance.clear_all 18 | # Don't want processes actually spawning 19 | allow_any_instance_of(Pact::MockService::AppRegistration).to receive(:spawn) 20 | allow(Pact).to receive(:consumer_world).and_return(world) 21 | 22 | my_app = application 23 | 24 | Pact.service_consumer "My Consumer" do 25 | app my_app 26 | port 1111 27 | 28 | has_pact_with "My Service" do 29 | mock_service :my_service do 30 | port 8888 31 | standalone true 32 | end 33 | end 34 | 35 | has_pact_with "My Other Service" do 36 | mock_service :my_other_service do 37 | port 1235 38 | standalone false 39 | end 40 | end 41 | end 42 | 43 | end 44 | 45 | describe "providers" do 46 | 47 | subject { TestHelper.new.my_service } 48 | 49 | it "should have defined methods in MockServices for the providers" do 50 | expect(subject).to be_instance_of(Pact::Consumer::ConsumerContractBuilder) 51 | end 52 | 53 | context "when standalone is true" do 54 | it "is not registerd with the AppManager" do 55 | expect(Pact::MockService::AppManager.instance.app_registered_on?(8888)).to eq false 56 | end 57 | end 58 | 59 | context "when standalone is false" do 60 | it "should register the MockServices on their given ports if they are not" do 61 | expect(Pact::MockService::AppManager.instance.app_registered_on?(1235)).to eq true 62 | end 63 | end 64 | end 65 | end 66 | end -------------------------------------------------------------------------------- /spec/integration/pact/provider_configuration_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pact/provider/rspec' 3 | 4 | describe "provider side" do 5 | describe "configure" do 6 | 7 | class TestHelper 8 | include Pact::Provider::RSpec::InstanceMethods 9 | end 10 | 11 | let(:application) { double("App")} 12 | 13 | before do 14 | app_block = ->{ application } 15 | Pact.service_provider "My Provider" do 16 | app &app_block 17 | end 18 | end 19 | 20 | it "makes the app available to the tests" do 21 | expect(TestHelper.new.app).to be(application) 22 | end 23 | 24 | end 25 | end -------------------------------------------------------------------------------- /spec/integration/publish_verification_spec.rb: -------------------------------------------------------------------------------- 1 | require 'pact/provider/verification_results/publish_all' 2 | require 'pact/provider/pact_uri' 3 | 4 | describe "publishing verifications" do 5 | before do 6 | allow(Pact.configuration).to receive(:provider).and_return(provider_configuration) 7 | allow($stdout).to receive(:puts) 8 | end 9 | 10 | let(:provider_configuration) do 11 | double('provider_configuration', 12 | application_version: '1.2.3', 13 | publish_verification_results?: true, 14 | branch: nil, 15 | tags: [], 16 | build_url: 'http://ci/build/1') 17 | end 18 | 19 | let(:pact_sources) do 20 | [instance_double('Pact::Provider::PactSource', consumer_contract: consumer_contract, pact_hash: pact_hash, uri: pact_uri)] 21 | end 22 | 23 | let(:pact_uri) do 24 | instance_double('Pact::Provider::PactURI', uri: 'pact.json', options: {}, metadata: metadata) 25 | end 26 | 27 | let(:consumer_contract) { instance_double('Pact::ConsumerContract', interactions: [pact_interaction])} 28 | let(:pact_interaction) { instance_double('Pact::Interaction', _id: "1") } 29 | 30 | let(:metadata) { { notices: notices} } 31 | let(:notices) { instance_double('Pact::PactBroker::Notices', after_verification_notices_text: ['hello'] ) } 32 | 33 | let(:pact_hash) do 34 | { 35 | 'interactions' => [{}], 36 | '_links' => { 37 | 'pb:publish-verification-results' => { 38 | 'href' => 'http://publish/' 39 | } 40 | } 41 | } 42 | end 43 | 44 | let(:created_verification_body) do 45 | { 46 | '_links' => { 47 | 'self' => { 48 | 'href' => 'http://created' 49 | } 50 | } 51 | }.to_json 52 | end 53 | 54 | let(:test_results_hash) do 55 | { 56 | tests: [ 57 | { 58 | testDescription: '1', 59 | status: 'passed', 60 | pact_uri: pact_uri, 61 | pact_interaction: pact_interaction 62 | } 63 | ] 64 | } 65 | end 66 | 67 | subject { Pact::Provider::VerificationResults::PublishAll.call(pact_sources, test_results_hash) } 68 | 69 | let!(:request) do 70 | stub_request(:post, 'http://publish').to_return(status: 200, headers: {'Content-Type' => 'application/hal+json'}, body: created_verification_body) 71 | end 72 | 73 | it "publishes the results" do 74 | subject 75 | expect(request).to have_been_made 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /spec/lib/pact/cli/spec_criteria_spec.rb: -------------------------------------------------------------------------------- 1 | require 'pact/cli/spec_criteria' 2 | 3 | module Pact 4 | module Cli 5 | describe SpecCriteria do 6 | describe "#spec_criteria" do 7 | 8 | let(:env_description) { "pact description set in ENV"} 9 | let(:env_provider_state) { "provider state set in ENV"} 10 | let(:env_pact_broker_interaction_id) { "interaction id set in ENV" } 11 | let(:interaction_index) { 2 } 12 | let(:env_criteria) do 13 | { 14 | :description=>/#{env_description}/, 15 | :provider_state=>/#{env_provider_state}/, 16 | :_id => env_pact_broker_interaction_id, 17 | :index => interaction_index 18 | } 19 | end 20 | 21 | let(:defaults) { {:description => default_description, :provider_state => default_provider_state} } 22 | 23 | let(:subject) { Pact::App.new } 24 | 25 | context "when options are defined" do 26 | let(:options) do 27 | { 28 | description: env_description, 29 | provider_state: env_provider_state, 30 | pact_broker_interaction_id: env_pact_broker_interaction_id, 31 | interaction_index: interaction_index 32 | } 33 | end 34 | 35 | it "returns the env vars as regexes" do 36 | expect(Pact::Cli::SpecCriteria.call(options)).to eq(env_criteria) 37 | end 38 | end 39 | 40 | context "when ENV variables are not defined" do 41 | let(:options) { {} } 42 | 43 | it "returns an empty hash" do 44 | expect(Pact::Cli::SpecCriteria.call(options)).to eq({}) 45 | end 46 | end 47 | 48 | context "when provider state is an empty string" do 49 | let(:options) { { provider_state: '' } } 50 | 51 | it "returns a nil provider state so that it matches a nil provider state on the interaction" do 52 | expect(Pact::Cli::SpecCriteria.call(options)[:provider_state]).to be_nil 53 | end 54 | end 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/lib/pact/cli_spec.rb: -------------------------------------------------------------------------------- 1 | require 'pact/cli' 2 | require 'pact/cli/generate_pact_docs' 3 | require 'pact/doc/generator' 4 | 5 | module Pact 6 | describe CLI do 7 | describe "docs" do 8 | 9 | let(:docs) { subject.invoke :docs } 10 | 11 | before do 12 | allow(Pact::Doc::Generate).to receive(:call) 13 | end 14 | 15 | it "generates Markdown documentation" do 16 | expect(Pact::Doc::Generate).to receive(:call).with(anything, anything, [Pact::Doc::Markdown::Generator]) 17 | docs 18 | end 19 | 20 | context "with no arguments" do 21 | 22 | subject { CLI.new } 23 | 24 | it 'uses the default Pact configuration for pact_dir and doc_dir' do 25 | expect(Pact::Doc::Generate).to receive(:call).with(Dir.pwd + '/spec/pacts', Dir.pwd + '/doc/pacts', anything) 26 | docs 27 | end 28 | end 29 | 30 | context "with a pact_dir specified" do 31 | 32 | subject { CLI.new([], pact_dir: 'pacts') } 33 | 34 | it 'uses the specified pact_dir' do 35 | expect(Pact::Doc::Generate).to receive(:call).with('pacts', anything, anything) 36 | docs 37 | end 38 | end 39 | 40 | context "with a doc_dir specified" do 41 | 42 | subject { CLI.new([], doc_dir: 'docs') } 43 | 44 | it 'uses the specified doc_dir' do 45 | expect(Pact::Doc::Generate).to receive(:call).with(anything, 'docs', anything) 46 | docs 47 | end 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/lib/pact/consumer/service_consumer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module Pact 4 | describe ServiceConsumer do 5 | describe "as_json" do 6 | it "returns a hash representation of the object" do 7 | expect(ServiceConsumer.new(:name => "Bob").as_json).to eq :name => "Bob" 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/lib/pact/doc/markdown/consumer_contract_renderer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pact/doc/markdown/consumer_contract_renderer' 3 | 4 | module Pact 5 | module Doc 6 | module Markdown 7 | describe ConsumerContractRenderer do 8 | 9 | subject { ConsumerContractRenderer.new(consumer_contract) } 10 | let(:consumer_contract) { Pact::ConsumerContract.from_uri './spec/support/markdown_pact.json' } 11 | 12 | let(:expected_output) { File.read("./spec/support/generated_markdown.md", external_encoding: Encoding::UTF_8) } 13 | 14 | describe "#call" do 15 | 16 | context "with markdown characters in the pacticipant names" do 17 | let(:consumer_contract) { Pact::ConsumerContract.from_uri './spec/support/markdown_pact_with_markdown_chars_in_names.json' } 18 | 19 | it "escapes the markdown characters" do 20 | expect(subject.call).to include '### A pact between Some\*Consumer\*App and Some\_Provider\_App' 21 | expect(subject.call).to include '#### Requests from Some\*Consumer\*App to Some\_Provider\_App' 22 | end 23 | end 24 | 25 | context "with ruby's default external encoding is not UTF-8" do 26 | around do |example| 27 | back = nil 28 | WarningSilencer.enable { back, Encoding.default_external = Encoding.default_external, Encoding::ASCII_8BIT } 29 | example.run 30 | WarningSilencer.enable { Encoding.default_external = back } 31 | end 32 | 33 | it "renders the interactions" do 34 | expect(subject.call).to eq(expected_output) 35 | end 36 | end 37 | 38 | it "renders the interactions" do 39 | expect(subject.call).to eq(expected_output) 40 | end 41 | end 42 | 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/lib/pact/doc/markdown/index_renderer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pact/doc/markdown/index_renderer' 3 | 4 | module Pact 5 | module Doc 6 | module Markdown 7 | describe IndexRenderer do 8 | 9 | let(:consumer_name) { "Some Consumer" } 10 | let(:docs) { {"Some Provider" => "Some Provider.md", "Some other provider" => "Some other provider.md"} } 11 | let(:subject) { IndexRenderer.new(consumer_name, docs) } 12 | let(:expected_content) { File.read('./spec/support/generated_index.md')} 13 | 14 | describe "#call" do 15 | it "renders the index" do 16 | expect(subject.call).to eq expected_content 17 | end 18 | end 19 | 20 | describe ".call" do 21 | it "renders the index" do 22 | expect(IndexRenderer.call(consumer_name, docs) ).to eq expected_content 23 | end 24 | end 25 | 26 | end 27 | end 28 | end 29 | end -------------------------------------------------------------------------------- /spec/lib/pact/hal/authorization_header_redactor_spec.rb: -------------------------------------------------------------------------------- 1 | require 'pact/hal/authorization_header_redactor' 2 | 3 | module Pact 4 | module Hal 5 | describe AuthorizationHeaderRedactor do 6 | let(:stream) { StringIO.new } 7 | let(:stream_redactor) { AuthorizationHeaderRedactor.new(stream) } 8 | 9 | it "redacts the authorizaton header" do 10 | stream_redactor << "\\r\\nAuthorization: Bearer TOKEN\\r\\n" 11 | expect(stream.string).to eq "\\r\\nAuthorization: [redacted]\\r\\n" 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/lib/pact/pact_broker/notices_spec.rb: -------------------------------------------------------------------------------- 1 | require 'pact/pact_broker/notices' 2 | 3 | module Pact 4 | module PactBroker 5 | describe Notices do 6 | 7 | let(:notice_hashes) do 8 | [ 9 | { text: "foo", when: "before_verification" } 10 | ] 11 | end 12 | 13 | subject(:notices) { Notices.new(notice_hashes) } 14 | 15 | it "behaves like an array" do 16 | expect(subject.size).to eq notice_hashes.size 17 | end 18 | 19 | describe "before_verification_notices" do 20 | let(:notice_hashes) do 21 | [ 22 | { text: "foo", when: "before_verification" }, 23 | { text: "bar", when: "blah" }, 24 | ] 25 | end 26 | 27 | its(:before_verification_notices_text) { is_expected.to eq [ "foo" ] } 28 | end 29 | 30 | describe "after_verification_notices_text" do 31 | let(:notice_hashes) do 32 | [ 33 | { text: "foo", when: "after_verification:success_false_published_true" }, 34 | { text: "bar", when: "blah" }, 35 | ] 36 | end 37 | 38 | subject { notices.after_verification_notices_text(false, true) } 39 | 40 | it { is_expected.to eq [ "foo" ] } 41 | end 42 | 43 | describe "after_verification_notices" do 44 | let(:notice_hashes) do 45 | [ 46 | { text: "meep", when: "after_verification" }, 47 | { text: "foo", when: "after_verification:success_false_published_true" }, 48 | { text: "bar", when: "blah" }, 49 | ] 50 | end 51 | 52 | subject { notices.after_verification_notices(false, true) } 53 | 54 | it { is_expected.to eq [{ text: "meep", when: "after_verification" }, { text: "foo", when: "after_verification" }] } 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/lib/pact/pact_broker_spec.rb: -------------------------------------------------------------------------------- 1 | require 'pact/pact_broker' 2 | require 'pact/provider/pact_uri' 3 | 4 | module Pact 5 | module PactBroker 6 | describe ".fetch_pact_uris_for_verification" do 7 | before do 8 | allow(Pact::PactBroker::FetchPactURIsForVerification).to receive(:call).and_return([pact_uri]) 9 | end 10 | 11 | let(:pact_uri) { Pact::Provider::PactURI.new("http://pact") } 12 | 13 | subject { Pact::PactBroker.fetch_pact_uris_for_verification("foo") } 14 | 15 | it "calls Pact::PactBroker::FetchPendingPacts" do 16 | expect(Pact::PactBroker::FetchPactURIsForVerification).to receive(:call).with("foo") 17 | subject 18 | end 19 | 20 | it "returns a list of pact uris" do 21 | expect(subject).to eq [pact_uri] 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/lib/pact/provider/configuration/configuration_extension_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pact/provider/configuration/configuration_extension' 3 | 4 | module Pact 5 | 6 | module Provider 7 | 8 | module Configuration 9 | 10 | describe ConfigurationExtension do 11 | 12 | subject { Object.new.extend(ConfigurationExtension) } 13 | 14 | it 'replays interactions in the recorded order by default' do 15 | expect(subject.interactions_replay_order).to eq :recorded 16 | end 17 | 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/lib/pact/provider/configuration/pact_verification_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pact/provider/configuration/pact_verification' 3 | 4 | module Pact 5 | module Provider 6 | module Configuration 7 | describe PactVerification do 8 | 9 | describe 'create_verification' do 10 | let(:url) { 'http://some/uri' } 11 | let(:pact_repository_uri_options) do 12 | { 13 | username: 'pact_broker_username', 14 | password: 'pact_broker_password' 15 | } 16 | end 17 | let(:consumer_name) {'some consumer'} 18 | let(:ref) { :prod } 19 | let(:options) { { ref: :prod } } 20 | 21 | context "with valid values" do 22 | subject do 23 | uri = url 24 | PactVerification.build(consumer_name, options) do 25 | pact_uri uri, pact_repository_uri_options 26 | end 27 | end 28 | 29 | it "creates a Verification" do 30 | pact_uri = Pact::Provider::PactURI.new(url, pact_repository_uri_options) 31 | expect(Pact::Provider::PactVerification).to receive(:new).with(consumer_name, pact_uri, ref) 32 | subject 33 | end 34 | end 35 | 36 | context "with a nil uri" do 37 | subject do 38 | PactVerification.build(consumer_name, options) do 39 | pact_uri nil 40 | end 41 | end 42 | 43 | it "raises a validation error" do 44 | expect { subject }.to raise_error /Please provide a pact_uri/ 45 | end 46 | end 47 | end 48 | end 49 | end 50 | end 51 | end -------------------------------------------------------------------------------- /spec/lib/pact/provider/configuration/service_provider_config_spec.rb: -------------------------------------------------------------------------------- 1 | require 'pact/provider/configuration/service_provider_config' 2 | 3 | module Pact 4 | module Provider 5 | module Configuration 6 | describe ServiceProviderConfig do 7 | describe "app" do 8 | 9 | let(:app_block) { ->{ Object.new } } 10 | 11 | subject { ServiceProviderConfig.new("1.2.3'", "main", [], true, 'http://ci/build/1', &app_block) } 12 | 13 | it "should execute the app_block each time" do 14 | expect(subject.app.object_id).to_not equal(subject.app.object_id) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/lib/pact/provider/configuration_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pact/provider/configuration' 3 | 4 | module Pact::Provider::Configuration 5 | 6 | describe ConfigurationExtension do 7 | 8 | before do 9 | Pact.clear_configuration 10 | end 11 | 12 | describe "service_provider" do 13 | 14 | context "when a provider is configured" do 15 | 16 | before do 17 | Pact.service_provider "Fred" do 18 | app { "An app" } 19 | end 20 | end 21 | 22 | it "should allow configuration of the test app" do 23 | expect(Pact.configuration.provider.app).to eql "An app" 24 | end 25 | 26 | end 27 | 28 | context "when a provider is not configured" do 29 | 30 | it "raises an error" do 31 | expect{ Pact.configuration.provider }.to raise_error(/Please configure your provider/) 32 | end 33 | 34 | end 35 | 36 | context "when a provider is configured without an app" do 37 | 38 | before do 39 | Pact.service_provider "Fred" do 40 | end 41 | end 42 | 43 | it "uses the app from config.ru" do 44 | expect( Pact.configuration.provider.app ).to be(AppForConfigRu) 45 | end 46 | 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/lib/pact/provider/generators_spec.rb: -------------------------------------------------------------------------------- 1 | require 'pact/generators' 2 | require 'pact/provider/request' 3 | 4 | describe Pact::Generators do 5 | it 'apply_generators for path' do 6 | expected_request = Pact::Request::Expected.from_hash({ 7 | method: 'GET', 8 | path: '/path/1', 9 | generators: { 10 | 'path' => { 11 | 'type' => 'ProviderState', 12 | 'expression' => '/path/${itemID}' 13 | } 14 | } 15 | }) 16 | state_params = { 17 | 'itemID' => 2 18 | } 19 | request = Pact::Provider::Request::Replayable.new(expected_request, state_params) 20 | expect(request.path).to eq('/path/2') 21 | end 22 | 23 | it 'apply_generators for headers' do 24 | expected_request = Pact::Request::Expected.from_hash({ 25 | method: 'GET', 26 | path: '/path/1', 27 | headers: { 28 | 'Authorization' => 'Bearer 123' 29 | }, 30 | generators: { 31 | 'header' => { 32 | '$.Authorization' => { 33 | 'expression' => 'Bearer ${accessToken}', 34 | 'type' => 'ProviderState' 35 | } 36 | } 37 | } 38 | }) 39 | state_params = { 40 | 'accessToken' => 'ABC' 41 | } 42 | request = Pact::Provider::Request::Replayable.new(expected_request, state_params) 43 | expect(request.headers).to eq({ 44 | 'HTTP_AUTHORIZATION' => 'Bearer ABC' 45 | }) 46 | end 47 | 48 | it 'apply_generators for body' do 49 | expected_request = Pact::Request::Expected.from_hash({ 50 | method: 'GET', 51 | path: '/path/1', 52 | body: { 53 | 'result' => [ 54 | '12345F' 55 | ] 56 | }, 57 | generators: { 58 | 'body' => { 59 | '$.result[0]' => { 60 | 'type' => 'RandomHexadecimal' 61 | } 62 | } 63 | } 64 | }) 65 | request = Pact::Provider::Request::Replayable.new(expected_request) 66 | expect(request.body['result'][0].length).to eq(8) 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/lib/pact/provider/help/console_text_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pact/provider/help/console_text' 3 | require 'fileutils' 4 | 5 | module Pact 6 | module Provider 7 | module Help 8 | describe ConsoleText do 9 | 10 | describe ".call" do 11 | let(:help_path) { File.join(reports_dir, Write::HELP_FILE_NAME) } 12 | let(:reports_dir) { "./tmp/reports/pacts" } 13 | let(:color) { false } 14 | 15 | subject { ConsoleText.call(reports_dir, color: color) } 16 | 17 | context "when the help file is found" do 18 | 19 | let(:help_text) do 20 | <<-EOS 21 | # Heading 22 | ## Another heading 23 | Text 24 | EOS 25 | end 26 | 27 | before do 28 | FileUtils.mkdir_p reports_dir 29 | File.open(help_path, "w") { |io| io << help_text } 30 | end 31 | 32 | it "returns the help file text" do 33 | expect(subject).to eq help_text 34 | end 35 | 36 | context "when the reports_dir is nil" do 37 | subject { ConsoleText.call(nil, color: false) } 38 | 39 | before do 40 | allow(Pact.configuration).to receive(:reports_dir).and_return(reports_dir) 41 | end 42 | 43 | it "uses the default reports_dir" do 44 | expect(subject).to eq help_text 45 | end 46 | end 47 | 48 | context "with color: true" do 49 | 50 | let(:color) { true } 51 | 52 | it "colourises the headings" do 53 | expect(subject).to_not include("# Heading") 54 | expect(subject).to include("Heading") 55 | end 56 | end 57 | 58 | context "when the help file cannot be found" do 59 | 60 | before do 61 | FileUtils.rm_rf reports_dir 62 | end 63 | 64 | it "returns an apologetic error message" do 65 | expect(subject).to include("Sorry") 66 | expect(subject).to include("tmp/reports/pacts") 67 | end 68 | end 69 | end 70 | end 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /spec/lib/pact/provider/help/content_spec.rb: -------------------------------------------------------------------------------- 1 | require 'pact/provider/help/content' 2 | 3 | module Pact 4 | module Provider 5 | module Help 6 | describe Content do 7 | describe "#text" do 8 | before do 9 | allow(PactDiff).to receive(:call).with(pact_source_1).and_return('diff 1') 10 | allow(PactDiff).to receive(:call).with(pact_source_2).and_return(nil) 11 | end 12 | 13 | let(:pact_source_1) { { some: 'json'}.to_json } 14 | let(:pact_source_2) { { some: 'other json'}.to_json } 15 | let(:pact_sources) { [pact_source_1, pact_source_2] } 16 | 17 | subject { Content.new(pact_sources) } 18 | 19 | it "displays the log path" do 20 | expect(subject.text).to include Pact.configuration.log_path 21 | end 22 | 23 | it "displays the tmp dir" do 24 | expect(subject.text).to include Pact.configuration.tmp_dir 25 | end 26 | 27 | it "displays the diff" do 28 | expect(subject.text).to include 'diff 1' 29 | end 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/lib/pact/provider/help/prompt_text_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pact/provider/help/prompt_text' 3 | 4 | module Pact 5 | module Provider 6 | module Help 7 | describe PromptText do 8 | 9 | describe ".call" do 10 | let(:reports_dir){ File.expand_path "./reports/pacts" } 11 | let(:color) { false } 12 | subject { PromptText.(reports_dir, color: color)} 13 | 14 | it "returns a prompt to tell the user how to get help" do 15 | expect(subject).to eq "For assistance debugging failures, run `bundle exec rake pact:verify:help`\n" 16 | end 17 | 18 | context "when color: true" do 19 | let(:color) { true } 20 | it "displays the message in color" do 21 | expect(subject).to include "\e[" 22 | end 23 | end 24 | 25 | context "when the reports_dir is not in the standard location" do 26 | let(:reports_dir) { File.expand_path "reportyporty/pacts" } 27 | it "includes the report dir as the rake task arg so that the rake task knows where to find the help file" do 28 | expect(subject).to include("[reportyporty/pacts]") 29 | end 30 | end 31 | end 32 | 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/lib/pact/provider/help/write_spec.rb: -------------------------------------------------------------------------------- 1 | require 'pact/provider/help/write' 2 | 3 | module Pact 4 | module Provider 5 | module Help 6 | describe Write do 7 | 8 | describe "#call" do 9 | 10 | let(:pact_sources) { double('pact jsons') } 11 | let(:reports_dir) { "./tmp/reports" } 12 | let(:text) { "help text" } 13 | 14 | before do 15 | FileUtils.rm_rf reports_dir 16 | allow_any_instance_of(Content).to receive(:text).and_return(text) 17 | end 18 | 19 | subject { Write.call(pact_sources, reports_dir) } 20 | 21 | let(:actual_contents) { File.read(File.join(reports_dir, Write::HELP_FILE_NAME)) } 22 | 23 | it "passes the pact_sources into the Content" do 24 | expect(Content).to receive(:new).with(pact_sources).and_return(double(text: '')) 25 | subject 26 | end 27 | 28 | it "writes the help content to a file" do 29 | subject 30 | expect(actual_contents).to eq(text) 31 | end 32 | 33 | end 34 | 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/lib/pact/provider/pact_helper_locator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pact/provider/pact_helper_locator' 3 | 4 | module Pact::Provider 5 | 6 | describe PactHelperLocater do 7 | describe "pact_helper_path", :fakefs => true, skip_jruby: true do 8 | 9 | subject { PactHelperLocater.pact_helper_path } 10 | 11 | def make_pactfile dir 12 | FileUtils.mkdir_p ".#{dir}" 13 | FileUtils.touch ".#{dir}/pact_helper.rb" 14 | end 15 | 16 | PACT_HELPER_FILE_DIRS = [ 17 | '/spec/blah/service-consumers', 18 | '/spec/consumers', 19 | '/spec/blah/service_consumers', 20 | '/spec/serviceconsumers', 21 | '/spec/consumer', 22 | '/spec', 23 | '/test/blah/service-consumers', 24 | '/test/consumers', 25 | '/test/blah/service_consumers', 26 | '/test/serviceconsumers', 27 | '/test/consumer', 28 | '/test', 29 | '/blah', 30 | '/blah/consumer', 31 | '' 32 | ] 33 | 34 | PACT_HELPER_FILE_DIRS.each do |dir| 35 | context "the pact_helper is stored in #{dir}" do 36 | it "finds the pact_helper" do 37 | make_pactfile dir 38 | expect(subject).to eq File.join(Dir.pwd, dir, 'pact_helper.rb') 39 | end 40 | end 41 | end 42 | 43 | context "when more than one pact_helper exists" do 44 | it "returns the one that matches the most explict search pattern" do 45 | make_pactfile '/spec/consumer' 46 | FileUtils.touch 'pact_helper.rb' 47 | expect(subject).to eq File.join(Dir.pwd, '/spec/consumer/pact_helper.rb') 48 | end 49 | end 50 | 51 | context "when a file exists ending in pact_helper.rb" do 52 | it "is not identifed as a pact helper" do 53 | FileUtils.mkdir_p './spec' 54 | FileUtils.touch './spec/not_pact_helper.rb' 55 | expect { subject }.to raise_error /Please create a pact_helper.rb file/ 56 | end 57 | end 58 | end 59 | end 60 | end -------------------------------------------------------------------------------- /spec/lib/pact/provider/print_missing_provider_states_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pact/provider/print_missing_provider_states' 3 | 4 | module Pact 5 | module Provider 6 | describe PrintMissingProviderStates do 7 | 8 | describe "text" do 9 | let(:missing_provider_states) { { 'Consumer 1' => ['state1', 'state2'], 'Consumer 2' => ['state3'] } } 10 | let(:expected_output) { File.read("./spec/support/missing_provider_states_output.txt") } 11 | 12 | subject { PrintMissingProviderStates.text missing_provider_states } 13 | 14 | it "returns the text" do 15 | expect(subject).to include expected_output 16 | end 17 | end 18 | 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /spec/lib/pact/provider/rspec/calculate_exit_code_spec.rb: -------------------------------------------------------------------------------- 1 | require 'pact/provider/rspec/calculate_exit_code' 2 | 3 | module Pact 4 | module Provider 5 | module RSpec 6 | module CalculateExitCode 7 | describe ".call" do 8 | let(:pact_source_1) { double('pact_source_1', pending?: pending_1) } 9 | let(:pending_1) { nil } 10 | let(:pact_source_2) { double('pact_source_2', pending?: pending_2) } 11 | let(:pending_2) { nil } 12 | let(:pact_source_3) { double('pact_source_3', pending?: pending_3) } 13 | let(:pending_3) { nil } 14 | let(:pact_sources) { [pact_source_1, pact_source_2, pact_source_3]} 15 | 16 | let(:failed_examples) { [ example_1, example_2, example_3 ] } 17 | let(:example_1) { double('example_1', metadata: { pact_source: pact_source_1 }) } 18 | let(:example_2) { double('example_2', metadata: { pact_source: pact_source_1 }) } 19 | let(:example_3) { double('example_3', metadata: { pact_source: pact_source_2 }) } 20 | 21 | subject { CalculateExitCode.call(pact_sources, failed_examples ) } 22 | 23 | context "when all pacts are pending" do 24 | let(:pending_1) { true } 25 | let(:pending_2) { true } 26 | let(:pending_3) { true } 27 | 28 | it "returns 0" do 29 | expect(subject).to eq 0 30 | end 31 | end 32 | 33 | context "when a non pending pact has no failures" do 34 | let(:pending_1) { true } 35 | let(:pending_2) { true } 36 | let(:pending_3) { false } 37 | 38 | it "returns 0" do 39 | expect(subject).to eq 0 40 | end 41 | end 42 | 43 | context "when a non pending pact no failures" do 44 | let(:pending_1) { true } 45 | let(:pending_2) { false } 46 | let(:pending_3) { false } 47 | 48 | it "returns 1" do 49 | expect(subject).to eq 1 50 | end 51 | end 52 | end 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /spec/lib/pact/provider/world_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | load 'pact/provider/world.rb' 3 | 4 | describe Pact do 5 | describe ".provider_world" do 6 | it "returns a world" do 7 | expect(Pact.provider_world).to be_instance_of Pact::Provider::World 8 | end 9 | it "returns the same world each time" do 10 | expect(Pact.provider_world).to be Pact.provider_world 11 | end 12 | end 13 | 14 | describe ".clear_provider_world" do 15 | it "clears the world" do 16 | original_world = Pact.provider_world 17 | Pact.clear_provider_world 18 | expect(original_world).to_not be Pact.provider_world 19 | end 20 | end 21 | 22 | end 23 | 24 | module Pact 25 | module Provider 26 | describe World do 27 | 28 | subject { World.new } 29 | 30 | describe "provider_states" do 31 | it "returns a provider state proxy" do 32 | expect(subject.provider_states).to be_instance_of State::ProviderStateProxy 33 | end 34 | it "returns the same object each time" do 35 | expect(subject.provider_states).to be subject.provider_states 36 | end 37 | end 38 | 39 | describe "pact_urls" do 40 | context "with pact_uri_sources" do 41 | before do 42 | subject.add_pact_uri_source(pact_uri_source_1) 43 | subject.add_pact_uri_source(pact_uri_source_2) 44 | end 45 | 46 | let(:pact_uri_source_1) { double('pact_uri_source_1', call: ["uri-1"]) } 47 | let(:pact_uri_source_2) { double('pact_uri_source_2', call: ["uri-2"]) } 48 | 49 | let(:pact_urls) { subject.pact_urls } 50 | 51 | it "invokes call on the pact_uri_sources" do 52 | expect(pact_uri_source_1).to receive(:call) 53 | expect(pact_uri_source_2).to receive(:call) 54 | pact_urls 55 | end 56 | 57 | it "concatenates the results" do 58 | expect(pact_urls).to eq ["uri-1", "uri-2"] 59 | end 60 | 61 | context "with a pact_verification" do 62 | before do 63 | subject.add_pact_verification(pact_verification) 64 | end 65 | 66 | let(:pact_verification) { double('PactVerification', uri: "uri-3") } 67 | 68 | it "concatenates the results with those of the pact_uri_sources" do 69 | expect(pact_urls).to eq ["uri-3", "uri-1", "uri-2"] 70 | end 71 | end 72 | end 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /spec/lib/pact/utils/metrics_spec.rb: -------------------------------------------------------------------------------- 1 | require "pact/utils/metrics" 2 | 3 | describe Pact::Utils::Metrics do 4 | describe ".report_metric" do 5 | before do 6 | ENV["COMPUTERNAME"] = "test" 7 | ENV["HOSTNAME"] = "test" 8 | stub_request(:post, "https://www.google-analytics.com/collect").to_return(status: 200, body: "", headers: {}) 9 | stub_const("RUBY_PLATFORM", "x86_64-darwin20") 10 | allow(Pact::Utils::Metrics).to receive(:in_thread) { |&block| block.call } 11 | allow(Pact.configuration).to receive(:output_stream).and_return(output_stream) 12 | end 13 | 14 | let(:output_stream) { double("stream").as_null_object } 15 | 16 | subject { Pact::Utils::Metrics.report_metric("Event", "Category", "Action", "Value") } 17 | 18 | context "when do not track is not set" do 19 | let(:expected_event) { { 20 | "v" => 1, 21 | "t" => "event", 22 | "tid" => "UA-117778936-1", 23 | "cid" => "098f6bcd4621d373cade4e832627b4f6", 24 | "an" => "Pact Ruby", 25 | "av" => Pact::VERSION, 26 | "aid" => "pact-ruby", 27 | "aip" => 1, 28 | "ds" => ENV["PACT_EXECUTING_LANGUAGE"] ? "client" : "cli", 29 | "cd2" => ENV["CI"] == "true" ? "CI" : "unknown", 30 | "cd3" => RUBY_PLATFORM, 31 | "cd6" => ENV["PACT_EXECUTING_LANGUAGE"] || "unknown", 32 | "cd7" => ENV["PACT_EXECUTING_LANGUAGE_VERSION"], 33 | "el" => "Event", 34 | "ec" => "Category", 35 | "ea" => "Action", 36 | "ev" => "Value" 37 | } } 38 | 39 | it "sends metrics" do 40 | subject 41 | 42 | expect(WebMock).to have_requested(:post, "https://www.google-analytics.com/collect"). 43 | with(body: Rack::Utils.build_query(expected_event)) 44 | end 45 | end 46 | 47 | context "when do not track is set to true" do 48 | before do 49 | ENV["PACT_DO_NOT_TRACK"] = "true" 50 | end 51 | 52 | it "does not send metrics" do 53 | subject 54 | expect(WebMock).to_not have_requested(:post, "https://www.google-analytics.com/collect") 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/pact_specification/compliance-1.0.0.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pact/consumer/request' 3 | require 'pact/consumer_contract/request' 4 | 5 | PACT_SPEC_DIR = "../pact-specification/testcases" 6 | REQUEST_TEST_CASE_FOLDERS = Dir.glob("#{PACT_SPEC_DIR}/request/**") 7 | REQUEST_TEST_CASE_FILES = Dir.glob("#{PACT_SPEC_DIR}/request/**/*.json") 8 | 9 | TEST_DESCRIPTIONS = {true => "matches", false => "does not match"} 10 | 11 | describe "Pact gem complicance with Pact Specification 1.0.0" do 12 | 13 | directories = Dir.glob("#{PACT_SPEC_DIR}/*") 14 | 15 | directories.each do | dir_name | 16 | 17 | describe File.basename(dir_name) do 18 | 19 | sub_directories = Dir.glob("#{dir_name}/*") 20 | 21 | sub_directories.each do | sub_dir_name | 22 | 23 | context File.basename(sub_dir_name) do 24 | testcases = Dir.glob("#{sub_dir_name}/**/*.json") 25 | 26 | testcases.each do | file_name | 27 | 28 | context File.basename(file_name).chomp(".json") do 29 | 30 | file_content = JSON.parse(File.read(file_name)) 31 | expected = Pact::Request::Expected.from_hash(file_content["expected"]) 32 | actual = Pact::Consumer::Request::Actual.from_hash(file_content["actual"]) 33 | expected_result = file_content.fetch("match") 34 | comment = file_content["comment"] 35 | 36 | it "#{TEST_DESCRIPTIONS[expected_result]} - #{comment}" do 37 | expect(expected.matches?(actual)).to eq expected_result 38 | end 39 | 40 | end 41 | 42 | end 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/service_providers/helper.rb: -------------------------------------------------------------------------------- 1 | require 'pact/consumer/rspec' 2 | 3 | Pact.service_consumer 'Pact Ruby' do 4 | has_pact_with 'Pact Broker' do 5 | mock_service :pact_broker do 6 | port 8888 7 | pact_specification_version '2.0.0' 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'rspec/its' 3 | require 'fakefs/spec_helpers' 4 | require 'pact' 5 | require 'webmock/rspec' 6 | require 'support/factories' 7 | require 'support/spec_support' 8 | require 'pact/provider/rspec' 9 | 10 | WebMock.disable_net_connect!(allow_localhost: true, allow: "https://www.google-analytics.com") 11 | 12 | require './spec/support/active_support_if_configured' 13 | require './spec/support/warning_silencer' 14 | 15 | is_jruby = defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby' 16 | is_windows = Gem.win_platform? 17 | 18 | RSpec.configure do | config | 19 | config.include(FakeFS::SpecHelpers, fakefs: true) 20 | 21 | config.extend Pact::Provider::RSpec::ClassMethods 22 | config.include Pact::Provider::RSpec::InstanceMethods 23 | config.include Pact::Provider::TestMethods 24 | config.include Pact::SpecSupport 25 | if config.respond_to?(:example_status_persistence_file_path=) 26 | config.example_status_persistence_file_path = "./spec/examples.txt" 27 | end 28 | config.filter_run_excluding skip_jruby: is_jruby 29 | config.filter_run_excluding skip_windows: is_windows 30 | end 31 | -------------------------------------------------------------------------------- /spec/standalone/consumer_fail_test.rb: -------------------------------------------------------------------------------- 1 | require 'pact/consumer/rspec' 2 | require './spec/support/active_support_if_configured' 3 | 4 | Pact.service_consumer "Standalone Consumer" do 5 | has_pact_with "Standalone Provider" do 6 | mock_service :standalone_service do 7 | port 1238 8 | end 9 | end 10 | end 11 | 12 | class StandaloneClient 13 | 14 | def initialize base_url 15 | @base_url = base_url 16 | end 17 | 18 | def call 19 | uri = URI("#{@base_url}/something") 20 | post_req = Net::HTTP::Post.new(uri.path) 21 | post_req['Content-Type'] = "application/json" 22 | post_req.body = {a: "not matching body"}.to_json 23 | response = Net::HTTP.start(uri.hostname, uri.port) do |http| 24 | http.request post_req 25 | end 26 | JSON.parse(response.body) 27 | end 28 | 29 | end 30 | 31 | describe StandaloneClient, pact: true do 32 | 33 | subject { StandaloneClient.new("http://localhost:1238") } 34 | 35 | describe "call" do 36 | 37 | let(:expected_body) { {a: "body"} } 38 | let(:expected_headers) { {'Content-Type' => "application/hal+json"} } 39 | 40 | before do 41 | standalone_service. 42 | upon_receiving("a request to create something").with(method: 'post', path: '/something', headers: expected_headers, body: expected_body). 43 | will_respond_with(status: 200, headers: {}, body: {a: 'response body'}) 44 | 45 | standalone_service. 46 | upon_receiving("a request to create something else").with(method: 'post', path: '/something-else', headers: expected_headers, body: expected_body). 47 | will_respond_with(status: 200, headers: {}, body: {a: 'response body'}) 48 | end 49 | 50 | it "will fail and display a helpful message" do 51 | subject.call 52 | end 53 | end 54 | 55 | end -------------------------------------------------------------------------------- /spec/standalone/consumer_pass_test.rb: -------------------------------------------------------------------------------- 1 | require 'pact/consumer/rspec' 2 | require './spec/support/active_support_if_configured' 3 | 4 | Pact.service_consumer "Standalone Consumer" do 5 | has_pact_with "Standalone Provider" do 6 | mock_service :standalone_service do 7 | port 1237 8 | end 9 | end 10 | end 11 | 12 | class StandaloneClient 13 | 14 | def initialize base_url 15 | @base_url = base_url 16 | end 17 | 18 | def call 19 | uri = URI("#{@base_url}/something") 20 | post_req = Net::HTTP::Post.new(uri.path) 21 | post_req['Content-Type'] = "application/json" 22 | post_req.body = {a: "body"}.to_json 23 | response = Net::HTTP.start(uri.hostname, uri.port) do |http| 24 | http.request post_req 25 | end 26 | response.body 27 | end 28 | 29 | end 30 | 31 | describe StandaloneClient, pact: true do 32 | 33 | subject { StandaloneClient.new("http://localhost:1237") } 34 | 35 | describe "call" do 36 | 37 | let(:expected_body) { {a: "body"} } 38 | let(:response_body) { {a: 'response body'} } 39 | 40 | before do 41 | standalone_service. 42 | upon_receiving("a request to create something").with(method: 'post', path: '/something', body: expected_body). 43 | will_respond_with(status: 200, headers: {}, body: response_body) 44 | end 45 | 46 | it "will pass" do 47 | expect(subject.call).to eq response_body.to_json 48 | end 49 | end 50 | 51 | end -------------------------------------------------------------------------------- /spec/support/a_consumer-a_producer.json: -------------------------------------------------------------------------------- 1 | { 2 | "producer": { 3 | "name": "an old producer" 4 | }, 5 | "consumer": { 6 | "name": "a consumer" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "request one", 11 | "request": { 12 | "method": "get", 13 | "path": "/path_one" 14 | }, 15 | "response": { 16 | }, 17 | "producer_state": "state one" 18 | }, 19 | { 20 | "description": "request two", 21 | "request": { 22 | "method": "get", 23 | "path": "/path_two" 24 | }, 25 | "response": { 26 | } 27 | } 28 | ], 29 | "metadata": { 30 | "pactSpecificationVersion": "1.0" 31 | } 32 | } -------------------------------------------------------------------------------- /spec/support/a_consumer-a_provider.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": { 3 | "name": "a provider" 4 | }, 5 | "consumer": { 6 | "name": "a consumer" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "request one", 11 | "request": { 12 | "method": "get", 13 | "path": "/path_one" 14 | }, 15 | "response": { 16 | }, 17 | "provider_state": "state one" 18 | }, 19 | { 20 | "description": "request two", 21 | "request": { 22 | "method": "get", 23 | "path": "/path_two" 24 | }, 25 | "response": { 26 | } 27 | } 28 | ], 29 | "metadata": { 30 | "pactSpecificationVersion": "1.0" 31 | } 32 | } -------------------------------------------------------------------------------- /spec/support/active_support_if_configured.rb: -------------------------------------------------------------------------------- 1 | if ENV['LOAD_ACTIVE_SUPPORT'] 2 | $stderr.puts 'LOADING ACTIVE SUPPORT!!!! Hopefully it all still works' 3 | require 'active_support/all' 4 | require 'active_support' 5 | require 'active_support/json' 6 | end -------------------------------------------------------------------------------- /spec/support/app_for_config_ru.rb: -------------------------------------------------------------------------------- 1 | class AppForConfigRu 2 | def call env 3 | end 4 | end -------------------------------------------------------------------------------- /spec/support/bar_fail_pact_helper.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'pact/provider/rspec' 3 | 4 | module Pact 5 | module Test 6 | class BarApp 7 | def call env 8 | [200, {'Content-Type' => 'application/hal+json'}, [{name: "Thing 2"}.to_json]] 9 | end 10 | end 11 | 12 | Pact.configure do | config | 13 | config.logger.level = Logger::DEBUG 14 | end 15 | 16 | Pact.service_provider "Bar" do 17 | app { BarApp.new } 18 | app_version '1.2.3' 19 | app_version_tags ['master'] 20 | publish_verification_results true 21 | 22 | honours_pact_with 'Foo' do 23 | pact_uri './spec/support/foo-bar.json' 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/support/bar_pact_helper.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'pact/provider/rspec' 3 | 4 | module Pact 5 | module Test 6 | class BarApp 7 | def call env 8 | [200, {'Content-Type' => 'application/json'}, [{name: "Thing 1"}.to_json]] 9 | end 10 | end 11 | 12 | Pact.configure do | config | 13 | config.logger.level = Logger::DEBUG 14 | end 15 | 16 | Pact.service_provider "Bar" do 17 | app { BarApp.new } 18 | app_version '1.2.3' 19 | app_version_branch 'master' 20 | app_version_tags ['master'] 21 | publish_verification_results true 22 | 23 | honours_pacts_from_pact_broker do 24 | pact_broker_base_url "http://localhost:9292" 25 | consumer_version_tags ["prod"] 26 | end 27 | 28 | honours_pact_with 'Foo' do 29 | pact_uri './spec/pacts/foo-bar.json' 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/support/case-insensitive-response-header-matching.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "an easy consumer" 4 | }, 5 | "provider": { 6 | "name": "a provider which returns headers that don't match the expected case" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "a test request", 11 | "request": { 12 | "method": "get", 13 | "path": "/" 14 | }, 15 | "response": { 16 | "status": 200, 17 | "headers": {"Content-Type": "application/hippo"} 18 | } 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /spec/support/case-insensitive-response-header-matching.rb: -------------------------------------------------------------------------------- 1 | module Pact 2 | module Test 3 | class CaseInsensitiveResponseHeadersApp 4 | 5 | def call env 6 | [200, {'cOnTent-tYpe' => 'application/hippo'},[]] 7 | end 8 | 9 | end 10 | end 11 | end 12 | 13 | Pact.service_provider "Provider" do 14 | app { Pact::Test::CaseInsensitiveResponseHeadersApp.new } 15 | end -------------------------------------------------------------------------------- /spec/support/cli.rb: -------------------------------------------------------------------------------- 1 | module Pact 2 | module Support 3 | module CLI 4 | 5 | def execute_command command, options = {} 6 | output = `#{command}` 7 | ensure_patterns_present(command, options, output) if options[:with] 8 | ensure_patterns_not_present(command, options, output) if options[:without] 9 | end 10 | 11 | def ensure_patterns_present command, options, output 12 | require 'rainbow' 13 | options[:with].each do | pattern | 14 | raise ("Could not find #{pattern.inspect} in output of #{command}" + "\n\n#{output}") unless output =~ pattern 15 | end 16 | end 17 | 18 | def ensure_patterns_not_present command, options, output 19 | require 'rainbow' 20 | options[:without].each do | pattern | 21 | raise ("Expected not to find #{pattern.inspect} in output of #{command}" + "\n\n#{output}") if output =~ pattern 22 | end 23 | end 24 | 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/support/consumer_contract_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": { 3 | "name": "a provider" 4 | }, 5 | "consumer": { 6 | "name": "a consumer" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "request one", 11 | "request": { 12 | "method": "get", 13 | "path": "/path_one" 14 | }, 15 | "response": { 16 | "status" : 200 17 | }, 18 | "provider_state": "state one" 19 | } 20 | ], 21 | "metadata": { 22 | "pactSpecificationVersion": "1.0" 23 | } 24 | } -------------------------------------------------------------------------------- /spec/support/docs/a_consumer-a_provider.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": { 3 | "name": "a provider" 4 | }, 5 | "consumer": { 6 | "name": "a consumer" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "request one", 11 | "request": { 12 | "method": "get", 13 | "path": "/path_one" 14 | }, 15 | "response": { 16 | }, 17 | "provider_state": "state one" 18 | }, 19 | { 20 | "description": "request two", 21 | "request": { 22 | "method": "get", 23 | "path": "/path_two" 24 | }, 25 | "response": { 26 | } 27 | } 28 | ], 29 | "metadata": { 30 | "pactSpecificationVersion": "1.0" 31 | } 32 | } -------------------------------------------------------------------------------- /spec/support/factories.rb: -------------------------------------------------------------------------------- 1 | require 'hashie' 2 | require 'hashie/extensions/key_conversion' 3 | 4 | module Pact 5 | module HashUtils 6 | 7 | class Converter < Hash 8 | include Hashie::Extensions::KeyConversion 9 | include Hashie::Extensions::DeepMerge 10 | end 11 | 12 | def symbolize_keys hash 13 | Hash[Converter[hash].symbolize_keys] 14 | end 15 | 16 | def stringify_keys hash 17 | Hash[Converter[hash].stringify_keys] 18 | end 19 | 20 | def deep_merge hash1, hash2 21 | Converter[hash1].deep_merge(Converter[hash2]) 22 | end 23 | end 24 | end 25 | 26 | class InteractionFactory 27 | 28 | extend Pact::HashUtils 29 | 30 | def self.create hash = {} 31 | defaults = { 32 | 'description' => 'a description', 33 | 'provider_state' => 'a thing exists', 34 | 'request' => { 35 | 'path' => '/path', 36 | 'method' => 'get', 37 | }, 38 | 'response' => { 39 | 'status' => 200, 40 | 'body' => {a: 'response body'} 41 | } 42 | } 43 | Pact::Interaction.from_hash(stringify_keys(deep_merge(defaults, stringify_keys(hash)))) 44 | end 45 | end 46 | 47 | 48 | class ConsumerContractFactory 49 | extend Pact::HashUtils 50 | DEFAULTS = {:consumer_name => 'consumer', 51 | :provider_name => 'provider', 52 | :interactions => [InteractionFactory.create]} 53 | 54 | def self.create overrides = {} 55 | options = deep_merge(symbolize_keys(DEFAULTS), symbolize_keys(overrides)) 56 | Pact::ConsumerContract.new({:consumer => Pact::ServiceConsumer.new(name: options[:consumer_name]), 57 | :provider => Pact::ServiceProvider.new(name: options[:provider_name]), 58 | :interactions => options[:interactions]}) 59 | end 60 | end 61 | 62 | 63 | 64 | class ResponseFactory 65 | extend Pact::HashUtils 66 | DEFAULTS = {:status => 200, :body => {a: 'body'}}.freeze 67 | def self.create_hash overrides = {} 68 | deep_merge(DEFAULTS, overrides) 69 | end 70 | end 71 | 72 | class RequestFactory 73 | extend Pact::HashUtils 74 | DEFAULTS = {:path => '/path', :method => 'get', :query => 'query', :headers => {}}.freeze 75 | def self.create_hash overrides = {} 76 | deep_merge(DEFAULTS, overrides) 77 | end 78 | 79 | def self.create_actual overrides = {} 80 | Pact::Consumer::Request::Actual.from_hash(create_hash(overrides)) 81 | end 82 | end -------------------------------------------------------------------------------- /spec/support/foo-bar-message.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "Foo" 4 | }, 5 | "provider": { 6 | "name": "Bar" 7 | }, 8 | "messages": [ 9 | { 10 | "description": "a message", 11 | "providerStates": [ 12 | { 13 | "name": "a world exists" 14 | } 15 | ], 16 | "contents": { 17 | "text": "Hello world" 18 | } 19 | } 20 | ], 21 | "metadata": { 22 | "pactSpecification": { 23 | "version": "2.0.0" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spec/support/generated_index.md: -------------------------------------------------------------------------------- 1 | ### Pacts for Some Consumer 2 | 3 | * [Some Provider](Some%20Provider.md) 4 | * [Some other provider](Some%20other%20provider.md) 5 | -------------------------------------------------------------------------------- /spec/support/generated_markdown.md: -------------------------------------------------------------------------------- 1 | ### A pact between Some Consumer and Some Provider 2 | 3 | #### Requests from Some Consumer to Some Provider 4 | 5 | * [A request for alligators in Brüssel](#a_request_for_alligators_in_Brüssel_given_alligators_exist) given alligators exist 6 | 7 | * [A request for polar bears](#a_request_for_polar_bears) 8 | 9 | #### Interactions 10 | 11 | 12 | Given **alligators exist**, upon receiving **a request for alligators in Brüssel** from Some Consumer, with 13 | ```json 14 | { 15 | "method": "get", 16 | "path": "/alligators" 17 | } 18 | ``` 19 | Some Provider will respond with: 20 | ```json 21 | { 22 | "status": 200, 23 | "headers": { 24 | "Content-Type": "application/json" 25 | }, 26 | "body": { 27 | "alligators": [ 28 | { 29 | "name": "Bob", 30 | "phoneNumber": "12345678" 31 | } 32 | ] 33 | } 34 | } 35 | ``` 36 | 37 | Upon receiving **a request for polar bears** from Some Consumer, with 38 | ```json 39 | { 40 | "method": "get", 41 | "path": "/polar-bears" 42 | } 43 | ``` 44 | Some Provider will respond with: 45 | ```json 46 | { 47 | "status": 404, 48 | "headers": { 49 | "Content-Type": "application/json" 50 | }, 51 | "body": { 52 | "message": "Sorry, due to climate change, the polar bears are currently unavailable." 53 | } 54 | } 55 | ``` 56 | -------------------------------------------------------------------------------- /spec/support/interaction_view_model.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": { 3 | "name": "a_provider" 4 | }, 5 | "consumer": { 6 | "name": "a*consumer" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "a request with a body and headers", 11 | "request": { 12 | "method": "get", 13 | "path": "/path", 14 | "query": "some=thing", 15 | "headers": { 16 | "key": "a header" 17 | }, 18 | "body": { 19 | "key": "a body" 20 | } 21 | }, 22 | "response": {} 23 | }, 24 | { 25 | "description": "a request with an empty body and empty headers", 26 | "request": { 27 | "method": "get", 28 | "path": "/", 29 | "headers": {}, 30 | "body": {} 31 | }, 32 | "response": {} 33 | }, 34 | { 35 | "description": "a response with a body and headers", 36 | "request": { 37 | "method": "get", 38 | "path": "/" 39 | }, 40 | "response": { 41 | "headers": { 42 | "key": "a header" 43 | }, 44 | "body": { 45 | "key": "a body" 46 | }, 47 | "status": 200 48 | } 49 | }, 50 | { 51 | "description": "a response with an empty body and empty headers", 52 | "request": { 53 | "method": "get", 54 | "path": "/" 55 | }, 56 | "response": { 57 | "status": 200, 58 | "headers": {}, 59 | "body": {} 60 | } 61 | } 62 | ] 63 | } -------------------------------------------------------------------------------- /spec/support/interaction_view_model_with_terms.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": { 3 | "name": "a provider" 4 | }, 5 | "consumer": { 6 | "name": "a consumer" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "an interaction with terms", 11 | "request": { 12 | "method": "post", 13 | "path": "/path", 14 | "query": "some=thing", 15 | "headers": { 16 | "key": "a header" 17 | }, 18 | "body": { 19 | "term": { 20 | "json_class": "Pact::Term", 21 | "data": { 22 | "generate": "sunny", 23 | "matcher": { 24 | "json_class": "Regexp", 25 | "o": 0, 26 | "s": "sun" 27 | } 28 | } 29 | } 30 | } 31 | }, 32 | "response": { 33 | "status": 200, 34 | "body": { 35 | "term": { 36 | "json_class": "Pact::Term", 37 | "data": { 38 | "generate": "rainy", 39 | "matcher": { 40 | "json_class": "Regexp", 41 | "o": 0, 42 | "s": "rain" 43 | } 44 | } 45 | } 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /spec/support/markdown_pact.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": { 3 | "name": "Some Provider" 4 | }, 5 | "consumer": { 6 | "name": "Some Consumer" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "a request for alligators in Brüssel", 11 | "provider_state": "alligators exist", 12 | "request": { 13 | "method": "get", 14 | "path": "/alligators" 15 | }, 16 | "response": { 17 | "headers" : {"Content-Type": "application/json"}, 18 | "status" : 200, 19 | "body" : { 20 | "alligators": [{ 21 | "name": "Bob", 22 | "phoneNumber" : { 23 | "json_class": "Pact::Term", 24 | "data": { 25 | "generate": "12345678", 26 | "matcher": {"json_class":"Regexp","o":0,"s":"\\d+"} 27 | } 28 | } 29 | }] 30 | } 31 | } 32 | },{ 33 | "description": "a request for polar bears", 34 | "provider_state": null, 35 | "request": { 36 | "method": "get", 37 | "path": "/polar-bears" 38 | }, 39 | "response": { 40 | "headers" : {"Content-Type": "application/json"}, 41 | "status" : 404, 42 | "body" : { 43 | "message": "Sorry, due to climate change, the polar bears are currently unavailable." 44 | } 45 | } 46 | } 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /spec/support/markdown_pact_with_markdown_chars_in_names.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": { 3 | "name": "Some_Provider_App" 4 | }, 5 | "consumer": { 6 | "name": "Some*Consumer*App" 7 | }, 8 | "interactions": [ 9 | 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /spec/support/message_spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'pact/message' 2 | 3 | # Example data store 4 | 5 | class DataStore 6 | def self.greeting_recipient= greeting_recipient 7 | @greeting_recipient = greeting_recipient 8 | end 9 | 10 | def self.greeting_recipient 11 | @greeting_recipient 12 | end 13 | end 14 | 15 | # Example message producer 16 | 17 | class BarProvider 18 | def create_message 19 | { 20 | text: "Hello #{DataStore.greeting_recipient}" 21 | } 22 | end 23 | end 24 | 25 | # Provider states 26 | 27 | Pact.provider_states_for "Foo" do 28 | provider_state "a world exists" do 29 | set_up do 30 | DataStore.greeting_recipient = "world" 31 | end 32 | end 33 | end 34 | 35 | CONFIG = { 36 | "a message" => lambda { BarProvider.new.create_message } 37 | } 38 | 39 | Pact.message_provider "Bar" do 40 | builder { |description| CONFIG[description].call } 41 | end 42 | -------------------------------------------------------------------------------- /spec/support/missing_provider_states_output.txt: -------------------------------------------------------------------------------- 1 | Pact.provider_states_for "Consumer 1" do 2 | 3 | provider_state "state1" do 4 | set_up do 5 | # Your set up code goes here 6 | end 7 | end 8 | 9 | provider_state "state2" do 10 | set_up do 11 | # Your set up code goes here 12 | end 13 | end 14 | 15 | end 16 | 17 | Pact.provider_states_for "Consumer 2" do 18 | 19 | provider_state "state3" do 20 | set_up do 21 | # Your set up code goes here 22 | end 23 | end 24 | 25 | end -------------------------------------------------------------------------------- /spec/support/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "Consumer" 4 | }, 5 | "provider": { 6 | "name": "Provider" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "an OPTIONS request", 11 | "request": { 12 | "method": "options", 13 | "path": "/" 14 | }, 15 | "response": { 16 | "status": 200 17 | }, 18 | "provider_state": null 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /spec/support/options_app.rb: -------------------------------------------------------------------------------- 1 | require 'pact/provider/rspec' 2 | 3 | class App 4 | def self.call env 5 | if env['REQUEST_METHOD'] == 'OPTIONS' 6 | [200, {}, []] 7 | else 8 | [500, {}, ["Expected an options request"]] 9 | end 10 | end 11 | end 12 | 13 | Pact.service_provider 'Provider' do 14 | app { App } 15 | end -------------------------------------------------------------------------------- /spec/support/pact_helper.rb: -------------------------------------------------------------------------------- 1 | # This is the pact_helper for rake pact:tests 2 | require 'json' 3 | require 'pact/provider/rspec' 4 | require './spec/support/active_support_if_configured' 5 | 6 | module Pact 7 | module Test 8 | class TestApp 9 | def call env 10 | if env['PATH_INFO'] == '/weather' 11 | [200, {'Content-Type' => 'application/json'}, [{message: WEATHER[:current_state], :array => [{"foo"=> "blah"}]}.to_json]] 12 | elsif env['PATH_INFO'] == '/sometext' 13 | [200, {'Content-Type' => 'text/plain'}, ['some text']] 14 | elsif env['PATH_INFO'] == '/content_type_is_important' 15 | [200, {'Content-Type' => 'application/json'}, [{message: "A message", note: "This will cause verify to fail if it using the wrong content type differ."}.to_json]] 16 | else 17 | raise "unexpected path #{env['PATH_INFO']}!!!" 18 | end 19 | end 20 | end 21 | 22 | Pact.configure do | config | 23 | config.logger.level = Logger::DEBUG 24 | config.diff_formatter = :unix 25 | config.reports_dir = 'tmp/spec_reports' 26 | end 27 | 28 | Pact.service_provider "Some Provider" do 29 | app { TestApp.new } 30 | app_version '1.2.3' 31 | 32 | honours_pact_with 'some-test-consumer' do 33 | pact_uri './spec/support/test_app_pass.json' 34 | end 35 | end 36 | 37 | Pact.set_up do 38 | WEATHER ||= {} 39 | end 40 | 41 | #one with a top level consumer 42 | Pact.provider_states_for 'some-test-consumer' do 43 | 44 | provider_state "the weather is sunny" do 45 | set_up do 46 | 47 | WEATHER[:current_state] = 'sunny' 48 | end 49 | end 50 | end 51 | 52 | #one without a top level consumer 53 | Pact.provider_state "the weather is cloudy" do 54 | set_up do 55 | WEATHER[:current_state] = 'cloudy' 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/support/pact_helper_for_provider_state_params_test.rb: -------------------------------------------------------------------------------- 1 | # This is the pact_helper for rake pact:tests 2 | require 'json' 3 | require 'pact/provider/rspec' 4 | require 'ostruct' 5 | 6 | module Pact 7 | module Test 8 | class ParamsTestApp 9 | 10 | ALLIGATORS = [] 11 | 12 | def call env 13 | [200, {'Content-Type' => 'application/json'}, [ALLIGATORS.first.to_h.to_json]] 14 | end 15 | end 16 | 17 | Pact.configure do | config | 18 | config.reports_dir = 'tmp/spec_reports' 19 | end 20 | 21 | Pact.service_provider "some-test-provider" do 22 | app { ParamsTestApp.new } 23 | app_version '1.2.3' 24 | end 25 | 26 | Pact.provider_states_for 'some-test-consumer' do 27 | provider_state "the first alligator exists" do 28 | set_up do | params | 29 | ParamsTestApp::ALLIGATORS << OpenStruct.new(name: params.fetch('name')) 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/support/provider_states_params_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer" : { "name" : "some-test-consumer" }, 3 | "provider" : { "name" : "some-test-provider" }, 4 | "interactions": [ 5 | { 6 | "description": "a request for the first alligator", 7 | "providerStates": [ 8 | { 9 | "name": "the first alligator exists", 10 | "params": { 11 | "name": "Mary" 12 | } 13 | } 14 | ], 15 | "request": { 16 | "method": "GET", 17 | "path": "/alligators/first" 18 | }, 19 | "response": { 20 | "status": 200, 21 | "body": { 22 | "name": "Mary" 23 | } 24 | } 25 | } 26 | ], 27 | "metadata": { 28 | "pactSpecification": { 29 | "version": "3" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /spec/support/response_body_term.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "Foo" 4 | }, 5 | "provider": { 6 | "name": "Bar" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "a retrieve thing request", 11 | "request": { 12 | "method": "get", 13 | "path": "/thing", 14 | "headers": { 15 | "Accept": "application/json" 16 | } 17 | }, 18 | "response": { 19 | "status": 200, 20 | "headers": { 21 | "Content-Type": "application/json" 22 | }, 23 | "body": { 24 | "action_history": [ 25 | { 26 | "at": "2016-02-11T12:00:00Z" 27 | } 28 | ] 29 | }, 30 | "matchingRules": { 31 | "$.body.action_history[0].at": { 32 | "match": "regex", 33 | "regex": "^\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z)$" 34 | } 35 | } 36 | } 37 | } 38 | ], 39 | "metadata": { 40 | "pactSpecification": { 41 | "version": "2.0.0" 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /spec/support/response_body_term_app.rb: -------------------------------------------------------------------------------- 1 | require 'pact/provider/rspec' 2 | 3 | class App 4 | def self.call env 5 | [200, {'Content-Type' => 'application/json'}, []] 6 | end 7 | end 8 | 9 | Pact.service_provider 'Provider' do 10 | app { App } 11 | end 12 | -------------------------------------------------------------------------------- /spec/support/spec_support.rb: -------------------------------------------------------------------------------- 1 | require 'pact/rspec' 2 | 3 | module Pact 4 | module SpecSupport 5 | 6 | extend self 7 | 8 | def remove_ansicolor string 9 | string.gsub(/\e\[(\d+)m/, '') 10 | end 11 | 12 | Pact::RSpec.with_rspec_2 do 13 | 14 | def instance_double *args 15 | double(*args) 16 | end 17 | 18 | end 19 | end 20 | end -------------------------------------------------------------------------------- /spec/support/ssl_server.rb: -------------------------------------------------------------------------------- 1 | if __FILE__ == $0 2 | 3 | SSL_KEY = "spec/fixtures/certificates/key.pem" 4 | SSL_CERT = "spec/fixtures/certificates/client_cert.pem" 5 | SSL_CA_CERT = "spec/fixtures/certificates/ca_cert.pem" 6 | 7 | trap(:INT) do 8 | @server.shutdown 9 | exit 10 | end 11 | 12 | def webrick_opts port 13 | certificate = OpenSSL::X509::Certificate.new(File.read(SSL_CERT)) 14 | cert_name = certificate.subject.to_a.collect{|a| a[0..1] } 15 | logger_stream = ENV["DEBUG"] ? $stderr : StringIO.new 16 | { 17 | Port: port, 18 | Host: "0.0.0.0", 19 | AccessLog: [], 20 | Logger: WEBrick::Log.new(logger_stream,WEBrick::Log::INFO), 21 | SSLVerifyClient: OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT | OpenSSL::SSL::VERIFY_PEER, 22 | SSLCACertificateFile: SSL_CA_CERT, 23 | SSLCertificate: certificate, 24 | SSLPrivateKey: OpenSSL::PKey::RSA.new(File.read(SSL_KEY)), 25 | SSLEnable: true, 26 | SSLCertName: cert_name, 27 | } 28 | end 29 | 30 | app = ->(_env) { puts "hello"; [200, {}, ["Hello world" + "\n"]] } 31 | 32 | require "webrick" 33 | require "webrick/https" 34 | require "rack" 35 | begin 36 | require "rackup/handler/webrick" # rack 3 37 | PactWEBrick = Rackup::Handler::WEBrick 38 | rescue LoadError 39 | require "rack/handler/webrick" # rack 2 40 | PactWEBrick = Rack::Handler::WEBrick 41 | end 42 | 43 | opts = webrick_opts(4444) 44 | 45 | PactWEBrick.run(app, **opts) do |server| 46 | @server = server 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/support/stubbing.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "Consumer" 4 | }, 5 | "provider": { 6 | "name": "Provider" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "a test request", 11 | "request": { 12 | "method": "get", 13 | "path": "/" 14 | }, 15 | "response": { 16 | "status": 200, 17 | "body": "stubbing works" 18 | }, 19 | "provider_state": "something is stubbed" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /spec/support/stubbing_using_allow.rb: -------------------------------------------------------------------------------- 1 | require 'pact/provider/rspec' 2 | require 'rspec/mocks' 3 | require './spec/support/active_support_if_configured' 4 | 5 | class StubbedThing 6 | def self.stub_me 7 | end 8 | end 9 | 10 | class App 11 | def self.call env 12 | [200, {}, [StubbedThing.stub_me]] 13 | end 14 | end 15 | 16 | Pact.provider_states_for 'Consumer' do 17 | provider_state 'something is stubbed' do 18 | set_up do 19 | allow(StubbedThing).to receive(:stub_me).and_return("stubbing works") 20 | end 21 | end 22 | end 23 | 24 | # Include the ExampleMethods module after the provider states are declared 25 | # to ensure the ordering doesn't matter 26 | 27 | Pact.service_provider 'Provider' do 28 | app { App } 29 | end -------------------------------------------------------------------------------- /spec/support/term-v2.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "some-test-consumer" 4 | }, 5 | "provider": { 6 | "name": "an unknown provider" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "a test request", 11 | "request": { 12 | "method": "get", 13 | "path": "/weather", 14 | "query": "" 15 | }, 16 | "response": { 17 | "matchingRules": { 18 | "$.headers.Content-Type" : { 19 | "match": "regex", "regex": "json" 20 | }, 21 | "$.body.message" : { 22 | "match": "regex", "regex": "sun" 23 | } 24 | }, 25 | "status": 200, 26 | "headers" : { 27 | "Content-Type": "foo/json" 28 | }, 29 | "body": { 30 | "message" : "sunful" 31 | } 32 | }, 33 | "provider_state": "the weather is sunny" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /spec/support/term.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "some-test-consumer" 4 | }, 5 | "provider": { 6 | "name": "an unknown provider" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "a test request", 11 | "request": { 12 | "method": "get", 13 | "path": "/weather", 14 | "query": "" 15 | }, 16 | "response": { 17 | "status": 200, 18 | "headers" : { 19 | "Content-type": { 20 | "json_class": "Pact::Term", 21 | "data": { 22 | "generate": "text/plain", 23 | "matcher": { 24 | "json_class": "Regexp", 25 | "o": 0, 26 | "s": "text" 27 | } 28 | } 29 | } 30 | }, 31 | "body": { 32 | "message" : { 33 | "json_class": "Pact::Term", 34 | "data": { 35 | "generate": "rainy", 36 | "matcher": { 37 | "json_class": "Regexp", 38 | "o": 0, 39 | "s": "rain" 40 | } 41 | } 42 | } 43 | } 44 | }, 45 | "provider_state": "the weather is sunny" 46 | } 47 | ] 48 | } -------------------------------------------------------------------------------- /spec/support/test_app_fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "an unknown consumer" 4 | }, 5 | "provider": { 6 | "name": "an unknown provider" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "a test request", 11 | "request": { 12 | "method": "get", 13 | "path": "/weather", 14 | "query": "" 15 | }, 16 | "response": { 17 | "status": 200, 18 | "headers" : {"Content-type": "application/json"}, 19 | "body": {"message" : "this is not the weather you are looking for", "array": [{"foo": "bar"}], "somethingElse" : {"blah" : {"nested" : "that is missing"} }} 20 | 21 | }, 22 | "provider_state": "the weather is cloudy" 23 | },{ 24 | "description": "another test request", 25 | "request": { 26 | "method": "get", 27 | "path": "/weather", 28 | "query": "" 29 | }, 30 | "response": { 31 | "status": 200, 32 | "headers" : { 33 | "Content-type": { 34 | "json_class": "Pact::Term", 35 | "data": { 36 | "generate": "application/hal+json", 37 | "matcher": {"json_class":"Regexp","o":0,"s":"hal"} 38 | } 39 | }, 40 | "X-Special-Header": "something" 41 | }, 42 | "body": {"message" : "this is not the weather you are looking for"} 43 | 44 | } 45 | },{ 46 | "description": "another test request", 47 | "provider_state": "a missing provider state", 48 | "request": { 49 | "method": "get", 50 | "path": "/weather", 51 | "query": "" 52 | }, 53 | "response": { 54 | "status": 200, 55 | "headers" : {"Content-type": "application/json"}, 56 | "body": {"message" : "this is not the weather you are looking for"} 57 | 58 | } 59 | } 60 | ] 61 | } -------------------------------------------------------------------------------- /spec/support/test_app_pass.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "some-test-consumer" 4 | }, 5 | "provider": { 6 | "name": "an unknown provider" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "a test request", 11 | "request": { 12 | "method": "get", 13 | "path": "/weather", 14 | "query": "" 15 | }, 16 | "response": { 17 | "status": 200, 18 | "headers" : {"Content-type": "application/json"}, 19 | "body": {"message" : "sunny"} 20 | }, 21 | "provider_state": "the weather is sunny" 22 | }, 23 | { 24 | "description": "a test request for text", 25 | "request": { 26 | "method": "get", 27 | "path": "/sometext", 28 | "query": "", 29 | "body" : "some request text" 30 | }, 31 | "response": { 32 | "status": 200, 33 | "headers" : {"Content-type": "text/plain"}, 34 | "body": "some text" 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /spec/support/test_app_with_right_content_type_differ.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumer": { 3 | "name": "some-test-consumer" 4 | }, 5 | "provider": { 6 | "name": "the test app in the pact_helper file" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "a test request expecting an application/json response", 11 | "request": { 12 | "method": "get", 13 | "path": "/content_type_is_important", 14 | "query": "" 15 | }, 16 | "response": { 17 | "status": 200, 18 | "headers" : {"Content-type": "application/json"}, 19 | "body": {"message" : "A message"} 20 | } 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /spec/support/text.txt: -------------------------------------------------------------------------------- 1 | This is a file -------------------------------------------------------------------------------- /spec/support/warning_silencer.rb: -------------------------------------------------------------------------------- 1 | module WarningSilencer 2 | extend self 3 | 4 | def enable 5 | old, $VERBOSE = $VERBOSE, nil 6 | yield 7 | ensure 8 | $VERBOSE = old 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /tasks/message-test.rake: -------------------------------------------------------------------------------- 1 | require 'pact/tasks' 2 | 3 | Pact::VerificationTask.new(:message) do | pact | 4 | pact.uri 'spec/support/foo-bar-message.json', pact_helper: 'spec/support/message_spec_helper.rb' 5 | end 6 | -------------------------------------------------------------------------------- /tasks/release.rake: -------------------------------------------------------------------------------- 1 | RELEASE_NOTES_TEMPLATE_PATH = "packaging/RELEASE_NOTES.md.template" 2 | RELEASE_NOTES_PATH = "build/RELEASE_NOTES.md" 3 | 4 | desc 'Generate change log' 5 | task :generate_changelog do 6 | require 'conventional_changelog' 7 | require 'pact/version' 8 | ConventionalChangelog::Generator.new.generate! version: "v#{Pact::VERSION}" 9 | end 10 | 11 | desc 'Tag for release' 12 | task :tag_for_release do | t, args | 13 | command = "git tag -a v#{Pact::VERSION} -m \"chore(release): version #{Pact::VERSION}\" && git push origin v#{Pact::VERSION}" 14 | puts command 15 | puts `#{command}` 16 | end 17 | -------------------------------------------------------------------------------- /tasks/spec.rake: -------------------------------------------------------------------------------- 1 | RSpec::Core::RakeTask.new(:spec) 2 | 3 | # Need to run this in separate process because left over state from 4 | # testing the actual pact framework messes up the tests that actually 5 | # use pact. 6 | RSpec::Core::RakeTask.new('spec:provider') do | task | 7 | task.pattern = "spec/service_providers/**/*_test.rb" 8 | end 9 | 10 | task :set_active_support_on do 11 | ENV["LOAD_ACTIVE_SUPPORT"] = 'true' 12 | end 13 | 14 | desc "This is to ensure that the gem still works even when active support JSON is loaded." 15 | task :spec_with_active_support => [:set_active_support_on] do 16 | Rake::Task['spec'].execute 17 | end 18 | --------------------------------------------------------------------------------