Skip to content

Commit

Permalink
Merge pull request #200 from pact-foundation/feat/graphql-support
Browse files Browse the repository at this point in the history
feat: GraphQL interface
  • Loading branch information
mefellows authored Jun 27, 2022
2 parents 583fe15 + 2e27286 commit bb90356
Show file tree
Hide file tree
Showing 237 changed files with 34,318 additions and 1,736 deletions.
45 changes: 45 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
on: [push, pull_request]
name: Test

env:
REACT_APP_API_BASE_URL: http://localhost:8080
APP_SHA: ${{ github.sha }}
APP_BRANCH: ${{ github.ref }}
LD_LIBRARY_PATH: /tmp
PACT_GO_LIB_DOWNLOAD_PATH: /tmp
LOG_LEVEL: trace
GIT_COMMIT: ${{ github.sha }}
GIT_REF: ${{ github.ref }}
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}

jobs:
test:
strategy:
matrix:
go-version: [1.17.x, 1.18.x]
# os: [ubuntu-latest, macos-latest, windows-latest]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
- name: Test
run: GIT_BRANCH=${GIT_REF:11} DOCKER_GATEWAY_HOST=172.17.0.1 DOCKER_HOST_HTTP="http://172.17.0.1" make
- name: Install goveralls
run: go install github.com/mattn/goveralls@latest
- name: Send coverage
run: goveralls -coverprofile=coverage.txt -service=github -parallel

finish:
needs: test
runs-on: ubuntu-latest
steps:
- name: Coveralls Finished
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel-finished: true
46 changes: 22 additions & 24 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
include make/config.mk

TEST?=./...

.DEFAULT_GOAL := ci
DOCKER_HOST_HTTP?="http://host.docker.internal"
PACT_CLI="docker run --rm -v ${PWD}:${PWD} -e PACT_BROKER_BASE_URL=$(DOCKER_HOST_HTTP) -e PACT_BROKER_USERNAME -e PACT_BROKER_PASSWORD pactfoundation/pact-cli"

ci:: docker deps snyk clean bin test pact goveralls
ci:: docker deps clean bin test pact

docker:
@echo "--- 🛠 Starting docker"
Expand All @@ -22,26 +23,31 @@ bin:
clean:
rm -rf build output dist

deps: snyk-install
deps:
@echo "--- 🐿 Fetching build dependencies "
go get github.com/axw/gocov/gocov
go get github.com/mattn/goveralls
go get golang.org/x/tools/cmd/cover
go get github.com/modocache/gover
go get github.com/mitchellh/gox

goveralls:
goveralls -service="travis-ci" -coverprofile=coverage.txt -repotoken $(COVERALLS_TOKEN)
cd /tmp; \
go install github.com/mitchellh/gox@latest; \
cd -

install:
@if [ ! -d pact/bin ]; then\
echo "--- 🐿 Installing Pact CLI dependencies"; \
curl -fsSL https://raw.githubusercontent.com/pact-foundation/pact-ruby-standalone/master/install.sh | bash -x; \
fi
fi

publish_pacts:
@echo "\n========== STAGE: publish pacts ==========\n"
@"${PACT_CLI}" publish ${PWD}/examples/pacts --consumer-app-version ${GIT_COMMIT} --tag ${GIT_BRANCH} --tag dev --tag prod

pact_local:
GIT_COMMIT=`git rev-parse --short HEAD`+`date +%s` \
GIT_BRANCH=`git rev-parse --abbrev-ref HEAD` \
make pact

pact: install docker
@echo "--- 🔨 Running Pact examples"
go test -tags=consumer -count=1 github.com/pact-foundation/pact-go/examples/... -run TestExample
make publish_pacts
go test -tags=provider -count=1 github.com/pact-foundation/pact-go/examples/... -run TestExample

release:
Expand All @@ -54,13 +60,13 @@ test: deps install
@echo "mode: count" > coverage.txt
@for d in $$(go list ./... | grep -v vendor | grep -v examples); \
do \
go test -race -coverprofile=profile.out -covermode=atomic $$d; \
go test -race -coverprofile=profile.cov -covermode=atomic $$d; \
if [ $$? != 0 ]; then \
exit 1; \
fi; \
if [ -f profile.out ]; then \
cat profile.out | tail -n +2 >> coverage.txt; \
rm profile.out; \
if [ -f profile.cov ]; then \
cat profile.cov | tail -n +2 >> coverage.txt; \
rm profile.cov; \
fi; \
done; \
go tool cover -func coverage.txt
Expand All @@ -71,12 +77,4 @@ testrace:
updatedeps:
go get -d -v -p 2 ./...

snyk-install:
which snyk || npm i -g snyk

snyk:
@if [ "$$TRAVIS_PULL_REQUEST" != "false" ]; then\
snyk test; \
fi

