README.md in pacto-0.3.0.pre vs README.md in pacto-0.3.0

- old
+ new

@@ -1,42 +1,180 @@ +[![Gem Version](https://badge.fury.io/rb/pacto.png)](http://badge.fury.io/rb/pacto) [![Build Status](https://travis-ci.org/thoughtworks/pacto.png)](https://travis-ci.org/thoughtworks/pacto) [![Code Climate](https://codeclimate.com/github/thoughtworks/pacto.png)](https://codeclimate.com/github/thoughtworks/pacto) [![Dependency Status](https://gemnasium.com/thoughtworks/pacto.png)](https://gemnasium.com/thoughtworks/pacto) [![Coverage Status](https://coveralls.io/repos/thoughtworks/pacto/badge.png)](https://coveralls.io/r/thoughtworks/pacto) +**If you're viewing this at https://github.com/thoughtworks/pacto, +you're reading the documentation for the master branch. +[View documentation for the latest release +(0.2.5).](https://github.com/thoughtworks/pacto/tree/v0.2.5)** + # Pacto -Pacto is a Ruby implementation of the [Consumer-Driven Contracts](http://martinfowler.com/articles/consumerDrivenContracts.html) -pattern for evolving services. Its main features are: +Pacto is a judge that arbitrates contract disputes between a **service provider** and one or more **consumers**. In other words, it is a framework for [Integration Contract Testing](http://martinfowler.com/bliki/IntegrationContractTest.html), and helps guide service evolution patterns like [Consumer-Driven Contracts](http://thoughtworks.github.io/pacto/patterns/cdc/) or [Documentation-Driven Contracts](http://thoughtworks.github.io/pacto/patterns/documentation_driven/). -- A simple language for specifying a contract; -- An automated way to validate that a producer meets its consumer's requirements; -- An auto-generated stub to be used in the consumer's acceptance tests. +Pacto considers two major terms in order decide if there has been a breach of contract: the **request clause** and the **response clause**. -It was developed in a micro-services environment, specifically a RESTful one, so expect it to be opinionated. Although -there is enough functionality implemented to motivate us to open-source this, it is still a work in progress and under active -development. Check the Constraints session for further information on what works and what doesn't. +The **request clause** defines what information must be sent by the **consumer** to the **provider** in order to compel them to render a service. The request clause often describes the required HTTP request headers like `Content-Type`, the required parameters, and the required request body (defined in [json-schema](http://json-schema.org/)) when applicable. Providers are not held liable for failing to deliver services for invalid requests. -## Specifying Contracts +The **response clause** defines what information must be returned by the **provider** to the **consumer** in order to successfully complete the transaction. This commonly includes HTTP response headers like `Location` as well as the required response body (also defined in [json-schema](http://json-schema.org/)). -A contract specifies a single message exchange between a consumer and a provider. In a RESTful world, this means -an HTTP interaction, which is composed of two main parts: a request and a response. +## Test Doubles -A request has the following attributes: +The consumer may also enter into an agreement with **test doubles** like [WebMock](http://robots.thoughtbot.com/how-to-stub-external-services-in-tests), [vcr](https://github.com/vcr/vcr) or [mountebank](http://www.mbtest.org/). The services delivered by the **test doubles** for the purposes of development and testing will be held to the same conditions as the service the final services rendered by the parent **provider**. This prevents misrepresentation of sample services as realistic, leading to damages during late integration. +Pacto can provide a [**test double**](#stubbing) if you cannot afford your own. + +## Due Diligence + +Pacto usually makes it clear if the **consumer** or **provider** is at fault, but if a contract is too vague Pacto cannot assign blame, and if it is too specific the matter may become litigious. + +Pacto can provide a [**contract writer**](#generating) that tries to strike a balance, but you may still need to adjust the terms. + +## Implied Terms + +- Pacto only arbitrates contracts for JSON services. +- Pacto requires Ruby 1.9.3 or newer, though you can use [Polyglot Testing](http://thoughtworks.github.io/pacto/patterns/polyglot/) techniques to support older Rubies and non-Ruby projects. + +## Roadmap + +- The **test double** provided by Pacto is only semi-competent. It handles simple cases, but we expect to find a more capable replacement in the near future. +- Pacto will provide additional Contract Writers for converting from apiblueprint, WADL, or other documentation formats in the future. It's part of our goal to support [Documentation-Driven Contracts](http://thoughtworks.github.io/pacto/patterns/documentation_driven/) +- Pacto reserves the right to consider other clauses in the future, like security and compliance to industry specifications. + +## Usage + +Pacto can perform three activities: generating, validating, or stubbing services. You can do each of these activities against either live or stubbed services. + +### Configuration + +In order to start with Pacto, you just need to require it and optionally customize the default [Configuration](https://www.relishapp.com/maxlinc/pacto/docs/configuration). For example: + +```ruby +require 'pacto' + +Pacto.configure do |config| + config.contracts_path = 'contracts' +end +``` + +### Generating + +The easiest way to get started with Pacto is to run a suite of live tests and tell Pacto to generate the contracts: + +```ruby +Pacto.generate! +# run your tests +``` + +If you're using the same configuration as above, this will produce Contracts in the contracts/ folder. + +We know we cannot generate perfect Contracts, especially if we only have one sample request. So we do our best to give you a good starting point, but you will likely want to customize the contract so the validation is more strict in some places and less strict in others. + +### Contract Lists + +In order to stub or validate or stub a group of contracts you need to create a ContractList. +A ContractList represent a collection of endpoints attached to the same host. + +```ruby +require 'pacto' + +default_contracts = Pacto.load_contracts('contracts/services', 'http://example.com') +authentication_contracts = Pacto.load_contracts('contracts/auth', 'http://example.com') +legacy_contracts = Pacto.load_contracts('contracts/legacy', 'http://example.com') +``` + +### Validating + +Once you have a ContractList, you can validate all the contracts against the live host. + +```ruby +contracts = Pacto.load_contracts('contracts/services', 'http://example.com') +contracts.validate_all +``` + +This method will hit the real endpoint, with a request created based on the request part of the contract. +The response will be compared against the response part of the contract and any structural difference will +generate a validation error. + +Running this in your build pipeline will ensure that your contracts actually match the real world, and that +you can run your acceptance tests against the contract stubs without worries. + +### Stubbing + +To generate stubs based on a ContractList you can run: + +```ruby +contracts = Pacto.load_contracts('contracts/services', 'http://example.com') +contracts.stub_all +``` + +This method uses webmock to ensure that whenever you make a request against one of contracts you actually get a stubbed response, +based on the default values specified on the contract, instead of hitting the real provider. + +You can override any default value on the contracts by providing a hash of optional values to the stub_all method. This +will override the keys for every contract in the list + +```ruby +contracts = Pacto.load_contracts('contracts/services', 'http://example.com') +contracts.stub_all(request_id: 14, name: "Marcos") +``` + +## Pacto Server (non-Ruby usage) + +It is really easy to embed Pacto inside a small server. We haven't bundled a server inside of Pacto, but check out [pacto-demo](https://github.com/thoughtworks/pacto-demo) to see how easily you can expose Pacto via server. + +That demo lets you easily run a server in several modes: +```sh +$ bundle exec ruby pacto_server.rb -sv --generate +# Runs a server that will generate Contracts for each request received +$ bundle exec ruby pacto_server.rb -sv --validate +# Runs the server that provides stubs and checks them against Contracts +$ bundle exec ruby pacto_server.rb -sv --validate --host http://example.com +# Runs the server that acts as a proxy to http://example.com, validating each request/response against a Contract +``` + +## Rake Tasks + +Pacto includes a few Rake tasks to help with common tasks. If you want to use these tasks, just add this top your Rakefile: + +```ruby +require 'pacto/rake_task' +``` + +This should add several new tasks to you project: +```sh +rake pacto:generate[input_dir,output_dir,host] # Generates contracts from partial contracts +rake pacto:meta_validate[dir] # Validates a directory of contract definitions +rake pacto:validate[host,dir] # Validates all contracts in a given directory against a given host +``` + +The pacto:generate task will take partially defined Contracts and generate the missing pieces. See [Generate](https://www.relishapp.com/maxlinc/pacto/docs/generate) for more details. + +The pacto:meta_validate task makes sure that your Contracts are valid. It only checks the Contracts, not the services that implement them. + +The pacto:validate task sends a request to an actual provider and ensures their response complies with the Contract. + +## Contracts + +Pacto works by associating a service with a Contract. The Contract is a JSON description of the service that uses json-schema to describe the response body. You don't need to write your contracts by hand. In fact, we recommend generating a Contract from your documentation or a service. + +A contract is composed of a request that has: + - Method: the method of the HTTP request (e.g. GET, POST, PUT, DELETE); - Path: the relative path (without host) of the provider's endpoint; - Headers: headers sent in the HTTP request; - Params: any data or parameters of the HTTP request (e.g. query string for GET, body for POST). -A response has the following attributes: +And a response has that has: - Status: the HTTP response status code (e.g. 200, 404, 500); - Headers: the HTTP response headers; - Body: a JSON Schema defining the expected structure of the HTTP response body. -Pacto relies on a simple, JSON based language for defining contracts. Below is an example contract for a GET request +Below is an example contract for a GET request to the /hello_world endpoint of a provider: ```json { "request": { @@ -64,96 +202,14 @@ } } } ``` -The host address is intentionally left out of the request specification so that we can validate a contract against any provider. -It also reinforces the fact that a contract defines the expectation of a consumer, and not the implementation of any specific provider. +## Constraints -## Validating Contracts +- Pacto only works with JSON services +- Pacto requires Ruby 1.9.3 or newer (though you can older Rubies or non-Ruby projects with a [Pacto Server](#pacto-server-non-ruby-usage)) +- Pacto cannot currently specify multiple acceptable status codes (e.g. 200 or 201) -There are two ways to validate a contract against a provider: through a Rake task or programatically. - -### Rake Task - -Pacto includes two Rake tasks. In order to use them, include this in your Rakefile: - -```ruby -require 'pacto/rake_task' -``` - -Pacto can validate the contract files: - -```sh -$ rake pacto:meta_validate[dir] # Validates a directory of contract definitions -``` - -Or it can validate contracts against a provider: - -```sh -$ rake pacto:validate[host,dir] # Validates all contracts in a given directory against a given host -``` - -It is recommended that you also include [colorize](https://github.com/fazibear/colorize) to get prettier, colorful output. - -### Programatically - -The easiest way to load a contract from a file and validate it against a host is by using the builder interface: - -```ruby -require 'pacto' - -WebMock.allow_net_connect! -contract = Pacto.build_from_file('/path/to/contract.json', 'http://dummyprovider.com') -contract.validate -``` - -If you don't want to depend on Pacto to do the request you can also validate a response from a real request: - -```ruby -require 'pacto' - -WebMock.allow_net_connect! -contract = Pacto.build_from_file('/path/to/contract.json', 'http://dummyprovider.com') -# Doing the request with ruby stdlib, you can use your favourite lib to do the request -response = Net::HTTP.get_response(URI.parse('http://dummyprovider.com')).body -contract.validate response, body_only: true -``` - -Pacto also has the ability to match a request signature to a contract that is currently in used, via ```Pacto.contract_for request_signature``` - -## Auto-Generated Stubs - -Pacto provides an API to be used in the consumer's acceptance tests. It uses a custom JSON Schema parser and generator -to generate a valid JSON document as the response body, and relies on [WebMock](https://github.com/bblimke/webmock) -to stub any HTTP requests made by your application. Important: the JSON generator is in very early stages and does not work -with the entire JSON Schema specification. - -First, register the contracts that are going to be used in the acceptance tests suite. The register_contract method accepts zero or more tags: -```ruby -require 'pacto' - -contract1 = Pacto.build_from_file('/path/to/contract1.json', 'http://dummyprovider.com') -contract2 = Pacto.build_from_file('/path/to/contract2.json', 'http://dummyprovider.com') -contract3 = Pacto.build_from_file('/path/to/contract3.json', 'http://dummyprovider.com') -Pacto.register_contract(contract1) -Pacto.register_contract(contract2, :public_api) -Pacto.register_contract(contract3, :public_api, :wip) -``` -Then, in the setup phase of the test, specify which contracts will be used for that test: -```ruby -Pacto.use('my_tag') -``` -If default values are not specified in the contract's response body, a default value will be automatically generated. It is possible -to overwrite those values, however, by passing a second argument: -```ruby -Pacto.use('my_tag', :value => 'new value') -``` -The values are merged using [hash-deep-merge](https://github.com/Offirmo/hash-deep-merge). - ## Contributing -1. Fork it -2. Create your feature branch (`git checkout -b my-new-feature`) -3. Commit your changes (`git commit -am 'Add some feature'`) -4. Push to the branch (`git push origin my-new-feature`) -5. Create new Pull Request +Read the CONTRIBUTING.md file.