Skip to content

Commit

Permalink
feat: add v3 matchers
Browse files Browse the repository at this point in the history
  • Loading branch information
mefellows committed May 17, 2021
1 parent 0fc423a commit 2a5b24b
Show file tree
Hide file tree
Showing 8 changed files with 312 additions and 53 deletions.
44 changes: 27 additions & 17 deletions examples/v3/consumer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,25 +107,28 @@ func TestConsumerV3(t *testing.T) {
// Set up our expected interactions.
mockProvider.
AddInteraction().
// TODO: map this to given_with_param interface!
Given(v3.ProviderStateV3{
Name: "User foo exists",
Parameters: map[string]string{
Parameters: map[string]interface{}{
"id": "foo",
},
}).
UponReceiving("A request to do a foo").
WithRequest(v3.Request{
Method: "POST",
Path: v3.Regex("/foobar", `\/foo.*`),
Headers: v3.MapMatcher{"Content-Type": s("application/json"), "Authorization": s("Bearer 1234")},
// Body: v3.MapMatcher{
// "name": s("billy"),
// "dateTime": v3.DateTimeGenerated("2020-02-02", "YYYY-MM-dd"),
// },
Headers: v3.MapMatcher{"Content-Type": s("application/json"), "Authorization": v3.Like("Bearer 1234")},
Body: v3.MapMatcher{
"id": v3.Like(27),
"name": v3.FromProviderState("${name}", "billy"),
"lastName": v3.Like("billy"),
"datetime": v3.DateTimeGenerated("2020-01-01T08:00:45", "yyyy-MM-dd'T'HH:mm:ss"),
},

// Alternative use MatchV3
// Body: v3.MatchV3(&User{}),
Body: v3.MatchV2(&User{}),
// Body: v3.MatchV2(&User{}),
Query: v3.QueryMatcher{
"baz": []v3.Matcher{
v3.Regex("bar", "[a-z]+"),
Expand All @@ -139,15 +142,22 @@ func TestConsumerV3(t *testing.T) {
Headers: v3.MapMatcher{"Content-Type": s("application/json")},
// Body: v3.MatchV3(&User{}),
Body: v3.MapMatcher{
"dateTime": v3.Regex("2020-01-01", "[0-9\\-]+"),
"name": s("FirstName"),
"lastName": s("LastName"),
// "superstring": v3.Includes("foo"),
// "id": v3.Integer(12),
// "accountBalance": v3.Decimal(123.76),
// "itemsMinMax": v3.ArrayMinMaxLike(27, 3, 5),
// "itemsMin": v3.ArrayMinLike("thereshouldbe3ofthese", 3),
// "equality": v3.Equality("a thing"),
"dateTime": v3.Regex("2020-01-01", "[0-9\\-]+"),
"name": s("FirstName"),
"lastName": s("LastName"),
"superstring": v3.Includes("foo"),
"id": v3.Integer(12),
"accountBalance": v3.Decimal(123.76),
"itemsMinMax": v3.ArrayMinMaxLike(27, 3, 5),
"itemsMin": v3.ArrayMinLike("thereshouldbe3ofthese", 3),
"equality": v3.Equality("a thing"),
"arrayContaining": v3.ArrayContaining([]interface{}{
v3.Like("string"),
v3.Integer(1),
v3.MapMatcher{
"foo": v3.Like("bar"),
},
}),
},
})

Expand Down Expand Up @@ -218,7 +228,7 @@ var test = func(config v3.MockServerConfig) error {
RawQuery: "baz=bat&baz=foo&baz=something", // Default behaviour, test matching
// RawQuery: "baz[]=bat&baz[]=foo&baz[]=something", // TODO: Rust v3 does not support this syntax
},
Body: ioutil.NopCloser(strings.NewReader(`{"id": 27, "name":"billy", "lastName":"sampson", "datetime":"2020-01-01'T'08:00:45"}`)),
Body: ioutil.NopCloser(strings.NewReader(`{"id": 27, "name":"billy", "lastName":"sampson", "datetime":"2021-01-01T08:00:45"}`)),
Header: make(http.Header),
}

Expand Down
2 changes: 1 addition & 1 deletion v3/http_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func NewHTTPMockProviderV2(config MockHTTPProviderConfigV2) (*HTTPMockProviderV2

// TODO: this needs to be plumbed into the new native interface
// We'll need a reference to the underlying object so that we can cross the FFI boundary
// with each additional modification to the InteractionV2 object
// with each additional modification to the Interaction object
func (p *HTTPMockProviderV2) AddInteraction() *InteractionV2 {
log.Println("[DEBUG] pact add v2 interaction")
interaction := p.httpMockProvider.mockserver.NewInteraction("")
Expand Down
3 changes: 2 additions & 1 deletion v3/interaction_v2.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package v3

// InteractionV2 is the main implementation of the Pact interface.
// InteractionV2 sets up an expected request/response on a mock server
// and is replayed on the provider side for verification
type InteractionV2 struct {
Interaction

Expand Down
7 changes: 4 additions & 3 deletions v3/interaction_v3.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package v3

// ProviderStateV3 allows parameters and a description to be passed to the verification process
type ProviderStateV3 struct {
Name string `json:"name"`
Parameters interface{} `json:"params,omitempty"`
Name string `json:"name"`
Parameters map[string]interface{} `json:"params,omitempty"`
}

// ProviderStateV3Response may return values in the state setup
Expand All @@ -12,16 +12,17 @@ type ProviderStateV3Response map[string]interface{}

// InteractionV3 sets up an expected request/response on a mock server
// and is replayed on the provider side for verification
// TODO: HTTPInteraction?
type InteractionV3 struct {
Interaction

// Provider state to be written into the Pact file
States []ProviderStateV3 `json:"providerStates,omitempty"`
}

// Given specifies a provider state. Optional.
func (i *InteractionV3) Given(state ProviderStateV3) *InteractionV3 {
i.States = append(i.States, state)
i.Interaction.interaction.GivenWithParameter(state.Name, state.Parameters)

return i
}
6 changes: 4 additions & 2 deletions v3/internal/native/mockserver/mock_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,14 +425,15 @@ func (i *Interaction) Given(state string) *Interaction {
return i
}

func (i *Interaction) GivenWithParameter(state string, params map[string]string) *Interaction {
func (i *Interaction) GivenWithParameter(state string, params map[string]interface{}) *Interaction {
cState := C.CString(state)
defer free(cState)

for k, v := range params {
cKey := C.CString(k)
defer free(cKey)
cValue := C.CString(v)
param := stringFromInterface(v)
cValue := C.CString(param)
defer free(cValue)

C.given_with_param(i.handle, cState, cKey, cValue)
Expand Down Expand Up @@ -477,6 +478,7 @@ func (i *Interaction) withHeaders(part interactionType, valueOrMatcher map[strin
cValue := C.CString(value)
defer free(cValue)

// TODO: the index here only applies to headers with multiple values
C.with_header(i.handle, C.int(part), cName, C.int(0), cValue)
}

Expand Down
37 changes: 11 additions & 26 deletions v3/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func (m eachLike) MarshalJSON() ([]byte, error) {
}

type like struct {
Type string `json:"pact:matcher:type"`
Value interface{} `json:"value"`
}

Expand All @@ -59,15 +60,6 @@ func (m like) string() string {
return fmt.Sprintf("%s", m.Value)
}

func (m like) MarshalJSON() ([]byte, error) {
type marshaler like

return json.Marshal(struct {
Type string `json:"pact:matcher:type"`
marshaler
}{"type", marshaler(m)})
}

type term struct {
Value string `json:"value"`
Type string `json:"pact:matcher:type"`
Expand Down Expand Up @@ -97,8 +89,16 @@ func (m term) MarshalJSON() ([]byte, error) {
// EachLike specifies that a given element in a JSON body can be repeated
// "minRequired" times. Number needs to be 1 or greater
func EachLike(content interface{}, minRequired int) Matcher {
if minRequired < 1 {
log.Println("[WARN] min value to an array matcher can't be less than one")
minRequired = 1
}
examples := make([]interface{}, minRequired)
for i := 0; i < minRequired; i++ {
examples[i] = content
}
return eachLike{
Value: content,
Value: examples,
Min: minRequired,
}
}
Expand All @@ -109,6 +109,7 @@ var ArrayMinLike = EachLike
// on type (int, string etc.) instead of a verbatim match.
func Like(content interface{}) Matcher {
return like{
Type: "type",
Value: content,
}
}
Expand All @@ -132,9 +133,6 @@ func Identifier() Matcher {
return Like(42)
}

// Integer defines a matcher that accepts ints. Identical to Identifier.
var Integer = Identifier

// IPAddress defines a matcher that accepts valid IPv4 addresses.
func IPAddress() Matcher {
return Regex("127.0.0.1", ipAddress)
Expand All @@ -148,11 +146,6 @@ func IPv6Address() Matcher {
return Regex("::ffff:192.0.2.128", ipAddress)
}

// Decimal defines a matcher that accepts any decimal value.
func Decimal() Matcher {
return Like(42.0)
}

// Timestamp matches a pattern corresponding to the ISO_DATETIME_FORMAT, which
// is "yyyy-MM-dd'T'HH:mm:ss". The current date and time is used as the eaxmple.
func Timestamp() Matcher {
Expand Down Expand Up @@ -193,14 +186,6 @@ type Matcher interface {
GetValue() interface{}
}

// MatcherV3 denotes a V3 specific Matcher
type MatcherV3 interface {
Matcher

// denote a v3 matcher
Generator()
}

// S is the string primitive wrapper (alias) for the Matcher type,
// it allows plain strings to be matched
// To keep backwards compatible with previous versions
Expand Down
Loading

0 comments on commit 2a5b24b

Please sign in to comment.