.PHONY: install bin default dev test pact updatedeps clean release
2 changes: 1 addition & 1 deletion command/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"github.com/spf13/cobra"
)

var version = "v1.6.9"
var version = "v1.7.0"
var cliToolsVersion = "1.82.3"
var versionCmd = &cobra.Command{
Use: "version",
Expand Down
139 changes: 139 additions & 0 deletions dsl/graphql/interaction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package graphql

import (
"fmt"
"regexp"

"github.com/pact-foundation/pact-go/dsl"
)

// 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 dsl.MapMatcher

// Path to GraphQL endpoint
Path dsl.Matcher

// HTTP Query String
QueryString dsl.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 dsl.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 = dsl.MapMatcher{"content-type": dsl.String("application/json")}

func (r *Query) setHeader(headerName string, value dsl.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) *dsl.Request {
if request.Headers == nil {
request.Headers = defaultHeaders
}

return &dsl.Request{
Method: request.Method,
Path: request.Path,
Query: request.QueryString,
Body: graphQLQueryBody{
Operation: request.Operation,
Query: dsl.Regex(request.Query, escapeGraphQlQuery(request.Query)),
Variables: request.Variables,
},
Headers: request.Headers,
}

}

type graphQLQueryBody struct {
Operation string `json:"operationName,omitempty"`
Query dsl.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))
}
9 changes: 9 additions & 0 deletions dsl/graphql/response.go
Original file line number Diff line number Diff line change
@@ -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"`
}
28 changes: 1 addition & 27 deletions examples/consumer/goconsumer/user_service_test.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
//go:build consumer
// +build consumer

package goconsumer

import (
"errors"
"fmt"
"log"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
"time"

"github.com/pact-foundation/pact-go/dsl"
ex "github.com/pact-foundation/pact-go/examples/types"
"github.com/pact-foundation/pact-go/types"
)

// Common test data
Expand Down Expand Up @@ -59,29 +56,6 @@ func TestMain(m *testing.M) {
pact.WritePact()
pact.Teardown()

// Enable when running E2E/integration tests before a release
version := fmt.Sprintf("1.0.%s-%d", os.Getenv("TRAVIS_COMMIT"), time.Now().Unix())

// Publish the Pacts...
p := dsl.Publisher{
LogLevel: "DEBUG",
}

err := p.Publish(types.PublishRequest{
PactURLs: []string{filepath.FromSlash(fmt.Sprintf("%s/jmarie-loginprovider.json", pactDir))},
PactBroker: fmt.Sprintf("%s://%s", os.Getenv("PACT_BROKER_PROTO"), os.Getenv("PACT_BROKER_URL")),
ConsumerVersion: version,
Tags: []string{"dev", "prod"},
BrokerToken: os.Getenv("PACT_BROKER_TOKEN"),
BrokerUsername: os.Getenv("PACT_BROKER_USERNAME"),
BrokerPassword: os.Getenv("PACT_BROKER_PASSWORD"),
})

if err != nil {
log.Println("ERROR: ", err)
os.Exit(1)
}

os.Exit(code)
}

Expand Down
5 changes: 3 additions & 2 deletions examples/gin/provider/user_service_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build provider
// +build provider

package provider
Expand Down Expand Up @@ -41,7 +42,7 @@ func TestExample_GinProvider(t *testing.T) {
// Verify the Provider - Latest Published Pacts for any known consumers
_, err := pact.VerifyProvider(t, types.VerifyRequest{
ProviderBaseURL: fmt.Sprintf("http://127.0.0.1:%d", port),
BrokerURL: fmt.Sprintf("%s://%s", os.Getenv("PACT_BROKER_PROTO"), os.Getenv("PACT_BROKER_URL")),
BrokerURL: os.Getenv("PACT_BROKER_BASE_URL"),
BrokerToken: os.Getenv("PACT_BROKER_TOKEN"),
BrokerUsername: os.Getenv("PACT_BROKER_USERNAME"),
BrokerPassword: os.Getenv("PACT_BROKER_PASSWORD"),
Expand All @@ -58,7 +59,7 @@ func TestExample_GinProvider(t *testing.T) {
// Verify the Provider - Tag-based Published Pacts for any known consumers
_, err = pact.VerifyProvider(t, types.VerifyRequest{
ProviderBaseURL: fmt.Sprintf("http://127.0.0.1:%d", port),
BrokerURL: fmt.Sprintf("%s://%s", os.Getenv("PACT_BROKER_PROTO"), os.Getenv("PACT_BROKER_URL")),
BrokerURL: os.Getenv("PACT_BROKER_BASE_URL"),
// Use ConsumerVersionSelectors instead of Tags for
ConsumerVersionSelectors: selectors,
BrokerToken: os.Getenv("PACT_BROKER_TOKEN"),
Expand Down
Loading

0 comments on commit bb90356

Please sign in to comment.