Skip to content

Commit

Permalink
feat: add all in one interface back into consumer package for simpler…
Browse files Browse the repository at this point in the history
… migrations
  • Loading branch information
mefellows committed Jun 8, 2021
1 parent 4525047 commit c2f5f83
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 49 deletions.
77 changes: 76 additions & 1 deletion consumer/interaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ type InteractionResponse struct {
interaction *Interaction
}

type AllInteractionRequest struct {
// Reference to the native rust handle
interactionHandle *mockserver.Interaction
interaction *Interaction
}

type AllInteractionResponse struct {
// Reference to the native rust handle
interactionHandle *mockserver.Interaction
interaction *Interaction
}

// UponReceiving specifies the name of the test case. This becomes the name of
// the consumer/provider pair in the Pact file. Mandatory.
func (i *Interaction) UponReceiving(description string) *Interaction {
Expand All @@ -39,10 +51,49 @@ func (i *Interaction) UponReceiving(description string) *Interaction {
return i
}

// WithCompleteRequest specifies the details of the HTTP request that will be used to
// confirm that the Provider provides an API listening on the given interface.
// Mandatory.
func (i *Interaction) WithCompleteRequest(request Request) *AllInteractionResponse {
i.interaction.WithRequest(string(request.Method), request.Path)

if request.Body != nil {
i.interaction.WithJSONRequestBody(request.Body)
}

if request.Headers != nil {
i.interaction.WithRequestHeaders(headersMapMatcherToNativeHeaders(request.Headers))
}

if request.Query != nil {
i.interaction.WithQuery(headersMapMatcherToNativeHeaders(request.Query))
}

return &AllInteractionResponse{
interactionHandle: i.interaction,
interaction: i,
}
}

// WithCompleteResponse specifies the details of the HTTP response required by the consumer
func (i *AllInteractionResponse) WithCompleteResponse(response Response) *Interaction {
if response.Body != nil {
i.interactionHandle.WithJSONResponseBody(response.Body)
}

if response.Headers != nil {
i.interactionHandle.WithResponseHeaders(headersMapMatcherToNativeHeaders(response.Headers))
}

i.interactionHandle.WithStatus(response.Status)

return i.interaction
}

// WithRequest specifies the details of the HTTP request that will be used to
// confirm that the Provider provides an API listening on the given interface.
// Mandatory.
func (i *Interaction) WithRequest(method models.Method, path matchers.Matcher) *InteractionRequest {
func (i *Interaction) WithRequest(method Method, path matchers.Matcher) *InteractionRequest {
i.interaction.WithRequest(string(method), path)

return &InteractionRequest{
Expand Down Expand Up @@ -97,6 +148,12 @@ func (i *InteractionRequest) WithBinaryBody(body []byte) *InteractionRequest {
return i
}

func (i *InteractionRequest) WithMultipartBody(contentType string, filename string, mimePartName string) *InteractionRequest {
i.interactionHandle.WithRequestMultipartFile(contentType, filename, mimePartName)

return i
}

func (i *InteractionRequest) WithBody(contentType string, body []byte) *InteractionRequest {
// Check if someone tried to add an object as a string representation
// as per original allowed implementation, e.g.
Expand Down Expand Up @@ -170,6 +227,12 @@ func (i *InteractionResponse) WithBinaryBody(body []byte) *InteractionResponse {
return i
}

func (i *InteractionResponse) WithMultipartBody(contentType string, filename string, mimePartName string) *InteractionResponse {
i.interactionHandle.WithResponseMultipartFile(contentType, filename, mimePartName)

return i
}

func (i *InteractionResponse) WithBody(contentType string, body []byte) *InteractionResponse {
i.interactionHandle.WithResponseBody(contentType, body)

Expand Down Expand Up @@ -271,3 +334,15 @@ func headersMatcherToNativeHeaders(headers matchers.HeadersMatcher) map[string][

return h
}

func headersMapMatcherToNativeHeaders(headers matchers.MapMatcher) map[string][]interface{} {
h := make(map[string][]interface{})

for k, v := range headers {
h[k] = []interface{}{
v,
}
}

return h
}
13 changes: 13 additions & 0 deletions consumer/request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package consumer

import "github.com/pact-foundation/pact-go/v2/matchers"

// Request is the default implementation of the Request interface.
type Request struct {
Method string `json:"method"`
Path matchers.Matcher `json:"path"`
Query matchers.MapMatcher `json:"query,omitempty"`
Headers matchers.MapMatcher `json:"headers,omitempty"`
Body interface{} `json:"body,omitempty"`
}
type Method string
10 changes: 10 additions & 0 deletions consumer/response.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package consumer

import "github.com/pact-foundation/pact-go/v2/matchers"

// Response is the default implementation of the Response interface.
type Response struct {
Status int `json:"status"`
Headers matchers.MapMatcher `json:"headers,omitempty"`
Body interface{} `json:"body,omitempty"`
}
117 changes: 92 additions & 25 deletions examples/consumer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"strings"
"testing"

"github.com/pact-foundation/pact-go/v2/consumer"
v3 "github.com/pact-foundation/pact-go/v2/consumer"
. "github.com/pact-foundation/pact-go/v2/sugar"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -148,6 +149,55 @@ func TestConsumerV3(t *testing.T) {
assert.NoError(t, err)
}

func TestConsumerV2AllInOne(t *testing.T) {
SetLogLevel("TRACE")

mockProvider, err := v3.NewV2Pact(v3.MockHTTPProviderConfig{
Consumer: "V2ConsumerAllInOne",
Provider: "V2Provider",
Host: "127.0.0.1",
TLS: true,
})

assert.NoError(t, err)

// Set up our expected interactions.
mockProvider.
AddInteraction().
Given("User foo exists").
UponReceiving("A request to do a foo").
WithCompleteRequest(consumer.Request{
Method: "POST",
Path: Regex("/foobar", `\/foo.*`),
Query: MapMatcher{
"baz": Regex("bat", "[a-zA-Z]+"),
},
Headers: commonHeaders,
Body: Map{
"id": Like(27),
"name": Like("billy"),
"datetime": Like("2020-01-01'T'08:00:45"),
"lastName": Like("billy"),
// "equality": Equality("a thing"), // Add this in and watch me panic
},
}).
WithCompleteResponse(consumer.Response{
Headers: commonHeaders,
Status: 200,
Body: Map{
"datetime": Regex("2020-01-01", "[0-9\\-]+"),
"name": S("Billy"),
"lastName": S("Sampson"),
"itemsMin": ArrayMinLike("thereshouldbe3ofthese", 3),
// "equality": Equality("a thing"), // Add this in and watch me panic
},
})

// Execute pact test
err = mockProvider.ExecuteTest(legacyTest)
assert.NoError(t, err)
}

func TestMessagePact(t *testing.T) {
SetLogLevel("TRACE")

Expand Down Expand Up @@ -189,34 +239,43 @@ type User struct {
}

// Pass in test case
var test = func(config MockServerConfig) error {
config.TLSConfig.InsecureSkipVerify = true
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: config.TLSConfig,
},
}
req := &http.Request{
Method: "POST",
URL: &url.URL{
Host: fmt.Sprintf("%s:%d", "localhost", config.Port),
Scheme: "https",
Path: "/foobar",
RawQuery: "baz=bat&baz=foo&baz=something", // Default behaviour, test matching
},
Body: ioutil.NopCloser(strings.NewReader(`{"id": 27, "name":"billy", "lastName":"sampson", "datetime":"2021-01-01T08:00:45"}`)),
Header: make(http.Header),
}

// 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")
req.Header.Set("Authorization", "Bearer 1234")
var test = func() func(config MockServerConfig) error {
return rawTest("baz=bat&baz=foo&baz=something")
}()

var rawTest = func(query string) func(config MockServerConfig) error {

_, err := client.Do(req)
return func(config MockServerConfig) error {

return err
config.TLSConfig.InsecureSkipVerify = true
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: config.TLSConfig,
},
}
req := &http.Request{
Method: "POST",
URL: &url.URL{
Host: fmt.Sprintf("%s:%d", "localhost", config.Port),
Scheme: "https",
Path: "/foobar",
RawQuery: query,
},
Body: ioutil.NopCloser(strings.NewReader(`{"id": 27, "name":"billy", "lastName":"sampson", "datetime":"2021-01-01T08:00:45"}`)),
Header: make(http.Header),
}

// 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")
req.Header.Set("Authorization", "Bearer 1234")

_, err := client.Do(req)

return err
}
}

// Message Pact - wrapped handler extracts the message
Expand All @@ -234,3 +293,11 @@ var userHandler = func(u User) error {

return nil
}

var commonHeaders = MapMatcher{
"Content-Type": Regex("application/json; charset=utf-8", `application\/json`),
}

var legacyTest = func() func(config MockServerConfig) error {
return rawTest("baz=bat")
}()
12 changes: 0 additions & 12 deletions models/request.go

This file was deleted.

8 changes: 0 additions & 8 deletions models/response.go

This file was deleted.

6 changes: 3 additions & 3 deletions sugar/sugar.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ type Map = matchers.MapMatcher
type String = matchers.String
type S = matchers.S

// Builders
var NewMessagePactV3 = message.NewMessagePactV3
// HTTP
var NewV2Pact = consumer.NewV2Pact
var NewV3Pact = consumer.NewV3Pact

// HTTP
type HTTPVerifier = provider.HTTPVerifier

// Message
var NewMessagePactV3 = message.NewMessagePactV3

type MessageVerifier = message.MessageVerifier
type MessageHandlers = message.MessageHandlers
type VerifyMessageRequest = message.VerifyMessageRequest
Expand Down

0 comments on commit c2f5f83

Please sign in to comment.