From 49eed2c6ac6af8a4f52e6553754b44095902c4cd Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Wed, 2 Jul 2014 19:03:20 -0400 Subject: [PATCH 01/26] Working on new documentation for the next release --- README.md | 251 ++++++++++++------------------------------------------ 1 file changed, 55 insertions(+), 196 deletions(-) diff --git a/README.md b/README.md index 30a4edb..8b449da 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,15 @@ you're reading the documentation for the master branch. (0.3.0).](https://github.com/thoughtworks/pacto/tree/v0.3.0)** # Pacto +## Who is Pacto? -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/). +Pacto is a judge that arbitrates contract disputes between a **service provider** and one or more **consumers**. It is a framework for [Integration Contract Testing](http://martinfowler.com/bliki/IntegrationContractTest.html), and 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/). + +## The litigants + +Pacto helps settle disputes between **service providers** and **service consumers** of RESTful JSON services. The **provider** is the one that implements the service, which may be used by multiple **consumers**. This is done by [Integration Contract Testing](http://martinfowler.com/bliki/IntegrationContractTest.html), where the contract stays the same but the provider changes. + +## Contracts Pacto considers two major terms in order decide if there has been a breach of contract: the **request clause** and the **response clause**. @@ -19,235 +26,87 @@ The **request clause** defines what information must be sent by the **consumer** 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/)). -## Test Doubles +See the [Contracts documentation](samples/contracts) for more details. -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. +## Enforcement -Pacto can provide a [**test double**](#stubbing) if you cannot afford your own. +### Cops +**Cops** help Pacto investigate interactions between **consumers** and **providers** and determine if either has violated the contract. -## Due Diligence +Pacto has a few built-in cops that are on-duty by default. These cops will: +- Ensure the request body matches the contract requirements (if a request body is needed) +- Ensure the response headers match the contract requirements +- Ensure the response HTTP status matches the contract requirements +- Ensure the response body matches the contract requirements -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. +### Sting operations -Pacto can provide a [**contract writer**](#generating) that tries to strike a balance, but you may still need to adjust the terms. +**Note: this is a preview of an upcoming feature. It is not currently available.** -## Implied Terms +Pacto **cops** merely observe the interactions between a **consumer** and **provider** and look for problems. A Pacto **sting operation** will alter the interaction in order to try an find problems. -- 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. +For example, HTTP header field names are supposed to be case-insensitive [according to RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1](http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2), but many implementations are tightly coupled to a certain server or implementation and assume header field names have a certain case, like "Content-Type" and not "content-type". Pacto can change the alter the character case of the field names in order to catch consumers or providers that are not following this part of the RFC. -## Roadmap +Another possible sting operation is to introduce network lag, dropped connections, simulate HTTP rate limiting errors, or other issues that a robust consumer are expected to handle. -- 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. +You can also add your own custom cops to extend Pacto's abilities or to ensure services are standards that are specific to your organization. See the [Cops documentation](samples/contracts) for more details. -## Usage +## Inside the courtroom -**See also: http://thoughtworks.github.io/pacto/usage/** +### Actors -Pacto can perform three activities: generating, validating, or stubbing services. You can do each of these activities against either live or stubbed services. +It's not always practical to test using a **real consumer** and or a **real provider**. Pacto can both **stub providers** and **simulate consumers** so you can test their counterpart in isolation. This makes [Consumer-Driven Contracts](http://thoughtworks.github.io/pacto/patterns/cdc/) easier. You can start testing a consumer against a stubbed provider before the real provider is available, and then hand your contracts and tests over to the team that is going to implement the provider so they can ensure it matches your assumptions. -You can also use [Pacto Server](#pacto-server-non-ruby-usage) if you are working with non-Ruby projects. +See the [Actors documentation](samples/actors) for more details. -### Configuration +### The courtroom reporter -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: +**Note: this is a preview of an upcoming feature. It is not currently available.** -```ruby -require 'pacto' +Pacto can keep track of which services have been called. If you're a consumer with contracts for a set of services you consumer, this helps you figure out "HTTP Service Coverage", which is quite different from code coverage, so you can see if there's any services you forgot to test, or contracts you're still registering with Pacto even though you no longer use those services. -Pacto.configure do |config| - config.contracts_path = 'contracts' -end -``` +If you are a provider that is being used by multiple consumers, you could merge coverage reports from each of them to see which services are being used by each consumer. Once this feature is available, you should be able to create reports that look something like this: -### Generating +![Courtroom Report](https://cloud.githubusercontent.com/assets/896878/2707078/21b0245e-c49d-11e3-8b4e-fa695aa56c4d.png) -The easiest way to get started with Pacto is to run a suite of live tests and tell Pacto to generate the contracts: +### The Stenographer -```ruby -Pacto.generate! -# run your tests -``` - -If you're using the same configuration as above, this will produce Contracts in the contracts/ folder. +The stenographer keeps a short-hand record of everything that happens in the courtroom. These logs can be used for troubleshooting, creating reports, or re-enacting the courtroom activities at a later date (and with different [actors](#actors). -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. +The stenographer's logs are similar to HTTP access logs in this respect, but in a format that's more compact and suited to Pacto. A typical HTTP access log might look like this: -### 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.simulate_consumers +#Fields: date time c-ip cs-username s-ip s-port cs-method cs-uri-stem cs-uri-query sc-status cs(User-Agent) +2014-07-01 17:42:15 127.0.0.1 - 127.0.0.1 80 PUT /store/album/123/cover_art - 201 curl/7.30.0 +2014-07-01 17:42:18 127.0.0.1 - 127.0.0.1 80 GET /store/album/123/cover_art size=small 200 curl/7.30.0 ``` -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_providers +If Pacto has the following services registered: ``` - -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_providers method. This -will override the keys for every contract in the list - -```ruby -contracts = Pacto.load_contracts('contracts/services', 'http://example.com') -contracts.stub_providers(request_id: 14, name: "Marcos") +"Upload Album Art": PUT /store/album/{album_id}/cover_art +"Download Album Art": GET /store/album/{album_id}/cover_art{?size} ``` -## Pacto Server (non-Ruby usage) - -**See also: http://thoughtworks.github.io/pacto/patterns/polyglot/** - -We've bundled a small server that embeds pacto so you can use it for non-Ruby projects. If you want to take advantage of the full features, you'll still need to use Ruby (usually rspec) to drive your API testing. You can run just the server in order to stub or to write validation issues to a log, but you won't have access to the full API fail your tests if there were validation problems. - -### Command-line - -The command-line version of the server will show you all the options. These same options are used when you launch the server from within rspec. In order to see the options: -`bundle exec pacto-server --help` - -Some examples: -```sh -$ bundle exec pacto-server -sv --generate -# Runs a server that will generate Contracts for each request received -$ bundle exec pacto-server -sv --stub --validate -# Runs the server that provides stubs and checks them against Contracts -$ bundle exec pacto-server -sv --live --validate --host -# Runs the server that acts as a proxy to http://example.com, validating each request/response against a Contract +Then it can match those requests to request and generate a stenographer log that looks like this: +```ruby +request 'Upload Album Art', values: {album_id: '123'}, response: {status: 201} # no contract violations +request 'Download Album Art', values: {album_id: '123', size: 'small'}, response: {status: 200} # no contract violations ``` -### RSpec test helper - -You can also launch a server from within an rspec test. The server does start up an external port and runs asynchronously so it doens't block your main test thread from kicking off your consumer code. This gives you an externally accessable server that non-Ruby clients can hit, but still gives you the full Pacto API in order to validate and spy on HTTP interactions. - -Example usage of the rspec test helper: +This log file is designed so Pacto it can be used by Pacto to simulate the requests: ```ruby -require 'rspec' -require 'pacto/rspec' -require 'pacto/test_helper' - -describe 'my consumer' do - include Pacto::TestHelper - - it 'calls a service' do - with_pacto( - :port => 5000, - :directory => '../spec/integration/data', - :stub => true) do |pacto_endpoint| - # call your code - system "curl #{pacto_endpoint}/echo" - # check results - expect(Pacto).to have_validated(:get, 'https://localhost/echo') - end - end +Pacto.simulate_consumer :my_client do + request 'Upload Album Art', values: {album_id: '123'}, response: {status: 201} # no contract violations + request 'Download Album Art', values: {album_id: '123', size: 'small'}, response: {status: 200} # no contract violations end ``` -## 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' +Since Pacto has added a layer of abstraction you can use the log to generate semantically equivalent requests even as you're expirementing with changes. For example, if you change the routing for the service from: ``` - -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 +"Upload Album Art": PUT /store/album/{album_id}/cover_art ``` - -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). - -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. - -Below is an example contract for a GET request -to the /hello_world endpoint of a provider: - -```json -{ - "request": { - "method": "GET", - "path": "/hello_world", - "headers": { - "Accept": "application/json" - }, - "params": {} - }, - - "response": { - "status": 200, - "headers": { - "Content-Type": "application/json" - }, - "body": { - "description": "A simple response", - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - } - } -} +to ``` - -## Constraints - -- 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) - -## Contributing - -Read the [CONTRIBUTING.md](CONTRIBUTING.md) file. +"Upload Album Art": PUT /cover_art{?album_id} +``` +then Pacto will automatically adjust the URLs. From 929441627ded272a4caf5e3da0c45121060b1c6b Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Thu, 3 Jul 2014 15:34:11 -0400 Subject: [PATCH 02/26] Update README.md --- README.md | 246 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 237 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 8b449da..b059475 100644 --- a/README.md +++ b/README.md @@ -82,10 +82,7 @@ The stenographer's logs are similar to HTTP access logs in this respect, but in ``` If Pacto has the following services registered: -``` -"Upload Album Art": PUT /store/album/{album_id}/cover_art -"Download Album Art": GET /store/album/{album_id}/cover_art{?size} -``` +![routes.png](https://draftin.com:443/images/16800?token=8Z2bmsbxOQ74ogeeWTXBwyvVaJ0YBfNJCfQTHa08L3AnvQXsnIf40htwMVudSIugAmpeJp8MD53mN7FPzfgqG9o) Then it can match those requests to request and generate a stenographer log that looks like this: ```ruby @@ -101,12 +98,243 @@ Pacto.simulate_consumer :my_client do end ``` -Since Pacto has added a layer of abstraction you can use the log to generate semantically equivalent requests even as you're expirementing with changes. For example, if you change the routing for the service from: +Since Pacto has added a layer of abstraction you can experiment with changes to the contracts (including routing) without needing to re-record the interactions with the stenographer. For example Pacto will adjust if you change the route from: +![original_put.png](https://draftin.com:443/images/16801?token=6yI7VugUqaLJAMpMvf4oAlPucfQBnfdrdcGpcCuUFET_FH5E0ZreFIrL1C7U2GwRuNndntc9OTIXLD-B2wbkiyg) +to +![new_put.png](https://draftin.com:443/images/16802?token=IagQX2ggHIaaCfRGhR5q85cK9oNN6OgFX2yc9aT0CTZkGzxUMXB1nR40mwIYRat7dUeWPOmLebNOHWXOKlpe-iU) + + + + + +# OLD CONTENT +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 + +**See also: http://thoughtworks.github.io/pacto/usage/** + +Pacto can perform three activities: generating, validating, or stubbing services. You can do each of these activities against either live or stubbed services. + +You can also use [Pacto Server](#pacto-server-non-ruby-usage) if you are working with non-Ruby projects. + +### 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 ``` -"Upload Album Art": PUT /store/album/{album_id}/cover_art + +### 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 ``` -to + +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') ``` -"Upload Album Art": PUT /cover_art{?album_id} + +### 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.simulate_consumers ``` -then Pacto will automatically adjust the URLs. + +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_providers +``` + +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_providers method. This +will override the keys for every contract in the list + +```ruby +contracts = Pacto.load_contracts('contracts/services', 'http://example.com') +contracts.stub_providers(request_id: 14, name: "Marcos") +``` + +## Pacto Server (non-Ruby usage) + +**See also: http://thoughtworks.github.io/pacto/patterns/polyglot/** + +We've bundled a small server that embeds pacto so you can use it for non-Ruby projects. If you want to take advantage of the full features, you'll still need to use Ruby (usually rspec) to drive your API testing. You can run just the server in order to stub or to write validation issues to a log, but you won't have access to the full API fail your tests if there were validation problems. + +### Command-line + +The command-line version of the server will show you all the options. These same options are used when you launch the server from within rspec. In order to see the options: +`bundle exec pacto-server --help` + +Some examples: +```sh +$ bundle exec pacto-server -sv --generate +# Runs a server that will generate Contracts for each request received +$ bundle exec pacto-server -sv --stub --validate +# Runs the server that provides stubs and checks them against Contracts +$ bundle exec pacto-server -sv --live --validate --host +# Runs the server that acts as a proxy to http://example.com, validating each request/response against a Contract +``` + +### RSpec test helper + +You can also launch a server from within an rspec test. The server does start up an external port and runs asynchronously so it doens't block your main test thread from kicking off your consumer code. This gives you an externally accessable server that non-Ruby clients can hit, but still gives you the full Pacto API in order to validate and spy on HTTP interactions. + +Example usage of the rspec test helper: +```ruby +require 'rspec' +require 'pacto/rspec' +require 'pacto/test_helper' + +describe 'my consumer' do + include Pacto::TestHelper + + it 'calls a service' do + with_pacto( + :port => 5000, + :directory => '../spec/integration/data', + :stub => true) do |pacto_endpoint| + # call your code + system "curl #{pacto_endpoint}/echo" + # check results + expect(Pacto).to have_validated(:get, 'https://localhost/echo') + end + end +end +``` + +## 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). + +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. + +Below is an example contract for a GET request +to the /hello_world endpoint of a provider: + +```json +{ + "request": { + "method": "GET", + "path": "/hello_world", + "headers": { + "Accept": "application/json" + }, + "params": {} + }, + + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "body": { + "description": "A simple response", + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } +} +``` + +## Constraints + +- 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) + +## Contributing + +Read the [CONTRIBUTING.md](CONTRIBUTING.md) file. From 00adb0f7b61a38dd432045e379f9e5ad1e944742 Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Thu, 3 Jul 2014 17:24:23 -0400 Subject: [PATCH 03/26] Update README.md --- README.md | 251 +++++++----------------------------------------------- 1 file changed, 30 insertions(+), 221 deletions(-) diff --git a/README.md b/README.md index b059475..fadcf46 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,14 @@ Pacto is a judge that arbitrates contract disputes between a **service provider* Pacto helps settle disputes between **service providers** and **service consumers** of RESTful JSON services. The **provider** is the one that implements the service, which may be used by multiple **consumers**. This is done by [Integration Contract Testing](http://martinfowler.com/bliki/IntegrationContractTest.html), where the contract stays the same but the provider changes. +## Litigators + +Someone needs to accuse the **providers** or **consumers** of wrongdoing! Pacto integrates with a few different test frameworks to give you options: + +- Pacto easily integrates with [RSpec](http://rspec.info/), including some [custom matchers](asdf). +- Pacto provides some [simple rake tasks](samples/rake_task/) to run some basic tests from the command line. +- If you're testing non-Ruby projects, you can use the [Pacto Server](samples/server) as a proxy to intercept and validate requests. You can also use it in conjunction with [Polytrix](https://github.com/rackerlabs/polytrix). + ## Contracts Pacto considers two major terms in order decide if there has been a breach of contract: the **request clause** and the **response clause**. @@ -28,6 +36,8 @@ The **response clause** defines what information must be returned by the **provi See the [Contracts documentation](samples/contracts) for more details. + + ## Enforcement ### Cops @@ -39,6 +49,12 @@ Pacto has a few built-in cops that are on-duty by default. These cops will: - Ensure the response HTTP status matches the contract requirements - Ensure the response body matches the contract requirements +### Forensics + +Sometimes it looks like you're following a contract, but digital forensics reveals there's some fraud going on. Pacto provides RSpec matchers to help you catch these patterns that let you do the [collaboration tests](http://programmers.stackexchange.com/questions/135011/removing-the-integration-test-scam-understanding-collaboration-and-contract) that integration contract testing alone would not catch. + +See the [forensics documetation](samples/forensics) for more details. + ### Sting operations **Note: this is a preview of an upcoming feature. It is not currently available.** @@ -103,238 +119,31 @@ Since Pacto has added a layer of abstraction you can experiment with changes to to ![new_put.png](https://draftin.com:443/images/16802?token=IagQX2ggHIaaCfRGhR5q85cK9oNN6OgFX2yc9aT0CTZkGzxUMXB1nR40mwIYRat7dUeWPOmLebNOHWXOKlpe-iU) +### Clerks +Clerks help Pacto with paperwork. Reading and writing legalese is hard. Pacto clerks help create and load contracts. Currently clerks are responsible for: +- Generating contracts from real HTTP requests +- Basic support for loading from custom formats +In the future, we plan for clerks to provide more complete support for: +- Converting from or loading other similar contract formats (e.g. [Swagger](https://github.com/wordnik/swagger-spec), [apiblueprint](http://apiblueprint.org/), or [RAML](http://raml.org/). +- Upgrading contracts from older Pacto versions -# OLD CONTENT -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 +See the [contract generation](samples/generations) and [clerks](samples/clerks) documentation for more info. -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 +# OLD CONTENT +# 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 - -**See also: http://thoughtworks.github.io/pacto/usage/** - -Pacto can perform three activities: generating, validating, or stubbing services. You can do each of these activities against either live or stubbed services. - -You can also use [Pacto Server](#pacto-server-non-ruby-usage) if you are working with non-Ruby projects. - -### 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.simulate_consumers -``` - -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_providers -``` - -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_providers method. This -will override the keys for every contract in the list - -```ruby -contracts = Pacto.load_contracts('contracts/services', 'http://example.com') -contracts.stub_providers(request_id: 14, name: "Marcos") -``` - -## Pacto Server (non-Ruby usage) - -**See also: http://thoughtworks.github.io/pacto/patterns/polyglot/** - -We've bundled a small server that embeds pacto so you can use it for non-Ruby projects. If you want to take advantage of the full features, you'll still need to use Ruby (usually rspec) to drive your API testing. You can run just the server in order to stub or to write validation issues to a log, but you won't have access to the full API fail your tests if there were validation problems. - -### Command-line - -The command-line version of the server will show you all the options. These same options are used when you launch the server from within rspec. In order to see the options: -`bundle exec pacto-server --help` - -Some examples: -```sh -$ bundle exec pacto-server -sv --generate -# Runs a server that will generate Contracts for each request received -$ bundle exec pacto-server -sv --stub --validate -# Runs the server that provides stubs and checks them against Contracts -$ bundle exec pacto-server -sv --live --validate --host -# Runs the server that acts as a proxy to http://example.com, validating each request/response against a Contract -``` - -### RSpec test helper - -You can also launch a server from within an rspec test. The server does start up an external port and runs asynchronously so it doens't block your main test thread from kicking off your consumer code. This gives you an externally accessable server that non-Ruby clients can hit, but still gives you the full Pacto API in order to validate and spy on HTTP interactions. - -Example usage of the rspec test helper: -```ruby -require 'rspec' -require 'pacto/rspec' -require 'pacto/test_helper' - -describe 'my consumer' do - include Pacto::TestHelper - - it 'calls a service' do - with_pacto( - :port => 5000, - :directory => '../spec/integration/data', - :stub => true) do |pacto_endpoint| - # call your code - system "curl #{pacto_endpoint}/echo" - # check results - expect(Pacto).to have_validated(:get, 'https://localhost/echo') - end - end -end -``` - -## 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). - -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. - -Below is an example contract for a GET request -to the /hello_world endpoint of a provider: - -```json -{ - "request": { - "method": "GET", - "path": "/hello_world", - "headers": { - "Accept": "application/json" - }, - "params": {} - }, - - "response": { - "status": 200, - "headers": { - "Content-Type": "application/json" - }, - "body": { - "description": "A simple response", - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - } - } -} -``` +# Roadmap -## Constraints +See the [Pacto Roadmap](https://github.com/thoughtworks/pacto/wiki/Pacto-Roadmap) -- 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) +# Contributing -## Contributing +See [CONTRIBUTING.md](CONTRIBUTING.md) Read the [CONTRIBUTING.md](CONTRIBUTING.md) file. From 0822258cd8c05831c34b279d77e67e50904d1bf7 Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Wed, 2 Jul 2014 19:03:20 -0400 Subject: [PATCH 04/26] Working on new documentation for the next release --- README.md | 251 ++++++++++++------------------------------------------ 1 file changed, 55 insertions(+), 196 deletions(-) diff --git a/README.md b/README.md index 30a4edb..8b449da 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,15 @@ you're reading the documentation for the master branch. (0.3.0).](https://github.com/thoughtworks/pacto/tree/v0.3.0)** # Pacto +## Who is Pacto? -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/). +Pacto is a judge that arbitrates contract disputes between a **service provider** and one or more **consumers**. It is a framework for [Integration Contract Testing](http://martinfowler.com/bliki/IntegrationContractTest.html), and 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/). + +## The litigants + +Pacto helps settle disputes between **service providers** and **service consumers** of RESTful JSON services. The **provider** is the one that implements the service, which may be used by multiple **consumers**. This is done by [Integration Contract Testing](http://martinfowler.com/bliki/IntegrationContractTest.html), where the contract stays the same but the provider changes. + +## Contracts Pacto considers two major terms in order decide if there has been a breach of contract: the **request clause** and the **response clause**. @@ -19,235 +26,87 @@ The **request clause** defines what information must be sent by the **consumer** 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/)). -## Test Doubles +See the [Contracts documentation](samples/contracts) for more details. -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. +## Enforcement -Pacto can provide a [**test double**](#stubbing) if you cannot afford your own. +### Cops +**Cops** help Pacto investigate interactions between **consumers** and **providers** and determine if either has violated the contract. -## Due Diligence +Pacto has a few built-in cops that are on-duty by default. These cops will: +- Ensure the request body matches the contract requirements (if a request body is needed) +- Ensure the response headers match the contract requirements +- Ensure the response HTTP status matches the contract requirements +- Ensure the response body matches the contract requirements -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. +### Sting operations -Pacto can provide a [**contract writer**](#generating) that tries to strike a balance, but you may still need to adjust the terms. +**Note: this is a preview of an upcoming feature. It is not currently available.** -## Implied Terms +Pacto **cops** merely observe the interactions between a **consumer** and **provider** and look for problems. A Pacto **sting operation** will alter the interaction in order to try an find problems. -- 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. +For example, HTTP header field names are supposed to be case-insensitive [according to RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1](http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2), but many implementations are tightly coupled to a certain server or implementation and assume header field names have a certain case, like "Content-Type" and not "content-type". Pacto can change the alter the character case of the field names in order to catch consumers or providers that are not following this part of the RFC. -## Roadmap +Another possible sting operation is to introduce network lag, dropped connections, simulate HTTP rate limiting errors, or other issues that a robust consumer are expected to handle. -- 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. +You can also add your own custom cops to extend Pacto's abilities or to ensure services are standards that are specific to your organization. See the [Cops documentation](samples/contracts) for more details. -## Usage +## Inside the courtroom -**See also: http://thoughtworks.github.io/pacto/usage/** +### Actors -Pacto can perform three activities: generating, validating, or stubbing services. You can do each of these activities against either live or stubbed services. +It's not always practical to test using a **real consumer** and or a **real provider**. Pacto can both **stub providers** and **simulate consumers** so you can test their counterpart in isolation. This makes [Consumer-Driven Contracts](http://thoughtworks.github.io/pacto/patterns/cdc/) easier. You can start testing a consumer against a stubbed provider before the real provider is available, and then hand your contracts and tests over to the team that is going to implement the provider so they can ensure it matches your assumptions. -You can also use [Pacto Server](#pacto-server-non-ruby-usage) if you are working with non-Ruby projects. +See the [Actors documentation](samples/actors) for more details. -### Configuration +### The courtroom reporter -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: +**Note: this is a preview of an upcoming feature. It is not currently available.** -```ruby -require 'pacto' +Pacto can keep track of which services have been called. If you're a consumer with contracts for a set of services you consumer, this helps you figure out "HTTP Service Coverage", which is quite different from code coverage, so you can see if there's any services you forgot to test, or contracts you're still registering with Pacto even though you no longer use those services. -Pacto.configure do |config| - config.contracts_path = 'contracts' -end -``` +If you are a provider that is being used by multiple consumers, you could merge coverage reports from each of them to see which services are being used by each consumer. Once this feature is available, you should be able to create reports that look something like this: -### Generating +![Courtroom Report](https://cloud.githubusercontent.com/assets/896878/2707078/21b0245e-c49d-11e3-8b4e-fa695aa56c4d.png) -The easiest way to get started with Pacto is to run a suite of live tests and tell Pacto to generate the contracts: +### The Stenographer -```ruby -Pacto.generate! -# run your tests -``` - -If you're using the same configuration as above, this will produce Contracts in the contracts/ folder. +The stenographer keeps a short-hand record of everything that happens in the courtroom. These logs can be used for troubleshooting, creating reports, or re-enacting the courtroom activities at a later date (and with different [actors](#actors). -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. +The stenographer's logs are similar to HTTP access logs in this respect, but in a format that's more compact and suited to Pacto. A typical HTTP access log might look like this: -### 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.simulate_consumers +#Fields: date time c-ip cs-username s-ip s-port cs-method cs-uri-stem cs-uri-query sc-status cs(User-Agent) +2014-07-01 17:42:15 127.0.0.1 - 127.0.0.1 80 PUT /store/album/123/cover_art - 201 curl/7.30.0 +2014-07-01 17:42:18 127.0.0.1 - 127.0.0.1 80 GET /store/album/123/cover_art size=small 200 curl/7.30.0 ``` -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_providers +If Pacto has the following services registered: ``` - -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_providers method. This -will override the keys for every contract in the list - -```ruby -contracts = Pacto.load_contracts('contracts/services', 'http://example.com') -contracts.stub_providers(request_id: 14, name: "Marcos") +"Upload Album Art": PUT /store/album/{album_id}/cover_art +"Download Album Art": GET /store/album/{album_id}/cover_art{?size} ``` -## Pacto Server (non-Ruby usage) - -**See also: http://thoughtworks.github.io/pacto/patterns/polyglot/** - -We've bundled a small server that embeds pacto so you can use it for non-Ruby projects. If you want to take advantage of the full features, you'll still need to use Ruby (usually rspec) to drive your API testing. You can run just the server in order to stub or to write validation issues to a log, but you won't have access to the full API fail your tests if there were validation problems. - -### Command-line - -The command-line version of the server will show you all the options. These same options are used when you launch the server from within rspec. In order to see the options: -`bundle exec pacto-server --help` - -Some examples: -```sh -$ bundle exec pacto-server -sv --generate -# Runs a server that will generate Contracts for each request received -$ bundle exec pacto-server -sv --stub --validate -# Runs the server that provides stubs and checks them against Contracts -$ bundle exec pacto-server -sv --live --validate --host -# Runs the server that acts as a proxy to http://example.com, validating each request/response against a Contract +Then it can match those requests to request and generate a stenographer log that looks like this: +```ruby +request 'Upload Album Art', values: {album_id: '123'}, response: {status: 201} # no contract violations +request 'Download Album Art', values: {album_id: '123', size: 'small'}, response: {status: 200} # no contract violations ``` -### RSpec test helper - -You can also launch a server from within an rspec test. The server does start up an external port and runs asynchronously so it doens't block your main test thread from kicking off your consumer code. This gives you an externally accessable server that non-Ruby clients can hit, but still gives you the full Pacto API in order to validate and spy on HTTP interactions. - -Example usage of the rspec test helper: +This log file is designed so Pacto it can be used by Pacto to simulate the requests: ```ruby -require 'rspec' -require 'pacto/rspec' -require 'pacto/test_helper' - -describe 'my consumer' do - include Pacto::TestHelper - - it 'calls a service' do - with_pacto( - :port => 5000, - :directory => '../spec/integration/data', - :stub => true) do |pacto_endpoint| - # call your code - system "curl #{pacto_endpoint}/echo" - # check results - expect(Pacto).to have_validated(:get, 'https://localhost/echo') - end - end +Pacto.simulate_consumer :my_client do + request 'Upload Album Art', values: {album_id: '123'}, response: {status: 201} # no contract violations + request 'Download Album Art', values: {album_id: '123', size: 'small'}, response: {status: 200} # no contract violations end ``` -## 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' +Since Pacto has added a layer of abstraction you can use the log to generate semantically equivalent requests even as you're expirementing with changes. For example, if you change the routing for the service from: ``` - -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 +"Upload Album Art": PUT /store/album/{album_id}/cover_art ``` - -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). - -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. - -Below is an example contract for a GET request -to the /hello_world endpoint of a provider: - -```json -{ - "request": { - "method": "GET", - "path": "/hello_world", - "headers": { - "Accept": "application/json" - }, - "params": {} - }, - - "response": { - "status": 200, - "headers": { - "Content-Type": "application/json" - }, - "body": { - "description": "A simple response", - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - } - } -} +to ``` - -## Constraints - -- 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) - -## Contributing - -Read the [CONTRIBUTING.md](CONTRIBUTING.md) file. +"Upload Album Art": PUT /cover_art{?album_id} +``` +then Pacto will automatically adjust the URLs. From 9dae3a75af8456d4b7657357ddd734ec1b57e4c5 Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Thu, 3 Jul 2014 15:34:11 -0400 Subject: [PATCH 05/26] Update README.md --- README.md | 246 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 237 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 8b449da..b059475 100644 --- a/README.md +++ b/README.md @@ -82,10 +82,7 @@ The stenographer's logs are similar to HTTP access logs in this respect, but in ``` If Pacto has the following services registered: -``` -"Upload Album Art": PUT /store/album/{album_id}/cover_art -"Download Album Art": GET /store/album/{album_id}/cover_art{?size} -``` +![routes.png](https://draftin.com:443/images/16800?token=8Z2bmsbxOQ74ogeeWTXBwyvVaJ0YBfNJCfQTHa08L3AnvQXsnIf40htwMVudSIugAmpeJp8MD53mN7FPzfgqG9o) Then it can match those requests to request and generate a stenographer log that looks like this: ```ruby @@ -101,12 +98,243 @@ Pacto.simulate_consumer :my_client do end ``` -Since Pacto has added a layer of abstraction you can use the log to generate semantically equivalent requests even as you're expirementing with changes. For example, if you change the routing for the service from: +Since Pacto has added a layer of abstraction you can experiment with changes to the contracts (including routing) without needing to re-record the interactions with the stenographer. For example Pacto will adjust if you change the route from: +![original_put.png](https://draftin.com:443/images/16801?token=6yI7VugUqaLJAMpMvf4oAlPucfQBnfdrdcGpcCuUFET_FH5E0ZreFIrL1C7U2GwRuNndntc9OTIXLD-B2wbkiyg) +to +![new_put.png](https://draftin.com:443/images/16802?token=IagQX2ggHIaaCfRGhR5q85cK9oNN6OgFX2yc9aT0CTZkGzxUMXB1nR40mwIYRat7dUeWPOmLebNOHWXOKlpe-iU) + + + + + +# OLD CONTENT +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 + +**See also: http://thoughtworks.github.io/pacto/usage/** + +Pacto can perform three activities: generating, validating, or stubbing services. You can do each of these activities against either live or stubbed services. + +You can also use [Pacto Server](#pacto-server-non-ruby-usage) if you are working with non-Ruby projects. + +### 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 ``` -"Upload Album Art": PUT /store/album/{album_id}/cover_art + +### 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 ``` -to + +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') ``` -"Upload Album Art": PUT /cover_art{?album_id} + +### 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.simulate_consumers ``` -then Pacto will automatically adjust the URLs. + +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_providers +``` + +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_providers method. This +will override the keys for every contract in the list + +```ruby +contracts = Pacto.load_contracts('contracts/services', 'http://example.com') +contracts.stub_providers(request_id: 14, name: "Marcos") +``` + +## Pacto Server (non-Ruby usage) + +**See also: http://thoughtworks.github.io/pacto/patterns/polyglot/** + +We've bundled a small server that embeds pacto so you can use it for non-Ruby projects. If you want to take advantage of the full features, you'll still need to use Ruby (usually rspec) to drive your API testing. You can run just the server in order to stub or to write validation issues to a log, but you won't have access to the full API fail your tests if there were validation problems. + +### Command-line + +The command-line version of the server will show you all the options. These same options are used when you launch the server from within rspec. In order to see the options: +`bundle exec pacto-server --help` + +Some examples: +```sh +$ bundle exec pacto-server -sv --generate +# Runs a server that will generate Contracts for each request received +$ bundle exec pacto-server -sv --stub --validate +# Runs the server that provides stubs and checks them against Contracts +$ bundle exec pacto-server -sv --live --validate --host +# Runs the server that acts as a proxy to http://example.com, validating each request/response against a Contract +``` + +### RSpec test helper + +You can also launch a server from within an rspec test. The server does start up an external port and runs asynchronously so it doens't block your main test thread from kicking off your consumer code. This gives you an externally accessable server that non-Ruby clients can hit, but still gives you the full Pacto API in order to validate and spy on HTTP interactions. + +Example usage of the rspec test helper: +```ruby +require 'rspec' +require 'pacto/rspec' +require 'pacto/test_helper' + +describe 'my consumer' do + include Pacto::TestHelper + + it 'calls a service' do + with_pacto( + :port => 5000, + :directory => '../spec/integration/data', + :stub => true) do |pacto_endpoint| + # call your code + system "curl #{pacto_endpoint}/echo" + # check results + expect(Pacto).to have_validated(:get, 'https://localhost/echo') + end + end +end +``` + +## 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). + +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. + +Below is an example contract for a GET request +to the /hello_world endpoint of a provider: + +```json +{ + "request": { + "method": "GET", + "path": "/hello_world", + "headers": { + "Accept": "application/json" + }, + "params": {} + }, + + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "body": { + "description": "A simple response", + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } +} +``` + +## Constraints + +- 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) + +## Contributing + +Read the [CONTRIBUTING.md](CONTRIBUTING.md) file. From 39acf1a86a6f91a89789c5089043cdab71445830 Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Thu, 3 Jul 2014 17:24:23 -0400 Subject: [PATCH 06/26] Update README.md --- README.md | 251 +++++++----------------------------------------------- 1 file changed, 30 insertions(+), 221 deletions(-) diff --git a/README.md b/README.md index b059475..fadcf46 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,14 @@ Pacto is a judge that arbitrates contract disputes between a **service provider* Pacto helps settle disputes between **service providers** and **service consumers** of RESTful JSON services. The **provider** is the one that implements the service, which may be used by multiple **consumers**. This is done by [Integration Contract Testing](http://martinfowler.com/bliki/IntegrationContractTest.html), where the contract stays the same but the provider changes. +## Litigators + +Someone needs to accuse the **providers** or **consumers** of wrongdoing! Pacto integrates with a few different test frameworks to give you options: + +- Pacto easily integrates with [RSpec](http://rspec.info/), including some [custom matchers](asdf). +- Pacto provides some [simple rake tasks](samples/rake_task/) to run some basic tests from the command line. +- If you're testing non-Ruby projects, you can use the [Pacto Server](samples/server) as a proxy to intercept and validate requests. You can also use it in conjunction with [Polytrix](https://github.com/rackerlabs/polytrix). + ## Contracts Pacto considers two major terms in order decide if there has been a breach of contract: the **request clause** and the **response clause**. @@ -28,6 +36,8 @@ The **response clause** defines what information must be returned by the **provi See the [Contracts documentation](samples/contracts) for more details. + + ## Enforcement ### Cops @@ -39,6 +49,12 @@ Pacto has a few built-in cops that are on-duty by default. These cops will: - Ensure the response HTTP status matches the contract requirements - Ensure the response body matches the contract requirements +### Forensics + +Sometimes it looks like you're following a contract, but digital forensics reveals there's some fraud going on. Pacto provides RSpec matchers to help you catch these patterns that let you do the [collaboration tests](http://programmers.stackexchange.com/questions/135011/removing-the-integration-test-scam-understanding-collaboration-and-contract) that integration contract testing alone would not catch. + +See the [forensics documetation](samples/forensics) for more details. + ### Sting operations **Note: this is a preview of an upcoming feature. It is not currently available.** @@ -103,238 +119,31 @@ Since Pacto has added a layer of abstraction you can experiment with changes to to ![new_put.png](https://draftin.com:443/images/16802?token=IagQX2ggHIaaCfRGhR5q85cK9oNN6OgFX2yc9aT0CTZkGzxUMXB1nR40mwIYRat7dUeWPOmLebNOHWXOKlpe-iU) +### Clerks +Clerks help Pacto with paperwork. Reading and writing legalese is hard. Pacto clerks help create and load contracts. Currently clerks are responsible for: +- Generating contracts from real HTTP requests +- Basic support for loading from custom formats +In the future, we plan for clerks to provide more complete support for: +- Converting from or loading other similar contract formats (e.g. [Swagger](https://github.com/wordnik/swagger-spec), [apiblueprint](http://apiblueprint.org/), or [RAML](http://raml.org/). +- Upgrading contracts from older Pacto versions -# OLD CONTENT -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 +See the [contract generation](samples/generations) and [clerks](samples/clerks) documentation for more info. -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 +# OLD CONTENT +# 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 - -**See also: http://thoughtworks.github.io/pacto/usage/** - -Pacto can perform three activities: generating, validating, or stubbing services. You can do each of these activities against either live or stubbed services. - -You can also use [Pacto Server](#pacto-server-non-ruby-usage) if you are working with non-Ruby projects. - -### 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.simulate_consumers -``` - -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_providers -``` - -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_providers method. This -will override the keys for every contract in the list - -```ruby -contracts = Pacto.load_contracts('contracts/services', 'http://example.com') -contracts.stub_providers(request_id: 14, name: "Marcos") -``` - -## Pacto Server (non-Ruby usage) - -**See also: http://thoughtworks.github.io/pacto/patterns/polyglot/** - -We've bundled a small server that embeds pacto so you can use it for non-Ruby projects. If you want to take advantage of the full features, you'll still need to use Ruby (usually rspec) to drive your API testing. You can run just the server in order to stub or to write validation issues to a log, but you won't have access to the full API fail your tests if there were validation problems. - -### Command-line - -The command-line version of the server will show you all the options. These same options are used when you launch the server from within rspec. In order to see the options: -`bundle exec pacto-server --help` - -Some examples: -```sh -$ bundle exec pacto-server -sv --generate -# Runs a server that will generate Contracts for each request received -$ bundle exec pacto-server -sv --stub --validate -# Runs the server that provides stubs and checks them against Contracts -$ bundle exec pacto-server -sv --live --validate --host -# Runs the server that acts as a proxy to http://example.com, validating each request/response against a Contract -``` - -### RSpec test helper - -You can also launch a server from within an rspec test. The server does start up an external port and runs asynchronously so it doens't block your main test thread from kicking off your consumer code. This gives you an externally accessable server that non-Ruby clients can hit, but still gives you the full Pacto API in order to validate and spy on HTTP interactions. - -Example usage of the rspec test helper: -```ruby -require 'rspec' -require 'pacto/rspec' -require 'pacto/test_helper' - -describe 'my consumer' do - include Pacto::TestHelper - - it 'calls a service' do - with_pacto( - :port => 5000, - :directory => '../spec/integration/data', - :stub => true) do |pacto_endpoint| - # call your code - system "curl #{pacto_endpoint}/echo" - # check results - expect(Pacto).to have_validated(:get, 'https://localhost/echo') - end - end -end -``` - -## 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). - -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. - -Below is an example contract for a GET request -to the /hello_world endpoint of a provider: - -```json -{ - "request": { - "method": "GET", - "path": "/hello_world", - "headers": { - "Accept": "application/json" - }, - "params": {} - }, - - "response": { - "status": 200, - "headers": { - "Content-Type": "application/json" - }, - "body": { - "description": "A simple response", - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - } - } -} -``` +# Roadmap -## Constraints +See the [Pacto Roadmap](https://github.com/thoughtworks/pacto/wiki/Pacto-Roadmap) -- 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) +# Contributing -## Contributing +See [CONTRIBUTING.md](CONTRIBUTING.md) Read the [CONTRIBUTING.md](CONTRIBUTING.md) file. From af52899f5a706e73cf2c6db1bfb713d8bc45ccf8 Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Sat, 5 Jul 2014 18:14:13 -0400 Subject: [PATCH 07/26] README links and README as default doc for viewdocs --- README.md | 21 +++++++++------------ docs/index.md | 1 + 2 files changed, 10 insertions(+), 12 deletions(-) create mode 120000 docs/index.md diff --git a/README.md b/README.md index fadcf46..fee957b 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,9 @@ Pacto helps settle disputes between **service providers** and **service consumer Someone needs to accuse the **providers** or **consumers** of wrongdoing! Pacto integrates with a few different test frameworks to give you options: -- Pacto easily integrates with [RSpec](http://rspec.info/), including some [custom matchers](asdf). -- Pacto provides some [simple rake tasks](samples/rake_task/) to run some basic tests from the command line. -- If you're testing non-Ruby projects, you can use the [Pacto Server](samples/server) as a proxy to intercept and validate requests. You can also use it in conjunction with [Polytrix](https://github.com/rackerlabs/polytrix). +- Pacto easily integrates with [RSpec](http://rspec.info/), including some [custom matchers](rspec/). +- Pacto provides some [simple rake tasks](rake_tasks/) to run some basic tests from the command line. +- If you're testing non-Ruby projects, you can use the [Pacto Server](server) as a proxy to intercept and validate requests. You can also use it in conjunction with [Polytrix](https://github.com/rackerlabs/polytrix). ## Contracts @@ -34,7 +34,7 @@ The **request clause** defines what information must be sent by the **consumer** 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/)). -See the [Contracts documentation](samples/contracts) for more details. +See the [Contracts documentation](contracts/) for more details. @@ -53,7 +53,7 @@ Pacto has a few built-in cops that are on-duty by default. These cops will: Sometimes it looks like you're following a contract, but digital forensics reveals there's some fraud going on. Pacto provides RSpec matchers to help you catch these patterns that let you do the [collaboration tests](http://programmers.stackexchange.com/questions/135011/removing-the-integration-test-scam-understanding-collaboration-and-contract) that integration contract testing alone would not catch. -See the [forensics documetation](samples/forensics) for more details. +See the [forensics documetation](forensics/) for more details. ### Sting operations @@ -65,7 +65,7 @@ For example, HTTP header field names are supposed to be case-insensitive [accord Another possible sting operation is to introduce network lag, dropped connections, simulate HTTP rate limiting errors, or other issues that a robust consumer are expected to handle. -You can also add your own custom cops to extend Pacto's abilities or to ensure services are standards that are specific to your organization. See the [Cops documentation](samples/contracts) for more details. +You can also add your own custom cops to extend Pacto's abilities or to ensure services are standards that are specific to your organization. See the [Cops documentation](cops/) for more details. ## Inside the courtroom @@ -73,7 +73,7 @@ You can also add your own custom cops to extend Pacto's abilities or to ensure s It's not always practical to test using a **real consumer** and or a **real provider**. Pacto can both **stub providers** and **simulate consumers** so you can test their counterpart in isolation. This makes [Consumer-Driven Contracts](http://thoughtworks.github.io/pacto/patterns/cdc/) easier. You can start testing a consumer against a stubbed provider before the real provider is available, and then hand your contracts and tests over to the team that is going to implement the provider so they can ensure it matches your assumptions. -See the [Actors documentation](samples/actors) for more details. +See the [Actors documentation](actors/) for more details. ### The courtroom reporter @@ -130,9 +130,8 @@ In the future, we plan for clerks to provide more complete support for: - Converting from or loading other similar contract formats (e.g. [Swagger](https://github.com/wordnik/swagger-spec), [apiblueprint](http://apiblueprint.org/), or [RAML](http://raml.org/). - Upgrading contracts from older Pacto versions -See the [contract generation](samples/generations) and [clerks](samples/clerks) documentation for more info. +See the [contract generation](generation/) and [clerks](clerks/) documentation for more info. -# OLD CONTENT # Implied Terms - Pacto only arbitrates contracts for JSON services. @@ -144,6 +143,4 @@ See the [Pacto Roadmap](https://github.com/thoughtworks/pacto/wiki/Pacto-Roadmap # Contributing -See [CONTRIBUTING.md](CONTRIBUTING.md) - -Read the [CONTRIBUTING.md](CONTRIBUTING.md) file. +See [CONTRIBUTING.md](https://github.com/thoughtworks/pacto/blob/master/CONTRIBUTING.md) diff --git a/docs/index.md b/docs/index.md new file mode 120000 index 0000000..32d46ee --- /dev/null +++ b/docs/index.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file From f540bda45f31b6b3cd0b80d7303ea4342a9b5d9f Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Sat, 5 Jul 2014 19:12:25 -0400 Subject: [PATCH 08/26] Clerks sample --- samples/clerks.rb | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 samples/clerks.rb diff --git a/samples/clerks.rb b/samples/clerks.rb new file mode 100644 index 0000000..c9d104f --- /dev/null +++ b/samples/clerks.rb @@ -0,0 +1,32 @@ +# Pacto clerks are responsible for loading contracts. Pacto has built-in support for a native +# contract format, but we've setup a Clerks plugin API so you can more easily load information +# from other formats, like [Swagger](https://github.com/wordnik/swagger-spec), +# [apiblueprint](http://apiblueprint.org/), or [RAML](http://raml.org/). + +# Note: This is a preliminary API and may change in the future, including adding support +# for conversion between formats, or generating each format from real HTTP interactions. + +# In order to add a loading clerk, you just implement a class that responds to build_from_file +# and returns Contract object or a collection of Contract objects. +require 'yaml' +require 'pacto' + +class SimpleYAMLClerk + def build_from_file(path, _host) + data = YAML.load(File.read(path)) + data['services'].map do | service_name, service_definition | + request_clause = Pacto::RequestClause.new service_definition['request'] + response_clause = Pacto::ResponseClause.new service_definition['response'] + Pacto::Contract.new(name: service_name, request: request_clause, response: response_clause) + end + end +end + +# You can then register the clerk with Pacto: +Pacto.contract_factory.add_factory :simple_yaml, SimpleYAMLClerk.new + +# And then you can use it with the normal clerks API, by passing the identifier you used to register +# the clerk: +contracts = Pacto.load_contracts 'simple_service_map.yaml', 'http://example.com', :simple_yaml +contract_names = contracts.map(&:name) +puts "Defined contracts: #{contract_names}" From f7b3689340a4f3c3593f30480c64eac2a31cb937 Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Sat, 5 Jul 2014 19:14:30 -0400 Subject: [PATCH 09/26] Added example file for custom clerk. --- samples/simple_service_map.yaml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 samples/simple_service_map.yaml diff --git a/samples/simple_service_map.yaml b/samples/simple_service_map.yaml new file mode 100644 index 0000000..99e4657 --- /dev/null +++ b/samples/simple_service_map.yaml @@ -0,0 +1,33 @@ +services: + List Servers: + request: + http_method: get + path: /v2/{tenant_id}/servers/detail{?limit} + Create Server: + request: + http_method: post + path: /v2/{tenant_id}/servers{?limit} + response: + status: 202 + Get Server Details: + request: + http_method: get + path: /v2/{tenant_id}/servers/{id} + Attach Volume: + request: + http_method: post + path: /v2/{tenant_id}/servers/{server_id}/os-volume_attachments{?limit} + response: + status: 200 + List Extensions: + request: + http_method: get + path: /v2/{tenant_id}/extensions{?limit} + Get Image Details: + request: + http_method: get + path: /v2/{tenant_id}/images/{image_id} + Get Flavor Details: + request: + http_method: get + path: /v2/{tenant_id}/flavors/{flavor_id} From 677846db6c185b27781736cefb1bf495c812ded6 Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Wed, 2 Jul 2014 19:03:20 -0400 Subject: [PATCH 10/26] Working on new documentation for the next release --- README.md | 251 ++++++++++++------------------------------------------ 1 file changed, 55 insertions(+), 196 deletions(-) diff --git a/README.md b/README.md index 30a4edb..8b449da 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,15 @@ you're reading the documentation for the master branch. (0.3.0).](https://github.com/thoughtworks/pacto/tree/v0.3.0)** # Pacto +## Who is Pacto? -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/). +Pacto is a judge that arbitrates contract disputes between a **service provider** and one or more **consumers**. It is a framework for [Integration Contract Testing](http://martinfowler.com/bliki/IntegrationContractTest.html), and 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/). + +## The litigants + +Pacto helps settle disputes between **service providers** and **service consumers** of RESTful JSON services. The **provider** is the one that implements the service, which may be used by multiple **consumers**. This is done by [Integration Contract Testing](http://martinfowler.com/bliki/IntegrationContractTest.html), where the contract stays the same but the provider changes. + +## Contracts Pacto considers two major terms in order decide if there has been a breach of contract: the **request clause** and the **response clause**. @@ -19,235 +26,87 @@ The **request clause** defines what information must be sent by the **consumer** 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/)). -## Test Doubles +See the [Contracts documentation](samples/contracts) for more details. -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. +## Enforcement -Pacto can provide a [**test double**](#stubbing) if you cannot afford your own. +### Cops +**Cops** help Pacto investigate interactions between **consumers** and **providers** and determine if either has violated the contract. -## Due Diligence +Pacto has a few built-in cops that are on-duty by default. These cops will: +- Ensure the request body matches the contract requirements (if a request body is needed) +- Ensure the response headers match the contract requirements +- Ensure the response HTTP status matches the contract requirements +- Ensure the response body matches the contract requirements -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. +### Sting operations -Pacto can provide a [**contract writer**](#generating) that tries to strike a balance, but you may still need to adjust the terms. +**Note: this is a preview of an upcoming feature. It is not currently available.** -## Implied Terms +Pacto **cops** merely observe the interactions between a **consumer** and **provider** and look for problems. A Pacto **sting operation** will alter the interaction in order to try an find problems. -- 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. +For example, HTTP header field names are supposed to be case-insensitive [according to RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1](http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2), but many implementations are tightly coupled to a certain server or implementation and assume header field names have a certain case, like "Content-Type" and not "content-type". Pacto can change the alter the character case of the field names in order to catch consumers or providers that are not following this part of the RFC. -## Roadmap +Another possible sting operation is to introduce network lag, dropped connections, simulate HTTP rate limiting errors, or other issues that a robust consumer are expected to handle. -- 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. +You can also add your own custom cops to extend Pacto's abilities or to ensure services are standards that are specific to your organization. See the [Cops documentation](samples/contracts) for more details. -## Usage +## Inside the courtroom -**See also: http://thoughtworks.github.io/pacto/usage/** +### Actors -Pacto can perform three activities: generating, validating, or stubbing services. You can do each of these activities against either live or stubbed services. +It's not always practical to test using a **real consumer** and or a **real provider**. Pacto can both **stub providers** and **simulate consumers** so you can test their counterpart in isolation. This makes [Consumer-Driven Contracts](http://thoughtworks.github.io/pacto/patterns/cdc/) easier. You can start testing a consumer against a stubbed provider before the real provider is available, and then hand your contracts and tests over to the team that is going to implement the provider so they can ensure it matches your assumptions. -You can also use [Pacto Server](#pacto-server-non-ruby-usage) if you are working with non-Ruby projects. +See the [Actors documentation](samples/actors) for more details. -### Configuration +### The courtroom reporter -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: +**Note: this is a preview of an upcoming feature. It is not currently available.** -```ruby -require 'pacto' +Pacto can keep track of which services have been called. If you're a consumer with contracts for a set of services you consumer, this helps you figure out "HTTP Service Coverage", which is quite different from code coverage, so you can see if there's any services you forgot to test, or contracts you're still registering with Pacto even though you no longer use those services. -Pacto.configure do |config| - config.contracts_path = 'contracts' -end -``` +If you are a provider that is being used by multiple consumers, you could merge coverage reports from each of them to see which services are being used by each consumer. Once this feature is available, you should be able to create reports that look something like this: -### Generating +![Courtroom Report](https://cloud.githubusercontent.com/assets/896878/2707078/21b0245e-c49d-11e3-8b4e-fa695aa56c4d.png) -The easiest way to get started with Pacto is to run a suite of live tests and tell Pacto to generate the contracts: +### The Stenographer -```ruby -Pacto.generate! -# run your tests -``` - -If you're using the same configuration as above, this will produce Contracts in the contracts/ folder. +The stenographer keeps a short-hand record of everything that happens in the courtroom. These logs can be used for troubleshooting, creating reports, or re-enacting the courtroom activities at a later date (and with different [actors](#actors). -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. +The stenographer's logs are similar to HTTP access logs in this respect, but in a format that's more compact and suited to Pacto. A typical HTTP access log might look like this: -### 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.simulate_consumers +#Fields: date time c-ip cs-username s-ip s-port cs-method cs-uri-stem cs-uri-query sc-status cs(User-Agent) +2014-07-01 17:42:15 127.0.0.1 - 127.0.0.1 80 PUT /store/album/123/cover_art - 201 curl/7.30.0 +2014-07-01 17:42:18 127.0.0.1 - 127.0.0.1 80 GET /store/album/123/cover_art size=small 200 curl/7.30.0 ``` -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_providers +If Pacto has the following services registered: ``` - -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_providers method. This -will override the keys for every contract in the list - -```ruby -contracts = Pacto.load_contracts('contracts/services', 'http://example.com') -contracts.stub_providers(request_id: 14, name: "Marcos") +"Upload Album Art": PUT /store/album/{album_id}/cover_art +"Download Album Art": GET /store/album/{album_id}/cover_art{?size} ``` -## Pacto Server (non-Ruby usage) - -**See also: http://thoughtworks.github.io/pacto/patterns/polyglot/** - -We've bundled a small server that embeds pacto so you can use it for non-Ruby projects. If you want to take advantage of the full features, you'll still need to use Ruby (usually rspec) to drive your API testing. You can run just the server in order to stub or to write validation issues to a log, but you won't have access to the full API fail your tests if there were validation problems. - -### Command-line - -The command-line version of the server will show you all the options. These same options are used when you launch the server from within rspec. In order to see the options: -`bundle exec pacto-server --help` - -Some examples: -```sh -$ bundle exec pacto-server -sv --generate -# Runs a server that will generate Contracts for each request received -$ bundle exec pacto-server -sv --stub --validate -# Runs the server that provides stubs and checks them against Contracts -$ bundle exec pacto-server -sv --live --validate --host -# Runs the server that acts as a proxy to http://example.com, validating each request/response against a Contract +Then it can match those requests to request and generate a stenographer log that looks like this: +```ruby +request 'Upload Album Art', values: {album_id: '123'}, response: {status: 201} # no contract violations +request 'Download Album Art', values: {album_id: '123', size: 'small'}, response: {status: 200} # no contract violations ``` -### RSpec test helper - -You can also launch a server from within an rspec test. The server does start up an external port and runs asynchronously so it doens't block your main test thread from kicking off your consumer code. This gives you an externally accessable server that non-Ruby clients can hit, but still gives you the full Pacto API in order to validate and spy on HTTP interactions. - -Example usage of the rspec test helper: +This log file is designed so Pacto it can be used by Pacto to simulate the requests: ```ruby -require 'rspec' -require 'pacto/rspec' -require 'pacto/test_helper' - -describe 'my consumer' do - include Pacto::TestHelper - - it 'calls a service' do - with_pacto( - :port => 5000, - :directory => '../spec/integration/data', - :stub => true) do |pacto_endpoint| - # call your code - system "curl #{pacto_endpoint}/echo" - # check results - expect(Pacto).to have_validated(:get, 'https://localhost/echo') - end - end +Pacto.simulate_consumer :my_client do + request 'Upload Album Art', values: {album_id: '123'}, response: {status: 201} # no contract violations + request 'Download Album Art', values: {album_id: '123', size: 'small'}, response: {status: 200} # no contract violations end ``` -## 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' +Since Pacto has added a layer of abstraction you can use the log to generate semantically equivalent requests even as you're expirementing with changes. For example, if you change the routing for the service from: ``` - -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 +"Upload Album Art": PUT /store/album/{album_id}/cover_art ``` - -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). - -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. - -Below is an example contract for a GET request -to the /hello_world endpoint of a provider: - -```json -{ - "request": { - "method": "GET", - "path": "/hello_world", - "headers": { - "Accept": "application/json" - }, - "params": {} - }, - - "response": { - "status": 200, - "headers": { - "Content-Type": "application/json" - }, - "body": { - "description": "A simple response", - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - } - } -} +to ``` - -## Constraints - -- 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) - -## Contributing - -Read the [CONTRIBUTING.md](CONTRIBUTING.md) file. +"Upload Album Art": PUT /cover_art{?album_id} +``` +then Pacto will automatically adjust the URLs. From d38e668edfbac1ce8ae3b6f5bd3b1ca190b2aa3b Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Thu, 3 Jul 2014 15:34:11 -0400 Subject: [PATCH 11/26] Update README.md --- README.md | 246 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 237 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 8b449da..b059475 100644 --- a/README.md +++ b/README.md @@ -82,10 +82,7 @@ The stenographer's logs are similar to HTTP access logs in this respect, but in ``` If Pacto has the following services registered: -``` -"Upload Album Art": PUT /store/album/{album_id}/cover_art -"Download Album Art": GET /store/album/{album_id}/cover_art{?size} -``` +![routes.png](https://draftin.com:443/images/16800?token=8Z2bmsbxOQ74ogeeWTXBwyvVaJ0YBfNJCfQTHa08L3AnvQXsnIf40htwMVudSIugAmpeJp8MD53mN7FPzfgqG9o) Then it can match those requests to request and generate a stenographer log that looks like this: ```ruby @@ -101,12 +98,243 @@ Pacto.simulate_consumer :my_client do end ``` -Since Pacto has added a layer of abstraction you can use the log to generate semantically equivalent requests even as you're expirementing with changes. For example, if you change the routing for the service from: +Since Pacto has added a layer of abstraction you can experiment with changes to the contracts (including routing) without needing to re-record the interactions with the stenographer. For example Pacto will adjust if you change the route from: +![original_put.png](https://draftin.com:443/images/16801?token=6yI7VugUqaLJAMpMvf4oAlPucfQBnfdrdcGpcCuUFET_FH5E0ZreFIrL1C7U2GwRuNndntc9OTIXLD-B2wbkiyg) +to +![new_put.png](https://draftin.com:443/images/16802?token=IagQX2ggHIaaCfRGhR5q85cK9oNN6OgFX2yc9aT0CTZkGzxUMXB1nR40mwIYRat7dUeWPOmLebNOHWXOKlpe-iU) + + + + + +# OLD CONTENT +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 + +**See also: http://thoughtworks.github.io/pacto/usage/** + +Pacto can perform three activities: generating, validating, or stubbing services. You can do each of these activities against either live or stubbed services. + +You can also use [Pacto Server](#pacto-server-non-ruby-usage) if you are working with non-Ruby projects. + +### 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 ``` -"Upload Album Art": PUT /store/album/{album_id}/cover_art + +### 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 ``` -to + +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') ``` -"Upload Album Art": PUT /cover_art{?album_id} + +### 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.simulate_consumers ``` -then Pacto will automatically adjust the URLs. + +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_providers +``` + +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_providers method. This +will override the keys for every contract in the list + +```ruby +contracts = Pacto.load_contracts('contracts/services', 'http://example.com') +contracts.stub_providers(request_id: 14, name: "Marcos") +``` + +## Pacto Server (non-Ruby usage) + +**See also: http://thoughtworks.github.io/pacto/patterns/polyglot/** + +We've bundled a small server that embeds pacto so you can use it for non-Ruby projects. If you want to take advantage of the full features, you'll still need to use Ruby (usually rspec) to drive your API testing. You can run just the server in order to stub or to write validation issues to a log, but you won't have access to the full API fail your tests if there were validation problems. + +### Command-line + +The command-line version of the server will show you all the options. These same options are used when you launch the server from within rspec. In order to see the options: +`bundle exec pacto-server --help` + +Some examples: +```sh +$ bundle exec pacto-server -sv --generate +# Runs a server that will generate Contracts for each request received +$ bundle exec pacto-server -sv --stub --validate +# Runs the server that provides stubs and checks them against Contracts +$ bundle exec pacto-server -sv --live --validate --host +# Runs the server that acts as a proxy to http://example.com, validating each request/response against a Contract +``` + +### RSpec test helper + +You can also launch a server from within an rspec test. The server does start up an external port and runs asynchronously so it doens't block your main test thread from kicking off your consumer code. This gives you an externally accessable server that non-Ruby clients can hit, but still gives you the full Pacto API in order to validate and spy on HTTP interactions. + +Example usage of the rspec test helper: +```ruby +require 'rspec' +require 'pacto/rspec' +require 'pacto/test_helper' + +describe 'my consumer' do + include Pacto::TestHelper + + it 'calls a service' do + with_pacto( + :port => 5000, + :directory => '../spec/integration/data', + :stub => true) do |pacto_endpoint| + # call your code + system "curl #{pacto_endpoint}/echo" + # check results + expect(Pacto).to have_validated(:get, 'https://localhost/echo') + end + end +end +``` + +## 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). + +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. + +Below is an example contract for a GET request +to the /hello_world endpoint of a provider: + +```json +{ + "request": { + "method": "GET", + "path": "/hello_world", + "headers": { + "Accept": "application/json" + }, + "params": {} + }, + + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "body": { + "description": "A simple response", + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } +} +``` + +## Constraints + +- 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) + +## Contributing + +Read the [CONTRIBUTING.md](CONTRIBUTING.md) file. From ab6a131ade5d6071effc4ddf9523ffcf11fac91d Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Thu, 3 Jul 2014 17:24:23 -0400 Subject: [PATCH 12/26] Update README.md --- README.md | 251 +++++++----------------------------------------------- 1 file changed, 30 insertions(+), 221 deletions(-) diff --git a/README.md b/README.md index b059475..fadcf46 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,14 @@ Pacto is a judge that arbitrates contract disputes between a **service provider* Pacto helps settle disputes between **service providers** and **service consumers** of RESTful JSON services. The **provider** is the one that implements the service, which may be used by multiple **consumers**. This is done by [Integration Contract Testing](http://martinfowler.com/bliki/IntegrationContractTest.html), where the contract stays the same but the provider changes. +## Litigators + +Someone needs to accuse the **providers** or **consumers** of wrongdoing! Pacto integrates with a few different test frameworks to give you options: + +- Pacto easily integrates with [RSpec](http://rspec.info/), including some [custom matchers](asdf). +- Pacto provides some [simple rake tasks](samples/rake_task/) to run some basic tests from the command line. +- If you're testing non-Ruby projects, you can use the [Pacto Server](samples/server) as a proxy to intercept and validate requests. You can also use it in conjunction with [Polytrix](https://github.com/rackerlabs/polytrix). + ## Contracts Pacto considers two major terms in order decide if there has been a breach of contract: the **request clause** and the **response clause**. @@ -28,6 +36,8 @@ The **response clause** defines what information must be returned by the **provi See the [Contracts documentation](samples/contracts) for more details. + + ## Enforcement ### Cops @@ -39,6 +49,12 @@ Pacto has a few built-in cops that are on-duty by default. These cops will: - Ensure the response HTTP status matches the contract requirements - Ensure the response body matches the contract requirements +### Forensics + +Sometimes it looks like you're following a contract, but digital forensics reveals there's some fraud going on. Pacto provides RSpec matchers to help you catch these patterns that let you do the [collaboration tests](http://programmers.stackexchange.com/questions/135011/removing-the-integration-test-scam-understanding-collaboration-and-contract) that integration contract testing alone would not catch. + +See the [forensics documetation](samples/forensics) for more details. + ### Sting operations **Note: this is a preview of an upcoming feature. It is not currently available.** @@ -103,238 +119,31 @@ Since Pacto has added a layer of abstraction you can experiment with changes to to ![new_put.png](https://draftin.com:443/images/16802?token=IagQX2ggHIaaCfRGhR5q85cK9oNN6OgFX2yc9aT0CTZkGzxUMXB1nR40mwIYRat7dUeWPOmLebNOHWXOKlpe-iU) +### Clerks +Clerks help Pacto with paperwork. Reading and writing legalese is hard. Pacto clerks help create and load contracts. Currently clerks are responsible for: +- Generating contracts from real HTTP requests +- Basic support for loading from custom formats +In the future, we plan for clerks to provide more complete support for: +- Converting from or loading other similar contract formats (e.g. [Swagger](https://github.com/wordnik/swagger-spec), [apiblueprint](http://apiblueprint.org/), or [RAML](http://raml.org/). +- Upgrading contracts from older Pacto versions -# OLD CONTENT -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 +See the [contract generation](samples/generations) and [clerks](samples/clerks) documentation for more info. -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 +# OLD CONTENT +# 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 - -**See also: http://thoughtworks.github.io/pacto/usage/** - -Pacto can perform three activities: generating, validating, or stubbing services. You can do each of these activities against either live or stubbed services. - -You can also use [Pacto Server](#pacto-server-non-ruby-usage) if you are working with non-Ruby projects. - -### 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.simulate_consumers -``` - -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_providers -``` - -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_providers method. This -will override the keys for every contract in the list - -```ruby -contracts = Pacto.load_contracts('contracts/services', 'http://example.com') -contracts.stub_providers(request_id: 14, name: "Marcos") -``` - -## Pacto Server (non-Ruby usage) - -**See also: http://thoughtworks.github.io/pacto/patterns/polyglot/** - -We've bundled a small server that embeds pacto so you can use it for non-Ruby projects. If you want to take advantage of the full features, you'll still need to use Ruby (usually rspec) to drive your API testing. You can run just the server in order to stub or to write validation issues to a log, but you won't have access to the full API fail your tests if there were validation problems. - -### Command-line - -The command-line version of the server will show you all the options. These same options are used when you launch the server from within rspec. In order to see the options: -`bundle exec pacto-server --help` - -Some examples: -```sh -$ bundle exec pacto-server -sv --generate -# Runs a server that will generate Contracts for each request received -$ bundle exec pacto-server -sv --stub --validate -# Runs the server that provides stubs and checks them against Contracts -$ bundle exec pacto-server -sv --live --validate --host -# Runs the server that acts as a proxy to http://example.com, validating each request/response against a Contract -``` - -### RSpec test helper - -You can also launch a server from within an rspec test. The server does start up an external port and runs asynchronously so it doens't block your main test thread from kicking off your consumer code. This gives you an externally accessable server that non-Ruby clients can hit, but still gives you the full Pacto API in order to validate and spy on HTTP interactions. - -Example usage of the rspec test helper: -```ruby -require 'rspec' -require 'pacto/rspec' -require 'pacto/test_helper' - -describe 'my consumer' do - include Pacto::TestHelper - - it 'calls a service' do - with_pacto( - :port => 5000, - :directory => '../spec/integration/data', - :stub => true) do |pacto_endpoint| - # call your code - system "curl #{pacto_endpoint}/echo" - # check results - expect(Pacto).to have_validated(:get, 'https://localhost/echo') - end - end -end -``` - -## 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). - -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. - -Below is an example contract for a GET request -to the /hello_world endpoint of a provider: - -```json -{ - "request": { - "method": "GET", - "path": "/hello_world", - "headers": { - "Accept": "application/json" - }, - "params": {} - }, - - "response": { - "status": 200, - "headers": { - "Content-Type": "application/json" - }, - "body": { - "description": "A simple response", - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - } - } -} -``` +# Roadmap -## Constraints +See the [Pacto Roadmap](https://github.com/thoughtworks/pacto/wiki/Pacto-Roadmap) -- 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) +# Contributing -## Contributing +See [CONTRIBUTING.md](CONTRIBUTING.md) Read the [CONTRIBUTING.md](CONTRIBUTING.md) file. From f64be00dfc95bcfc17e08fc90eb9a6adf89564eb Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Wed, 2 Jul 2014 19:03:20 -0400 Subject: [PATCH 13/26] Working on new documentation for the next release --- README.md | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index fadcf46..fee957b 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,9 @@ Pacto helps settle disputes between **service providers** and **service consumer Someone needs to accuse the **providers** or **consumers** of wrongdoing! Pacto integrates with a few different test frameworks to give you options: -- Pacto easily integrates with [RSpec](http://rspec.info/), including some [custom matchers](asdf). -- Pacto provides some [simple rake tasks](samples/rake_task/) to run some basic tests from the command line. -- If you're testing non-Ruby projects, you can use the [Pacto Server](samples/server) as a proxy to intercept and validate requests. You can also use it in conjunction with [Polytrix](https://github.com/rackerlabs/polytrix). +- Pacto easily integrates with [RSpec](http://rspec.info/), including some [custom matchers](rspec/). +- Pacto provides some [simple rake tasks](rake_tasks/) to run some basic tests from the command line. +- If you're testing non-Ruby projects, you can use the [Pacto Server](server) as a proxy to intercept and validate requests. You can also use it in conjunction with [Polytrix](https://github.com/rackerlabs/polytrix). ## Contracts @@ -34,7 +34,7 @@ The **request clause** defines what information must be sent by the **consumer** 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/)). -See the [Contracts documentation](samples/contracts) for more details. +See the [Contracts documentation](contracts/) for more details. @@ -53,7 +53,7 @@ Pacto has a few built-in cops that are on-duty by default. These cops will: Sometimes it looks like you're following a contract, but digital forensics reveals there's some fraud going on. Pacto provides RSpec matchers to help you catch these patterns that let you do the [collaboration tests](http://programmers.stackexchange.com/questions/135011/removing-the-integration-test-scam-understanding-collaboration-and-contract) that integration contract testing alone would not catch. -See the [forensics documetation](samples/forensics) for more details. +See the [forensics documetation](forensics/) for more details. ### Sting operations @@ -65,7 +65,7 @@ For example, HTTP header field names are supposed to be case-insensitive [accord Another possible sting operation is to introduce network lag, dropped connections, simulate HTTP rate limiting errors, or other issues that a robust consumer are expected to handle. -You can also add your own custom cops to extend Pacto's abilities or to ensure services are standards that are specific to your organization. See the [Cops documentation](samples/contracts) for more details. +You can also add your own custom cops to extend Pacto's abilities or to ensure services are standards that are specific to your organization. See the [Cops documentation](cops/) for more details. ## Inside the courtroom @@ -73,7 +73,7 @@ You can also add your own custom cops to extend Pacto's abilities or to ensure s It's not always practical to test using a **real consumer** and or a **real provider**. Pacto can both **stub providers** and **simulate consumers** so you can test their counterpart in isolation. This makes [Consumer-Driven Contracts](http://thoughtworks.github.io/pacto/patterns/cdc/) easier. You can start testing a consumer against a stubbed provider before the real provider is available, and then hand your contracts and tests over to the team that is going to implement the provider so they can ensure it matches your assumptions. -See the [Actors documentation](samples/actors) for more details. +See the [Actors documentation](actors/) for more details. ### The courtroom reporter @@ -130,9 +130,8 @@ In the future, we plan for clerks to provide more complete support for: - Converting from or loading other similar contract formats (e.g. [Swagger](https://github.com/wordnik/swagger-spec), [apiblueprint](http://apiblueprint.org/), or [RAML](http://raml.org/). - Upgrading contracts from older Pacto versions -See the [contract generation](samples/generations) and [clerks](samples/clerks) documentation for more info. +See the [contract generation](generation/) and [clerks](clerks/) documentation for more info. -# OLD CONTENT # Implied Terms - Pacto only arbitrates contracts for JSON services. @@ -144,6 +143,4 @@ See the [Pacto Roadmap](https://github.com/thoughtworks/pacto/wiki/Pacto-Roadmap # Contributing -See [CONTRIBUTING.md](CONTRIBUTING.md) - -Read the [CONTRIBUTING.md](CONTRIBUTING.md) file. +See [CONTRIBUTING.md](https://github.com/thoughtworks/pacto/blob/master/CONTRIBUTING.md) From afc061b86d85935252391d33e2f175f706cd56b0 Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Thu, 3 Jul 2014 15:34:11 -0400 Subject: [PATCH 14/26] Update README.md --- README.md | 237 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) diff --git a/README.md b/README.md index fee957b..d9514f2 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,7 @@ Since Pacto has added a layer of abstraction you can experiment with changes to to ![new_put.png](https://draftin.com:443/images/16802?token=IagQX2ggHIaaCfRGhR5q85cK9oNN6OgFX2yc9aT0CTZkGzxUMXB1nR40mwIYRat7dUeWPOmLebNOHWXOKlpe-iU) +<<<<<<< HEAD ### Clerks Clerks help Pacto with paperwork. Reading and writing legalese is hard. Pacto clerks help create and load contracts. Currently clerks are responsible for: @@ -133,10 +134,29 @@ In the future, we plan for clerks to provide more complete support for: See the [contract generation](generation/) and [clerks](clerks/) documentation for more info. # Implied Terms +======= + + + + +# OLD CONTENT +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 +>>>>>>> Update README.md - 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. +<<<<<<< HEAD # Roadmap See the [Pacto Roadmap](https://github.com/thoughtworks/pacto/wiki/Pacto-Roadmap) @@ -144,3 +164,220 @@ See the [Pacto Roadmap](https://github.com/thoughtworks/pacto/wiki/Pacto-Roadmap # Contributing See [CONTRIBUTING.md](https://github.com/thoughtworks/pacto/blob/master/CONTRIBUTING.md) +======= +## 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 + +**See also: http://thoughtworks.github.io/pacto/usage/** + +Pacto can perform three activities: generating, validating, or stubbing services. You can do each of these activities against either live or stubbed services. + +You can also use [Pacto Server](#pacto-server-non-ruby-usage) if you are working with non-Ruby projects. + +### 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.simulate_consumers +``` + +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_providers +``` + +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_providers method. This +will override the keys for every contract in the list + +```ruby +contracts = Pacto.load_contracts('contracts/services', 'http://example.com') +contracts.stub_providers(request_id: 14, name: "Marcos") +``` + +## Pacto Server (non-Ruby usage) + +**See also: http://thoughtworks.github.io/pacto/patterns/polyglot/** + +We've bundled a small server that embeds pacto so you can use it for non-Ruby projects. If you want to take advantage of the full features, you'll still need to use Ruby (usually rspec) to drive your API testing. You can run just the server in order to stub or to write validation issues to a log, but you won't have access to the full API fail your tests if there were validation problems. + +### Command-line + +The command-line version of the server will show you all the options. These same options are used when you launch the server from within rspec. In order to see the options: +`bundle exec pacto-server --help` + +Some examples: +```sh +$ bundle exec pacto-server -sv --generate +# Runs a server that will generate Contracts for each request received +$ bundle exec pacto-server -sv --stub --validate +# Runs the server that provides stubs and checks them against Contracts +$ bundle exec pacto-server -sv --live --validate --host +# Runs the server that acts as a proxy to http://example.com, validating each request/response against a Contract +``` + +### RSpec test helper + +You can also launch a server from within an rspec test. The server does start up an external port and runs asynchronously so it doens't block your main test thread from kicking off your consumer code. This gives you an externally accessable server that non-Ruby clients can hit, but still gives you the full Pacto API in order to validate and spy on HTTP interactions. + +Example usage of the rspec test helper: +```ruby +require 'rspec' +require 'pacto/rspec' +require 'pacto/test_helper' + +describe 'my consumer' do + include Pacto::TestHelper + + it 'calls a service' do + with_pacto( + :port => 5000, + :directory => '../spec/integration/data', + :stub => true) do |pacto_endpoint| + # call your code + system "curl #{pacto_endpoint}/echo" + # check results + expect(Pacto).to have_validated(:get, 'https://localhost/echo') + end + end +end +``` + +## 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). + +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. + +Below is an example contract for a GET request +to the /hello_world endpoint of a provider: + +```json +{ + "request": { + "method": "GET", + "path": "/hello_world", + "headers": { + "Accept": "application/json" + }, + "params": {} + }, + + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "body": { + "description": "A simple response", + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } +} +``` + +## Constraints + +- 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) + +## Contributing + +Read the [CONTRIBUTING.md](CONTRIBUTING.md) file. +>>>>>>> Update README.md From dc3a1c0bafb397ee0ed4f6ff75e45b32fc049ea6 Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Sat, 5 Jul 2014 19:27:54 -0400 Subject: [PATCH 15/26] Merged master --- README.md | 237 ------------------------------------------------------ 1 file changed, 237 deletions(-) diff --git a/README.md b/README.md index d9514f2..fee957b 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,6 @@ Since Pacto has added a layer of abstraction you can experiment with changes to to ![new_put.png](https://draftin.com:443/images/16802?token=IagQX2ggHIaaCfRGhR5q85cK9oNN6OgFX2yc9aT0CTZkGzxUMXB1nR40mwIYRat7dUeWPOmLebNOHWXOKlpe-iU) -<<<<<<< HEAD ### Clerks Clerks help Pacto with paperwork. Reading and writing legalese is hard. Pacto clerks help create and load contracts. Currently clerks are responsible for: @@ -134,29 +133,10 @@ In the future, we plan for clerks to provide more complete support for: See the [contract generation](generation/) and [clerks](clerks/) documentation for more info. # Implied Terms -======= - - - - -# OLD CONTENT -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 ->>>>>>> Update README.md - 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. -<<<<<<< HEAD # Roadmap See the [Pacto Roadmap](https://github.com/thoughtworks/pacto/wiki/Pacto-Roadmap) @@ -164,220 +144,3 @@ See the [Pacto Roadmap](https://github.com/thoughtworks/pacto/wiki/Pacto-Roadmap # Contributing See [CONTRIBUTING.md](https://github.com/thoughtworks/pacto/blob/master/CONTRIBUTING.md) -======= -## 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 - -**See also: http://thoughtworks.github.io/pacto/usage/** - -Pacto can perform three activities: generating, validating, or stubbing services. You can do each of these activities against either live or stubbed services. - -You can also use [Pacto Server](#pacto-server-non-ruby-usage) if you are working with non-Ruby projects. - -### 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.simulate_consumers -``` - -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_providers -``` - -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_providers method. This -will override the keys for every contract in the list - -```ruby -contracts = Pacto.load_contracts('contracts/services', 'http://example.com') -contracts.stub_providers(request_id: 14, name: "Marcos") -``` - -## Pacto Server (non-Ruby usage) - -**See also: http://thoughtworks.github.io/pacto/patterns/polyglot/** - -We've bundled a small server that embeds pacto so you can use it for non-Ruby projects. If you want to take advantage of the full features, you'll still need to use Ruby (usually rspec) to drive your API testing. You can run just the server in order to stub or to write validation issues to a log, but you won't have access to the full API fail your tests if there were validation problems. - -### Command-line - -The command-line version of the server will show you all the options. These same options are used when you launch the server from within rspec. In order to see the options: -`bundle exec pacto-server --help` - -Some examples: -```sh -$ bundle exec pacto-server -sv --generate -# Runs a server that will generate Contracts for each request received -$ bundle exec pacto-server -sv --stub --validate -# Runs the server that provides stubs and checks them against Contracts -$ bundle exec pacto-server -sv --live --validate --host -# Runs the server that acts as a proxy to http://example.com, validating each request/response against a Contract -``` - -### RSpec test helper - -You can also launch a server from within an rspec test. The server does start up an external port and runs asynchronously so it doens't block your main test thread from kicking off your consumer code. This gives you an externally accessable server that non-Ruby clients can hit, but still gives you the full Pacto API in order to validate and spy on HTTP interactions. - -Example usage of the rspec test helper: -```ruby -require 'rspec' -require 'pacto/rspec' -require 'pacto/test_helper' - -describe 'my consumer' do - include Pacto::TestHelper - - it 'calls a service' do - with_pacto( - :port => 5000, - :directory => '../spec/integration/data', - :stub => true) do |pacto_endpoint| - # call your code - system "curl #{pacto_endpoint}/echo" - # check results - expect(Pacto).to have_validated(:get, 'https://localhost/echo') - end - end -end -``` - -## 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). - -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. - -Below is an example contract for a GET request -to the /hello_world endpoint of a provider: - -```json -{ - "request": { - "method": "GET", - "path": "/hello_world", - "headers": { - "Accept": "application/json" - }, - "params": {} - }, - - "response": { - "status": 200, - "headers": { - "Content-Type": "application/json" - }, - "body": { - "description": "A simple response", - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - } - } -} -``` - -## Constraints - -- 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) - -## Contributing - -Read the [CONTRIBUTING.md](CONTRIBUTING.md) file. ->>>>>>> Update README.md From 0d1e20b9ce10e40b0eead1d7287ca0893231861f Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Sat, 5 Jul 2014 18:14:13 -0400 Subject: [PATCH 16/26] README links and README as default doc for viewdocs --- docs/index.md | 1 + 1 file changed, 1 insertion(+) create mode 120000 docs/index.md diff --git a/docs/index.md b/docs/index.md new file mode 120000 index 0000000..32d46ee --- /dev/null +++ b/docs/index.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file From 799fed2676a7bd2ee0b90dd64677d4d8367a47f5 Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Sat, 5 Jul 2014 19:12:25 -0400 Subject: [PATCH 17/26] Clerks sample --- samples/clerks.rb | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 samples/clerks.rb diff --git a/samples/clerks.rb b/samples/clerks.rb new file mode 100644 index 0000000..c9d104f --- /dev/null +++ b/samples/clerks.rb @@ -0,0 +1,32 @@ +# Pacto clerks are responsible for loading contracts. Pacto has built-in support for a native +# contract format, but we've setup a Clerks plugin API so you can more easily load information +# from other formats, like [Swagger](https://github.com/wordnik/swagger-spec), +# [apiblueprint](http://apiblueprint.org/), or [RAML](http://raml.org/). + +# Note: This is a preliminary API and may change in the future, including adding support +# for conversion between formats, or generating each format from real HTTP interactions. + +# In order to add a loading clerk, you just implement a class that responds to build_from_file +# and returns Contract object or a collection of Contract objects. +require 'yaml' +require 'pacto' + +class SimpleYAMLClerk + def build_from_file(path, _host) + data = YAML.load(File.read(path)) + data['services'].map do | service_name, service_definition | + request_clause = Pacto::RequestClause.new service_definition['request'] + response_clause = Pacto::ResponseClause.new service_definition['response'] + Pacto::Contract.new(name: service_name, request: request_clause, response: response_clause) + end + end +end + +# You can then register the clerk with Pacto: +Pacto.contract_factory.add_factory :simple_yaml, SimpleYAMLClerk.new + +# And then you can use it with the normal clerks API, by passing the identifier you used to register +# the clerk: +contracts = Pacto.load_contracts 'simple_service_map.yaml', 'http://example.com', :simple_yaml +contract_names = contracts.map(&:name) +puts "Defined contracts: #{contract_names}" From c70e734332a351a7499f74828f57613fdacf2aba Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Sat, 5 Jul 2014 19:14:30 -0400 Subject: [PATCH 18/26] Added example file for custom clerk. --- samples/simple_service_map.yaml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 samples/simple_service_map.yaml diff --git a/samples/simple_service_map.yaml b/samples/simple_service_map.yaml new file mode 100644 index 0000000..99e4657 --- /dev/null +++ b/samples/simple_service_map.yaml @@ -0,0 +1,33 @@ +services: + List Servers: + request: + http_method: get + path: /v2/{tenant_id}/servers/detail{?limit} + Create Server: + request: + http_method: post + path: /v2/{tenant_id}/servers{?limit} + response: + status: 202 + Get Server Details: + request: + http_method: get + path: /v2/{tenant_id}/servers/{id} + Attach Volume: + request: + http_method: post + path: /v2/{tenant_id}/servers/{server_id}/os-volume_attachments{?limit} + response: + status: 200 + List Extensions: + request: + http_method: get + path: /v2/{tenant_id}/extensions{?limit} + Get Image Details: + request: + http_method: get + path: /v2/{tenant_id}/images/{image_id} + Get Flavor Details: + request: + http_method: get + path: /v2/{tenant_id}/flavors/{flavor_id} From 972bd94303d300e5cb3357ade6b66a2048327afe Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Sat, 5 Jul 2014 20:01:10 -0400 Subject: [PATCH 19/26] Updated configuration sample/doc --- samples/configuration.rb | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/samples/configuration.rb b/samples/configuration.rb index b87fec2..127e53f 100755 --- a/samples/configuration.rb +++ b/samples/configuration.rb @@ -1,30 +1,24 @@ -# Just require pacto to add it to your project. -require 'pacto' # Pacto will disable live connections, so you will get an error if # your code unexpectedly calls an service that was not stubbed. If you -# want to re-enable connections, run `WebMock.allow_net_connect!` +# want to re-enable connections, run `WebMock.allow_net_connect!` after +# requiring pacto. +require 'pacto' WebMock.allow_net_connect! # Pacto can be configured via a block: Pacto.configure do |c| - # Path for loading/storing contracts. - c.contracts_path = 'contracts' - # If the request matching should be strict (especially regarding HTTP Headers). - c.strict_matchers = true - # You can set the Ruby Logger used by Pacto. - c.logger = Pacto::Logger::SimpleLogger.instance - # (Deprecated) You can specify a callback for post-processing responses. Note that only one hook - # can be active, and specifying your own will disable ERB post-processing. - c.register_hook do |_contracts, request, _response| - puts "Received #{request}" - end - # Options to pass to the [json-schema-generator](https://github.com/maxlinc/json-schema-generator) while generating contracts. - c.generator_options = { schema_version: 'draft3' } + c.contracts_path = 'contracts' # Path for loading/storing contracts. + c.strict_matchers = true # If the request matching should be strict (especially regarding HTTP Headers). + c.stenographer_log_file = nil # Set to nil to disable the stenographer log. end -# You can also do inline configuration. This example tells the json-schema-generator to store default values in the schema. +# You can also do inline configuration. This example tells the +# [json-schema-generator](https://github.com/maxlinc/json-schema-generator) to +# store default values in the schema. Pacto.configuration.generator_options = { defaults: true } +# All Pacto configuration and metrics can be reset ia `Pacto.clear!`. If you're using +# RSpec you may want to clear between each scenario: # If you're using Pacto's rspec matchers you might want to configure a reset between each scenario require 'pacto/rspec' RSpec.configure do |c| From 18f08a88a118cb6575fe8be32dda9eb53d741896 Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Sat, 5 Jul 2014 20:09:15 -0400 Subject: [PATCH 20/26] Updated samples/docs for cops --- samples/cops.rb | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/samples/cops.rb b/samples/cops.rb index a131f88..5fc8ea2 100644 --- a/samples/cops.rb +++ b/samples/cops.rb @@ -1,11 +1,6 @@ -require 'pacto' -Pacto.configure do |c| - c.contracts_path = 'contracts' -end -Pacto.validate! - # You can create a custom cop that investigates the request/response and sees if it complies with a # contract. The cop should return a list of citations if it finds any problems. +require 'pacto' class MyCustomCop def investigate(_request, _response, contract) citations = [] @@ -15,13 +10,23 @@ def investigate(_request, _response, contract) end end +# You can activate the cop by adding it to the active_cops. The active_cops are reset +# by `Pacto.clear!` Pacto::Cops.active_cops << MyCustomCop.new +# Or you could add it as a registered cop. These cops are not cleared - they form the +# default set of Cops used by Pacto: +Pacto::Cops.register_cop MyCustomCop.new + +# The cops will be used to validate any service requests/responses detected by Pacto, +# including when we simulate consumers: +Pacto.validate! contracts = Pacto.load_contracts('contracts', 'http://localhost:5000') contracts.stub_providers puts contracts.simulate_consumers -# Or you can completely replace the default set of validators +# You could also completely reset the registered cops if you don't want to use +# all of Pacto's built-in cops: Pacto::Cops.registered_cops.clear Pacto::Cops.register_cop Pacto::Cops::ResponseBodyCop From f53d730854ad732b7eaae28bb58cfd73d0ef59f4 Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Mon, 7 Jul 2014 15:49:29 -0400 Subject: [PATCH 21/26] Actors docs --- README.md | 2 +- samples/actors.rb | 60 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 samples/actors.rb diff --git a/README.md b/README.md index fee957b..eb49b01 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Pacto helps settle disputes between **service providers** and **service consumer Someone needs to accuse the **providers** or **consumers** of wrongdoing! Pacto integrates with a few different test frameworks to give you options: -- Pacto easily integrates with [RSpec](http://rspec.info/), including some [custom matchers](rspec/). +- Pacto easily integrates with [RSpec](http://rspec.info/), including some [custom matchers](#forensics). - Pacto provides some [simple rake tasks](rake_tasks/) to run some basic tests from the command line. - If you're testing non-Ruby projects, you can use the [Pacto Server](server) as a proxy to intercept and validate requests. You can also use it in conjunction with [Polytrix](https://github.com/rackerlabs/polytrix). diff --git a/samples/actors.rb b/samples/actors.rb new file mode 100644 index 0000000..c5f412e --- /dev/null +++ b/samples/actors.rb @@ -0,0 +1,60 @@ +# Pacto uses actors stub **providers** or simulate **consumers** based on the contract. There +# are two built-in actors. The FromExamples actor will produce requests or responses based on +# the examples in the contract. The JSONGenerator actor will attempt to generate requests or +# responses that match the JSON schema, though it only works for simple schemas. The FromExamples +# actor is the default, but falls back to the JSONGenerator actor if there are no examples available. + +# Consider the following contract: +# +# ```json +# { +# "name": "Ping", +# "request": { +# "headers": { +# }, +# "http_method": "get", +# "path": "/api/ping" +# }, +# "response": { +# "headers": { +# "Content-Type": "application/json" +# }, +# "status": 200, +# "schema": { +# "$schema": "http://json-schema.org/draft-03/schema#", +# "type": "object", +# "required": true, +# "properties": { +# "ping": { +# "type": "string", +# "required": true +# } +# } +# } +# }, +# "examples": { +# "default": { +# "request": { +# }, +# "response": { +# "body": { +# "ping": "pong - from the example!" +# } +# } +# } +# } +# } +# ``` + +# Then Pacto will generate the following response by default (via FromExamples): +# +# ```json +# {"ping":"pong - from the example!"} +# ``` +# +# If you didn't have an example, then Pacto generate very basic mock data based on the schema types, +# producing something like: +# +# ```json +# {"ping":"bar"} +# ``` From 1b1b71cb2f10feb3a1954cd844eeb4e4fa77e1d4 Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Mon, 7 Jul 2014 18:05:47 -0400 Subject: [PATCH 22/26] Add forensics doc (even though the feature's not implemented yet) --- samples/forensics.rb | 52 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 samples/forensics.rb diff --git a/samples/forensics.rb b/samples/forensics.rb new file mode 100644 index 0000000..6749ad2 --- /dev/null +++ b/samples/forensics.rb @@ -0,0 +1,52 @@ +# Pacto has a few RSpec matchers to help you ensure a **consumer** and **producer** are +# interacting properly. First, let's setup the rspec suite. +require 'rspec/autorun' # Not generally needed +require 'pacto/rspec' +WebMock.allow_net_connect! +Pacto.validate! +Pacto.load_contracts('contracts', 'http://localhost:5000').stub_providers + +# It's usually a good idea to reset Pacto between each scenario. `Pacto.reset` just clears the +# data and metrics about which services were called. `Pacto.clear!` also resets all configuration +# and plugins. +RSpec.configure do |c| + c.after(:each) { Pacto.reset } +end + + +# Pacto provides some RSpec matchers related to contract testing, like making sure +# Pacto didn't received any unrecognized requests (`have_unmatched_requests`) and that +# the HTTP requests matched up with the terms of the contract (`have_failed_investigations`). +describe Faraday do + let(:connection) { Faraday.new(url: 'http://localhost:5000') } + + it 'passes contract tests' do + response = connection.get '/api/ping' + expect(Pacto).to_not have_failed_investigations + expect(Pacto).to_not have_unmatched_requests + end +end + +# There are also some matchers for collaboration testing, so you can make sure each scenario is +# calling the expected services and sending the right type of data. +describe Faraday do + let(:connection) { Faraday.new(url: 'http://localhost:5000') } + before(:each) do + response = connection.get '/api/ping' + + connection.post do |req| + req.url '/api/echo' + req.headers['Content-Type'] = 'application/json' + req.body = '{"foo": "bar"}' + end + end + + it 'calls the ping service' do + expect(Pacto).to have_validated(:get, 'http://localhost:5000/api/ping').against_contract('Ping') + end + + it 'sends data to the echo service' do + expect(Pacto).to have_investigated('Echo').with_request(body: hash_including('foo' => 'bar')) + expect(Pacto).to have_investigated('Echo').with_response(body: hash_including('foo' => 'bar')) + end +end From f9841ab02d5a3ec3b54db6894dc3d86e382f7c09 Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Tue, 8 Jul 2014 09:40:20 -0400 Subject: [PATCH 23/26] Add docs generated from samples --- docs/actors.md | 58 ++++++++++++++++++ docs/clerks.md | 40 ++++++++++++ docs/configuration.md | 39 ++++++++++++ docs/cops.md | 50 +++++++++++++++ docs/forensics.md | 65 ++++++++++++++++++++ docs/generation.md | 48 +++++++++++++++ docs/rake_tasks.md | 10 +++ docs/samples.md | 137 ++++++++++++++++++++++++++++++++++++++++++ docs/server.md | 34 +++++++++++ docs/server_cli.md | 18 ++++++ docs/stenographer.md | 20 ++++++ samples/rspec.rb | 0 12 files changed, 519 insertions(+) create mode 100644 docs/actors.md create mode 100644 docs/clerks.md create mode 100644 docs/configuration.md create mode 100644 docs/cops.md create mode 100644 docs/forensics.md create mode 100644 docs/generation.md create mode 100644 docs/rake_tasks.md create mode 100644 docs/samples.md create mode 100644 docs/server.md create mode 100644 docs/server_cli.md create mode 100644 docs/stenographer.md delete mode 100644 samples/rspec.rb diff --git a/docs/actors.md b/docs/actors.md new file mode 100644 index 0000000..8638575 --- /dev/null +++ b/docs/actors.md @@ -0,0 +1,58 @@ +Pacto uses actors stub **providers** or simulate **consumers** based on the contract. There +are two built-in actors. The FromExamples actor will produce requests or responses based on +the examples in the contract. The JSONGenerator actor will attempt to generate requests or +responses that match the JSON schema, though it only works for simple schemas. The FromExamples +actor is the default, but falls back to the JSONGenerator actor if there are no examples available. +Consider the following contract: + +```json +{ + "name": "Ping", + "request": { + "headers": { + }, + "http_method": "get", + "path": "/api/ping" + }, + "response": { + "headers": { + "Content-Type": "application/json" + }, + "status": 200, + "schema": { + "$schema": "http://json-schema.org/draft-03/schema#", + "type": "object", + "required": true, + "properties": { + "ping": { + "type": "string", + "required": true + } + } + } + }, + "examples": { + "default": { + "request": { + }, + "response": { + "body": { + "ping": "pong - from the example!" + } + } + } + } +} +``` +Then Pacto will generate the following response by default (via FromExamples): + +```json +{"ping":"pong - from the example!"} +``` + +If you didn't have an example, then Pacto generate very basic mock data based on the schema types, +producing something like: + +```json +{"ping":"bar"} +``` diff --git a/docs/clerks.md b/docs/clerks.md new file mode 100644 index 0000000..20a93f7 --- /dev/null +++ b/docs/clerks.md @@ -0,0 +1,40 @@ +Pacto clerks are responsible for loading contracts. Pacto has built-in support for a native +contract format, but we've setup a Clerks plugin API so you can more easily load information +from other formats, like [Swagger](https://github.com/wordnik/swagger-spec), +[apiblueprint](http://apiblueprint.org/), or [RAML](http://raml.org/). +Note: This is a preliminary API and may change in the future, including adding support +for conversion between formats, or generating each format from real HTTP interactions. +In order to add a loading clerk, you just implement a class that responds to build_from_file +and returns Contract object or a collection of Contract objects. + +```rb +require 'yaml' +require 'pacto' + +class SimpleYAMLClerk + def build_from_file(path, _host) + data = YAML.load(File.read(path)) + data['services'].map do | service_name, service_definition | + request_clause = Pacto::RequestClause.new service_definition['request'] + response_clause = Pacto::ResponseClause.new service_definition['response'] + Pacto::Contract.new(name: service_name, request: request_clause, response: response_clause) + end + end +end +``` + +You can then register the clerk with Pacto: + +```rb +Pacto.contract_factory.add_factory :simple_yaml, SimpleYAMLClerk.new +``` + +And then you can use it with the normal clerks API, by passing the identifier you used to register +the clerk: + +```rb +contracts = Pacto.load_contracts 'simple_service_map.yaml', 'http://example.com', :simple_yaml +contract_names = contracts.map(&:name) +puts "Defined contracts: #{contract_names}" +``` + diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..5e60a3a --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,39 @@ +Pacto will disable live connections, so you will get an error if +your code unexpectedly calls an service that was not stubbed. If you +want to re-enable connections, run `WebMock.allow_net_connect!` after +requiring pacto. + +```rb +require 'pacto' +WebMock.allow_net_connect! +``` + +Pacto can be configured via a block: + +```rb +Pacto.configure do |c| + c.contracts_path = 'contracts' # Path for loading/storing contracts. + c.strict_matchers = true # If the request matching should be strict (especially regarding HTTP Headers). + c.stenographer_log_file = nil # Set to nil to disable the stenographer log. +end +``` + +You can also do inline configuration. This example tells the +[json-schema-generator](https://github.com/maxlinc/json-schema-generator) to +store default values in the schema. + +```rb +Pacto.configuration.generator_options = { defaults: true } +``` + +All Pacto configuration and metrics can be reset ia `Pacto.clear!`. If you're using +RSpec you may want to clear between each scenario: +If you're using Pacto's rspec matchers you might want to configure a reset between each scenario + +```rb +require 'pacto/rspec' +RSpec.configure do |c| + c.after(:each) { Pacto.clear! } +end +``` + diff --git a/docs/cops.md b/docs/cops.md new file mode 100644 index 0000000..2e022ad --- /dev/null +++ b/docs/cops.md @@ -0,0 +1,50 @@ +You can create a custom cop that investigates the request/response and sees if it complies with a +contract. The cop should return a list of citations if it finds any problems. + +```rb +require 'pacto' +class MyCustomCop + def investigate(_request, _response, contract) + citations = [] + citations << 'Contract must have a request schema' if contract.request.schema.empty? + citations << 'Contract must have a response schema' if contract.response.schema.empty? + citations + end +end +``` + +You can activate the cop by adding it to the active_cops. The active_cops are reset +by `Pacto.clear!` + +```rb +Pacto::Cops.active_cops << MyCustomCop.new +``` + +Or you could add it as a registered cop. These cops are not cleared - they form the +default set of Cops used by Pacto: + +```rb +Pacto::Cops.register_cop MyCustomCop.new +``` + +The cops will be used to validate any service requests/responses detected by Pacto, +including when we simulate consumers: + +```rb +Pacto.validate! +contracts = Pacto.load_contracts('contracts', 'http://localhost:5000') +contracts.stub_providers +puts contracts.simulate_consumers +``` + +You could also completely reset the registered cops if you don't want to use +all of Pacto's built-in cops: + +```rb +Pacto::Cops.registered_cops.clear +Pacto::Cops.register_cop Pacto::Cops::ResponseBodyCop + +contracts = Pacto.load_contracts('contracts', 'http://localhost:5000') +puts contracts.simulate_consumers +``` + diff --git a/docs/forensics.md b/docs/forensics.md new file mode 100644 index 0000000..bc7f7e8 --- /dev/null +++ b/docs/forensics.md @@ -0,0 +1,65 @@ +Pacto has a few RSpec matchers to help you ensure a **consumer** and **producer** are +interacting properly. First, let's setup the rspec suite. + +```rb +require 'rspec/autorun' # Not generally needed +require 'pacto/rspec' +WebMock.allow_net_connect! +Pacto.validate! +Pacto.load_contracts('contracts', 'http://localhost:5000').stub_providers +``` + +It's usually a good idea to reset Pacto between each scenario. `Pacto.reset` just clears the +data and metrics about which services were called. `Pacto.clear!` also resets all configuration +and plugins. + +```rb +RSpec.configure do |c| + c.after(:each) { Pacto.reset } +end + +``` + +Pacto provides some RSpec matchers related to contract testing, like making sure +Pacto didn't received any unrecognized requests (`have_unmatched_requests`) and that +the HTTP requests matched up with the terms of the contract (`have_failed_investigations`). + +```rb +describe Faraday do + let(:connection) { Faraday.new(url: 'http://localhost:5000') } + + it 'passes contract tests' do + response = connection.get '/api/ping' + expect(Pacto).to_not have_failed_investigations + expect(Pacto).to_not have_unmatched_requests + end +end +``` + +There are also some matchers for collaboration testing, so you can make sure each scenario is +calling the expected services and sending the right type of data. + +```rb +describe Faraday do + let(:connection) { Faraday.new(url: 'http://localhost:5000') } + before(:each) do + response = connection.get '/api/ping' + + connection.post do |req| + req.url '/api/echo' + req.headers['Content-Type'] = 'application/json' + req.body = '{"foo": "bar"}' + end + end + + it 'calls the ping service' do + expect(Pacto).to have_validated(:get, 'http://localhost:5000/api/ping').against_contract('Ping') + end + + it 'sends data to the echo service' do + expect(Pacto).to have_investigated('Echo').with_request(body: hash_including('foo' => 'bar')) + expect(Pacto).to have_investigated('Echo').with_response(body: hash_including('foo' => 'bar')) + end +end +``` + diff --git a/docs/generation.md b/docs/generation.md new file mode 100644 index 0000000..7ec7ab4 --- /dev/null +++ b/docs/generation.md @@ -0,0 +1,48 @@ +Some generation related [configuration](configuration.rb). + +```rb +require 'pacto' +WebMock.allow_net_connect! +Pacto.configure do |c| + c.contracts_path = 'contracts' +end +WebMock.allow_net_connect! +``` + +Once we call `Pacto.generate!`, Pacto will record contracts for all requests it detects. + +```rb +Pacto.generate! +``` + +Now, if we run any code that makes an HTTP call (using an +[HTTP library supported by WebMock](https://github.com/bblimke/webmock#supported-http-libraries)) +then Pacto will generate a Contract based on the HTTP request/response. + +This code snippet will generate a Contract and save it to `contracts/samples/contracts/localhost/api/ping.json`. + +```rb +require 'faraday' +conn = Faraday.new(url: 'http://localhost:5000') +response = conn.get '/api/ping' +``` + +We're getting back real data from GitHub, so this should be the actual file encoding. + +```rb +puts response.body +``` + +The generated contract will contain expectations based on the request/response we observed, +including a best-guess at an appropriate json-schema. Our heuristics certainly aren't foolproof, +so you might want to customize schema! +Here's another sample that sends a post request. + +```rb +conn.post do |req| + req.url '/api/echo' + req.headers['Content-Type'] = 'application/json' + req.body = '{"red fish": "blue fish"}' +end +``` + diff --git a/docs/rake_tasks.md b/docs/rake_tasks.md new file mode 100644 index 0000000..7c6bedf --- /dev/null +++ b/docs/rake_tasks.md @@ -0,0 +1,10 @@ +# Rake tasks +## This is a test! +[That](www.google.com) markdown works + +```sh +bundle exec rake pacto:meta_validate['contracts'] + +bundle exec rake pacto:validate['http://localhost:5000','contracts'] +``` + diff --git a/docs/samples.md b/docs/samples.md new file mode 100644 index 0000000..2bee87e --- /dev/null +++ b/docs/samples.md @@ -0,0 +1,137 @@ +# Overview +Welcome to the Pacto usage samples! +This document gives a quick overview of the main features. + +You can browse the Table of Contents (upper right corner) to view additional samples. + +In addition to this document, here are some highlighted samples: +
    +
  • Configuration: Shows all available configuration options
  • +
  • Generation: More details on generation
  • +
  • RSpec: More samples for RSpec expectations
  • +
