{"id":13450162,"url":"https://github.com/pact-foundation/pact-ruby","last_synced_at":"2026-04-01T17:43:57.661Z","repository":{"id":12138923,"uuid":"14731610","full_name":"pact-foundation/pact-ruby","owner":"pact-foundation","description":"Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project.","archived":false,"fork":false,"pushed_at":"2026-03-26T18:59:04.000Z","size":6500,"stargazers_count":2193,"open_issues_count":5,"forks_count":220,"subscribers_count":64,"default_branch":"master","last_synced_at":"2026-03-27T08:32:18.838Z","etag":null,"topics":["contract-testing","hacktoberfest","smartbear-supported"],"latest_commit_sha":null,"homepage":"https://pact.io","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pact-foundation.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2013-11-26T22:55:05.000Z","updated_at":"2026-03-23T21:02:12.000Z","dependencies_parsed_at":"2023-10-15T16:52:56.494Z","dependency_job_id":"c04d2d4f-6e29-432d-b40f-44c5afef00cb","html_url":"https://github.com/pact-foundation/pact-ruby","commit_stats":{"total_commits":1471,"total_committers":93,"mean_commits":"15.817204301075268","dds":"0.43711760707002034","last_synced_commit":"de1f3c788d8584b8da602ce713e5b4d4f76cc756"},"previous_names":["pact-foundation/pact"],"tags_count":138,"template":false,"template_full_name":null,"purl":"pkg:github/pact-foundation/pact-ruby","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pact-foundation%2Fpact-ruby","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pact-foundation%2Fpact-ruby/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pact-foundation%2Fpact-ruby/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pact-foundation%2Fpact-ruby/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pact-foundation","download_url":"https://codeload.github.com/pact-foundation/pact-ruby/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pact-foundation%2Fpact-ruby/sbom","scorecard":{"id":717572,"data":{"date":"2025-08-11","repo":{"name":"github.com/pact-foundation/pact-ruby","commit":"fa2f28263f6a5ee6a70ebdc7568eeaa4323cf9aa"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.3,"checks":[{"name":"Maintained","score":5,"reason":"6 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 5","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":5,"reason":"Found 3/6 approved changesets -- score normalized to 5","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/release_gem.yml:1","Warn: no topLevel permission defined: .github/workflows/smartbear-issue-label-added.yml:1","Warn: no topLevel permission defined: .github/workflows/test.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release_gem.yml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/pact-foundation/pact-ruby/release_gem.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release_gem.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/pact-foundation/pact-ruby/release_gem.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release_gem.yml:31: update your workflow using https://app.stepsecurity.io/secureworkflow/pact-foundation/pact-ruby/release_gem.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release_gem.yml:35: update your workflow using https://app.stepsecurity.io/secureworkflow/pact-foundation/pact-ruby/release_gem.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release_gem.yml:49: update your workflow using https://app.stepsecurity.io/secureworkflow/pact-foundation/pact-ruby/release_gem.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/smartbear-issue-label-added.yml:10: update your workflow using https://app.stepsecurity.io/secureworkflow/pact-foundation/pact-ruby/smartbear-issue-label-added.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:59: update your workflow using https://app.stepsecurity.io/secureworkflow/pact-foundation/pact-ruby/test.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/test.yml:60: update your workflow using https://app.stepsecurity.io/secureworkflow/pact-foundation/pact-ruby/test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/pact-foundation/pact-ruby/test.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/test.yml:22: update your workflow using https://app.stepsecurity.io/secureworkflow/pact-foundation/pact-ruby/test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:41: update your workflow using https://app.stepsecurity.io/secureworkflow/pact-foundation/pact-ruby/test.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/test.yml:42: update your workflow using https://app.stepsecurity.io/secureworkflow/pact-foundation/pact-ruby/test.yml/master?enable=pin","Info:   0 out of   5 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   7 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.txt:0","Info: FSF or OSI recognized license: MIT License: LICENSE.txt:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":9,"reason":"1 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-9j94-67jr-4cqj"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 28 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-22T10:10:39.326Z","repository_id":12138923,"created_at":"2025-08-22T10:10:39.326Z","updated_at":"2025-08-22T10:10:39.326Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31290560,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["contract-testing","hacktoberfest","smartbear-supported"],"created_at":"2024-07-31T07:00:31.708Z","updated_at":"2026-04-01T17:43:57.654Z","avatar_url":"https://github.com/pact-foundation.png","language":"Ruby","readme":"# Pact Ruby\n\n`pact-ruby v2+` implements support for the latest versions of Pact specifications:\n\n- It's based on pact-ffi and pact-ruby-ffi\n- It provides a convenient DSL, simplifying the writing of contract tests in Ruby/RSpec\n- Writing contract tests with HTTP transports\n- Writing contract tests with non-HTTP transports (for example, gRPC)\n- Writing contract tests for async messages (Kafka, etc.)\n- Verifying contract tests for HTTP/non-HTTP/async message transport\n  - V4 specification supports mixed pact interactions in a single file.\n\n## Architecture\n\n![Pact tests architecture](./documentation/pact-arch.png)\n\n- DSL - implementation of RSpec-DSL for convenient writing of Pact tests\n- Matchers - implementation of Pact matchers, which are convenient helpers used in consumer-DSL, encapsulating all the logic for serialization into Pact format\n- Mock servers - mock servers that allow for correct execution of provider tests\n\n## Installation\n\nThe `pact/v2` namespace was introduced in `pact-ruby` v1.67.0 and moved to `pact` in v2\n\nIt introduces a suite of new depedencies, including a reliance on the `pact-ffi` and `ffi` gems.\n\n```rb\n gem \"pact\"\n```\n\n`pact-ffi` ships prebuilt binary gems, and does not support platforms outside of the released [pact_ffi](https://github.com/pact-foundation/pact-reference/tree/master/rust/pact_ffi) libraries\n\n| Version   | Platform             |\n|-----------|----------------------|\n| 0.4.28.0  | x86_64-darwin        |\n| 0.4.28.0  | arm64-darwin         |\n| 0.4.28.0  | x86_64-linux         |\n| 0.4.28.0  | aarch64-linux        |\n| 0.4.28.0  | x86_64-linux-musl    |\n| 0.4.28.0  | aarch64-linux-musl   |\n| 0.4.28.0  | x64-mingw32          |\n| 0.4.28.0  | x64-mingw-ucrt       |\n\nIf you require a pure ruby gem, you are advised to pin to v1.\n\n## Usage\n\nFor each type of interaction (due to their specific features), a separate version of DSL has been implemented. However, the general principles remain the same for each type of interaction.\n\nPlace your consumer tests under\n\n`spec/pact/provider/**`\n\n**it's not an error: consumer tests contain `providers` subdirectory (because we're testing against different providers)**\n\n```ruby\n\n# Declaration of a consumer test, always include the :pact tag\n# This is used in CI/CD pipelines to separate Pact tests from other RSpec tests\n# Pact tests are not run as part of the general RSpec pipeline\nRSpec.describe \"SomePactConsumerTestForAnyTransport\", :pact do\n  # declaration of the type of interaction - here we determine which consumer and provider interact on which transport\n  has_http_pact_between \"CONSUMER-NAME\", \"PROVIDER-NAME\"\n  # or\n  has_grpc_pact_between \"CONSUMER-NAME\", \"PROVIDER-NAME\"\n  # or\n  has_message_pact_between \"CONSUMER-NAME\", \"PROVIDER-NAME\"\n\n  # the context for one of the interactions, for example GET /api/v2/stores\n  context \"with GET /api/v2/stores\" do\n      let(:interaction) do\n        # creating a new interaction - within which we describe the contract\n        new_interaction\n          # if you need to save any metadata for subsequent use by the test provider,\n          # for example, specify the entity ID that will need to be moved to the database in the test provider\n          # we use the provider states, see more at https://docs.pact.io/getting_started/provider_states\n          .given(\"UNIQUE PROVIDER STATE\", key1: value1, key2: value2)\n          # the description of the interaction, used for identification inside the package binding,\n          # is optional in some cases, but it is recommended to always specify\n          .upon_receiving(\"UNIQUE INTERACTION DESCRIPTION\")\n          # the description of the request using the matchers\n          # the name and parameters of the method differ for different transports\n          .with_request(...)\n          # the description of the response using the matchers\n          # the name and parameters of the method differ for different transports\n          .will_respond_with(...)\n          # further, there are differences for different types of transports,\n          # for more information, see the relevant sections of the documentation\n      end\n\n      it \"executes the pact test without errors\" do | mock_server |\n        interaction.execute do\n          # the url of the started mock server, you should pass this into your api client in the next step\n          mock_server_url = mock_server.url\n          # here our client is called for the API being tested\n          # in this context, the client can be: http client, grpc client, kafka consumer\n          expect(make_request).to be_success\n        end\n      end\n    end\n  end\n\n```\n\nCommon DSL Methods:\n\n- `new_interaction` - initializes a new interaction\n- `given` - allows specifying a provider state with or without parameters, for more details see [provider_states](https://docs.pact.io/getting_started/provider_states)\n- `upon_receiving` - allows specifying the name of the interaction\n\nMultiple interactions can be declared within a single rspec example, in order to call the mock server\n\n- `execute_http_pact`: Use this instead of `interaction.execute`\n\n### HTTP consumers\n\nSpecific DSL methods:\n\n- `with_request({method: string, path: string, headers: kv_hash, body: kv_hash})` - request definition\n- `will_respond_with({status: int, headers: kv_hash, body: kv_hash})` - response definition\n\nMore at [http_client_spec.rb](../spec/pact/providers/pact-ruby-test-app/http_client_spec.rb)\n\n### gRPC consumers\n\nSpecific DSL methods:\n\n- `with_service(PROTO_PATH, RPC_SERVICE_AND_ACTION)` - specifies the contract used, PROTO_PATH is relative from the app root\n- `with_request(request_kv_hash)` - request definition\n- `will_respond_with(response_kv_hash)` - response definition\n\nMore at [grpc_client_spec.rb](../spec/pact/providers/pact-ruby-test-app/grpc_client_spec.rb)\n\n### Message consumers\n\nSpecific DSL methods:\n\n- `with_headers(kv_hash)` - message-headers definition; you can use matchers\n- `with_metadata(kv_hash)` - message-metadata definition (special keys are `key` and `topic`, where, respectively, you can specify the matchers for the partitioning key and the topic\n\nNext, the specifics are one of two options for describing the format:\n\n**JSON** (to describe a message in a JSON representation):\n\n- `with_json_contents(kv_hash)` - message format definition\n\n**PROTO** (to describe the message in the protobuf view):\n\n- `with_proto_class(PROTO_PATH, PROTO_MESSAGE_NAME)` - specifies the contract used, PROTO_PATH is relative to the root, PROTO_MESSAGE_NAME is the name of the message used from the proto file\n- `with_proto_contents(kv_hash)` - message format definition\n\nMore at [message_spec.rb](../spec/pact/providers/pact-ruby-test-app/message.spec.rb)\n\n### Kafka consumers\n\nSpecific DSL methods:\n\n- `with_headers(kv_hash)` - message-headers definition; you can use matchers\n- `with_metadata(kv_hash)` - message-metadata definition (special keys are `key` and `topic`, where, respectively, you can specify the matchers for the partitioning key and the topic\n\nNext, the specifics are one of two options for describing the format:\n\n**JSON** (to describe a message in a JSON representation):\n\n- `with_json_contents(kv_hash)` - message format definition\n\n**PROTO** (to describe the message in the protobuf view):\n\n- `with_proto_class(PROTO_PATH, PROTO_MESSAGE_NAME)` - specifies the contract used, PROTO_PATH is relative to the root, PROTO_MESSAGE_NAME is the name of the message used from the proto file\n- `with_proto_contents(kv_hash)` - message format definition\n\nMore at [kafka_spec.rb](../spec/pact/providers/pact-ruby-test-app/kafka_spec.rb)\n\nRequires the following gems, to use this wrapper\n\n- sbmt-kafka_consumer\n- sbmt-kafka_provider\n\n### Matchers\n\nMatchers are special helper methods that allow you to define rules for matching request/response parameters at the level of the pact manifest.\nThe matchers are described in the [Pact specifications](https://github.com/pact-foundation/pact-specification). In this gem, the matchers are implemented as RSpec helpers.\n\nFor details of the implementation, see [matchers.rb](../lib/pact/matchers.rb)\n\n- `match_exactly(sample)` - match the exact value specified in the sample\n- `match_type_of(sample)` - match the data type (integer, string, boolean) specified in the sample\n- `match_include(sample)` - match a substring\n- `match_any_string(sample)` - match any string, because of the peculiarities, null and empty strings will also be matched here\n- `match_any_integer(sample)` - match any integer\n- `match_any_decimal(sample)` - match any float/double\n- `match_any_number(sample)` - match any integer/float/double\n- `match_any_boolean(sample)` - match any true/false\n- `match_uuid(sample)` - match any UUID (`match_regex` is used under the hood)\n- `match_regex(regex, sample)` - match by regexp\n- `match_datetime(format, sample)` - match any datetime\n- `match_iso8601(sample)` - match datetime in ISO8601 (the matcher does not fully comply with ISO8601, matches only the most common variants, `match_regex` is used under the hood)\n- `match_date(format, sample)` - match any date (rust datetime)\n- `match_time(format, sample)` - match any time (rust datetime)\n- `match_each(template)` - match all the elements of the array according to the specified template, you can use it for nested elements\n- `match_each_regex(regex, sample)` - match all array elements by regex, used for arrays with string elements\n- `match_each_key(template, key_matchers)` - match each hash key according to the specified template\n- `match_each_value(template)` - match each hash value according to the specified template, can be used for nested elements\n- `match_each_kv(template, key_matchers)` - match all the keys/values of Hash according to the specified template and key_matchers, can be used for nested elements\n\nSee the different uses of the matchers in [matchers_spec.rb](../spec/pact/matchers_spec.rb)\n\n### Generators\n\nGenerators are helper methods that allow you to specify dynamic values in your contract tests. These values are generated at runtime, making your contracts more flexible and robust. Below are the available generator methods:\n\nFor details of the implementation, see [matchers.rb](../lib/pact/generators.rb)\n\n- `generate_random_int(min:, max:)`  - Generates a random integer between the specified `min` and `max`.\n- `generate_random_decimal(digits:)` - Generates a random decimal number with the specified number of `digits`.\n- `generate_random_hexadecimal(digits:)` - Generates a random hexadecimal string with the specified number of `digits`.\n- `generate_random_string(size:)` - Generates a random string of the specified `size`.\n- `generate_uuid(example: nil)` - Generates a random UUID. Optionally, provide an `example` value.\n- `generate_date(format: nil, example: nil)` - Generates a date string in the specified `format`. Optionally, provide an `example`.\n- `generate_time(format: nil)` - Generates a time string in the specified `format`.\n- `generate_datetime(format: nil)` - Generates a datetime string in the specified `format`.\n- `generate_random_boolean` - Generates a random boolean value (`true` or `false`).\n- `generate_from_provider_state(expression:, example:)` - Generates a value from the provider state using the given `expression` and `example` value. Allows templating of url and query paths with values only know at provider verification time.\n- `generate_mock_server_url(regex: nil, example: nil)` - Generates a mock server URL. Optionally, specify a `regex` matches and/or an `example` value.\n\nThese generators can be used in your DSL definitions to provide dynamic values for requests, responses, or messages in your contract tests.\n\n#### Generator Examples\n\n```rb\n  .with_request(\n    method: :get, \n    path: generate_from_provider_state(\n      expression: '/alligators/${alligator_name}',\n      example: '/alligators/Mary'),\n    headers: headers)\n\n...\n\n  body: {\n    _links: {\n      :'pf:publish-provider-contract' =\u003e {\n        href: generate_mock_server_url(\n          regex: \".*(\\\\/provider-contracts\\\\/provider\\\\/.*\\\\/publish)$\",\n          example: \"/provider-contracts/provider/{provider}/publish\"\n        ),\n        boolean: generate_random_boolean,\n        integer: generate_random_int(min: 1, max: 100),\n        decimal: generate_random_decimal(digits: 2),\n        hexidecimal: generate_random_hexadecimal(digits: 8),\n        string: generate_random_string(size: 10),\n        uuid: generate_uuid,\n        date: generate_date(format: \"yyyyy.MMMMM.dd GGG\"),\n        time: generate_time(),\n        datetime: generate_datetime(format: \"%Y-%m-%dT%H:%M:%S%z\")\n      }\n    }\n  }\n```\n\n## Provider verification\n\nPlace your provider verification file under\n\n`spec/pact/consumers/**`\n\n**it's not an error: provider tests contain `consumers` subdirectory (because we're verifying against different consumer)**\n\n### Provider verification options\n\n```rb\n            @provider_name = provider_name\n            @log_level = opts[:log_level] || :info\n            @pact_dir = opts[:pact_dir] || nil\n            @provider_setup_port = opts[:provider_setup_port] || 9001\n            @pact_proxy_port = opts[:pact_proxy_port] || 9002\n            @pact_uri = ENV.fetch(\"PACT_URL\", nil) || opts.fetch(:pact_uri, nil)\n            @publish_verification_results = ENV.fetch(\"PACT_PUBLISH_VERIFICATION_RESULTS\", nil) == \"true\" || opts.fetch(:publish_verification_results, false)\n            @provider_version = ENV.fetch(\"PACT_PROVIDER_VERSION\", nil) || opts.fetch(:provider_version, nil)\n            @provider_build_uri = ENV.fetch(\"PACT_PROVIDER_BUILD_URL\", nil) || opts.fetch(:provider_build_uri, nil)\n            @provider_version_branch = ENV.fetch(\"PACT_PROVIDER_BRANCH\", nil) || opts.fetch(:provider_version_branch, nil)\n            @provider_version_tags = ENV.fetch(\"PACT_PROVIDER_VERSION_TAGS\", nil) || opts.fetch(:provider_version_tags, [])\n            @consumer_version_tags = ENV.fetch(\"PACT_CONSUMER_VERSION_TAGS\", nil) || opts.fetch(:consumer_version_tags, [])\n            @consumer_version_selectors = ENV.fetch(\"PACT_CONSUMER_VERSION_SELECTORS\", nil) || opts.fetch(:consumer_version_selectors, nil)\n            @enable_pending = ENV.fetch(\"PACT_VERIFIER_ENABLE_PENDING\", nil) == \"true\" || opts.fetch(:enable_pending, false)\n            @include_wip_pacts_since = ENV.fetch(\"PACT_INCLUDE_WIP_PACTS_SINCE\", nil) || opts.fetch(:include_wip_pacts_since, nil)\n            @fail_if_no_pacts_found = ENV.fetch(\"PACT_FAIL_IF_NO_PACTS_FOUND\", nil) == \"true\" || opts.fetch(:fail_if_no_pacts_found, true)\n            @consumer_branch = ENV.fetch(\"PACT_CONSUMER_BRANCH\", nil) || opts.fetch(:consumer_branch, nil)\n            @consumer_version = ENV.fetch(\"PACT_CONSUMER_VERSION\", nil) || opts.fetch(:consumer_version, nil)\n            @consumer_name = opts[:consumer_name]\n            @broker_url = ENV.fetch(\"PACT_BROKER_BASE_URL\", nil) || opts.fetch(:broker_url, nil)\n            @broker_username = ENV.fetch(\"PACT_BROKER_USERNAME\", nil) || opts.fetch(:broker_username, nil)\n            @broker_password = ENV.fetch(\"PACT_BROKER_PASSWORD\", nil) || opts.fetch(:broker_password, nil)\n            @broker_token = ENV.fetch(\"PACT_BROKER_TOKEN\", nil) || opts.fetch(:broker_token, nil)\n            @verify_only = [ENV.fetch(\"PACT_CONSUMER_FULL_NAME\", nil)].compact || opts.fetch(:verify_only, [])\n```\n\n### Single transport providers\n\n```rb\n# frozen_string_literal: true\n\nrequire \"pact_broker\"\nrequire \"pact_broker/app\"\nrequire \"rspec/mocks\"\ninclude RSpec::Mocks::ExampleMethods\nrequire_relative \"../../service_consumers/hal_relation_proxy_app\"\n\nPactBroker.configuration.base_urls = [\"http://example.org\"]\n\npact_broker = PactBroker::App.new { |c| c.database_connection = PactBroker::TestDatabase.connection_for_test_database }\napp_to_verify = HalRelationProxyApp.new(pact_broker)\n\nrequire \"pact\"\nrequire \"pact/rspec\"\nrequire_relative \"../../service_consumers/shared_provider_states\"\nRSpec.describe \"Verify consumers for Pact Broker\", :pact do\n\n  http_pact_provider \"Pact Broker\", opts: { \n\n    # rails apps should be automatically detected\n    # if you need to configure your own app, you can do so here\n\n    app: app_to_verify,\n    # start rackup with a different port. Useful if you already have something\n    # running on the default port *9292*\n    http_port: 9393, \n    \n    # Set the log level, default is :info\n  \n    log_level: :info,\n    \n    fail_if_no_pacts_found: true,\n\n    # Pact Sources\n\n    # 1. Local pacts from a directory\n\n    # Default is pacts directory in the current working directory\n    # pact_dir: File.expand_path('../../../../consumer/spec/internal/pacts', __dir__),\n    \n    # 2. Broker based pacts\n\n    # Broker credentials\n  \n    # broker_username: \"pact_workshop\", # can be set via PACT_BROKER_USERNAME env var\n    # broker_password: \"pact_workshop\", # can be set via PACT_BROKER_PASSWORD env var\n    # broker_token: \"pact_workshop\", # can be set via PACT_BROKER_TOKEN env var\n  \n    # Remote pact via a uri, traditionally triggered via webhooks\n    # when a pact that requires verification is published\n  \n    # 2a. Webhook triggered pacts\n    # Can be a local file or a remote URL\n    # Most used via webhooks\n    # Can be set via PACT_URL env var\n    # pact_uri: File.expand_path(\"../../../pacts/pact.json\", __dir__),\n    pact_uri: \"https://raw.githubusercontent.com/YOU54F/pact_broker-client/refs/heads/feat/pact-ruby/spec/pacts/Pact%20Broker%20Client%20V2-Pact%20Broker.json\",\n    # pact_uri: \"https://raw.githubusercontent.com/YOU54F/pact_broker-client/refs/heads/feat/pact-ruby/spec/pacts/pact_broker_client-pact_broker.json\",\n    # pact_uri: \"http://localhost:9292/pacts/provider/Pact%20Broker/consumer/Pact%20Broker%20Client/version/96532124f3a53a499276c69ff2df785b8377588e\",\n    \n    # 2b. Dynamically fetched pacts from broker\n\n    # i. Set the broker url\n    # broker_url: \"http://localhost:9292\", # can be set via PACT_BROKER_URL env var\n\n    # ii. Set the consumer version selectors \n    # Consumer version selectors\n    # The pact broker will return the following pacts by default, if no selectors are specified\n    # For the recommended setup, you dont _actually_ need to specify these selectors in ruby\n    # consumer_version_selectors: [{\"deployedOrReleased\" =\u003e true},{\"mainBranch\" =\u003e true},{\"matchingBranch\" =\u003e true}],\n \n    # iii. Set additional dynamic selection verification options\n    # additional dynamic selection verification options\n    enable_pending: true,\n    include_wip_pacts_since: \"2021-01-01\",\n\n    # Publish verification results to the broker\n    publish_verification_results: ENV[\"PACT_PUBLISH_VERIFICATION_RESULTS\"] == \"true\",\n    provider_version: `git rev-parse HEAD`.strip,\n    provider_version_branch: `git rev-parse --abbrev-ref HEAD`.strip,\n    provider_version_tags: [`git rev-parse --abbrev-ref HEAD`.strip],\n    # provider_build_uri: \"YOUR CI URL HERE - must be a valid url\",\n    \n  }\n\n  before_state_setup do\n    PactBroker::TestDatabase.truncate\n  end\n\n  after_state_teardown do\n    PactBroker::TestDatabase.truncate\n  end\n\n  shared_provider_states\n  \nend\n```\n\n### Multiple transport providers\n\nYou may have a consumer pact which consumes multiple transport protocols, if they are using pact specification v4.\n\nIn order to validate an entire pact in a single test run, you will need to configure each transport as appropriate.\n\n```rb\n# frozen_string_literal: true\n\nrequire \"pact/rspec\"\n\nRSpec.describe \"Pact::Consumers::Http\", :pact do\n  mixed_pact_provider \"pact-test-app\", opts: {\n    http: {\n      http_port: 3000,\n      log_level: :info,\n      pact_dir: File.expand_path('../../pacts', __dir__),\n    },\n    grpc: {\n      grpc_port: 3009\n    },\n    async: {\n      message_handlers: {\n        # \"pet message as json\" =\u003e proc do |provider_state|\n        #   pet_id = provider_state.dig(\"params\", \"pet_id\")\n        #   with_pact_producer { |client| PetJsonProducer.new(client: client).call(pet_id) }\n        # end,\n        # \"pet message as proto\" =\u003e proc do |provider_state|\n        #   pet_id = provider_state.dig(\"params\", \"pet_id\")\n        #   with_pact_producer { |client| PetProtoProducer.new(client: client).call(pet_id) }\n        # end\n      }\n    }\n  }\n\n  handle_message \"pet message as json\" do |provider_state|\n    pet_id = provider_state.dig(\"params\", \"pet_id\")\n    with_pact_producer { |client| PetJsonProducer.new(client: client).call(pet_id) }\n  end\n\n  handle_message \"pet message as proto\" do |provider_state|\n    pet_id = provider_state.dig(\"params\", \"pet_id\")\n    with_pact_producer { |client| PetProtoProducer.new(client: client).call(pet_id) }\n  end\n  \nend\n\n```\n\n## Development \u0026 Test\n\n### Setup\n\n```shell\nbundle install\n```\n\n### Run unit tests\n\n```shell\nbundle exec rake spec\n```\n\n### Run pact tests\n\nThe Pact tests are not run within the general rspec pipeline, they need to be run separately, see below\n\n#### Consumer tests\n\n```shell\nbundle exec rspec -t pact spec/pact/providers/**/*_spec.rb\nor \nbundle exec rake pact:spec\n```\n\n**NOTE** If you have never run it, you need to run it at least once to generate the pact files that will be used in provider tests (below)\n\n#### Provider tests\n\n```shell\nbundle exec rspec -t pact spec/pact/consumers/*_spec.rb\nor \nbundle exec rake pact:spec\n```\n\n## Examples\n\n### Migration\n\n1. add `gem \"pact-ffi\", \"~\u003e 0.4.28\"` to Gemfile, or Gemspec\n2. pact ruby v2 uses activesupport classes, so you may need to add\n    1. `gem 'combustion'` to load active support during tests\n    1. add a pact helper to load it\n\n          ```rb\n          require \"combustion\"\n          begin\n            Combustion.initialize! :action_controller do\n              config.log_level = :fatal if ENV[\"LOG\"].to_s.empty?\n            end\n          rescue =\u003e e\n            # Fail fast if application couldn't be loaded\n            warn \"💥 Failed to load the app: #{e.message}\\n#{e.backtrace.join(\"\\n\")}\"\n            exit(1)\n          end\n          ```\n\n3. Add a new rake task\n\n    - require your helper file created above\n    - add a tag, we will use `pact` to namespace away from our existing `pact` tagged tests\n\n    ```rb\n    RSpec::Core::RakeTask.new('spec') do |task|\n      task.pattern = 'spec/pact/providers/**/*_spec.rb'\n      task.rspec_opts = ['-t pact', '--require rails_helper']\n    end\n    ```\n\n4. File paths have moved for consumer tests, and provider verification tasks\n\n    - consumer test location\n        1. pact v1 `spec/service_providers`\n        2. pact v2 - `spec/pact/providers`\n    - provider verification location\n        1. pact v1 `spec/service_consumers`\n        2. pact v2 - `spec/pact/consumers`\n\nThe following projects were designed for pact-ruby-v1 and have been migrated to pact-ruby. They can serve as an example of the work required.\n\n- pact broker client\n  - v2 [pact_broker-client#198](https://github.com/pact-foundation/pact_broker-client/pull/198)\n- pact broker\n  - v2 [pact_broker#843](https://github.com/pact-foundation/pact_broker/pull/843)\n- animal service\n  - v1 [example/animal-service](../example/animal-service/)\n  - v2 [example/animal-service-v2](../example/animal-service-v2/)\n- zoo app\n  - v1 [example/zoo-app](../example/zoo-app/)\n  - v2 [example/zoo-app-v2](../example/zoo-app-v2/)\n- message consumer/provider\n  - v1 [pact-ruby-demo-v1](https://github.com/AndrewJanuary/pact-ruby-demo)\n  - v2 [pact-ruby-demo-v2](https://github.com/safdotdev/pact-ruby-demo)\n- e2e http consumer/provider\n  - [pact-ruby-e2e-example](https://github.com/pact-foundation/pact-ruby-e2e-example)\n    - Plus http, message, grpc \u0026 mixed consumer \u0026 provider examples\n\n### Demos\n\nThe demos are stored in this codebase for regression test, but exist as standalone in https://github.com/pact-foundation/pact-ruby-e2e-example\n\n- http consumer [http_client_spec.rb](../spec/pact/providers/pact-ruby-test-app/http_client_spec.rb)\n- kafka consumer with pact-ruby wrapper [kafka_spec.rb](../spec/pact/providers/pact-ruby-test-app/kafka_spec.rb)\n- message consumer [message_spec.rb](../spec/pact/providers/pact-ruby-test-app/message_spec.rb)\n- plugin consumer http [plugin_matt_http_spec.rb](../spec/pact/providers/pact-ruby-test-app/plugin_matt_http_spec.rb)\n- plugin consumer http [plugin_matt_sync_message_spec.rb](../spec/pact/providers/pact-ruby-test-app/plugin_matt_sync_message_spec.rb)\n- plugin consumer http [plugin_matt_async_message_spec.rb](../spec/pact/providers/pact-ruby-test-app/plugin_matt_async_message_spec.rb)\n- plugin consumer http [plugin_matt_http_spec.rb](../spec/pact/providers/pact-ruby-test-app/plugin_matt_http_spec.rb)\n- grpc consumer with pact-ruby wrapper [grpc_client_spec.rb](../spec/pact/providers/pact-ruby-test-app/grpc_client_spec.rb)\n- grpc consumer using direct plugin interface [plugin_grpc_sync_message_spec.rb](../spec/pact/providers/pact-ruby-test-app/plugin_grpc_sync_message_spec.rb)\n- mixed(http/kafka/grpc) provider [multi_spec.rb](../spec/pact/consumers/multi_spec.rb)\n","funding_links":[],"categories":["Ruby","Contract Testing"],"sub_categories":["Pact Ecosystem"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpact-foundation%2Fpact-ruby","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpact-foundation%2Fpact-ruby","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpact-foundation%2Fpact-ruby/lists"}