Skip to content

Commit

Permalink
fix(matchers): allow matchers in request fields
Browse files Browse the repository at this point in the history
- Allows path, query and headers to contain Matchers
in order.
- Update integration test examples to demonstrate capability
- Speeds up test suite significantly

BREAKING CHANGE

Change updates the field definitions within the Request and
Response types (See MatcherMap and MatcherString).

Fixes #72, Fixes #73
  • Loading branch information
mefellows committed Mar 24, 2018
1 parent a9c7f4b commit a13c8f5
Show file tree
Hide file tree
Showing 13 changed files with 116 additions and 29 deletions.
24 changes: 24 additions & 0 deletions dsl/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,27 @@ func TestHelperProcess(t *testing.T) {
fmt.Fprintf(os.Stdout, `{"summary_line":"1 examples, 0 failures"}`)
os.Exit(0)
}

func Test_sanitiseRubyResponse(t *testing.T) {
var tests = map[string]string{
"this is a sentence with a hash # so it should be in tact": "this is a sentence with a hash # so it should be in tact",
"this is a sentence with a hash and newline\n#so it should not be in tact": "this is a sentence with a hash and newline",
"this is a sentence with a ruby statement bundle exec rake pact:verify so it should not be in tact": "",
"this is a sentence with a ruby statement\nbundle exec rake pact:verify so it should not be in tact": "this is a sentence with a ruby statement",
"this is a sentence with multiple new lines \n\n\n\n\nit should not be in tact": "this is a sentence with multiple new lines \nit should not be in tact",
}
for k, v := range tests {
test := sanitiseRubyResponse(k)
if !strings.EqualFold(strings.TrimSpace(test), strings.TrimSpace(v)) {
log.Fatalf("Got `%s', Expected `%s`", strings.TrimSpace(test), strings.TrimSpace(v))
}
}
}

func stubPorts() func() {
old := waitForPort
waitForPort = func(int, string, string, string) error {
return nil
}
return func() { waitForPort = old }
}
6 changes: 3 additions & 3 deletions dsl/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ func Like(content interface{}) string {

// Term specifies that the matching should generate a value
// and also match using a regular expression.
func Term(generate string, matcher string) string {
return fmt.Sprintf(`
func Term(generate string, matcher string) MatcherString {
return MatcherString(fmt.Sprintf(`
{
"json_class": "Pact::Term",
"data": {
Expand All @@ -37,5 +37,5 @@ func Term(generate string, matcher string) string {
"s": "%s"
}
}
}`, generate, matcher)
}`, generate, matcher))
}
14 changes: 10 additions & 4 deletions dsl/matcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

func TestMatcher_TermString(t *testing.T) {
expected := formatJSON(`
expected := formatJSON(MatcherString(`
{
"json_class": "Pact::Term",
"data": {
Expand All @@ -19,7 +19,7 @@ func TestMatcher_TermString(t *testing.T) {
"s": "\\w+"
}
}
}`)
}`))

