Skip to content

Commit

Permalink
Merge pull request #4 from pact-foundation/feature/publish-from-tag
Browse files Browse the repository at this point in the history
verify pacts from a broker with tags
  • Loading branch information
mefellows authored Jul 1, 2016
2 parents 4e1539b + deb83ef commit f6e39b2
Show file tree
Hide file tree
Showing 12 changed files with 669 additions and 75 deletions.
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,55 @@ Read more about [flexible matching](https://github.com/realestate-com-au/pact/wi
See the `Skip()'ed` [integration tests](https://github.com/pact-foundation/pact-go/blob/master/dsl/pact_test.go)
for a more complete E2E example.

#### Provider Verification

When validating a Provider, you have 3 options to provide the Pact files:

1. Use `PactURLs` to specify the exact set of pacts to be replayed:

```go
response = pact.VerifyProvider(&types.VerifyRequest{
ProviderBaseURL: "http://myproviderhost",
PactURLs: []string{"http://broker/pacts/provider/them/consumer/me/latest/dev"},
ProviderStatesURL: "http://myproviderhost/states",
ProviderStatesSetupURL: "http://myproviderhost/setup",
BrokerUsername: os.Getenv("PACT_BROKER_USERNAME"),
BrokerPassword: os.Getenv("PACT_BROKER_PASSWORD"),
})
```
1. Use `PactBroker` to automatically find all of the latest consumers:

```go
response = pact.VerifyProvider(&types.VerifyRequest{
ProviderBaseURL: "http://myproviderhost",
BrokerURL: "http://brokerHost",
ProviderStatesURL: "http://myproviderhost/states",
ProviderStatesSetupURL: "http://myproviderhost/setup",
BrokerUsername: os.Getenv("PACT_BROKER_USERNAME"),
BrokerPassword: os.Getenv("PACT_BROKER_PASSWORD"),
})
```
1. Use `PactBroker` and `Tags` to automatically find all of the latest consumers:

```go
response = pact.VerifyProvider(&types.VerifyRequest{
ProviderBaseURL: "http://myproviderhost",
BrokerURL: "http://brokerHost",
Tags: []string{"latest", "sit4"},
ProviderStatesURL: "http://myproviderhost/states",
ProviderStatesSetupURL: "http://myproviderhost/setup",
BrokerUsername: os.Getenv("PACT_BROKER_USERNAME"),
BrokerPassword: os.Getenv("PACT_BROKER_PASSWORD"),
})
```

Options 2 and 3 are particularly useful when you want to validate that your
Provider is able to meet the contracts of what's in Production and also the latest
in development.

See this [article](http://rea.tech/enter-the-pact-matrix-or-how-to-decouple-the-release-cycles-of-your-microservices/)
for more on this strategy.

#### Provider States

Each interaction in a pact should be verified in isolation, with no context
Expand Down
109 changes: 77 additions & 32 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Pact Go enables consumer driven contract testing, providing a mock service and
DSL for the consumer project, and interaction playback and verification
for the service provider project.
Consumer tests
Consumer Tests
Consumer side Pact testing is an isolated test that ensures a given component
is able to collaborate with another (remote) component. Pact will automatically
Expand Down Expand Up @@ -61,37 +61,7 @@ If this test completed successfully, a Pact file should have been written to
./pacts/my_consumer-my_provider.json containing all of the interactions
expected to occur between the Consumer and Provider.
Provider tests
Provider side Pact testing, involves verifying that the contract - the Pact file
- can be satisfied by the Provider.
A typical Provider side test would like something like:
func TestProvider_PactContract(t *testing.T) {
go startMyAPI("http://localhost:8000")
response := pact.VerifyProvider(&types.VerifyRequest{
ProviderBaseURL: "http://localhost:8000",
PactURLs: []string{"./pacts/my_consumer-my_provider.json"},
ProviderStatesURL: "http://localhost:8000/states",
ProviderStatesSetupURL: "http://localhost:8000/setup",
})
if response.ExitCode != 0 {
t.Fatalf("Got non-zero exit code '%d', expected 0", response.ExitCode)
}
}
Note that `PactURLs` can be a list of local pact files or remote based
urls (possibly from a Pact Broker
- http://docs.pact.io/documentation/sharings_pacts.html).
Pact reads the specified pact files (from remote or local sources) and replays
the interactions against a running Provider. If all of the interactions are met
we can say that both sides of the contract are satisfied and the test passes.
Matching - Consumer Tests
Matching
In addition to verbatim value matching, you have 3 useful matching functions
in the `dsl` package that can increase expressiveness and reduce brittle test
Expand Down Expand Up @@ -151,6 +121,81 @@ NOTE: You will need to use valid Ruby regular expressions
Read more about flexible matching (https://github.com/realestate-com-au/pact/wiki/Regular-expressions-and-type-matching-with-Pact.
Provider Tests
Provider side Pact testing, involves verifying that the contract - the Pact file
- can be satisfied by the Provider.
A typical Provider side test would like something like:
func TestProvider_PactContract(t *testing.T) {
go startMyAPI("http://localhost:8000")
response := pact.VerifyProvider(&types.VerifyRequest{
ProviderBaseURL: "http://localhost:8000",
PactURLs: []string{"./pacts/my_consumer-my_provider.json"},
ProviderStatesURL: "http://localhost:8000/states",
ProviderStatesSetupURL: "http://localhost:8000/setup",
})
if response.ExitCode != 0 {
t.Fatalf("Got non-zero exit code '%d', expected 0", response.ExitCode)
}
}
Note that `PactURLs` can be a list of local pact files or remote based
urls (possibly from a Pact Broker
- http://docs.pact.io/documentation/sharings_pacts.html).
Pact reads the specified pact files (from remote or local sources) and replays
the interactions against a running Provider. If all of the interactions are met
we can say that both sides of the contract are satisfied and the test passes.
Provider Verification
When validating a Provider, you have 3 options to provide the Pact files:
1. Use "PactURLs" to specify the exact set of pacts to be replayed:
response = pact.VerifyProvider(&types.VerifyRequest{
ProviderBaseURL: "http://myproviderhost",
PactURLs: []string{"http://broker/pacts/provider/them/consumer/me/latest/dev"},
ProviderStatesURL: "http://myproviderhost/states",
ProviderStatesSetupURL: "http://myproviderhost/setup",
BrokerUsername: os.Getenv("PACT_BROKER_USERNAME"),
BrokerPassword: os.Getenv("PACT_BROKER_PASSWORD"),
})
2. Use "PactBroker" to automatically find all of the latest consumers:
response = pact.VerifyProvider(&types.VerifyRequest{
ProviderBaseURL: "http://myproviderhost",
BrokerURL: brokerHost,
ProviderStatesURL: "http://myproviderhost/states",
ProviderStatesSetupURL: "http://myproviderhost/setup",
BrokerUsername: os.Getenv("PACT_BROKER_USERNAME"),
BrokerPassword: os.Getenv("PACT_BROKER_PASSWORD"),
})
3. Use "PactBroker" and "Tags" to automatically find all of the latest consumers:
response = pact.VerifyProvider(&types.VerifyRequest{
ProviderBaseURL: "http://myproviderhost",
BrokerURL: brokerHost,
Tags: []string{"latest", "sit4"},
ProviderStatesURL: "http://myproviderhost/states",
ProviderStatesSetupURL: "http://myproviderhost/setup",
BrokerUsername: os.Getenv("PACT_BROKER_USERNAME"),
BrokerPassword: os.Getenv("PACT_BROKER_PASSWORD"),
})
Options 2 and 3 are particularly useful when you want to validate that your
Provider is able to meet the contracts of what's in Production and also the latest
in development.
See this [article](http://rea.tech/enter-the-pact-matrix-or-how-to-decouple-the-release-cycles-of-your-microservices/)
for more on this strategy.
Provider States
Each interaction in a pact should be verified in isolation, with no context
Expand Down
115 changes: 115 additions & 0 deletions dsl/broker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package dsl

import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"

"github.com/pact-foundation/pact-go/types"
)

var (
pactURLPattern = "%s/pacts/provider/%s/latest"
pactURLPatternWithTag = "%s/pacts/provider/%s/latest/%s"

// ErrNoConsumers is returned when no consumer are not found for a provider.
ErrNoConsumers = errors.New("no consumers found")

// ErrUnauthorized represents a Forbidden (403).
ErrUnauthorized = errors.New("unauthorized")
)

// PactLink represents the Pact object in the HAL response.
type PactLink struct {
Href string `json:"href"`
Title string `json:"title"`
Name string `json:"name"`
}

// HalLinks represents the _links key in a HAL document.
type HalLinks struct {
Pacts []PactLink `json:"pacts"`
}

// HalDoc is a simple representation of the HAL response from a Pact Broker.
type HalDoc struct {
Links HalLinks `json:"_links"`
}

// findConsumers navigates a Pact Broker's HAL system to find consumers
// based on the latest Pacts or using tags.
//
// There are 2 Scenarios:
//
// 1. Ask for all 'latest' consumers
// 2. Pass a set of tags (e.g. 'latest' and 'prod') and find all consumers
// that match
func findConsumers(provider string, request *types.VerifyRequest) error {
log.Println("[DEBUG] broker - find consumers for provider:", provider)

client := &http.Client{}
var urls []string
pactURLs := make(map[string]string)

if len(request.Tags) > 0 {
for _, tag := range request.Tags {
urls = append(urls, fmt.Sprintf(pactURLPatternWithTag, request.BrokerURL, provider, tag))
}
} else {
urls = append(urls, fmt.Sprintf(pactURLPattern, request.BrokerURL, provider))
}

for _, url := range urls {
var req *http.Request
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return err
}

req.Header.Set("Accept", "application/hal+json")

if request != nil && request.BrokerUsername != "" && request.BrokerPassword != "" {
req.SetBasicAuth(request.BrokerUsername, request.BrokerPassword)
}

res, err := client.Do(req)
if err != nil {
return err
}

switch res.StatusCode {
case 401:
return ErrUnauthorized
case 404:
return ErrNoConsumers
}

responseBody, err := ioutil.ReadAll(res.Body)
res.Body.Close()
log.Printf("[DEBUG] pact broker response Body: %s\n", responseBody)

if res.StatusCode < 200 || res.StatusCode >= 300 {
return errors.New(string(responseBody))
}

var doc HalDoc
err = json.Unmarshal(responseBody, &doc)
if err != nil {
return err
}

for _, p := range doc.Links.Pacts {
pactURLs[p.Title] = p.Href
}
}

// Scrub out duplicate pacts across tags (e.g. 'latest' may equal 'prod' pact)
for _, p := range pactURLs {
request.PactURLs = append(request.PactURLs, p)
}

return nil
}
Loading

0 comments on commit f6e39b2

Please sign in to comment.