+You can also find other samples using the Table of Content (upper right corner), including sample contracts. +# Getting started +Once you've installed the Pacto gem, you just require it. If you want, you can also require the Pacto rspec expectations. + +```rb +require 'pacto' +require 'pacto/rspec' +``` + +Pacto will disable live connections, so you will get an error if +your code unexpectedly calls an service that was not stubbed. If you +want to re-enable connections, run `WebMock.allow_net_connect!` + +```rb +WebMock.allow_net_connect! +``` + +Pacto can be configured via a block. The `contracts_path` option tells Pacto where it should load or save contracts. See the [Configuration](configuration.html) for all the available options. + +```rb +Pacto.configure do |c| + c.contracts_path = 'contracts' +end +``` + +# Generating a Contract +Calling `Pacto.generate!` enables contract generation. + +```rb +Pacto.generate! +``` + +Now, if we run any code that makes an HTTP call (using an +[HTTP library supported by WebMock](https://github.com/bblimke/webmock#supported-http-libraries)) +then Pacto will generate a Contract based on the HTTP request/response. + +We're using the sample APIs in the sample_apis directory. + +```rb +require 'faraday' +conn = Faraday.new(url: 'http://localhost:5000') +response = conn.get '/api/ping' +``` + +This is the real request, so you should see {"ping":"pong"} + +```rb +puts response.body +``` + +# Testing providers by simulating consumers +The generated contract will contain expectations based on the request/response we observed, +including a best-guess at an appropriate json-schema. Our heuristics certainly aren't foolproof, +so you might want to modify the output! +We can load the contract and validate it, by sending a new request and making sure +the response matches the JSON schema. Obviously it will pass since we just recorded it, +but if the service has made a change, or if you alter the contract with new expectations, +then you will see a contract investigation message. + +```rb +contracts = Pacto.load_contracts('contracts', 'http://localhost:5000') +contracts.simulate_consumers +``` + +# Stubbing providers for consumer testing +We can also use Pacto to stub the service based on the contract. + +```rb +contracts.stub_providers +``` + +The stubbed data won't be very realistic, the default behavior is to return the simplest data +that complies with the schema. That basically means that you'll have "bar" for every string. + +```rb +response = conn.get '/api/ping' +``` + +You're now getting stubbed data. You should see {"ping":"bar"} unless you recorded with +the `defaults` option enabled, in which case you will still seee {"ping":"pong"}. + +```rb +puts response.body +``` + +# Collaboration tests with RSpec +Pacto comes with rspec matchers + +```rb +require 'pacto/rspec' +``` + +It's probably a good idea to reset Pacto between each rspec scenario + +```rb +RSpec.configure do |c| + c.after(:each) { Pacto.clear! } +end +``` + +Load your contracts, and stub them if you'd like. + +```rb +Pacto.load_contracts('contracts', 'http://localhost:5000').stub_providers +``` + +You can turn on investigation mode so Pacto will detect and validate HTTP requests. + +```rb +Pacto.validate! + +describe 'my_code' do + it 'calls a service' do + conn = Faraday.new(url: 'http://localhost:5000') + response = conn.get '/api/ping' +``` + +The have_validated matcher makes sure that Pacto received and successfully validated a request + +```rb + expect(Pacto).to have_validated(:get, 'http://localhost:5000/api/ping') + end +end +``` + diff --git a/docs/server.md b/docs/server.md new file mode 100644 index 0000000..db4a824 --- /dev/null +++ b/docs/server.md @@ -0,0 +1,34 @@ + +```rb +require 'pacto/rspec' +require 'pacto/test_helper' + +describe 'ping service' do + include Pacto::TestHelper + + it 'pongs' do + with_pacto( + port: 6000, + backend_host: 'http://localhost:5000', + live: true, + stub: false, + generate: false, + directory: 'contracts' + ) do |pacto_endpoint| +``` + +call your code + +```rb + system "curl #{pacto_endpoint}/api/ping" + end +``` + +check citations + +```rb + expect(Pacto).to have_validated(:get, 'http://localhost:5000/api/ping') + end +end +``` + diff --git a/docs/server_cli.md b/docs/server_cli.md new file mode 100644 index 0000000..42d3cb5 --- /dev/null +++ b/docs/server_cli.md @@ -0,0 +1,18 @@ +# Standalone server +You can run Pacto as a server in order to test non-Ruby projects. In order to get the full set +of options, run: + +```sh +bundle exec pacto-server -h +``` + +You probably want to run with the -sv option, which will display verbose output to stdout. You can +run server that proxies to a live endpoint: + +```sh +bundle exec pacto-server -sv --port 9000 --live http://example.com & +bundle exec pacto-server -sv --port 9001 --stub & + +pkill -f pacto-server +``` + diff --git a/docs/stenographer.md b/docs/stenographer.md new file mode 100644 index 0000000..28e1b3c --- /dev/null +++ b/docs/stenographer.md @@ -0,0 +1,20 @@ + +```rb +require 'pacto' +Pacto.configure do |c| + c.contracts_path = 'contracts' +end +contracts = Pacto.load_contracts('contracts', 'http://localhost:5000') +contracts.stub_providers + +Pacto.simulate_consumer do + request 'Echo', values: nil, response: { status: 200 } # 0 contract violations + request 'Ping', values: nil, response: { status: 200 } # 0 contract violations + request 'Unknown (http://localhost:8000/404)', values: nil, response: { status: 500 } # 0 contract violations +end + +Pacto.simulate_consumer :my_consumer do + playback 'pacto_stenographer.log' +end +``` + diff --git a/samples/rspec.rb b/samples/rspec.rb deleted file mode 100644 index e69de29..0000000 From 98e4266af03068666f41f23551b578f6fd5ef380 Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Tue, 8 Jul 2014 10:11:01 -0400 Subject: [PATCH 24/26] Don't document have_investigated matcher... yet --- .rubocop.yml | 3 +++ docs/forensics.md | 9 ++++----- samples/forensics.rb | 9 ++++----- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index e8d9080..1e991b8 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -27,3 +27,6 @@ RSpec/DescribeClass: Exclude: - samples/**/* - spec/integration/**/* +RSpec/MultipleDescribes: + Exclude: + - samples/**/* diff --git a/docs/forensics.md b/docs/forensics.md index bc7f7e8..b0267fe 100644 --- a/docs/forensics.md +++ b/docs/forensics.md @@ -17,7 +17,6 @@ and plugins. RSpec.configure do |c| c.after(:each) { Pacto.reset } end - ``` Pacto provides some RSpec matchers related to contract testing, like making sure @@ -26,10 +25,10 @@ the HTTP requests matched up with the terms of the contract (`have_failed_invest ```rb describe Faraday do - let(:connection) { Faraday.new(url: 'http://localhost:5000') } + let(:connection) { described_class.new(url: 'http://localhost:5000') } it 'passes contract tests' do - response = connection.get '/api/ping' + connection.get '/api/ping' expect(Pacto).to_not have_failed_investigations expect(Pacto).to_not have_unmatched_requests end @@ -41,9 +40,9 @@ calling the expected services and sending the right type of data. ```rb describe Faraday do - let(:connection) { Faraday.new(url: 'http://localhost:5000') } + let(:connection) { described_class.new(url: 'http://localhost:5000') } before(:each) do - response = connection.get '/api/ping' + connection.get '/api/ping' connection.post do |req| req.url '/api/echo' diff --git a/samples/forensics.rb b/samples/forensics.rb index 6749ad2..8da65ca 100644 --- a/samples/forensics.rb +++ b/samples/forensics.rb @@ -13,15 +13,14 @@ c.after(:each) { Pacto.reset } end - # Pacto provides some RSpec matchers related to contract testing, like making sure # Pacto didn't received any unrecognized requests (`have_unmatched_requests`) and that # the HTTP requests matched up with the terms of the contract (`have_failed_investigations`). describe Faraday do - let(:connection) { Faraday.new(url: 'http://localhost:5000') } + let(:connection) { described_class.new(url: 'http://localhost:5000') } it 'passes contract tests' do - response = connection.get '/api/ping' + connection.get '/api/ping' expect(Pacto).to_not have_failed_investigations expect(Pacto).to_not have_unmatched_requests end @@ -30,9 +29,9 @@ # There are also some matchers for collaboration testing, so you can make sure each scenario is # calling the expected services and sending the right type of data. describe Faraday do - let(:connection) { Faraday.new(url: 'http://localhost:5000') } + let(:connection) { described_class.new(url: 'http://localhost:5000') } before(:each) do - response = connection.get '/api/ping' + connection.get '/api/ping' connection.post do |req| req.url '/api/echo' From c7d6a3289f7bc7231c0548cbcca1714e585f7a0e Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Tue, 8 Jul 2014 11:06:58 -0400 Subject: [PATCH 25/26] Separate README.md (project overview) from docs/index.md (detailed usage overview). Use GitHub friendly URLs. --- README.md | 257 +++++++++++++++++++++++++++++++++++--------------- docs/index.md | 147 ++++++++++++++++++++++++++++- 2 files changed, 328 insertions(+), 76 deletions(-) mode change 120000 => 100644 docs/index.md diff --git a/README.md b/README.md index eb49b01..30a4edb 100644 --- a/README.md +++ b/README.md @@ -10,137 +10,244 @@ you're reading the documentation for the master branch. (0.3.0).](https://github.com/thoughtworks/pacto/tree/v0.3.0)** # Pacto -## Who is Pacto? -Pacto is a judge that arbitrates contract disputes between a **service provider** and one or more **consumers**. It is a framework for [Integration Contract Testing](http://martinfowler.com/bliki/IntegrationContractTest.html), and 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/). +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/). -## The litigants +Pacto considers two major terms in order decide if there has been a breach of contract: the **request clause** and the **response clause**. -Pacto helps settle disputes between **service providers** and **service consumers** of RESTful JSON services. The **provider** is the one that implements the service, which may be used by multiple **consumers**. This is done by [Integration Contract Testing](http://martinfowler.com/bliki/IntegrationContractTest.html), where the contract stays the same but the provider changes. +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. -## Litigators +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/)). -Someone needs to accuse the **providers** or **consumers** of wrongdoing! Pacto integrates with a few different test frameworks to give you options: +## Test Doubles -- Pacto easily integrates with [RSpec](http://rspec.info/), including some [custom matchers](#forensics). -- Pacto provides some [simple rake tasks](rake_tasks/) to run some basic tests from the command line. -- If you're testing non-Ruby projects, you can use the [Pacto Server](server) as a proxy to intercept and validate requests. You can also use it in conjunction with [Polytrix](https://github.com/rackerlabs/polytrix). +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. -## Contracts +Pacto can provide a [**test double**](#stubbing) if you cannot afford your own. -Pacto considers two major terms in order decide if there has been a breach of contract: the **request clause** and the **response clause**. +## Due Diligence -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. +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. -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/)). +Pacto can provide a [**contract writer**](#generating) that tries to strike a balance, but you may still need to adjust the terms. + +## Implied Terms -See the [Contracts documentation](contracts/) for more details. +- 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. -## Enforcement +## Usage -### Cops -**Cops** help Pacto investigate interactions between **consumers** and **providers** and determine if either has violated the contract. +**See also: http://thoughtworks.github.io/pacto/usage/** -Pacto has a few built-in cops that are on-duty by default. These cops will: -- Ensure the request body matches the contract requirements (if a request body is needed) -- Ensure the response headers match the contract requirements -- Ensure the response HTTP status matches the contract requirements -- Ensure the response body matches the contract requirements +Pacto can perform three activities: generating, validating, or stubbing services. You can do each of these activities against either live or stubbed services. -### Forensics +You can also use [Pacto Server](#pacto-server-non-ruby-usage) if you are working with non-Ruby projects. -Sometimes it looks like you're following a contract, but digital forensics reveals there's some fraud going on. Pacto provides RSpec matchers to help you catch these patterns that let you do the [collaboration tests](http://programmers.stackexchange.com/questions/135011/removing-the-integration-test-scam-understanding-collaboration-and-contract) that integration contract testing alone would not catch. +### Configuration -See the [forensics documetation](forensics/) for more details. +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: -### Sting operations +```ruby +require 'pacto' -**Note: this is a preview of an upcoming feature. It is not currently available.** +Pacto.configure do |config| + config.contracts_path = 'contracts' +end +``` -Pacto **cops** merely observe the interactions between a **consumer** and **provider** and look for problems. A Pacto **sting operation** will alter the interaction in order to try an find problems. +### Generating -For example, HTTP header field names are supposed to be case-insensitive [according to RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1](http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2), but many implementations are tightly coupled to a certain server or implementation and assume header field names have a certain case, like "Content-Type" and not "content-type". Pacto can change the alter the character case of the field names in order to catch consumers or providers that are not following this part of the RFC. +The easiest way to get started with Pacto is to run a suite of live tests and tell Pacto to generate the contracts: -Another possible sting operation is to introduce network lag, dropped connections, simulate HTTP rate limiting errors, or other issues that a robust consumer are expected to handle. +```ruby +Pacto.generate! +# run your tests +``` -You can also add your own custom cops to extend Pacto's abilities or to ensure services are standards that are specific to your organization. See the [Cops documentation](cops/) for more details. +If you're using the same configuration as above, this will produce Contracts in the contracts/ folder. -## Inside the courtroom +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. -### Actors +### Contract Lists -It's not always practical to test using a **real consumer** and or a **real provider**. Pacto can both **stub providers** and **simulate consumers** so you can test their counterpart in isolation. This makes [Consumer-Driven Contracts](http://thoughtworks.github.io/pacto/patterns/cdc/) easier. You can start testing a consumer against a stubbed provider before the real provider is available, and then hand your contracts and tests over to the team that is going to implement the provider so they can ensure it matches your assumptions. +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. -See the [Actors documentation](actors/) for more details. +```ruby +require 'pacto' -### The courtroom reporter +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') +``` -**Note: this is a preview of an upcoming feature. It is not currently available.** +### Validating -Pacto can keep track of which services have been called. If you're a consumer with contracts for a set of services you consumer, this helps you figure out "HTTP Service Coverage", which is quite different from code coverage, so you can see if there's any services you forgot to test, or contracts you're still registering with Pacto even though you no longer use those services. +Once you have a ContractList, you can validate all the contracts against the live host. -If you are a provider that is being used by multiple consumers, you could merge coverage reports from each of them to see which services are being used by each consumer. Once this feature is available, you should be able to create reports that look something like this: +```ruby +contracts = Pacto.load_contracts('contracts/services', 'http://example.com') +contracts.simulate_consumers +``` -![Courtroom Report](https://cloud.githubusercontent.com/assets/896878/2707078/21b0245e-c49d-11e3-8b4e-fa695aa56c4d.png) +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. -### The Stenographer +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. -The stenographer keeps a short-hand record of everything that happens in the courtroom. These logs can be used for troubleshooting, creating reports, or re-enacting the courtroom activities at a later date (and with different [actors](#actors). +### Stubbing -The stenographer's logs are similar to HTTP access logs in this respect, but in a format that's more compact and suited to Pacto. A typical HTTP access log might look like this: +To generate stubs based on a ContractList you can run: +```ruby +contracts = Pacto.load_contracts('contracts/services', 'http://example.com') +contracts.stub_providers ``` -#Fields: date time c-ip cs-username s-ip s-port cs-method cs-uri-stem cs-uri-query sc-status cs(User-Agent) -2014-07-01 17:42:15 127.0.0.1 - 127.0.0.1 80 PUT /store/album/123/cover_art - 201 curl/7.30.0 -2014-07-01 17:42:18 127.0.0.1 - 127.0.0.1 80 GET /store/album/123/cover_art size=small 200 curl/7.30.0 -``` -If Pacto has the following services registered: -![routes.png](https://draftin.com:443/images/16800?token=8Z2bmsbxOQ74ogeeWTXBwyvVaJ0YBfNJCfQTHa08L3AnvQXsnIf40htwMVudSIugAmpeJp8MD53mN7FPzfgqG9o) +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_providers method. This +will override the keys for every contract in the list -Then it can match those requests to request and generate a stenographer log that looks like this: ```ruby -request 'Upload Album Art', values: {album_id: '123'}, response: {status: 201} # no contract violations -request 'Download Album Art', values: {album_id: '123', size: 'small'}, response: {status: 200} # no contract violations +contracts = Pacto.load_contracts('contracts/services', 'http://example.com') +contracts.stub_providers(request_id: 14, name: "Marcos") +``` + +## Pacto Server (non-Ruby usage) + +**See also: http://thoughtworks.github.io/pacto/patterns/polyglot/** + +We've bundled a small server that embeds pacto so you can use it for non-Ruby projects. If you want to take advantage of the full features, you'll still need to use Ruby (usually rspec) to drive your API testing. You can run just the server in order to stub or to write validation issues to a log, but you won't have access to the full API fail your tests if there were validation problems. + +### Command-line + +The command-line version of the server will show you all the options. These same options are used when you launch the server from within rspec. In order to see the options: +`bundle exec pacto-server --help` + +Some examples: +```sh +$ bundle exec pacto-server -sv --generate +# Runs a server that will generate Contracts for each request received +$ bundle exec pacto-server -sv --stub --validate +# Runs the server that provides stubs and checks them against Contracts +$ bundle exec pacto-server -sv --live --validate --host +# Runs the server that acts as a proxy to http://example.com, validating each request/response against a Contract ``` -This log file is designed so Pacto it can be used by Pacto to simulate the requests: +### RSpec test helper + +You can also launch a server from within an rspec test. The server does start up an external port and runs asynchronously so it doens't block your main test thread from kicking off your consumer code. This gives you an externally accessable server that non-Ruby clients can hit, but still gives you the full Pacto API in order to validate and spy on HTTP interactions. + +Example usage of the rspec test helper: ```ruby -Pacto.simulate_consumer :my_client do - request 'Upload Album Art', values: {album_id: '123'}, response: {status: 201} # no contract violations - request 'Download Album Art', values: {album_id: '123', size: 'small'}, response: {status: 200} # no contract violations +require 'rspec' +require 'pacto/rspec' +require 'pacto/test_helper' + +describe 'my consumer' do + include Pacto::TestHelper + + it 'calls a service' do + with_pacto( + :port => 5000, + :directory => '../spec/integration/data', + :stub => true) do |pacto_endpoint| + # call your code + system "curl #{pacto_endpoint}/echo" + # check results + expect(Pacto).to have_validated(:get, 'https://localhost/echo') + end + end end ``` -Since Pacto has added a layer of abstraction you can experiment with changes to the contracts (including routing) without needing to re-record the interactions with the stenographer. For example Pacto will adjust if you change the route from: -![original_put.png](https://draftin.com:443/images/16801?token=6yI7VugUqaLJAMpMvf4oAlPucfQBnfdrdcGpcCuUFET_FH5E0ZreFIrL1C7U2GwRuNndntc9OTIXLD-B2wbkiyg) -to -![new_put.png](https://draftin.com:443/images/16802?token=IagQX2ggHIaaCfRGhR5q85cK9oNN6OgFX2yc9aT0CTZkGzxUMXB1nR40mwIYRat7dUeWPOmLebNOHWXOKlpe-iU) +## Rake Tasks -### Clerks +Pacto includes a few Rake tasks to help with common tasks. If you want to use these tasks, just add this top your Rakefile: -Clerks help Pacto with paperwork. Reading and writing legalese is hard. Pacto clerks help create and load contracts. Currently clerks are responsible for: +```ruby +require 'pacto/rake_task' +``` -- Generating contracts from real HTTP requests -- Basic support for loading from custom formats +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 +``` -In the future, we plan for clerks to provide more complete support for: -- Converting from or loading other similar contract formats (e.g. [Swagger](https://github.com/wordnik/swagger-spec), [apiblueprint](http://apiblueprint.org/), or [RAML](http://raml.org/). -- Upgrading contracts from older Pacto versions +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. -See the [contract generation](generation/) and [clerks](clerks/) documentation for more info. +The pacto:meta_validate task makes sure that your Contracts are valid. It only checks the Contracts, not the services that implement them. -# Implied Terms +The pacto:validate task sends a request to an actual provider and ensures their response complies with the Contract. -- 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. +## 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). + +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. + +Below is an example contract for a GET request +to the /hello_world endpoint of a provider: + +```json +{ + "request": { + "method": "GET", + "path": "/hello_world", + "headers": { + "Accept": "application/json" + }, + "params": {} + }, + + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "body": { + "description": "A simple response", + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } +} +``` -# Roadmap +## Constraints -See the [Pacto Roadmap](https://github.com/thoughtworks/pacto/wiki/Pacto-Roadmap) +- 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) -# Contributing +## Contributing -See [CONTRIBUTING.md](https://github.com/thoughtworks/pacto/blob/master/CONTRIBUTING.md) +Read the [CONTRIBUTING.md](CONTRIBUTING.md) file. diff --git a/docs/index.md b/docs/index.md deleted file mode 120000 index 32d46ee..0000000 --- a/docs/index.md +++ /dev/null @@ -1 +0,0 @@ -../README.md \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..313570f --- /dev/null +++ b/docs/index.md @@ -0,0 +1,146 @@ +[![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.3.0).](https://github.com/thoughtworks/pacto/tree/v0.3.0)** + +# Pacto +## Who is Pacto? + +Pacto is a judge that arbitrates contract disputes between a **service provider** and one or more **consumers**. It is a framework for [Integration Contract Testing](http://martinfowler.com/bliki/IntegrationContractTest.html), and 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/). + +## The litigants + +Pacto helps settle disputes between **service providers** and **service consumers** of RESTful JSON services. The **provider** is the one that implements the service, which may be used by multiple **consumers**. This is done by [Integration Contract Testing](http://martinfowler.com/bliki/IntegrationContractTest.html), where the contract stays the same but the provider changes. + +## Litigators + +Someone needs to accuse the **providers** or **consumers** of wrongdoing! Pacto integrates with a few different test frameworks to give you options: + +- Pacto easily integrates with [RSpec](http://rspec.info/), including some [custom matchers](#forensics). +- Pacto provides some [simple rake tasks](rake_tasks.md) to run some basic tests from the command line. +- If you're testing non-Ruby projects, you can use the [Pacto Server](server.md) as a proxy to intercept and validate requests. You can also use it in conjunction with [Polytrix](https://github.com/rackerlabs/polytrix). + +## Contracts + +Pacto considers two major terms in order decide if there has been a breach of contract: the **request clause** and the **response clause**. + +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. + +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/)). + +See the [Contracts documentation](contracts.md) for more details. + + + +## Enforcement + +### Cops +**Cops** help Pacto investigate interactions between **consumers** and **providers** and determine if either has violated the contract. + +Pacto has a few built-in cops that are on-duty by default. These cops will: +- Ensure the request body matches the contract requirements (if a request body is needed) +- Ensure the response headers match the contract requirements +- Ensure the response HTTP status matches the contract requirements +- Ensure the response body matches the contract requirements + +### Forensics + +Sometimes it looks like you're following a contract, but digital forensics reveals there's some fraud going on. Pacto provides RSpec matchers to help you catch these patterns that let you do the [collaboration tests](http://programmers.stackexchange.com/questions/135011/removing-the-integration-test-scam-understanding-collaboration-and-contract) that integration contract testing alone would not catch. + +See the [forensics documetation](forensics.md) for more details. + +### Sting operations + +**Note: this is a preview of an upcoming feature. It is not currently available.** + +Pacto **cops** merely observe the interactions between a **consumer** and **provider** and look for problems. A Pacto **sting operation** will alter the interaction in order to try an find problems. + +For example, HTTP header field names are supposed to be case-insensitive [according to RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1](http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2), but many implementations are tightly coupled to a certain server or implementation and assume header field names have a certain case, like "Content-Type" and not "content-type". Pacto can change the alter the character case of the field names in order to catch consumers or providers that are not following this part of the RFC. + +Another possible sting operation is to introduce network lag, dropped connections, simulate HTTP rate limiting errors, or other issues that a robust consumer are expected to handle. + +You can also add your own custom cops to extend Pacto's abilities or to ensure services are standards that are specific to your organization. See the [Cops documentation](cops.md) for more details. + +## Inside the courtroom + +### Actors + +It's not always practical to test using a **real consumer** and or a **real provider**. Pacto can both **stub providers** and **simulate consumers** so you can test their counterpart in isolation. This makes [Consumer-Driven Contracts](http://thoughtworks.github.io/pacto/patterns/cdc/) easier. You can start testing a consumer against a stubbed provider before the real provider is available, and then hand your contracts and tests over to the team that is going to implement the provider so they can ensure it matches your assumptions. + +See the [Actors documentation](actors.md) for more details. + +### The courtroom reporter + +**Note: this is a preview of an upcoming feature. It is not currently available.** + +Pacto can keep track of which services have been called. If you're a consumer with contracts for a set of services you consumer, this helps you figure out "HTTP Service Coverage", which is quite different from code coverage, so you can see if there's any services you forgot to test, or contracts you're still registering with Pacto even though you no longer use those services. + +If you are a provider that is being used by multiple consumers, you could merge coverage reports from each of them to see which services are being used by each consumer. Once this feature is available, you should be able to create reports that look something like this: + +![Courtroom Report](https://cloud.githubusercontent.com/assets/896878/2707078/21b0245e-c49d-11e3-8b4e-fa695aa56c4d.png) + +### The Stenographer + +The stenographer keeps a short-hand record of everything that happens in the courtroom. These logs can be used for troubleshooting, creating reports, or re-enacting the courtroom activities at a later date (and with different [actors](#actors). + +The stenographer's logs are similar to HTTP access logs in this respect, but in a format that's more compact and suited to Pacto. A typical HTTP access log might look like this: + +``` +#Fields: date time c-ip cs-username s-ip s-port cs-method cs-uri-stem cs-uri-query sc-status cs(User-Agent) +2014-07-01 17:42:15 127.0.0.1 - 127.0.0.1 80 PUT /store/album/123/cover_art - 201 curl/7.30.0 +2014-07-01 17:42:18 127.0.0.1 - 127.0.0.1 80 GET /store/album/123/cover_art size=small 200 curl/7.30.0 +``` + +If Pacto has the following services registered: +![routes.png](https://draftin.com:443/images/16800?token=8Z2bmsbxOQ74ogeeWTXBwyvVaJ0YBfNJCfQTHa08L3AnvQXsnIf40htwMVudSIugAmpeJp8MD53mN7FPzfgqG9o) + +Then it can match those requests to request and generate a stenographer log that looks like this: +```ruby +request 'Upload Album Art', values: {album_id: '123'}, response: {status: 201} # no contract violations +request 'Download Album Art', values: {album_id: '123', size: 'small'}, response: {status: 200} # no contract violations +``` + +This log file is designed so Pacto it can be used by Pacto to simulate the requests: +```ruby +Pacto.simulate_consumer :my_client do + request 'Upload Album Art', values: {album_id: '123'}, response: {status: 201} # no contract violations + request 'Download Album Art', values: {album_id: '123', size: 'small'}, response: {status: 200} # no contract violations +end +``` + +Since Pacto has added a layer of abstraction you can experiment with changes to the contracts (including routing) without needing to re-record the interactions with the stenographer. For example Pacto will adjust if you change the route from: +![original_put.png](https://draftin.com:443/images/16801?token=6yI7VugUqaLJAMpMvf4oAlPucfQBnfdrdcGpcCuUFET_FH5E0ZreFIrL1C7U2GwRuNndntc9OTIXLD-B2wbkiyg) +to +![new_put.png](https://draftin.com:443/images/16802?token=IagQX2ggHIaaCfRGhR5q85cK9oNN6OgFX2yc9aT0CTZkGzxUMXB1nR40mwIYRat7dUeWPOmLebNOHWXOKlpe-iU) + +### Clerks + +Clerks help Pacto with paperwork. Reading and writing legalese is hard. Pacto clerks help create and load contracts. Currently clerks are responsible for: + +- Generating contracts from real HTTP requests +- Basic support for loading from custom formats + +In the future, we plan for clerks to provide more complete support for: +- Converting from or loading other similar contract formats (e.g. [Swagger](https://github.com/wordnik/swagger-spec), [apiblueprint](http://apiblueprint.org/), or [RAML](http://raml.org/). +- Upgrading contracts from older Pacto versions + +See the [contract generation](generation.md) and [clerks](clerks.md) documentation for more info. + +# 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 + +See the [Pacto Roadmap](https://github.com/thoughtworks/pacto/wiki/Pacto-Roadmap) + +# Contributing + +See [CONTRIBUTING.md](https://github.com/thoughtworks/pacto/blob/master/CONTRIBUTING.md) From ef436c6fb65674db50a95204200ec92710bb0976 Mon Sep 17 00:00:00 2001 From: Max Lincoln Date: Tue, 8 Jul 2014 13:28:24 -0400 Subject: [PATCH 26/26] Generate contract doc --- Rakefile | 1 + docs/contracts.md | 109 ++++++++++++++++++ .../{contracts/contract.js => contracts.json} | 6 +- 3 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 docs/contracts.md rename samples/{contracts/contract.js => contracts.json} (98%) diff --git a/Rakefile b/Rakefile index fc5bd75..ae15e73 100644 --- a/Rakefile +++ b/Rakefile @@ -37,6 +37,7 @@ desc 'Run the samples' task :samples do FileUtils.rm_rf('samples/tmp') sh 'bundle exec polytrix exec --code2doc samples/*.rb samples/*.sh' + sh 'bundle exec polytrix exec --code2doc samples/*.json --lang js' end desc 'Build the documentation from the samples' diff --git a/docs/contracts.md b/docs/contracts.md new file mode 100644 index 0000000..7843ce5 --- /dev/null +++ b/docs/contracts.md @@ -0,0 +1,109 @@ +Pacto Contracts describe the constraints we want to put on interactions between a consumer and a provider. It sets some expectations about the headers expected for both the request and response, the expected response status code. It also uses [json-schema](http://json-schema.org/) to define the allowable request body (if one should exist) and response body. + +```js +{ +``` + +The Request section comes first. In this case, we're just describing a simple get request that does not require any parameters or a request body. + +```js + "request": { + "headers": { +``` + +A request must exactly match these headers for Pacto to believe the request matches the contract, unless `Pacto.configuration.strict_matchers` is false. + +```js + "Accept": "application/vnd.github.beta+json", + "Accept-Encoding": "gzip;q=1.0,deflate;q=0.6,identity;q=0.3" + }, +``` + +The `method` and `path` are required. The `path` may be an [rfc6570 URI template](http://tools.ietf.org/html/rfc6570) for more flexible matching. + +```js + "http_method": "get", + "path": "/repos/thoughtworks/pacto/readme" + }, + "response": { + "headers": { + "Content-Type": "application/json; charset=utf-8", + "Status": "200 OK", + "Cache-Control": "public, max-age=60, s-maxage=60", + "Etag": "\"fc8e78b0a9694de66d47317768b20820\"", + "Vary": "Accept, Accept-Encoding", + "Access-Control-Allow-Credentials": "true", + "Access-Control-Expose-Headers": "ETag, Link, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval", + "Access-Control-Allow-Origin": "*" + }, + "status": 200, + "schema": { + "$schema": "http://json-schema.org/draft-03/schema#", + "description": "Generated from https://api.github.com/repos/thoughtworks/pacto/readme with shasum 3ae59164c6d9f84c0a81f21fb63e17b3b8ce6894", + "type": "object", + "required": true, + "properties": { + "name": { + "type": "string", + "required": true + }, + "path": { + "type": "string", + "required": true + }, + "sha": { + "type": "string", + "required": true + }, + "size": { + "type": "integer", + "required": true + }, + "url": { + "type": "string", + "required": true + }, + "html_url": { + "type": "string", + "required": true + }, + "git_url": { + "type": "string", + "required": true + }, + "type": { + "type": "string", + "required": true + }, + "content": { + "type": "string", + "required": true + }, + "encoding": { + "type": "string", + "required": true + }, + "_links": { + "type": "object", + "required": true, + "properties": { + "self": { + "type": "string", + "required": true + }, + "git": { + "type": "string", + "required": true + }, + "html": { + "type": "string", + "required": true + } + } + } + } + } + } +} +``` + diff --git a/samples/contracts/contract.js b/samples/contracts.json similarity index 98% rename from samples/contracts/contract.js rename to samples/contracts.json index ca19325..a87c1ea 100644 --- a/samples/contracts/contract.js +++ b/samples/contracts.json @@ -8,7 +8,7 @@ "Accept-Encoding": "gzip;q=1.0,deflate;q=0.6,identity;q=0.3" }, // The `method` and `path` are required. The `path` may be an [rfc6570 URI template](http://tools.ietf.org/html/rfc6570) for more flexible matching. - "method": "get", + "http_method": "get", "path": "/repos/thoughtworks/pacto/readme" }, "response": { @@ -23,7 +23,7 @@ "Access-Control-Allow-Origin": "*" }, "status": 200, - "body": { + "schema": { "$schema": "http://json-schema.org/draft-03/schema#", "description": "Generated from https://api.github.com/repos/thoughtworks/pacto/readme with shasum 3ae59164c6d9f84c0a81f21fb63e17b3b8ce6894", "type": "object", @@ -90,4 +90,4 @@ } } } -} \ No newline at end of file +}