From 339903405e2eda8b231193f4b906a605dcb4c763 Mon Sep 17 00:00:00 2001 From: Matt Fellows <53900+mefellows@users.noreply.github.com> Date: Mon, 22 Apr 2024 21:59:23 +1000 Subject: [PATCH] feat: support graphql in v2 interface --- consumer/graphql/interaction.go | 140 ++++++++ consumer/graphql/response.go | 9 + examples/graphql/graphql_consumer_test.go | 134 ++++++++ .../GraphQLConsumer-GraphQLProvider.json | 96 ++++++ examples/pacts/AvroConsumer-AvroProvider.json | 81 +++++ examples/pacts/MattConsumer-MattProvider.json | 62 ++++ ...GoProductAPIConsumer-PactGoProductAPI.json | 50 +++ .../pacts/PactGoV2Consumer-V2Provider.json | 102 ++++++ .../PactGoV2ConsumerAllInOne-V2Provider.json | 86 +++++ ...PactGoV2ConsumerMatch-V2ProviderMatch.json | 98 ++++++ .../pacts/PactGoV3Consumer-V3Provider.json | 274 ++++++++++++++++ ...GoV3MessageConsumer-V3MessageProvider.json | 61 ++++ .../pacts/PactGoV4Consumer-V4Provider.json | 291 +++++++++++++++++ examples/pacts/grpcconsumer-grpcprovider.json | 303 ++++++++++++++++++ .../matttcpconsumer-matttcpprovider.json | 56 ++++ ...ssageconsumer-protobufmessageprovider.json | 88 +++++ go.mod | 4 + go.sum | 16 + 18 files changed, 1951 insertions(+) create mode 100644 consumer/graphql/interaction.go create mode 100644 consumer/graphql/response.go create mode 100644 examples/graphql/graphql_consumer_test.go create mode 100644 examples/graphql/pacts/GraphQLConsumer-GraphQLProvider.json create mode 100644 examples/pacts/AvroConsumer-AvroProvider.json create mode 100644 examples/pacts/MattConsumer-MattProvider.json create mode 100644 examples/pacts/PactGoProductAPIConsumer-PactGoProductAPI.json create mode 100644 examples/pacts/PactGoV2Consumer-V2Provider.json create mode 100644 examples/pacts/PactGoV2ConsumerAllInOne-V2Provider.json create mode 100644 examples/pacts/PactGoV2ConsumerMatch-V2ProviderMatch.json create mode 100644 examples/pacts/PactGoV3Consumer-V3Provider.json create mode 100644 examples/pacts/PactGoV3MessageConsumer-V3MessageProvider.json create mode 100644 examples/pacts/PactGoV4Consumer-V4Provider.json create mode 100644 examples/pacts/grpcconsumer-grpcprovider.json create mode 100644 examples/pacts/matttcpconsumer-matttcpprovider.json create mode 100644 examples/pacts/protobufmessageconsumer-protobufmessageprovider.json diff --git a/consumer/graphql/interaction.go b/consumer/graphql/interaction.go new file mode 100644 index 000000000..78326c7c2 --- /dev/null +++ b/consumer/graphql/interaction.go @@ -0,0 +1,140 @@ +package graphql + +import ( + "fmt" + "regexp" + + "github.com/pact-foundation/pact-go/v2/consumer" + "github.com/pact-foundation/pact-go/v2/matchers" +) + +// Variables represents values to be substituted into the query +type Variables map[string]interface{} + +// Query is the main implementation of the Pact interface. +type Query struct { + // HTTP Headers + Headers matchers.MapMatcher + + // Path to GraphQL endpoint + Path matchers.Matcher + + // HTTP Query String + QueryString matchers.MapMatcher + + // GraphQL Query + Query string + + // GraphQL Variables + Variables Variables + + // GraphQL Operation + Operation string + + // GraphQL method (usually POST, but can be get with a query string) + // NOTE: for query string users, the standard HTTP interaction should suffice + Method string + + // Supports graphql extensions such as https://www.apollographql.com/docs/apollo-server/performance/apq/ + Extensions Extensions +} +type Extensions map[string]interface{} + +// Specify the operation (if any) +func (r *Query) WithOperation(operation string) *Query { + r.Operation = operation + + return r +} + +// WithContentType overrides the default content-type (application/json) +// for the GraphQL Query +func (r *Query) WithContentType(contentType matchers.Matcher) *Query { + r.setHeader("content-type", contentType) + + return r +} + +// Specify the method (defaults to POST) +func (r *Query) WithMethod(method string) *Query { + r.Method = method + + return r +} + +// Given specifies a provider state. Optional. +func (r *Query) WithQuery(query string) *Query { + r.Query = query + + return r +} + +// Given specifies a provider state. Optional. +func (r *Query) WithVariables(variables Variables) *Query { + r.Variables = variables + + return r +} + +// Set the query extensions +func (r *Query) WithExtensions(extensions Extensions) *Query { + r.Extensions = extensions + + return r +} + +var defaultHeaders = matchers.MapMatcher{"content-type": matchers.String("application/json")} + +func (r *Query) setHeader(headerName string, value matchers.Matcher) *Query { + if r.Headers == nil { + r.Headers = defaultHeaders + } + + r.Headers[headerName] = value + + return r +} + +// Construct a Pact HTTP request for a GraphQL interaction +func Interaction(request Query) *consumer.Request { + if request.Headers == nil { + request.Headers = defaultHeaders + } + + return &consumer.Request{ + Method: request.Method, + Path: request.Path, + Query: request.QueryString, + Body: graphQLQueryBody{ + Operation: request.Operation, + Query: matchers.Regex(request.Query, escapeGraphQlQuery(request.Query)), + Variables: request.Variables, + }, + Headers: request.Headers, + } + +} + +type graphQLQueryBody struct { + Operation string `json:"operationName,omitempty"` + Query matchers.Matcher `json:"query"` + Variables Variables `json:"variables,omitempty"` +} + +func escapeSpace(s string) string { + r := regexp.MustCompile(`\s+`) + return r.ReplaceAllString(s, `\s*`) +} + +func escapeRegexChars(s string) string { + r := regexp.MustCompile(`(?m)[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]`) + + f := func(s string) string { + return fmt.Sprintf(`\%s`, s) + } + return r.ReplaceAllStringFunc(s, f) +} + +func escapeGraphQlQuery(s string) string { + return escapeSpace(escapeRegexChars(s)) +} diff --git a/consumer/graphql/response.go b/consumer/graphql/response.go new file mode 100644 index 000000000..0179bab5a --- /dev/null +++ b/consumer/graphql/response.go @@ -0,0 +1,9 @@ +package graphql + +// GraphQLRseponse models the GraphQL Response format. +// See also http://spec.graphql.org/October2021/#sec-Response-Format +type Response struct { + Data interface{} `json:"data,omitempty"` + Errors []interface{} `json:"errors,omitempty"` + Extensions map[string]interface{} `json:"extensions,omitempty"` +} diff --git a/examples/graphql/graphql_consumer_test.go b/examples/graphql/graphql_consumer_test.go new file mode 100644 index 000000000..2e8af80e1 --- /dev/null +++ b/examples/graphql/graphql_consumer_test.go @@ -0,0 +1,134 @@ +//go:build consumer +// +build consumer + +package graphql + +import ( + "context" + "fmt" + "net/http" + "testing" + + graphqlserver "github.com/graph-gophers/graphql-go" + "github.com/graph-gophers/graphql-go/example/starwars" + "github.com/graph-gophers/graphql-go/relay" + graphql "github.com/hasura/go-graphql-client" + "github.com/pact-foundation/pact-go/v2/consumer" + g "github.com/pact-foundation/pact-go/v2/consumer/graphql" + "github.com/pact-foundation/pact-go/v2/matchers" + "github.com/stretchr/testify/assert" +) + +func TestGraphQLConsumer(t *testing.T) { + // Create Pact connecting to local Daemon + pact, err := consumer.NewV4Pact(consumer.MockHTTPProviderConfig{ + Consumer: "GraphQLConsumer", + Provider: "GraphQLProvider", + }) + assert.NoError(t, err) + + // Set up our expected interactions. + err = pact. + AddInteraction(). + Given("User foo exists"). + UponReceiving("A request to get foo"). + WithCompleteRequest(*g.Interaction(g.Query{ + Method: "POST", + Path: matchers.String("/query"), + Query: `query ($characterID:ID!){ + hero { + id, + name + }, + character(id: $characterID) + { + name, + friends{ + name, + __typename + }, + appearsIn + } + }`, + // Operation: "SomeOperation", // if needed + Variables: g.Variables{ + "characterID": "1003", + }, + })).WithCompleteResponse(consumer.Response{ + Status: 200, + Headers: matchers.MapMatcher{"Content-Type": matchers.String("application/json")}, + Body: g.Response{ + Data: heroQuery{ + Hero: hero{ + ID: graphql.ID("1003"), + Name: "Darth Vader", + }, + Character: character{ + Name: "Darth Vader", + AppearsIn: []graphql.String{ + "EMPIRE", + }, + Friends: []friend{ + { + Name: "Wilhuff Tarkin", + Typename: "friends", + }, + }, + }, + }, + }}). + ExecuteTest(t, func(s consumer.MockServerConfig) error { + res, err := executeQuery(fmt.Sprintf("http://%s:%d", s.Host, s.Port)) + + fmt.Println(res) + assert.NoError(t, err) + assert.NotNil(t, res.Hero.ID) + + return nil + }) + + assert.NoError(t, err) +} + +func executeQuery(baseURL string) (heroQuery, error) { + var q heroQuery + + // Set up a GraphQL server. + schema, err := graphqlserver.ParseSchema(starwars.Schema, &starwars.Resolver{}) + if err != nil { + return q, err + } + mux := http.NewServeMux() + mux.Handle("/query", &relay.Handler{Schema: schema}) + + client := graphql.NewClient(fmt.Sprintf("%s/query", baseURL), nil) + + variables := map[string]interface{}{ + "characterID": graphql.ID("1003"), + } + err = client.Query(context.Background(), &q, variables) + if err != nil { + return q, err + } + + return q, nil +} + +type hero struct { + ID graphql.ID `json:"ID"` + Name graphql.String `json:"Name"` +} +type friend struct { + Name graphql.String `json:"Name"` + Typename graphql.String `json:"__typename" graphql:"__typename"` +} +type character struct { + Name graphql.String `json:"Name"` + Friends []friend `json:"Friends"` + AppearsIn []graphql.String `json:"AppearsIn"` +} + +type heroQuery struct { + Hero hero `json:"Hero"` + Character character `json:"character" graphql:"character(id: $characterID)"` +} diff --git a/examples/graphql/pacts/GraphQLConsumer-GraphQLProvider.json b/examples/graphql/pacts/GraphQLConsumer-GraphQLProvider.json new file mode 100644 index 000000000..0a0135c44 --- /dev/null +++ b/examples/graphql/pacts/GraphQLConsumer-GraphQLProvider.json @@ -0,0 +1,96 @@ +{ + "consumer": { + "name": "GraphQLConsumer" + }, + "interactions": [ + { + "description": "A request to get foo", + "pending": false, + "providerStates": [ + { + "name": "User foo exists" + } + ], + "request": { + "body": { + "content": { + "query": "query ($characterID:ID!){\n\t\t\t\thero {\n\t\t\t\t\tid,\n\t\t\t\t\tname\n\t\t\t\t},\n\t\t\t\tcharacter(id: $characterID)\n\t\t\t\t{\n\t\t\t\t\tname,\n\t\t\t\t\tfriends{\n\t\t\t\t\t\tname,\n\t\t\t\t\t\t__typename\n\t\t\t\t\t},\n\t\t\t\t\tappearsIn\n\t\t\t\t}\n\t\t\t}", + "variables": { + "characterID": "1003" + } + }, + "contentType": "application/json", + "encoded": false + }, + "headers": { + "Content-Type": [ + "application/json" + ] + }, + "matchingRules": { + "body": { + "$.query": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "query\\s*\\(\\$characterID:ID!\\)\\{\\s*hero\\s*\\{\\s*id,\\s*name\\s*\\},\\s*character\\(id:\\s*\\$characterID\\)\\s*\\{\\s*name,\\s*friends\\{\\s*name,\\s*__typename\\s*\\},\\s*appearsIn\\s*\\}\\s*\\}" + } + ] + } + }, + "header": {} + }, + "method": "POST", + "path": "/query" + }, + "response": { + "body": { + "content": { + "data": { + "Hero": { + "ID": "1003", + "Name": "Darth Vader" + }, + "character": { + "AppearsIn": [ + "EMPIRE" + ], + "Friends": [ + { + "Name": "Wilhuff Tarkin", + "__typename": "friends" + } + ], + "Name": "Darth Vader" + } + } + }, + "contentType": "application/json", + "encoded": false + }, + "headers": { + "Content-Type": [ + "application/json" + ] + }, + "status": 200 + }, + "transport": "http", + "type": "Synchronous/HTTP" + } + ], + "metadata": { + "pactRust": { + "ffi": "0.4.16", + "mockserver": "1.2.5", + "models": "1.1.19" + }, + "pactSpecification": { + "version": "4.0" + } + }, + "provider": { + "name": "GraphQLProvider" + } +} \ No newline at end of file diff --git a/examples/pacts/AvroConsumer-AvroProvider.json b/examples/pacts/AvroConsumer-AvroProvider.json new file mode 100644 index 000000000..736f74a97 --- /dev/null +++ b/examples/pacts/AvroConsumer-AvroProvider.json @@ -0,0 +1,81 @@ +{ + "consumer": { + "name": "AvroConsumer" + }, + "interactions": [ + { + "description": "A request to do get some Avro stuff", + "pending": false, + "pluginConfiguration": { + "avro": { + "record": "User", + "schemaKey": "1184dbf3292cee8bc7390762dd15fc52" + } + }, + "request": { + "method": "GET", + "path": "/avro" + }, + "response": { + "body": { + "content": "AghtYXR0", + "contentType": "avro/binary;record=User", + "contentTypeHint": "BINARY", + "encoded": "base64" + }, + "headers": { + "content-type": [ + "avro/binary" + ] + }, + "matchingRules": { + "body": { + "$.id": { + "combine": "AND", + "matchers": [ + { + "match": "number" + } + ] + }, + "$.username": { + "combine": "AND", + "matchers": [ + { + "match": "notEmpty" + } + ] + } + } + }, + "status": 200 + }, + "transport": "http", + "type": "Synchronous/HTTP" + } + ], + "metadata": { + "pactRust": { + "ffi": "0.4.16", + "mockserver": "1.2.5", + "models": "1.1.19" + }, + "pactSpecification": { + "version": "4.0" + }, + "plugins": [ + { + "configuration": { + "1184dbf3292cee8bc7390762dd15fc52": { + "avroSchema": "{\"type\":\"record\",\"name\":\"User\",\"namespace\":\"io.pact\",\"fields\":[{\"name\":\"id\",\"type\":\"long\"},{\"name\":\"username\",\"type\":\"string\"}]}" + } + }, + "name": "avro", + "version": "0.0.4" + } + ] + }, + "provider": { + "name": "AvroProvider" + } +} \ No newline at end of file diff --git a/examples/pacts/MattConsumer-MattProvider.json b/examples/pacts/MattConsumer-MattProvider.json new file mode 100644 index 000000000..e668382b2 --- /dev/null +++ b/examples/pacts/MattConsumer-MattProvider.json @@ -0,0 +1,62 @@ +{ + "consumer": { + "name": "MattConsumer" + }, + "interactions": [ + { + "description": "A request to do a matt", + "pending": false, + "request": { + "body": { + "content": "TUFUVGhlbGxvTUFUVA==", + "contentType": "application/matt", + "contentTypeHint": "DEFAULT", + "encoded": "base64" + }, + "headers": { + "content-type": [ + "application/matt" + ] + }, + "method": "POST", + "path": "/matt" + }, + "response": { + "body": { + "content": "TUFUVHdvcmxkTUFUVA==", + "contentType": "application/matt", + "contentTypeHint": "DEFAULT", + "encoded": "base64" + }, + "headers": { + "content-type": [ + "application/matt" + ] + }, + "status": 200 + }, + "transport": "http", + "type": "Synchronous/HTTP" + } + ], + "metadata": { + "pactRust": { + "ffi": "0.4.16", + "mockserver": "1.2.5", + "models": "1.1.19" + }, + "pactSpecification": { + "version": "4.0" + }, + "plugins": [ + { + "configuration": {}, + "name": "matt", + "version": "0.1.1" + } + ] + }, + "provider": { + "name": "MattProvider" + } +} \ No newline at end of file diff --git a/examples/pacts/PactGoProductAPIConsumer-PactGoProductAPI.json b/examples/pacts/PactGoProductAPIConsumer-PactGoProductAPI.json new file mode 100644 index 000000000..38765b208 --- /dev/null +++ b/examples/pacts/PactGoProductAPIConsumer-PactGoProductAPI.json @@ -0,0 +1,50 @@ +{ + "consumer": { + "name": "PactGoProductAPIConsumer" + }, + "interactions": [ + { + "description": "A request for Product 10", + "providerState": "A product with ID 10 exists", + "request": { + "method": "GET", + "path": "/products/10" + }, + "response": { + "body": { + "id": 10, + "name": "Billy", + "price": "23.33" + }, + "headers": { + "Content-Type": "application/json" + }, + "matchingRules": { + "$.body.id": { + "match": "type" + }, + "$.body.name": { + "match": "type" + }, + "$.body.price": { + "match": "type" + } + }, + "status": 200 + } + } + ], + "metadata": { + "pactRust": { + "ffi": "0.4.16", + "mockserver": "1.2.5", + "models": "1.1.19" + }, + "pactSpecification": { + "version": "2.0.0" + } + }, + "provider": { + "name": "PactGoProductAPI" + } +} \ No newline at end of file diff --git a/examples/pacts/PactGoV2Consumer-V2Provider.json b/examples/pacts/PactGoV2Consumer-V2Provider.json new file mode 100644 index 000000000..f001b3d99 --- /dev/null +++ b/examples/pacts/PactGoV2Consumer-V2Provider.json @@ -0,0 +1,102 @@ +{ + "consumer": { + "name": "PactGoV2Consumer" + }, + "interactions": [ + { + "description": "A request to do a foo", + "providerState": "User foo exists", + "request": { + "body": { + "datetime": "2020-01-01'T'08:00:45", + "id": 27, + "lastName": "billy", + "name": "billy" + }, + "headers": { + "Authorization": "Bearer 1234", + "Content-Type": "application/json" + }, + "matchingRules": { + "$.body.datetime": { + "match": "type" + }, + "$.body.id": { + "match": "type" + }, + "$.body.lastName": { + "match": "type" + }, + "$.body.name": { + "match": "type" + }, + "$.header.Authorization": { + "match": "type" + }, + "$.path": { + "match": "regex", + "regex": "\\/foo.*" + }, + "$.query.baz[0]": { + "match": "regex", + "regex": "[a-z]+" + }, + "$.query.baz[1]": { + "match": "regex", + "regex": "[a-z]+" + }, + "$.query.baz[2]": { + "match": "regex", + "regex": "[a-z]+" + } + }, + "method": "POST", + "path": "/foobar", + "query": "baz=bar&baz=bat&baz=baz" + }, + "response": { + "body": { + "datetime": "2020-01-01", + "itemsMin": [ + "thereshouldbe3ofthese", + "thereshouldbe3ofthese", + "thereshouldbe3ofthese" + ], + "lastName": "Sampson", + "name": "Billy" + }, + "headers": { + "Content-Type": "application/json" + }, + "matchingRules": { + "$.body.datetime": { + "match": "regex", + "regex": "[0-9\\-]+" + }, + "$.body.itemsMin": { + "match": "type", + "min": 3 + }, + "$.header['Content-Type']": { + "match": "regex", + "regex": "application\\/json" + } + }, + "status": 200 + } + } + ], + "metadata": { + "pactRust": { + "ffi": "0.4.16", + "mockserver": "1.2.5", + "models": "1.1.19" + }, + "pactSpecification": { + "version": "2.0.0" + } + }, + "provider": { + "name": "V2Provider" + } +} \ No newline at end of file diff --git a/examples/pacts/PactGoV2ConsumerAllInOne-V2Provider.json b/examples/pacts/PactGoV2ConsumerAllInOne-V2Provider.json new file mode 100644 index 000000000..097dc1cda --- /dev/null +++ b/examples/pacts/PactGoV2ConsumerAllInOne-V2Provider.json @@ -0,0 +1,86 @@ +{ + "consumer": { + "name": "PactGoV2ConsumerAllInOne" + }, + "interactions": [ + { + "description": "A request to do a foo", + "providerState": "User foo exists", + "request": { + "body": { + "datetime": "2020-01-01'T'08:00:45", + "id": 27, + "lastName": "billy", + "name": "billy" + }, + "headers": { + "Content-Type": "application/json" + }, + "matchingRules": { + "$.body.datetime": { + "match": "type" + }, + "$.body.id": { + "match": "type" + }, + "$.body.lastName": { + "match": "type" + }, + "$.body.name": { + "match": "type" + }, + "$.path": { + "match": "regex", + "regex": "\\/foo.*" + }, + "$.query.baz": { + "match": "regex", + "regex": "[a-zA-Z]+" + } + }, + "method": "POST", + "path": "/foobar", + "query": "baz=bat" + }, + "response": { + "body": { + "datetime": "2020-01-01", + "itemsMin": [ + "thereshouldbe3ofthese", + "thereshouldbe3ofthese", + "thereshouldbe3ofthese" + ], + "lastName": "Sampson", + "name": "Billy" + }, + "headers": { + "Content-Type": "application/json" + }, + "matchingRules": { + "$.body.datetime": { + "match": "regex", + "regex": "[0-9\\-]+" + }, + "$.body.itemsMin": { + "match": "type", + "min": 3 + } + }, + "status": 200 + } + } + ], + "metadata": { + "pactRust": { + "ffi": "0.4.16", + "mockserver": "1.2.5", + "models": "1.1.19" + }, + "pactSpecification": { + "version": "2.0.0" + } + }, + "provider": { + "name": "V2Provider" + } +} \ No newline at end of file diff --git a/examples/pacts/PactGoV2ConsumerMatch-V2ProviderMatch.json b/examples/pacts/PactGoV2ConsumerMatch-V2ProviderMatch.json new file mode 100644 index 000000000..7fc04631a --- /dev/null +++ b/examples/pacts/PactGoV2ConsumerMatch-V2ProviderMatch.json @@ -0,0 +1,98 @@ +{ + "consumer": { + "name": "PactGoV2ConsumerMatch" + }, + "interactions": [ + { + "description": "A request to do a foo", + "providerState": "User foo exists", + "request": { + "body": { + "datetime": "2020-01-01'T'08:00:45,format=yyyy-MM-dd'T'HH:mm:ss,generator=datetime", + "id": 27, + "lastName": "Sampson", + "name": "Billy" + }, + "headers": { + "Authorization": "Bearer 1234", + "Content-Type": "application/json" + }, + "matchingRules": { + "$.body.datetime": { + "match": "type" + }, + "$.body.id": { + "match": "type" + }, + "$.body.lastName": { + "match": "type" + }, + "$.body.name": { + "match": "type" + }, + "$.header.Authorization": { + "match": "type" + }, + "$.query.baz[0]": { + "match": "regex", + "regex": "[a-z]+" + }, + "$.query.baz[1]": { + "match": "regex", + "regex": "[a-z]+" + }, + "$.query.baz[2]": { + "match": "regex", + "regex": "[a-z]+" + } + }, + "method": "POST", + "path": "/foobar", + "query": "baz=bar&baz=bat&baz=baz" + }, + "response": { + "body": { + "datetime": "2020-01-01'T'08:00:45,format=yyyy-MM-dd'T'HH:mm:ss,generator=datetime", + "id": 27, + "lastName": "Sampson", + "name": "Billy" + }, + "headers": { + "Content-Type": "application/json" + }, + "matchingRules": { + "$.body.datetime": { + "match": "type" + }, + "$.body.id": { + "match": "type" + }, + "$.body.lastName": { + "match": "type" + }, + "$.body.name": { + "match": "type" + }, + "$.header['Content-Type']": { + "match": "regex", + "regex": "application\\/json" + } + }, + "status": 200 + } + } + ], + "metadata": { + "pactRust": { + "ffi": "0.4.16", + "mockserver": "1.2.5", + "models": "1.1.19" + }, + "pactSpecification": { + "version": "2.0.0" + } + }, + "provider": { + "name": "V2ProviderMatch" + } +} \ No newline at end of file diff --git a/examples/pacts/PactGoV3Consumer-V3Provider.json b/examples/pacts/PactGoV3Consumer-V3Provider.json new file mode 100644 index 000000000..2cd459905 --- /dev/null +++ b/examples/pacts/PactGoV3Consumer-V3Provider.json @@ -0,0 +1,274 @@ +{ + "consumer": { + "name": "PactGoV3Consumer" + }, + "interactions": [ + { + "description": "A request to do a foo", + "providerStates": [ + { + "name": "state 1" + }, + { + "name": "User foo exists", + "params": { + "id": "foo" + } + } + ], + "request": { + "body": { + "datetime": "2020-01-01T08:00:45", + "id": 27, + "lastName": "billy", + "name": "billy" + }, + "generators": { + "body": { + "$.datetime": { + "format": "yyyy-MM-dd'T'HH:mm:ss", + "type": "DateTime" + }, + "$.name": { + "expression": "${name}", + "type": "ProviderState" + } + } + }, + "headers": { + "Authorization": "Bearer 1234", + "Content-Type": "application/json" + }, + "matchingRules": { + "body": { + "$.datetime": { + "combine": "AND", + "matchers": [ + { + "format": "yyyy-MM-dd'T'HH:mm:ss", + "match": "datetime" + } + ] + }, + "$.id": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.lastName": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.name": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + }, + "header": { + "Authorization": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + }, + "query": { + "baz": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "[a-z]+" + } + ] + } + } + }, + "method": "POST", + "path": "/foobar", + "query": { + "baz": [ + "bar", + "bat", + "baz" + ] + } + }, + "response": { + "body": { + "accountBalance": 123.76, + "arrayContaining": [ + "string", + 1, + { + "foo": "bar" + } + ], + "datetime": "2020-01-01", + "equality": "a thing", + "id": 12, + "itemsMin": [ + "thereshouldbe3ofthese", + "thereshouldbe3ofthese", + "thereshouldbe3ofthese" + ], + "itemsMinMax": [ + 27, + 27, + 27, + 27, + 27 + ], + "lastName": "Sampson", + "name": "Billy", + "superstring": "foo" + }, + "headers": { + "Content-Type": "application/json" + }, + "matchingRules": { + "body": { + "$.accountBalance": { + "combine": "AND", + "matchers": [ + { + "match": "decimal" + } + ] + }, + "$.arrayContaining": { + "combine": "AND", + "matchers": [ + { + "match": "arrayContains", + "variants": [ + { + "index": 0, + "rules": { + "$": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + } + }, + { + "index": 1, + "rules": { + "$": { + "combine": "AND", + "matchers": [ + { + "match": "integer" + } + ] + } + } + }, + { + "index": 2, + "rules": { + "$.foo": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + } + } + ] + } + ] + }, + "$.datetime": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "[0-9\\-]+" + } + ] + }, + "$.equality": { + "combine": "AND", + "matchers": [ + { + "match": "equality" + } + ] + }, + "$.id": { + "combine": "AND", + "matchers": [ + { + "match": "integer" + } + ] + }, + "$.itemsMin": { + "combine": "AND", + "matchers": [ + { + "match": "type", + "min": 3 + } + ] + }, + "$.itemsMinMax": { + "combine": "AND", + "matchers": [ + { + "match": "type", + "max": 5, + "min": 3 + } + ] + }, + "$.superstring": { + "combine": "AND", + "matchers": [ + { + "match": "include", + "value": "foo" + } + ] + } + }, + "header": {} + }, + "status": 200 + } + } + ], + "metadata": { + "pactRust": { + "ffi": "0.4.16", + "mockserver": "1.2.5", + "models": "1.1.19" + }, + "pactSpecification": { + "version": "3.0.0" + } + }, + "provider": { + "name": "V3Provider" + } +} \ No newline at end of file diff --git a/examples/pacts/PactGoV3MessageConsumer-V3MessageProvider.json b/examples/pacts/PactGoV3MessageConsumer-V3MessageProvider.json new file mode 100644 index 000000000..57c4b4053 --- /dev/null +++ b/examples/pacts/PactGoV3MessageConsumer-V3MessageProvider.json @@ -0,0 +1,61 @@ +{ + "consumer": { + "name": "PactGoV3MessageConsumer" + }, + "messages": [ + { + "contents": { + "datetime": "2020-01-01", + "id": 12, + "lastName": "Sampson", + "name": "Billy" + }, + "description": "a user event", + "matchingRules": { + "body": { + "$.datetime": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "[0-9\\-]+" + } + ] + }, + "$.id": { + "combine": "AND", + "matchers": [ + { + "match": "integer" + } + ] + } + } + }, + "metadata": { + "Content-Type": "application/json", + "contentType": "application/json" + }, + "providerStates": [ + { + "name": "User with id 127 exists", + "params": { + "id": 127 + } + } + ] + } + ], + "metadata": { + "pactRust": { + "ffi": "0.4.16", + "models": "1.1.19" + }, + "pactSpecification": { + "version": "3.0.0" + } + }, + "provider": { + "name": "V3MessageProvider" + } +} \ No newline at end of file diff --git a/examples/pacts/PactGoV4Consumer-V4Provider.json b/examples/pacts/PactGoV4Consumer-V4Provider.json new file mode 100644 index 000000000..4198d742b --- /dev/null +++ b/examples/pacts/PactGoV4Consumer-V4Provider.json @@ -0,0 +1,291 @@ +{ + "consumer": { + "name": "PactGoV4Consumer" + }, + "interactions": [ + { + "description": "A request to do a foo", + "pending": false, + "providerStates": [ + { + "name": "state 1" + }, + { + "name": "User foo exists", + "params": { + "id": "foo" + } + } + ], + "request": { + "body": { + "content": { + "datetime": "2020-01-01T08:00:45", + "id": 27, + "lastName": "billy", + "name": "billy" + }, + "contentType": "application/json", + "encoded": false + }, + "generators": { + "body": { + "$.datetime": { + "format": "yyyy-MM-dd'T'HH:mm:ss", + "type": "DateTime" + }, + "$.name": { + "expression": "${name}", + "type": "ProviderState" + } + } + }, + "headers": { + "Authorization": [ + "Bearer 1234" + ], + "Content-Type": [ + "application/json" + ] + }, + "matchingRules": { + "body": { + "$.datetime": { + "combine": "AND", + "matchers": [ + { + "format": "yyyy-MM-dd'T'HH:mm:ss", + "match": "datetime" + } + ] + }, + "$.id": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.lastName": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.name": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + }, + "header": { + "Authorization": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + }, + "query": { + "baz": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "[a-z]+" + } + ] + } + } + }, + "method": "POST", + "path": "/foobar", + "query": { + "baz": [ + "bar", + "bat", + "baz" + ] + } + }, + "response": { + "body": { + "content": { + "accountBalance": 123.76, + "arrayContaining": [ + "string", + 1, + { + "foo": "bar" + } + ], + "datetime": "2020-01-01", + "equality": "a thing", + "id": 12, + "itemsMin": [ + "thereshouldbe3ofthese", + "thereshouldbe3ofthese", + "thereshouldbe3ofthese" + ], + "itemsMinMax": [ + 27, + 27, + 27, + 27, + 27 + ], + "lastName": "Sampson", + "name": "Billy", + "superstring": "foo" + }, + "contentType": "application/json", + "encoded": false + }, + "headers": { + "Content-Type": [ + "application/json" + ] + }, + "matchingRules": { + "body": { + "$.accountBalance": { + "combine": "AND", + "matchers": [ + { + "match": "decimal" + } + ] + }, + "$.arrayContaining": { + "combine": "AND", + "matchers": [ + { + "match": "arrayContains", + "variants": [ + { + "index": 0, + "rules": { + "$": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + } + }, + { + "index": 1, + "rules": { + "$": { + "combine": "AND", + "matchers": [ + { + "match": "integer" + } + ] + } + } + }, + { + "index": 2, + "rules": { + "$.foo": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + } + } + ] + } + ] + }, + "$.datetime": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "[0-9\\-]+" + } + ] + }, + "$.equality": { + "combine": "AND", + "matchers": [ + { + "match": "equality" + } + ] + }, + "$.id": { + "combine": "AND", + "matchers": [ + { + "match": "integer" + } + ] + }, + "$.itemsMin": { + "combine": "AND", + "matchers": [ + { + "match": "type", + "min": 3 + } + ] + }, + "$.itemsMinMax": { + "combine": "AND", + "matchers": [ + { + "match": "type", + "max": 5, + "min": 3 + } + ] + }, + "$.superstring": { + "combine": "AND", + "matchers": [ + { + "match": "include", + "value": "foo" + } + ] + } + }, + "header": {} + }, + "status": 200 + }, + "transport": "http", + "type": "Synchronous/HTTP" + } + ], + "metadata": { + "pactRust": { + "ffi": "0.4.16", + "mockserver": "1.2.5", + "models": "1.1.19" + }, + "pactSpecification": { + "version": "4.0" + } + }, + "provider": { + "name": "V4Provider" + } +} \ No newline at end of file diff --git a/examples/pacts/grpcconsumer-grpcprovider.json b/examples/pacts/grpcconsumer-grpcprovider.json new file mode 100644 index 000000000..e19d936ae --- /dev/null +++ b/examples/pacts/grpcconsumer-grpcprovider.json @@ -0,0 +1,303 @@ +{ + "consumer": { + "name": "grpcconsumer" + }, + "interactions": [ + { + "description": "Route guide - GetFeature", + "interactionMarkup": { + "markup": "```protobuf\nmessage Feature {\n string name = 1;\n message .routeguide.Point location = 2;\n}\n```\n", + "markupType": "COMMON_MARK" + }, + "pending": false, + "pluginConfiguration": { + "protobuf": { + "descriptorKey": "32be40995693643591f1bdcb49997b85", + "service": "RouteGuide/GetFeature" + } + }, + "providerStates": [ + { + "name": "feature 'Big Tree' exists" + } + ], + "request": { + "contents": { + "content": "CLQBEMgB", + "contentType": "application/protobuf;message=Point", + "contentTypeHint": "BINARY", + "encoded": "base64" + }, + "matchingRules": { + "body": { + "$.latitude": { + "combine": "AND", + "matchers": [ + { + "match": "number" + } + ] + }, + "$.longitude": { + "combine": "AND", + "matchers": [ + { + "match": "number" + } + ] + } + } + }, + "metadata": { + "contentType": "application/protobuf;message=Point" + } + }, + "response": [ + { + "contents": { + "content": "CghCaWcgVHJlZRIGCLQBEMgB", + "contentType": "application/protobuf;message=Feature", + "contentTypeHint": "BINARY", + "encoded": "base64" + }, + "matchingRules": { + "body": { + "$.location.latitude": { + "combine": "AND", + "matchers": [ + { + "match": "number" + } + ] + }, + "$.location.longitude": { + "combine": "AND", + "matchers": [ + { + "match": "number" + } + ] + }, + "$.name": { + "combine": "AND", + "matchers": [ + { + "match": "notEmpty" + } + ] + } + } + }, + "metadata": { + "contentType": "application/protobuf;message=Feature" + } + } + ], + "transport": "grpc", + "type": "Synchronous/Messages" + }, + { + "description": "Route guide - GetFeature - error response", + "interactionMarkup": { + "markup": "```protobuf\nmessage Feature {\n}\n```\n", + "markupType": "COMMON_MARK" + }, + "pending": false, + "pluginConfiguration": { + "protobuf": { + "descriptorKey": "32be40995693643591f1bdcb49997b85", + "service": "RouteGuide/GetFeature" + } + }, + "providerStates": [ + { + "name": "feature does not exist at -1, -1" + } + ], + "request": { + "contents": { + "content": "CP///////////wEQ////////////AQ==", + "contentType": "application/protobuf;message=Point", + "contentTypeHint": "BINARY", + "encoded": "base64" + }, + "matchingRules": { + "body": { + "$.latitude": { + "combine": "AND", + "matchers": [ + { + "match": "number" + } + ] + }, + "$.longitude": { + "combine": "AND", + "matchers": [ + { + "match": "number" + } + ] + } + } + }, + "metadata": { + "contentType": "application/protobuf;message=Point" + } + }, + "response": [ + { + "contents": { + "content": "" + }, + "matchingRules": { + "body": {}, + "metadata": { + "grpc-message": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + } + }, + "metadata": { + "contentType": "application/protobuf;message=Feature", + "grpc-message": "no feature was found at latitude:-1 longitude:-1", + "grpc-status": "NOT_FOUND" + } + } + ], + "transport": "grpc", + "type": "Synchronous/Messages" + }, + { + "description": "Route guide - SaveFeature", + "interactionMarkup": { + "markup": "```protobuf\nmessage Feature {\n string name = 1;\n message .routeguide.Point location = 2;\n}\n```\n", + "markupType": "COMMON_MARK" + }, + "pending": false, + "pluginConfiguration": { + "protobuf": { + "descriptorKey": "32be40995693643591f1bdcb49997b85", + "service": "RouteGuide/SaveFeature" + } + }, + "providerStates": [ + { + "name": "feature does not exist at -1, -1" + } + ], + "request": { + "contents": { + "content": "CgZBIHNoZWQSBAhjEGM=", + "contentType": "application/protobuf;message=Feature", + "contentTypeHint": "BINARY", + "encoded": "base64" + }, + "matchingRules": { + "body": { + "$.location.latitude": { + "combine": "AND", + "matchers": [ + { + "match": "number" + } + ] + }, + "$.location.longitude": { + "combine": "AND", + "matchers": [ + { + "match": "number" + } + ] + }, + "$.name": { + "combine": "AND", + "matchers": [ + { + "match": "notEmpty" + } + ] + } + } + }, + "metadata": { + "contentType": "application/protobuf;message=Feature" + } + }, + "response": [ + { + "contents": { + "content": "CgZBIHNoZWQSBAhjEGM=", + "contentType": "application/protobuf;message=Feature", + "contentTypeHint": "BINARY", + "encoded": "base64" + }, + "matchingRules": { + "body": { + "$.location.latitude": { + "combine": "AND", + "matchers": [ + { + "match": "number" + } + ] + }, + "$.location.longitude": { + "combine": "AND", + "matchers": [ + { + "match": "number" + } + ] + }, + "$.name": { + "combine": "AND", + "matchers": [ + { + "match": "notEmpty" + } + ] + } + } + }, + "metadata": { + "contentType": "application/protobuf;message=Feature" + } + } + ], + "transport": "grpc", + "type": "Synchronous/Messages" + } + ], + "metadata": { + "pactRust": { + "ffi": "0.4.16", + "mockserver": "1.2.5", + "models": "1.1.19" + }, + "pactSpecification": { + "version": "4.0" + }, + "plugins": [ + { + "configuration": { + "32be40995693643591f1bdcb49997b85": { + "protoDescriptors": "CsYHChFyb3V0ZV9ndWlkZS5wcm90bxIKcm91dGVndWlkZSJBCgVQb2ludBIaCghsYXRpdHVkZRgBIAEoBVIIbGF0aXR1ZGUSHAoJbG9uZ2l0dWRlGAIgASgFUglsb25naXR1ZGUiUQoJUmVjdGFuZ2xlEiEKAmxvGAEgASgLMhEucm91dGVndWlkZS5Qb2ludFICbG8SIQoCaGkYAiABKAsyES5yb3V0ZWd1aWRlLlBvaW50UgJoaSJuCgdGZWF0dXJlEhIKBG5hbWUYASABKAlSBG5hbWUSLQoIbG9jYXRpb24YAiABKAsyES5yb3V0ZWd1aWRlLlBvaW50Ughsb2NhdGlvbhIgCgtkZXNjcmlwdGlvbhgDIAEoCVILZGVzY3JpcHRpb24iVAoJUm91dGVOb3RlEi0KCGxvY2F0aW9uGAEgASgLMhEucm91dGVndWlkZS5Qb2ludFIIbG9jYXRpb24SGAoHbWVzc2FnZRgCIAEoCVIHbWVzc2FnZSKTAQoMUm91dGVTdW1tYXJ5Eh8KC3BvaW50X2NvdW50GAEgASgFUgpwb2ludENvdW50EiMKDWZlYXR1cmVfY291bnQYAiABKAVSDGZlYXR1cmVDb3VudBIaCghkaXN0YW5jZRgDIAEoBVIIZGlzdGFuY2USIQoMZWxhcHNlZF90aW1lGAQgASgFUgtlbGFwc2VkVGltZTLAAgoKUm91dGVHdWlkZRI2CgpHZXRGZWF0dXJlEhEucm91dGVndWlkZS5Qb2ludBoTLnJvdXRlZ3VpZGUuRmVhdHVyZSIAEjkKC1NhdmVGZWF0dXJlEhMucm91dGVndWlkZS5GZWF0dXJlGhMucm91dGVndWlkZS5GZWF0dXJlIgASPgoMTGlzdEZlYXR1cmVzEhUucm91dGVndWlkZS5SZWN0YW5nbGUaEy5yb3V0ZWd1aWRlLkZlYXR1cmUiADABEj4KC1JlY29yZFJvdXRlEhEucm91dGVndWlkZS5Qb2ludBoYLnJvdXRlZ3VpZGUuUm91dGVTdW1tYXJ5IgAoARI/CglSb3V0ZUNoYXQSFS5yb3V0ZWd1aWRlLlJvdXRlTm90ZRoVLnJvdXRlZ3VpZGUuUm91dGVOb3RlIgAoATABQmgKG2lvLmdycGMuZXhhbXBsZXMucm91dGVndWlkZUIPUm91dGVHdWlkZVByb3RvUAFaNmdvb2dsZS5nb2xhbmcub3JnL2dycGMvZXhhbXBsZXMvcm91dGVfZ3VpZGUvcm91dGVndWlkZWIGcHJvdG8z", + "protoFile": "// Copyright 2015 gRPC authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nsyntax = \"proto3\";\n\noption go_package = \"google.golang.org/grpc/examples/route_guide/routeguide\";\noption java_multiple_files = true;\noption java_package = \"io.grpc.examples.routeguide\";\noption java_outer_classname = \"RouteGuideProto\";\n\npackage routeguide;\n\n// Interface exported by the server.\nservice RouteGuide {\n // A simple RPC.\n //\n // Obtains the feature at a given position.\n //\n // A feature with an empty name is returned if there's no feature at the given\n // position.\n rpc GetFeature(Point) returns (Feature) {}\n\n // Save the feature.\n rpc SaveFeature(Feature) returns (Feature) {}\n\n // A server-to-client streaming RPC.\n //\n // Obtains the Features available within the given Rectangle. Results are\n // streamed rather than returned at once (e.g. in a response message with a\n // repeated field), as the rectangle may cover a large area and contain a\n // huge number of features.\n rpc ListFeatures(Rectangle) returns (stream Feature) {}\n\n // A client-to-server streaming RPC.\n //\n // Accepts a stream of Points on a route being traversed, returning a\n // RouteSummary when traversal is completed.\n rpc RecordRoute(stream Point) returns (RouteSummary) {}\n\n // A Bidirectional streaming RPC.\n //\n // Accepts a stream of RouteNotes sent while a route is being traversed,\n // while receiving other RouteNotes (e.g. from other users).\n rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}\n}\n\n// Points are represented as latitude-longitude pairs in the E7 representation\n// (degrees multiplied by 10**7 and rounded to the nearest integer).\n// Latitudes should be in the range +/- 90 degrees and longitude should be in\n// the range +/- 180 degrees (inclusive).\nmessage Point {\n int32 latitude = 1;\n int32 longitude = 2;\n}\n\n// A latitude-longitude rectangle, represented as two diagonally opposite\n// points \"lo\" and \"hi\".\nmessage Rectangle {\n // One corner of the rectangle.\n Point lo = 1;\n\n // The other corner of the rectangle.\n Point hi = 2;\n}\n\n// A feature names something at a given point.\n//\n// If a feature could not be named, the name is empty.\nmessage Feature {\n // The name of the feature.\n string name = 1;\n\n // The point where the feature is detected.\n Point location = 2;\n\n // A description of the feature.\n string description = 3;\n}\n\n// A RouteNote is a message sent while at a given point.\nmessage RouteNote {\n // The location from which the message is sent.\n Point location = 1;\n\n // The message to be sent.\n string message = 2;\n}\n\n// A RouteSummary is received in response to a RecordRoute rpc.\n//\n// It contains the number of individual points received, the number of\n// detected features, and the total distance covered as the cumulative sum of\n// the distance between each point.\nmessage RouteSummary {\n // The number of points received.\n int32 point_count = 1;\n\n // The number of known features passed while traversing the route.\n int32 feature_count = 2;\n\n // The distance covered in metres.\n int32 distance = 3;\n\n // The duration of the traversal in seconds.\n int32 elapsed_time = 4;\n}\n" + } + }, + "name": "protobuf", + "version": "0.3.13" + } + ] + }, + "provider": { + "name": "grpcprovider" + } +} \ No newline at end of file diff --git a/examples/pacts/matttcpconsumer-matttcpprovider.json b/examples/pacts/matttcpconsumer-matttcpprovider.json new file mode 100644 index 000000000..9c0d3e693 --- /dev/null +++ b/examples/pacts/matttcpconsumer-matttcpprovider.json @@ -0,0 +1,56 @@ +{ + "consumer": { + "name": "matttcpconsumer" + }, + "interactions": [ + { + "description": "Matt message", + "pending": false, + "providerStates": [ + { + "name": "the world exists" + } + ], + "request": { + "contents": { + "content": "TUFUVGhlbGxvdGNwTUFUVA==", + "contentType": "application/matt", + "contentTypeHint": "DEFAULT", + "encoded": "base64" + } + }, + "response": [ + { + "contents": { + "content": "TUFUVHRjcHdvcmxkTUFUVA==", + "contentType": "application/matt", + "contentTypeHint": "DEFAULT", + "encoded": "base64" + } + } + ], + "transport": "matt", + "type": "Synchronous/Messages" + } + ], + "metadata": { + "pactRust": { + "ffi": "0.4.16", + "mockserver": "1.2.5", + "models": "1.1.19" + }, + "pactSpecification": { + "version": "4.0" + }, + "plugins": [ + { + "configuration": {}, + "name": "matt", + "version": "0.1.1" + } + ] + }, + "provider": { + "name": "matttcpprovider" + } +} \ No newline at end of file diff --git a/examples/pacts/protobufmessageconsumer-protobufmessageprovider.json b/examples/pacts/protobufmessageconsumer-protobufmessageprovider.json new file mode 100644 index 000000000..1a1cc2bb4 --- /dev/null +++ b/examples/pacts/protobufmessageconsumer-protobufmessageprovider.json @@ -0,0 +1,88 @@ +{ + "consumer": { + "name": "protobufmessageconsumer" + }, + "interactions": [ + { + "contents": { + "content": "CghCaWcgVHJlZRIGCLQBEMgB", + "contentType": "application/protobuf;message=Feature", + "contentTypeHint": "BINARY", + "encoded": "base64" + }, + "description": "feature message", + "interactionMarkup": { + "markup": "```protobuf\nmessage Feature {\n string name = 1;\n message .routeguide.Point location = 2;\n}\n```\n", + "markupType": "COMMON_MARK" + }, + "matchingRules": { + "body": { + "$.location.latitude": { + "combine": "AND", + "matchers": [ + { + "match": "number" + } + ] + }, + "$.location.longitude": { + "combine": "AND", + "matchers": [ + { + "match": "number" + } + ] + }, + "$.name": { + "combine": "AND", + "matchers": [ + { + "match": "notEmpty" + } + ] + } + } + }, + "metadata": { + "contentType": "application/protobuf;message=Feature" + }, + "pending": false, + "pluginConfiguration": { + "protobuf": { + "descriptorKey": "32be40995693643591f1bdcb49997b85", + "message": "Feature" + } + }, + "providerStates": [ + { + "name": "the world exists" + } + ], + "type": "Asynchronous/Messages" + } + ], + "metadata": { + "pactRust": { + "ffi": "0.4.16", + "models": "1.1.19" + }, + "pactSpecification": { + "version": "4.0" + }, + "plugins": [ + { + "configuration": { + "32be40995693643591f1bdcb49997b85": { + "protoDescriptors": "CsYHChFyb3V0ZV9ndWlkZS5wcm90bxIKcm91dGVndWlkZSJBCgVQb2ludBIaCghsYXRpdHVkZRgBIAEoBVIIbGF0aXR1ZGUSHAoJbG9uZ2l0dWRlGAIgASgFUglsb25naXR1ZGUiUQoJUmVjdGFuZ2xlEiEKAmxvGAEgASgLMhEucm91dGVndWlkZS5Qb2ludFICbG8SIQoCaGkYAiABKAsyES5yb3V0ZWd1aWRlLlBvaW50UgJoaSJuCgdGZWF0dXJlEhIKBG5hbWUYASABKAlSBG5hbWUSLQoIbG9jYXRpb24YAiABKAsyES5yb3V0ZWd1aWRlLlBvaW50Ughsb2NhdGlvbhIgCgtkZXNjcmlwdGlvbhgDIAEoCVILZGVzY3JpcHRpb24iVAoJUm91dGVOb3RlEi0KCGxvY2F0aW9uGAEgASgLMhEucm91dGVndWlkZS5Qb2ludFIIbG9jYXRpb24SGAoHbWVzc2FnZRgCIAEoCVIHbWVzc2FnZSKTAQoMUm91dGVTdW1tYXJ5Eh8KC3BvaW50X2NvdW50GAEgASgFUgpwb2ludENvdW50EiMKDWZlYXR1cmVfY291bnQYAiABKAVSDGZlYXR1cmVDb3VudBIaCghkaXN0YW5jZRgDIAEoBVIIZGlzdGFuY2USIQoMZWxhcHNlZF90aW1lGAQgASgFUgtlbGFwc2VkVGltZTLAAgoKUm91dGVHdWlkZRI2CgpHZXRGZWF0dXJlEhEucm91dGVndWlkZS5Qb2ludBoTLnJvdXRlZ3VpZGUuRmVhdHVyZSIAEjkKC1NhdmVGZWF0dXJlEhMucm91dGVndWlkZS5GZWF0dXJlGhMucm91dGVndWlkZS5GZWF0dXJlIgASPgoMTGlzdEZlYXR1cmVzEhUucm91dGVndWlkZS5SZWN0YW5nbGUaEy5yb3V0ZWd1aWRlLkZlYXR1cmUiADABEj4KC1JlY29yZFJvdXRlEhEucm91dGVndWlkZS5Qb2ludBoYLnJvdXRlZ3VpZGUuUm91dGVTdW1tYXJ5IgAoARI/CglSb3V0ZUNoYXQSFS5yb3V0ZWd1aWRlLlJvdXRlTm90ZRoVLnJvdXRlZ3VpZGUuUm91dGVOb3RlIgAoATABQmgKG2lvLmdycGMuZXhhbXBsZXMucm91dGVndWlkZUIPUm91dGVHdWlkZVByb3RvUAFaNmdvb2dsZS5nb2xhbmcub3JnL2dycGMvZXhhbXBsZXMvcm91dGVfZ3VpZGUvcm91dGVndWlkZWIGcHJvdG8z", + "protoFile": "// Copyright 2015 gRPC authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nsyntax = \"proto3\";\n\noption go_package = \"google.golang.org/grpc/examples/route_guide/routeguide\";\noption java_multiple_files = true;\noption java_package = \"io.grpc.examples.routeguide\";\noption java_outer_classname = \"RouteGuideProto\";\n\npackage routeguide;\n\n// Interface exported by the server.\nservice RouteGuide {\n // A simple RPC.\n //\n // Obtains the feature at a given position.\n //\n // A feature with an empty name is returned if there's no feature at the given\n // position.\n rpc GetFeature(Point) returns (Feature) {}\n\n // Save the feature.\n rpc SaveFeature(Feature) returns (Feature) {}\n\n // A server-to-client streaming RPC.\n //\n // Obtains the Features available within the given Rectangle. Results are\n // streamed rather than returned at once (e.g. in a response message with a\n // repeated field), as the rectangle may cover a large area and contain a\n // huge number of features.\n rpc ListFeatures(Rectangle) returns (stream Feature) {}\n\n // A client-to-server streaming RPC.\n //\n // Accepts a stream of Points on a route being traversed, returning a\n // RouteSummary when traversal is completed.\n rpc RecordRoute(stream Point) returns (RouteSummary) {}\n\n // A Bidirectional streaming RPC.\n //\n // Accepts a stream of RouteNotes sent while a route is being traversed,\n // while receiving other RouteNotes (e.g. from other users).\n rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}\n}\n\n// Points are represented as latitude-longitude pairs in the E7 representation\n// (degrees multiplied by 10**7 and rounded to the nearest integer).\n// Latitudes should be in the range +/- 90 degrees and longitude should be in\n// the range +/- 180 degrees (inclusive).\nmessage Point {\n int32 latitude = 1;\n int32 longitude = 2;\n}\n\n// A latitude-longitude rectangle, represented as two diagonally opposite\n// points \"lo\" and \"hi\".\nmessage Rectangle {\n // One corner of the rectangle.\n Point lo = 1;\n\n // The other corner of the rectangle.\n Point hi = 2;\n}\n\n// A feature names something at a given point.\n//\n// If a feature could not be named, the name is empty.\nmessage Feature {\n // The name of the feature.\n string name = 1;\n\n // The point where the feature is detected.\n Point location = 2;\n\n // A description of the feature.\n string description = 3;\n}\n\n// A RouteNote is a message sent while at a given point.\nmessage RouteNote {\n // The location from which the message is sent.\n Point location = 1;\n\n // The message to be sent.\n string message = 2;\n}\n\n// A RouteSummary is received in response to a RecordRoute rpc.\n//\n// It contains the number of individual points received, the number of\n// detected features, and the total distance covered as the cumulative sum of\n// the distance between each point.\nmessage RouteSummary {\n // The number of points received.\n int32 point_count = 1;\n\n // The number of known features passed while traversing the route.\n int32 feature_count = 2;\n\n // The distance covered in metres.\n int32 distance = 3;\n\n // The duration of the traversal in seconds.\n int32 elapsed_time = 4;\n}\n" + } + }, + "name": "protobuf", + "version": "0.3.13" + } + ] + }, + "provider": { + "name": "protobufmessageprovider" + } +} \ No newline at end of file diff --git a/go.mod b/go.mod index ceefea54d..5c78c69ec 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,9 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/go-cmp v0.6.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/graph-gophers/graphql-go v1.5.0 // indirect + github.com/hasura/go-graphql-client v0.12.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kr/pretty v0.3.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -30,4 +33,5 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + nhooyr.io/websocket v1.8.10 // indirect ) diff --git a/go.sum b/go.sum index f3fed8386..c45164609 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,9 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= @@ -10,12 +13,19 @@ github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/graph-gophers/graphql-go v1.5.0 h1:fDqblo50TEpD0LY7RXk/LFVYEVqo3+tXMNMPSVXA1yc= +github.com/graph-gophers/graphql-go v1.5.0/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hasura/go-graphql-client v0.12.1 h1:tL+BCoyubkYYyaQ+tJz+oPe/pSxYwOJHwe5SSqqi6WI= +github.com/hasura/go-graphql-client v0.12.1/go.mod h1:F4N4kR6vY8amio3gEu3tjSZr8GPOXJr3zj72DKixfLE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -28,6 +38,7 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -42,10 +53,13 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI= +go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= @@ -71,3 +85,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= +nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=