Skip to content

Commit

Permalink
feat: add query string serialisation to v2
Browse files Browse the repository at this point in the history
  • Loading branch information
mefellows committed Aug 22, 2020
1 parent b61ffa7 commit f2a921c
Show file tree
Hide file tree
Showing 14 changed files with 493 additions and 186 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
language: go
dist: focal
go:
- 1.10.x
# - 1.10.x #TODO: reflect.MapRange()
- 1.11.x
- 1.12.x
- 1.13.x
Expand Down
87 changes: 67 additions & 20 deletions examples/v3/consumer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,21 @@ type s = v3.String
func TestConsumerV2(t *testing.T) {
v3.SetLogLevel("TRACE")

// Create Pact connecting to local Daemon
mockProvider, err := v3.NewHTTPMockProviderV2(v3.MockHTTPProviderConfigV2{
Consumer: "MyConsumer",
Provider: "MyProvider",
Host: "127.0.0.1",
Port: 8080,
SpecificationVersion: v3.V2,
TLS: true,
Consumer: "MyConsumer",
Provider: "MyProvider",
Host: "127.0.0.1",
Port: 8080,
TLS: true,
})

// Override default matching behaviour
// mockProvider.SetMatchingConfig(v3.PactSerialisationOptionsV2{
// QueryStringStyle: v3.AlwaysArray,
// QueryStringStyle: v3.Array,
// QueryStringStyle: v3.Default,
// })

// TODO: probably better than deferring to the execute test phase, but not sure
if err != nil {
t.Fatal(err)
Expand All @@ -38,12 +43,20 @@ func TestConsumerV2(t *testing.T) {
// Set up our expected interactions.
mockProvider.
AddInteraction().
// Given("User foo exists").
Given("User foo exists").
UponReceiving("A request to do a foo").
WithRequest(v3.Request{
Method: "POST",
Path: s("/foobar"),
Method: "POST",
Path: s("/foobar"),
// Path: v3.Regex("/foobar", `/\/foo+/`),
Headers: v3.MapMatcher{"Content-Type": s("application/json"), "Authorization": s("Bearer 1234")},
Query: v3.QueryMatcher{
"baz": []interface{}{
v3.Regex("bar", "[a-z]+"),
v3.Regex("bat", "[a-z]+"),
v3.Regex("baz", "[a-z]+"),
},
},
Body: v3.MapMatcher{
"name": s("billy"),
},
Expand All @@ -68,20 +81,52 @@ func TestConsumerV2(t *testing.T) {
func TestConsumerV3(t *testing.T) {
v3.SetLogLevel("TRACE")

// Create Pact connecting to local Daemon
mockProvider, err := v3.NewHTTPMockProviderV3(v3.MockHTTPProviderConfigV2{
Consumer: "MyConsumer",
Provider: "MyProvider",
Host: "127.0.0.1",
Port: 8080,
SpecificationVersion: v3.V2,
TLS: true,
Consumer: "MyConsumer",
Provider: "MyProvider",
Host: "127.0.0.1",
Port: 8080,
TLS: true,
})

if err != nil {
t.Fatal(err)
}

// Set up our expected interactions.
mockProvider.
AddInteraction().
Given(v3.ProviderStateV3{
Description: "User foo exists",
Parameters: map[string]string{
"id": "foo",
},
}).
UponReceiving("A request to do a foo").
WithRequest(v3.Request{
Method: "POST",
Path: s("/foobar"),
Headers: v3.MapMatcher{"Content-Type": s("application/json"), "Authorization": s("Bearer 1234")},
Body: v3.MapMatcher{
"name": s("billy"),
},
Query: v3.QueryMatcher{
"baz": []interface{}{
v3.Regex("bat", "ba+"),
},
},
}).
WillRespondWith(v3.Response{
Status: 200,
Headers: v3.MapMatcher{"Content-Type": s("application/json")},
// Body: v3.Match(&User{}),
Body: v3.MapMatcher{
"dateTime": v3.Regex("2020-01-01", "[0-9\\-]+"),
"name": s("FirstName"),
"lastName": s("LastName"),
},
})

// Execute pact test
if err := mockProvider.ExecuteTest(test); err != nil {
log.Fatalf("Error on Verify: %v", err)
Expand All @@ -104,9 +149,11 @@ var test = func(config v3.MockServerConfig) error {
req := &http.Request{
Method: "POST",
URL: &url.URL{
Host: fmt.Sprintf("%s:%d", "localhost", config.Port),
Scheme: "https",
Path: "/foobar",
Host: fmt.Sprintf("%s:%d", "localhost", config.Port),
Scheme: "https",
Path: "/foobar",
RawQuery: "baz=bat&baz=foo&baz=something", // Default behaviour
// RawQuery: "baz[]=bat&baz[]=foo&baz[]=something", // AlwaysArray example
},
Body: ioutil.NopCloser(strings.NewReader(`{"name":"billy"}`)),
Header: make(http.Header),
Expand Down
25 changes: 24 additions & 1 deletion examples/v3/pacts/consumer-provider.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"interactions": [
{
"description": "A request to do a foo",
"providerState": "User foo exists",
"request": {
"body": {
"name": "billy"
Expand All @@ -16,10 +17,29 @@
"matchingRules": {
"$.body.name": {
"match": "type"
},
"$.header.Authorization": {
"match": "type"
},
"$.header.Content-Type": {
"match": "type"
},
"$.query.baz[0]": {
"match": "regex",
"regex": "[a-z]+"
},
"$.query.baz[1]": {
"match": "regex",
"regex": "[a-z]+"
},
"$.query.baz[2]": {
"match": "regex",
"regex": "[a-z]+"
}
},
"method": "POST",
"path": "/foobar"
"path": "/foobar",
"query": "baz=bar&baz=bat&baz=baz"
},
"response": {
"body": {
Expand All @@ -40,6 +60,9 @@
},
"$.body.name": {
"match": "type"
},
"$.header.Content-Type": {
"match": "type"
}
},
"status": 200
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ require (
github.com/hashicorp/logutils v1.0.0
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/mattn/go-isatty v0.0.3 // indirect
github.com/mattn/goveralls v0.0.5 // indirect
github.com/mattn/goveralls v0.0.6 // indirect
github.com/mitchellh/gox v1.0.1 // indirect
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5 // indirect
github.com/spf13/cobra v0.0.0-20160604044732-f447048345b6
github.com/spf13/viper v1.7.0 // indirect
github.com/stretchr/testify v1.4.0
github.com/twinj/uuid v1.0.0
github.com/ugorji/go v1.1.1 // indirect
golang.org/x/tools v0.0.0-20200305224536-de023d59a5d1 // indirect
golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d // indirect
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
gopkg.in/go-playground/validator.v8 v8.18.2 // indirect
)
13 changes: 13 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/goveralls v0.0.5 h1:spfq8AyZ0cCk57Za6/juJ5btQxeE1FaEGMdfcI+XO48=
github.com/mattn/goveralls v0.0.5/go.mod h1:Xg2LHi51faXLyKXwsndxiW6uxEEQT9+3sjGzzwU4xy0=
github.com/mattn/goveralls v0.0.6 h1:cr8Y0VMo/MnEZBjxNN/vh6G90SZ7IMb6lms1dzMoO+Y=
github.com/mattn/goveralls v0.0.6/go.mod h1:h8b4ow6FxSPMQHF6o2ve3qsclnffZjYTNEKmLesRwqw=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
Expand Down Expand Up @@ -200,6 +202,8 @@ github.com/twinj/uuid v1.0.0/go.mod h1:mMgcE1RHFUFqe5AfiwlINXisXfDGro23fWdPUfOMj
github.com/ugorji/go v1.1.1 h1:gmervu+jDMvXTbcHQ0pd2wee85nEoE0BsVyEuzkfK8w=
github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
Expand All @@ -212,6 +216,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
Expand All @@ -232,6 +237,7 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand All @@ -249,6 +255,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwL
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
Expand All @@ -260,6 +267,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEha
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand All @@ -276,6 +284,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
Expand Down Expand Up @@ -306,6 +315,10 @@ golang.org/x/tools v0.0.0-20200113040837-eac381796e91 h1:OOkytthzFBKHY5EfEgLUabp
golang.org/x/tools v0.0.0-20200113040837-eac381796e91/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200305224536-de023d59a5d1 h1:A6Mu2vcvuNXbBiGKuVHG74fmEPmzsZ5dzG0WhV2GcqI=
golang.org/x/tools v0.0.0-20200305224536-de023d59a5d1/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375 h1:SjQ2+AKWgZLc1xej6WSzL+Dfs5Uyd5xcZH1mGC411IA=
golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d h1:XZxUC4/ZNKTjrT4/Oc9gCgIYnzPW3/CefdPjsndrVWM=
golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
35 changes: 30 additions & 5 deletions v3/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,29 @@ func init() {
native.Init()
}

// QueryStringStyle allows a user to specific the v2 query string serialisation format
// Different frameworks have different ways to serialise this, which is why
// v3 moved to storing this as a map
type QueryStringStyle int

const (
// Default uses the param=value1&param=value2 style
Default QueryStringStyle = iota

// AlwaysArray uses the [] style even if a parameter only has a single value
// e.g. param[]=value1&param[]=value2
AlwaysArray

// Array uses the [] style only if a parameter only has multiple values
// e.g. param[]=value1&param[]=value2&param2=value
Array
)

// PactSerialisationOptionsV2 allows a user to override specific pact serialisation options
type PactSerialisationOptionsV2 struct {
QueryStringStyle QueryStringStyle
}

type mockHTTPProviderConfig struct {
// Consumer is the name of the Consumer/Client.
Consumer string
Expand Down Expand Up @@ -75,11 +98,11 @@ type mockHTTPProviderConfig struct {
// Defaults to 10s
ClientTimeout time.Duration

matchingConfig PactSerialisationOptionsV2

// Check if CLI tools are up to date
toolValidityCheck bool

SpecificationVersion SpecificationVersion

// TLS enables a mock service behind a self-signed certificate
// TODO: document and test this
TLS bool
Expand All @@ -94,7 +117,8 @@ type MockHTTPProviderConfigV3 = mockHTTPProviderConfig
// httpMockProvider is the entrypoint for http consumer tests and provides the base capability for the
// exported types HTTPMockProviderV2 and HTTPMockProviderV3
type httpMockProvider struct {
config mockHTTPProviderConfig
specificationVersion SpecificationVersion
config mockHTTPProviderConfig

v2Interactions []*InteractionV2
v3Interactions []*InteractionV3
Expand Down Expand Up @@ -169,9 +193,10 @@ func (p *httpMockProvider) ExecuteTest(integrationTest func(MockServerConfig) er

// Generate interactions for Pact file
var serialisedPact interface{}
if p.config.SpecificationVersion == V2 {
serialisedPact = newPactFileV2(p.config.Consumer, p.config.Provider, p.v2Interactions)
if p.specificationVersion == V2 {
serialisedPact = newPactFileV2(p.config.Consumer, p.config.Provider, p.v2Interactions, p.config.matchingConfig)
} else {

serialisedPact = newPactFileV3(p.config.Consumer, p.config.Provider, p.v3Interactions)
}

Expand Down
10 changes: 9 additions & 1 deletion v3/http_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ type HTTPMockProviderV2 struct {
func NewHTTPMockProviderV2(config MockHTTPProviderConfigV2) (*HTTPMockProviderV2, error) {
provider := &HTTPMockProviderV2{
httpMockProvider: &httpMockProvider{
config: config,
config: config,
specificationVersion: V2,
},
}
err := provider.validateConfig()
Expand All @@ -33,3 +34,10 @@ func (p *HTTPMockProviderV2) AddInteraction() *InteractionV2 {
p.httpMockProvider.v2Interactions = append(p.httpMockProvider.v2Interactions, i)
return i
}

// SetMatchingConfig allows specific contract file serialisation adjustments
func (p *HTTPMockProviderV2) SetMatchingConfig(config PactSerialisationOptionsV2) *HTTPMockProviderV2 {
p.config.matchingConfig = config

return p
}
3 changes: 2 additions & 1 deletion v3/http_v3.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ type HTTPMockProviderV3 struct {
func NewHTTPMockProviderV3(config MockHTTPProviderConfigV3) (*HTTPMockProviderV3, error) {
provider := &HTTPMockProviderV3{
httpMockProvider: &httpMockProvider{
config: config,
config: config,
specificationVersion: V3,
},
}
err := provider.validateConfig()
Expand Down
10 changes: 0 additions & 10 deletions v3/interaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,6 @@ type Interaction struct {

// Description to be written into the Pact file
Description string `json:"description"`

// Provider state to be written into the Pact file
State string `json:"providerState,omitempty"`
}

// Given specifies a provider state. Optional.
func (i *Interaction) Given(state string) *Interaction {
i.State = state

return i
}

// UponReceiving specifies the name of the test case. This becomes the name of
Expand Down
Loading

0 comments on commit f2a921c

Please sign in to comment.