From 4d2cdeb7d638592bd4b89a9a0b7a291e1ba6a602 Mon Sep 17 00:00:00 2001 From: Matt Fellows Date: Tue, 30 May 2023 17:36:58 +1000 Subject: [PATCH] feat: add an Avro example --- Makefile | 1 + examples/avro/avro_consumer_test.go | 103 ++++++++++++++++++++++++++++ examples/avro/avro_provider_test.go | 71 +++++++++++++++++++ examples/avro/codec.go | 21 ++++++ examples/avro/user.avsc | 9 +++ examples/avro/user.go | 6 ++ go.mod | 4 +- go.sum | 8 ++- 8 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 examples/avro/avro_consumer_test.go create mode 100644 examples/avro/avro_provider_test.go create mode 100644 examples/avro/codec.go create mode 100644 examples/avro/user.avsc create mode 100644 examples/avro/user.go diff --git a/Makefile b/Makefile index b7a6d5d0..cbe424e1 100755 --- a/Makefile +++ b/Makefile @@ -46,6 +46,7 @@ download_plugins: ~/.pact/bin/pact-plugin-cli -y install https://github.com/pactflow/pact-protobuf-plugin/releases/tag/v-0.3.0 ~/.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.7 + ~/.pact/bin/pact-plugin-cli -y install https://github.com/austek/pact-avro-plugin/releases/tag/v0.0.3 cli: @if [ ! -d pact/bin ]; then\ diff --git a/examples/avro/avro_consumer_test.go b/examples/avro/avro_consumer_test.go new file mode 100644 index 00000000..5f44ad5d --- /dev/null +++ b/examples/avro/avro_consumer_test.go @@ -0,0 +1,103 @@ +//go:build consumer +// +build consumer + +package avro + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/url" + "os" + "testing" + + "github.com/pact-foundation/pact-go/v2/consumer" + + "path/filepath" + + "github.com/stretchr/testify/assert" +) + +var dir, _ = os.Getwd() + +func TestAvroHTTP(t *testing.T) { + mockProvider, err := consumer.NewV4Pact(consumer.MockHTTPProviderConfig{ + Consumer: "AvroConsumer", + Provider: "AvroProvider", + PactDir: filepath.ToSlash(fmt.Sprintf("%s/../pacts", dir)), + }) + assert.NoError(t, err) + + dir, _ := os.Getwd() + path := fmt.Sprintf("%s/user.avsc", dir) + + avroResponse := `{ + "pact:avro": "` + path + `", + "pact:record-name": "User", + "pact:content-type": "avro/binary", + "id": "matching(number, 1)", + "username": "notEmpty('matt')" + }` + + // Set up our expected interactions. + err = mockProvider. + AddInteraction(). + UponReceiving("A request to do get some Avro stuff"). + UsingPlugin(consumer.PluginConfig{ + Plugin: "avro", + Version: "0.0.2", + }). + WithRequest("GET", "/avro"). + WillRespondWith(200, func(res *consumer.V4InteractionWithPluginResponseBuilder) { + res.PluginContents("avro/binary", avroResponse) + }). + ExecuteTest(t, func(msc consumer.MockServerConfig) error { + resp, err := callServiceHTTP(msc) + + assert.Equal(t, int64(1), resp.ID) + assert.Equal(t, "matt", resp.Username) // ??????! + + return err + }) + assert.NoError(t, err) +} + +func callServiceHTTP(msc consumer.MockServerConfig) (*User, error) { + client := &http.Client{} + req := &http.Request{ + Method: "GET", + URL: &url.URL{ + Host: fmt.Sprintf("%s:%d", msc.Host, msc.Port), + Scheme: "http", + Path: "/avro", + }, + Header: make(http.Header), + } + + req.Header.Set("Content-Type", "avro/binary;record=User") + + res, err := client.Do(req) + + if err != nil { + return nil, err + } + + bytes, err := ioutil.ReadAll(res.Body) + + if err != nil { + return nil, err + } + + codec := getCodec() + native, _, err := codec.NativeFromBinary(bytes) + if err != nil { + return nil, err + } + + user := &User{ + ID: native.(map[string]interface{})["id"].(int64), + Username: native.(map[string]interface{})["username"].(string), + } + + return user, err +} diff --git a/examples/avro/avro_provider_test.go b/examples/avro/avro_provider_test.go new file mode 100644 index 00000000..d8cbaf33 --- /dev/null +++ b/examples/avro/avro_provider_test.go @@ -0,0 +1,71 @@ +//go:build provider +// +build provider + +package avro + +import ( + "fmt" + "log" + "net/http" + + "os" + "path/filepath" + "testing" + + "github.com/pact-foundation/pact-go/v2/provider" + "github.com/pact-foundation/pact-go/v2/utils" + "github.com/stretchr/testify/assert" +) + +var dir, _ = os.Getwd() +var pactDir = fmt.Sprintf("%s/../pacts", dir) + +func TestAvroHTTPProvider(t *testing.T) { + httpPort, _ := utils.GetFreePort() + + // Start provider API in the background + go startHTTPProvider(httpPort) + + verifier := provider.NewVerifier() + + // Verify the Provider with local Pact Files + err := verifier.VerifyProvider(t, provider.VerifyRequest{ + ProviderBaseURL: fmt.Sprintf("http://127.0.0.1:%d", httpPort), + Provider: "AvroProvider", + PactFiles: []string{ + filepath.ToSlash(fmt.Sprintf("%s/AvroConsumer-AvroProvider.json", pactDir)), + }, + }) + + assert.NoError(t, err) +} + +func startHTTPProvider(port int) { + mux := http.NewServeMux() + + mux.HandleFunc("/avro", func(w http.ResponseWriter, req *http.Request) { + w.Header().Add("Content-Type", "avro/binary;record=User") + + user := &User{ + ID: 1, + Username: "matt", + // Username: "sally", // matching rules not supported? + } + + codec := getCodec() + binary, err := codec.BinaryFromNative(nil, map[string]interface{}{ + "id": user.ID, + "username": user.Username, + }) + if err != nil { + log.Println("ERROR: ", err) + w.WriteHeader(500) + } else { + fmt.Fprintf(w, string(binary)) + w.WriteHeader(200) + } + }) + + log.Printf("started HTTP server on port: %d\n", port) + log.Fatal(http.ListenAndServe(fmt.Sprintf("127.0.0.1:%d", port), mux)) +} diff --git a/examples/avro/codec.go b/examples/avro/codec.go new file mode 100644 index 00000000..b1260694 --- /dev/null +++ b/examples/avro/codec.go @@ -0,0 +1,21 @@ +package avro + +import ( + "io/ioutil" + + "github.com/linkedin/goavro/v2" +) + +func getCodec() *goavro.Codec { + schema, err := ioutil.ReadFile("user.avsc") + if err != nil { + panic(err) + } + + codec, err := goavro.NewCodec(string(schema)) + if err != nil { + panic(err) + } + + return codec +} diff --git a/examples/avro/user.avsc b/examples/avro/user.avsc new file mode 100644 index 00000000..a4add243 --- /dev/null +++ b/examples/avro/user.avsc @@ -0,0 +1,9 @@ +{ + "type": "record", + "name": "User", + "namespace": "io.pact", + "fields" : [ + {"name": "id", "type": "long"}, + {"name": "username", "type": "string"} + ] +} \ No newline at end of file diff --git a/examples/avro/user.go b/examples/avro/user.go new file mode 100644 index 00000000..390c36a1 --- /dev/null +++ b/examples/avro/user.go @@ -0,0 +1,6 @@ +package avro + +type User struct { + ID int64 `json:"id"` + Username string `json:"username"` +} diff --git a/go.mod b/go.mod index 1a631011..435f4ced 100644 --- a/go.mod +++ b/go.mod @@ -14,10 +14,11 @@ require ( github.com/hashicorp/go-version v1.5.0 github.com/hashicorp/logutils v1.0.0 github.com/klauspost/compress v1.15.4 // indirect + github.com/linkedin/goavro/v2 v2.12.0 github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/spf13/afero v1.6.0 github.com/spf13/cobra v1.1.3 - github.com/stretchr/testify v1.7.0 + github.com/stretchr/testify v1.7.5 github.com/ulikunitz/xz v0.5.10 // indirect golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect @@ -27,5 +28,4 @@ require ( google.golang.org/grpc v1.46.2 google.golang.org/protobuf v1.28.0 gopkg.in/yaml.v2 v2.4.0 - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 1b4c569d..3e216ba8 100644 --- a/go.sum +++ b/go.sum @@ -161,6 +161,7 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -279,6 +280,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/linkedin/goavro/v2 v2.12.0 h1:rIQQSj8jdAUlKQh6DttK8wCRv4t4QO09g1C4aBWXslg= +github.com/linkedin/goavro/v2 v2.12.0/go.mod h1:KXx+erlq+RPlGSPmLF7xGo6SAbh8sCQ53x064+ioxhk= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -345,13 +348,16 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.5 h1:s5PTfem8p8EbKQOctVV53k6jCJt3UX4IEJzwh+C324Q= +github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=