Skip to content

Commit

Permalink
feat(auto-matching): allow complex expressions in pact tags
Browse files Browse the repository at this point in the history
- Tag extractor now allows strings with spaces, unicode characters etc.
- Regex no longer mandatory, to allow for better Like(...) examples
  • Loading branch information
mefellows committed May 17, 2018
1 parent 393645b commit ab9ce17
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 32 deletions.
36 changes: 27 additions & 9 deletions dsl/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"log"
"reflect"
"regexp"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -343,6 +344,10 @@ func match(srcType reflect.Type, params params) Matcher {
if params.str.regEx != "" {
return Term(params.str.example, params.str.regEx)
}
if params.str.example != "" {
return Like(params.str.example)
}

return Like(`"string"`)
case reflect.Bool:
return Like(true)
Expand Down Expand Up @@ -397,17 +402,30 @@ func pluckParams(srcType reflect.Type, pactTag string) params {
triggerInvalidPactTagPanic(pactTag, err)
}
case reflect.String:
components := strings.Split(pactTag, ",regex=")
fullRegex, _ := regexp.Compile(`regex=(.*)$`)
exampleRegex, _ := regexp.Compile(`^example=(.*)`)

if len(components) != 2 {
triggerInvalidPactTagPanic(pactTag, fmt.Errorf("invalid format: unable to split on ',regex='"))
} else if len(components[1]) == 0 {
triggerInvalidPactTagPanic(pactTag, fmt.Errorf("invalid format: regex must not be empty"))
} else if _, err := fmt.Sscanf(components[0], "example=%s", &params.str.example); err != nil {
triggerInvalidPactTagPanic(pactTag, err)
}
if fullRegex.Match([]byte(pactTag)) {
components := strings.Split(pactTag, ",regex=")

if len(components[1]) == 0 {
triggerInvalidPactTagPanic(pactTag, fmt.Errorf("invalid format: regex must not be empty"))
}

params.str.regEx = strings.Replace(components[1], `\`, `\\`, -1)
if _, err := fmt.Sscanf(components[0], "example=%s", &params.str.example); err != nil {
triggerInvalidPactTagPanic(pactTag, err)
}
params.str.regEx = strings.Replace(components[1], `\`, `\\`, -1)

} else if exampleRegex.Match([]byte(pactTag)) {
components := strings.Split(pactTag, "example=")

if len(components) != 2 {
triggerInvalidPactTagPanic(pactTag, fmt.Errorf("invalid format: example must not be empty"))
}

params.str.example = components[1]
}
}

return params
Expand Down
47 changes: 31 additions & 16 deletions dsl/matcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,37 @@ func Test_pluckParams(t *testing.T) {
},
},
},
{
name: "expected use - string tag with complex string",
args: args{
srcType: reflect.TypeOf(""),
pactTag: "example=Jean-Marie de La Beaujardière😀😍",
},
want: params{
slice: sliceParams{
min: getDefaults().slice.min,
},
str: stringParams{
example: "Jean-Marie de La Beaujardière😀😍",
},
},
},
{
name: "expected use - example with no regex",
args: args{
srcType: reflect.TypeOf(""),
pactTag: "example=aBcD123",
},
want: params{
slice: sliceParams{
min: getDefaults().slice.min,
},
str: stringParams{
example: "aBcD123",
},
},
wantPanic: false,
},
{
name: "empty string tag",
args: args{
Expand Down Expand Up @@ -854,22 +885,6 @@ func Test_pluckParams(t *testing.T) {
},
wantPanic: true,
},
{
name: "invalid string tag - no regex",
args: args{
srcType: reflect.TypeOf(""),
pactTag: "example=aBcD123",
},
wantPanic: true,
},
{
name: "invalid string tag - regex typo",
args: args{
srcType: reflect.TypeOf(""),
pactTag: "example=aBcD123,regx=[A-Za-z0-9]",
},
wantPanic: true,
},
{
name: "invalid string tag - space inserted",
args: args{
Expand Down
11 changes: 8 additions & 3 deletions examples/consumer/goconsumer/user_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"testing"

"github.com/pact-foundation/pact-go/dsl"
ex "github.com/pact-foundation/pact-go/examples/types"
"github.com/pact-foundation/pact-go/types"
)

Expand All @@ -25,14 +26,18 @@ var form url.Values
var rr http.ResponseWriter
var req *http.Request
var name = "Jean-Marie de La Beaujardière😀😍"
var password = "issilly"
var like = dsl.Like
var eachLike = dsl.EachLike
var term = dsl.Term

type s = dsl.String
type request = dsl.Request

var loginRequest = map[string]string{"username": name, "password": "issilly"}
var loginRequest = ex.LoginRequest{
Username: name,
Password: password,
}
var commonHeaders = dsl.MapMatcher{
"Content-Type": term("application/json; charset=utf-8", `application\/json`),
}
Expand Down Expand Up @@ -81,7 +86,7 @@ func setup() {
// Login form values
form = url.Values{}
form.Add("username", name)
form.Add("password", "issilly")
form.Add("password", password)

// Create a request to pass to our handler.
req, _ = http.NewRequest("POST", "/login", strings.NewReader(form.Encode()))
Expand Down Expand Up @@ -140,7 +145,7 @@ func TestPactConsumerLoginHandler_UserExists(t *testing.T) {
Query: dsl.MapMatcher{
"foo": term("bar", "[a-zA-Z]+"),
},
Body: loginRequest,
Body: dsl.Match(loginRequest),
Headers: commonHeaders,
}).
WillRespondWith(dsl.Response{
Expand Down
8 changes: 4 additions & 4 deletions examples/types/user_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import "errors"
// User is a representation of a User. Dah.
type User struct {
Name string `json:"name"`
Username string
Password string
Username string `json:"username"`
Password string `json:"password"`
Type string `json:"type"`
}

Expand All @@ -23,8 +23,8 @@ var (

// LoginRequest is the login request API struct.
type LoginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
Username string `json:"username" pact:"example=Jean-Marie de La Beaujardière😀😍"`
Password string `json:"password" pact:"example=issilly"`
}

// LoginResponse is the login response API struct.
Expand Down

0 comments on commit ab9ce17

Please sign in to comment.