Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
gopkg.in/v0id3r/pact-go.v0
Golang version of Pact. 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.
From the Pact website:
The Pact family of frameworks provide support for Consumer Driven Contracts testing.
A Contract is a collection of agreements between a client (Consumer) and an API (Provider) that describes the interactions that can take place between them.
Consumer Driven Contracts is a pattern that drives the development of the Provider from its Consumers point of view.
Pact is a testing tool that guarantees those Contracts are satisfied.
Read Getting started with Pact for more information on how to get going.
Pact Go implements Pact Specification v2, including flexible matching.
NOTE: This is the deprecated, however stable, 0.x.x
release branch, for the latest see the master release.
pact-go
binary is on the PATH
, next to the pact
folder.pact-go
to see what options are available.go get -d github.com/pact-foundation/pact-go
to install the source packagesPact Go runs a two-step process:
pact-go daemon
in a separate process/shell. The Consumer and Provider
DSLs communicate over a local (RPC) connection, and is transparent to clients.6666
.NOTE: The daemon is thread safe and it is normal to leave it running for long periods (e.g. on a CI server).
We'll run through a simple example to get an understanding the concepts:
./pact-go daemon
.go get github.com/pact-foundation/pact-go
cd $GOPATH/src/github.com/pact-foundation/pact-go/examples/
go test -v -run TestConsumer
.The simple example looks like this:
package main
import (
"fmt"
"log"
"net/http"
"strings"
"testing"
"github.com/pact-foundation/pact-go/dsl"
)
// Example Pact: How to run me!
// 1. Start the daemon with `./pact-go daemon`
// 2. cd <pact-go>/examples
// 3. go test -v -run TestConsumer
func TestConsumer(t *testing.T) {
// Create Pact connecting to local Daemon
pact := &dsl.Pact{
Port: 6666, // Ensure this port matches the daemon port!
Consumer: "MyConsumer",
Provider: "MyProvider",
Host: "localhost",
}
defer pact.Teardown()
// Pass in test case
var test = func() error {
u := fmt.Sprintf("http://localhost:%d/foobar", pact.Server.Port)
req, err := http.NewRequest("GET", u, strings.NewReader(`{"s":"foo"}`))
// NOTE: by default, request bodies are expected to be sent with a Content-Type
// of application/json. If you don't explicitly set the content-type, you
// will get a mismatch during Verification.
req.Header.Set("Content-Type", "application/json")
if err != nil {
return err
}
if _, err = http.DefaultClient.Do(req); err != nil {
return err
}
return err
}
// Set up our expected interactions.
pact.
AddInteraction().
Given("User foo exists").
UponReceiving("A request to get foo").
WithRequest(dsl.Request{
Method: "GET",
Path: "/foobar",
Headers: map[string]string{"Content-Type": "application/json"},
Body: `{"s":"foo"}`,
}).
WillRespondWith(dsl.Response{
Status: 200,
Headers: map[string]string{"Content-Type": "application/json"},
Body: `{"s":"bar"}`,
})
// Verify
if err := pact.Verify(test); err != nil {
log.Fatalf("Error on Verify: %v", err)
}
}
In addition to verbatim value matching, you have 3 useful matching functions
in the dsl
package that can increase expressiveness and reduce brittle test
cases.
dsl.Term(example, matcher)
- tells Pact that the value should match using
a given regular expression, using example
in mock responses. example
must be
a string.dsl.Like(content)
- tells Pact that the value itself is not important, as long
as the element type (valid JSON number, string, object etc.) itself matches.dsl.EachLike(content, min)
- tells Pact that the value should be an array type,
consisting of elements like those passed in. min
must be >= 1. content
may
be a valid JSON value: e.g. strings, numbers and objects.Example:
Here is a complex example that shows how all 3 terms can be used together:
colour := Term("red", "red|green|blue")
match := EachLike(
EachLike(
fmt.Sprintf(`{
"size": 10,
"colour": %s,
"tag": [["jumper", "shirt]]
}`, colour)
1),
1))
This example will result in a response body from the mock server that looks like:
[
[
{
"size": 10,
"colour": "red",
"tag": [
[
"jumper",
"shirt"
],
[
"jumper",
"shirt"
]
]
}
]
]
Furthermore, if you isolate your Data Transfer Objects (DTOs) to an adapters package so that they exactly reflect the interface between you and your provider, then you can leverage dsl.Match
to auto-generate the expected response body in your contract tests. Under the hood, Match
recursively traverses the DTO struct and uses Term, Like, and EachLike
to create the contract.
This saves the trouble of declaring the contract by hand. It also maintains one source of truth. To change the consumer-provider interface, you only have to update your DTO struct and the contract will automatically follow suit.
Example:
type DTO struct {
ID string `json:"id"`
Title string `json:"title"`
Tags []string `json:"tags" pact:"min=2"`
Date string `json:"date" pact:"example=2000-01-01,regex=^\\d{4}-\\d{2}-\\d{2}$"`
}
then specifying a response body is as simple as:
// Set up our expected interactions.
pact.
AddInteraction().
Given("User foo exists").
UponReceiving("A request to get foo").
WithRequest(dsl.Request{
Method: "GET",
Path: "/foobar",
Headers: map[string]string{"Content-Type": "application/json"},
}).
WillRespondWith(dsl.Response{
Status: 200,
Headers: map[string]string{"Content-Type": "application/json"},
Body: Match(DTO{}), // That's it!!!
})
The pact
struct tags shown above are optional. By default, dsl.Match just asserts that the JSON shape matches the struct and that the field types match.
See dsl.Match for more information.
See the matcher tests for more matching examples.
NOTE: One caveat to note, is that you will need to use valid Ruby regular expressions and double escape backslashes.
Read more about flexible matching.
./pact-go daemon
.go get github.com/pact-foundation/pact-go
cd $GOPATH/src/github.com/pact-foundation/pact-go/examples/
go test -v -run TestProvider
.Here is the Provider test process broker down:
Start your Provider API:
You need to be able to first start your API in the background as part of your tests
before you can run the verification process. Here we create startServer
which can be
started in its own goroutine:
func startServer() {
mux := http.NewServeMux()
lastName := "billy"
mux.HandleFunc("/foobar", func(w http.ResponseWriter, req *http.Request) {
w.Header().Add("Content-Type", "application/json")
fmt.Fprintf(w, fmt.Sprintf(`{"lastName":"%s"}`, lastName))
// Break the API by replacing the above and uncommenting one of these
// w.WriteHeader(http.StatusUnauthorized)
// fmt.Fprintf(w, `{"s":"baz"}`)
})
// This function handles state requests for a particular test
// In this case, we ensure that the user being requested is available
// before the Verification process invokes the API.
mux.HandleFunc("/setup", func(w http.ResponseWriter, req *http.Request) {
var s *types.ProviderState
decoder := json.NewDecoder(req.Body)
decoder.Decode(&s)
if s.State == "User foo exists" {
lastName = "bar"
}
w.Header().Add("Content-Type", "application/json")
})
log.Fatal(http.ListenAndServe(":8000", mux))
}
Note that the server has a /setup
endpoint that is given a types.ProviderState
and allows the
verifier to setup any
provider states before
each test is run.
Verify provider API
You can now tell Pact to read in your Pact files and verify that your API will satisfy the requirements of each of your known consumers:
func TestProvider(t *testing.T) {
// Create Pact connecting to local Daemon
pact := &dsl.Pact{
Port: 6666, // Ensure this port matches the daemon port!
Consumer: "MyConsumer",
Provider: "MyProvider",
}
// Start provider API in the background
go startServer()
// Verify the Provider with local Pact Files
pact.VerifyProvider(t, types.VerifyRequest{
ProviderBaseURL: "http://localhost:8000",
PactURLs: []string{filepath.ToSlash(fmt.Sprintf("%s/myconsumer-myprovider.json", pactDir))},
ProviderStatesSetupURL: "http://localhost:8000/setup",
})
}
The VerifyProvider
will handle all verifications, treating them as subtests
and giving you granular test reporting. If you don't like this behaviour, you may call VerifyProviderRaw
directly and handle the errors manually.
Note that PactURLs
may be a list of local pact files or remote based
urls (e.g. from a
Pact Broker).
See the Skip()'ed
integration tests
for a more complete E2E example.
When validating a Provider, you have 3 options to provide the Pact files:
Use PactURLs
to specify the exact set of pacts to be replayed:
pact.VerifyProvider(t, types.VerifyRequest{
ProviderBaseURL: "http://myproviderhost",
PactURLs: []string{"http://broker/pacts/provider/them/consumer/me/latest/dev"},
ProviderStatesSetupURL: "http://myproviderhost/setup",
BrokerUsername: os.Getenv("PACT_BROKER_USERNAME"),
BrokerPassword: os.Getenv("PACT_BROKER_PASSWORD"),
})
Use PactBroker
to automatically find all of the latest consumers:
pact.VerifyProvider(t, types.VerifyRequest{
ProviderBaseURL: "http://myproviderhost",
BrokerURL: "http://brokerHost",
ProviderStatesSetupURL: "http://myproviderhost/setup",
BrokerUsername: os.Getenv("PACT_BROKER_USERNAME"),
BrokerPassword: os.Getenv("PACT_BROKER_PASSWORD"),
})
Use PactBroker
and Tags
to automatically find all of the latest consumers:
pact.VerifyProvider(t, types.VerifyRequest{
ProviderBaseURL: "http://myproviderhost",
BrokerURL: "http://brokerHost",
Tags: []string{"latest", "sit4"},
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 for more on this strategy.
For more on provider states, refer to http://docs.pact.io/documentation/provider_states.html.
Sometimes you may need to add things to the requests that can't be persisted in a pact file. Examples of these would be authentication tokens, which have a small life span. e.g. an OAuth bearer token: Authorization: Bearer 0b79bab50daca910b000d4f1a2b675d604257e42
.
For this case, we have a facility that should be carefully used during verification - the ability to specificy custom headers to be sent during provider verification. The property to achieve this is CustomProviderHeaders
.
For example, to have an Authorization
header sent as part of the verification request, modify the VerifyRequest
parameter as per below:
pact.VerifyProvider(t, types.VerifyRequest{
...
CustomProviderHeaders: []string{"Authorization: Bearer 0b79bab50daca910b000d4f1a2b675d604257e42"},
})
As you can see, this is your opportunity to modify\add to headers being sent to the Provider API, for example to create a valid time-bound token.
Important Note: You should only use this feature for things that can not be persisted in the pact file. By modifying the request, you are potentially modifying the contract from the consumer tests!
See the Pact Broker documentation for more details on the Broker and this article on how to make it work for you.
p := Publisher{}
err := p.Publish(types.PublishRequest{
PactURLs: []string{"./pacts/my_consumer-my_provider.json"},
PactBroker: "http://pactbroker:8000",
ConsumerVersion: "1.0.0",
Tags: []string{"latest", "dev"},
})
If you're using a Pact Broker (e.g. a hosted one at pact.dius.com.au), you can publish your verification results so that consumers can query if they are safe to release.
It looks like this:
You need to specify the following:
PublishVerificationResults: true,
ProviderVersion: "1.0.0",
NOTE: You need to be already pulling pacts from the broker for this feature to work.
Use a cURL request like the following to PUT the pact to the right location, specifying your consumer name, provider name and consumer version.
curl -v \
-X PUT \
-H "Content-Type: application/json" \
-d@spec/pacts/a_consumer-a_provider.json \
http://your-pact-broker/pacts/provider/A%20Provider/consumer/A%20Consumer/version/1.0.0
The following flags are required to use basic authentication when publishing or retrieving Pact files to/from a Pact Broker:
BrokerUsername
- the username for Pact Broker basic authentication.BrokerPassword
- the password for Pact Broker basic authentication.Pact tests tend to be quite long, due to the need to be specific about request/response payloads. Often times it is nicer to be able to split your tests across multiple files for manageability.
You have two options to achieve this feat:
Set PactFileWriteMode
to "merge"
when creating a Pact
struct:
This will allow you to have multiple independent tests for a given Consumer-Provider pair, without it clobbering previous interactions.
See this PR for background.
NOTE: If using this approach, you must be careful to clear out existing pact files (e.g. rm ./pacts/*.json
) before you run tests to ensure you don't have left over requests that are no longer relevent.
Create a Pact test helper to orchestrate the setup and teardown of the mock service for multiple tests.
In larger test bases, this can reduce test suite time and the amount of code you have to manage.
Pact Go uses a simple log utility (logutils) to filter log messages. The CLI already contains flags to manage this, should you want to control log level in your tests, you can set it like so:
pact := Pact{
...
LogLevel: "DEBUG", // One of DEBUG, INFO, ERROR, NONE
}
There is a number of examples we use as end-to-end integration test prior to releasing a new binary, including publishing to a Pact Broker. You can run them all by running make pact
in the project root, or manually (after starting the daemon) as follows:
cd $GOPATH/src/github.com/pact-foundation/pact-go/examples
export PACT_INTEGRATED_TESTS=1
export PACT_BROKER_USERNAME="dXfltyFMgNOFZAxr8io9wJ37iUpY42M"
export PACT_BROKER_PASSWORD="O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1"
export PACT_BROKER_HOST="https://test.pact.dius.com.au"
Once these variables have been exported, cd into one of the directories containing a test and run go test -v .
:
Additional documentation can be found at the main Pact website and in the Pact Wiki.
See TROUBLESHOOTING for some helpful tips/tricks.
The roadmap for Pact and Pact Go is outlined on our main website. Detail on the native Go implementation can be found here.
See CONTRIBUTING.
FAQs
Unknown package
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.