match := formatJSON(Term("myawesomeword", `\\w+`))
if expected != match {
Expand Down Expand Up @@ -286,9 +286,15 @@ func TestMatcher_NestAllTheThings(t *testing.T) {
}

// Format a JSON document to make comparison easier.
func formatJSON(object string) string {
func formatJSON(object interface{}) interface{} {
var out bytes.Buffer
json.Indent(&out, []byte(object), "", "\t")
switch content := object.(type) {
case string:
json.Indent(&out, []byte(content), "", "\t")
case MatcherString:
json.Indent(&out, []byte(content), "", "\t")
}

return string(out.Bytes())
}

Expand Down
1 change: 1 addition & 0 deletions dsl/pact_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ func TestPact_VerifyProviderFail(t *testing.T) {

func TestPact_AddInteraction(t *testing.T) {
pact := &Pact{}
defer stubPorts()()

pact.
AddInteraction().
Expand Down
61 changes: 56 additions & 5 deletions dsl/request.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,61 @@
package dsl

import (
"encoding/json"
)

// Request is the default implementation of the Request interface.
type Request struct {
Method string `json:"method"`
Path string `json:"path"`
Query string `json:"query,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
Body interface{} `json:"body,omitempty"`
Method string `json:"method"`
Path MatcherString `json:"path"`
Query MatcherMap `json:"query,omitempty"`
Headers MatcherMap `json:"headers,omitempty"`
Body interface{} `json:"body,omitempty"`
}

// MatcherMap allows a map[string]string-like object
// to also contain complex matchers
type MatcherMap map[string]MatcherString

// MarshalJSON is a custom encoder for Header type
func (h MatcherMap) MarshalJSON() ([]byte, error) {
obj := map[string]interface{}{}

for header, value := range h {
obj[header] = toObject([]byte(value))
}

return json.Marshal(obj)
}

// UnmarshalJSON is a custom decoder for Header type
func (h *MatcherMap) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &h); err != nil {
return err
}

return nil
}

// MatcherString allows the use of Matchers in string types
// It convert any matchers in interaction type to abstract JSON objects
// See https://github.com/pact-foundation/pact-go/issues/71 for background
type MatcherString string

// MarshalJSON is a custom encoder for Header type
func (m MatcherString) MarshalJSON() ([]byte, error) {
var obj interface{}

obj = toObject([]byte(m))

return json.Marshal(obj)
}

// UnmarshalJSON is a custom decoder for Header type
func (m *MatcherString) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &m); err != nil {
return err
}

return nil
}
6 changes: 3 additions & 3 deletions dsl/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package dsl

// Response is the default implementation of the Response interface.
type Response struct {
Status int `json:"status"`
Headers map[string]string `json:"headers,omitempty"`
Body interface{} `json:"body,omitempty"`
Status int `json:"status"`
Headers MatcherMap `json:"headers,omitempty"`
Body interface{} `json:"body,omitempty"`
}
2 changes: 1 addition & 1 deletion examples/consumer/goconsumer/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (c *Client) login(username string, password string) (*User, error) {
"password": "%s"
}`, username, password)

res, err := http.Post(fmt.Sprintf("%s/users/login", c.Host), "application/json; charset=utf-8", bytes.NewReader([]byte(loginRequest)))
res, err := http.Post(fmt.Sprintf("%s/users/login/10?foo=anything", c.Host), "application/json; charset=utf-8", bytes.NewReader([]byte(loginRequest)))
if res.StatusCode != 200 || err != nil {
return nil, fmt.Errorf("login failed")
}
Expand Down
21 changes: 13 additions & 8 deletions examples/consumer/goconsumer/user_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ var eachLike = dsl.EachLike
var term = dsl.Term
var loginRequest = fmt.Sprintf(`{ "username":"%s", "password": "issilly" }`, name)

var commonHeaders = map[string]string{
"Content-Type": "application/json; charset=utf-8",
var commonHeaders = dsl.MatcherMap{
"Content-Type": dsl.Term("application/json; charset=utf-8", `application\/json`),
}

// Use this to control the setup and teardown of Pact
Expand Down Expand Up @@ -130,10 +130,12 @@ func TestPactConsumerLoginHandler_UserExists(t *testing.T) {
Given("User billy exists").
UponReceiving("A request to login with user 'billy'").
WithRequest(dsl.Request{
Method: "POST",
Path: "/users/login",
Body: loginRequest,
Headers: commonHeaders,
Method: "POST",
Path: dsl.Term("/users/login/1", "/users/login/[0-9]+"),
Query: dsl.MatcherMap{
"foo": dsl.Term("bar", "[a-zA-Z]+"),
},
Body: loginRequest,
}).
WillRespondWith(dsl.Response{
Status: 200,
Expand Down Expand Up @@ -167,9 +169,12 @@ func TestPactConsumerLoginHandler_UserDoesNotExist(t *testing.T) {
UponReceiving("A request to login with user 'billy'").
WithRequest(dsl.Request{
Method: "POST",
Path: "/users/login",
Path: "/users/login/10",
Body: loginRequest,
Headers: commonHeaders,
Query: dsl.MatcherMap{
"foo": "anything",
},
}).
WillRespondWith(dsl.Response{
Status: 404,
Expand Down Expand Up @@ -202,7 +207,7 @@ func TestPactConsumerLoginHandler_UserUnauthorised(t *testing.T) {
UponReceiving("A request to login with user 'billy'").
WithRequest(dsl.Request{
Method: "POST",
Path: "/users/login",
Path: "/users/login/10",
Body: loginRequest,
Headers: commonHeaders,
}).
Expand Down
2 changes: 1 addition & 1 deletion examples/gin/provider/cmd/usersvc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ import "github.com/pact-foundation/pact-go/examples/gin/provider"

func main() {
router := gin.Default()
router.POST("/users/login", provider.UserLogin)
router.POST("/users/login/:id", provider.UserLogin)
router.Run(":8080")
}
2 changes: 1 addition & 1 deletion examples/gin/provider/user_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func TestPact_GinProvider(t *testing.T) {
// This essentially mirrors the main.go file, with extra routes added.
func startInstrumentedProvider() {
router := gin.Default()
router.POST("/users/login", UserLogin)
router.POST("/users/login/:id", UserLogin)
router.POST("/setup", providerStateSetup)

router.Run(fmt.Sprintf(":%d", port))
Expand Down
2 changes: 1 addition & 1 deletion examples/go-kit/provider/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func MakeHTTPHandler(ctx context.Context, s Service, logger log.Logger) http.Han
}
e := MakeServerEndpoints(s)

r.Methods("POST").Path("/users/login").Handler(httptransport.NewServer(
r.Methods("POST").Path("/users/login/{id}").Handler(httptransport.NewServer(
e.LoginEndpoint,
decodeUserRequest,
encodeResponse,
Expand Down
2 changes: 1 addition & 1 deletion examples/mux/provider/cmd/usersvc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (

func main() {
mux := http.NewServeMux()
mux.HandleFunc("/users/login", provider.UserLogin)
mux.HandleFunc("/users/login/", provider.UserLogin)

port, _ := utils.GetFreePort()
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
Expand Down
2 changes: 1 addition & 1 deletion examples/mux/provider/user_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func TestPact_MuxProvider(t *testing.T) {
// This essentially mirrors the main.go file, with extra routes added.
func startInstrumentedProvider() {
mux := http.NewServeMux()
mux.HandleFunc("/users/login", UserLogin)
mux.HandleFunc("/users/login/", UserLogin)
mux.HandleFunc("/setup", providerStateSetupFunc)

ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
Expand Down

0 comments on commit a13c8f5

Please sign in to comment.