Skip to content

Commit

Permalink
feat: support binary bodies in async plugin messages
Browse files Browse the repository at this point in the history
Fixes #265
  • Loading branch information
mefellows committed Jul 10, 2023
1 parent a037e53 commit cd68594
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 10 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ deps: download_plugins
download_plugins:
@echo "--- 🐿 Installing plugins"; \
./scripts/install-cli.sh
~/.pact/bin/pact-plugin-cli -y install https://github.com/pactflow/pact-protobuf-plugin/releases/tag/v-0.3.1
~/.pact/bin/pact-plugin-cli -y install https://github.com/pactflow/pact-protobuf-plugin/releases/tag/v-0.3.4
~/.pact/bin/pact-plugin-cli -y install https://github.com/pact-foundation/pact-plugins/releases/tag/csv-plugin-0.0.1
~/.pact/bin/pact-plugin-cli -y install https://github.com/mefellows/pact-matt-plugin/releases/tag/v0.0.9

Expand Down
2 changes: 1 addition & 1 deletion examples/grpc/grpc_consumer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestGrpcInteraction(t *testing.T) {
Given("feature 'Big Tree' exists").
UsingPlugin(message.PluginConfig{
Plugin: "protobuf",
Version: "0.3.1",
Version: "0.3.4",
}).
WithContents(grpcInteraction, "application/protobuf").
StartTransport("grpc", "127.0.0.1", nil). // For plugin tests, we can't assume if a transport is needed, so this is optional
Expand Down
57 changes: 57 additions & 0 deletions examples/protobuf-message/protobuf_consumer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//go:build consumer
// +build consumer

package protobuf

import (
"fmt"
"log"
"os"
"path/filepath"
"testing"

message "github.com/pact-foundation/pact-go/v2/message/v4"
"github.com/stretchr/testify/assert"
)

var dir, _ = os.Getwd()

func TestPluginMessageConsumer(t *testing.T) {
p, _ := message.NewAsynchronousPact(message.Config{
Consumer: "protobufmessageconsumer",
Provider: "protobufmessageprovider",
PactDir: filepath.ToSlash(fmt.Sprintf("%s/../pacts", dir)),
})

dir, _ := os.Getwd()
path := fmt.Sprintf("%s/../grpc/routeguide/route_guide.proto", dir)

protoMessage := `{
"pact:proto": "` + path + `",
"pact:message-type": "Feature",
"pact:content-type": "application/protobuf",
"name": "notEmpty('Big Tree')",
"location": {
"latitude": "matching(number, 180)",
"longitude": "matching(number, 200)"
}
}`

err := p.AddAsynchronousMessage().
Given("the world exists").
ExpectsToReceive("feature message").
UsingPlugin(message.PluginConfig{
Plugin: "protobuf",
Version: "0.3.4",
}).
WithContents(protoMessage, "application/protobuf").
ExecuteTest(t, func(m message.AsynchronousMessage) error {
log.Println("asynchronous message")
log.Println(m)
// TODO: normally read the message
return nil
})

assert.NoError(t, err)
}
58 changes: 58 additions & 0 deletions examples/protobuf-message/protobuf_provider_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//go:build provider
// +build provider

package protobuf

import (
"fmt"
"os"
"path/filepath"
"testing"

"github.com/golang/protobuf/proto"
"github.com/pact-foundation/pact-go/v2/examples/grpc/routeguide"
pactlog "github.com/pact-foundation/pact-go/v2/log"
"github.com/pact-foundation/pact-go/v2/message"
"github.com/pact-foundation/pact-go/v2/models"
"github.com/pact-foundation/pact-go/v2/provider"
pactversion "github.com/pact-foundation/pact-go/v2/version"
"github.com/stretchr/testify/assert"
)

func TestPluginMessageProvider(t *testing.T) {
var dir, _ = os.Getwd()
var pactDir = fmt.Sprintf("%s/../pacts", dir)

err := pactlog.SetLogLevel("TRACE")
assert.NoError(t, err)

pactversion.CheckVersion()

verifier := provider.NewVerifier()

functionMappings := message.Handlers{
"feature message": func([]models.ProviderState) (message.Body, message.Metadata, error) {
fmt.Println("feature message handler")
feature, _ := proto.Marshal(&routeguide.Feature{
Name: "fake feature",
Location: &routeguide.Point{
Latitude: int32(1),
Longitude: int32(1),
},
})
return feature, message.Metadata{
"contentType": "application/protobuf;message=Feature", // <- This is required to ensure the correct type is matched
}, nil
},
}

err = verifier.VerifyProvider(t, provider.VerifyRequest{
PactFiles: []string{
filepath.ToSlash(fmt.Sprintf("%s/protobufmessageconsumer-protobufmessageprovider.json", pactDir)),
},
Provider: "protobufmessageprovider",
MessageHandlers: functionMappings,
})

assert.NoError(t, err)
}
31 changes: 23 additions & 8 deletions message/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,18 @@ func appendMetadataToResponseHeaders(metadata Metadata, w http.ResponseWriter) {

w.Header().Add(PACT_MESSAGE_METADATA_HEADER, encoded)
w.Header().Add(PACT_MESSAGE_METADATA_HEADER2, encoded)
// Content-Type must match the pact file if provider
if metadata["contentType"] != nil {
w.Header().Set("Content-Type", metadata["contentType"].(string))
} else if metadata["content-type"] != nil {
w.Header().Set("Content-Type", metadata["content-type"].(string))
} else if metadata["Content-Type"] != nil {
w.Header().Set("Content-Type", metadata["Content-Type"].(string))
} else {
defaultContentType := "application/json; charset=utf-8"
log.Println("[WARN] no content type (key 'contentType') found in message metadata. Defaulting to", defaultContentType)
w.Header().Set("Content-Type", defaultContentType)
}
}
}

Expand All @@ -202,9 +214,6 @@ func CreateMessageHandler(messageHandlers Handlers) proxy.Middleware {

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/__messages" {
// TODO: should this be set by the provider itself? How does the metadata go back?
w.Header().Set("Content-Type", "application/json; charset=utf-8")

log.Printf("[TRACE] message verification handler")

// Extract message
Expand Down Expand Up @@ -248,11 +257,17 @@ func CreateMessageHandler(messageHandlers Handlers) proxy.Middleware {
// Write the body back
appendMetadataToResponseHeaders(metadata, w)

body, errM := json.Marshal(res)
if errM != nil {
w.WriteHeader(http.StatusServiceUnavailable)
log.Println("[ERROR] error marshalling objcet:", errM)
return
if bytes, ok := res.([]byte); ok {
log.Println("[DEBUG] checking type of message is []byte")
body = bytes
} else {
log.Println("[DEBUG] message body is not []byte, serialising as JSON")
body, err = json.Marshal(res)
if err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
log.Println("[ERROR] error marshalling object:", err)
return
}
}

w.WriteHeader(http.StatusOK)
Expand Down

0 comments on commit cd68594

Please sign in to comment.