diff --git a/Makefile b/Makefile index 1fecdca2e..65cbdd44f 100755 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/examples/grpc/grpc_consumer_test.go b/examples/grpc/grpc_consumer_test.go index 0e8ed36d2..d2d70a4da 100644 --- a/examples/grpc/grpc_consumer_test.go +++ b/examples/grpc/grpc_consumer_test.go @@ -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 diff --git a/examples/protobuf-message/protobuf_consumer_test.go b/examples/protobuf-message/protobuf_consumer_test.go new file mode 100644 index 000000000..92e4ef2c3 --- /dev/null +++ b/examples/protobuf-message/protobuf_consumer_test.go @@ -0,0 +1,54 @@ +//go:build consumer +// +build consumer + +package protobuf + +import ( + "fmt" + "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 { + // TODO: normally would actually read/consume the message + return nil + }) + + assert.NoError(t, err) +} diff --git a/examples/protobuf-message/protobuf_provider_test.go b/examples/protobuf-message/protobuf_provider_test.go new file mode 100644 index 000000000..94770cf51 --- /dev/null +++ b/examples/protobuf-message/protobuf_provider_test.go @@ -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) +} diff --git a/message/verifier.go b/message/verifier.go index e9fe5fd36..54b2f3fa5 100644 --- a/message/verifier.go +++ b/message/verifier.go @@ -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) + } } } @@ -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 @@ -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)