Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add handler test #6

Merged
merged 9 commits into from
Apr 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .air.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ testdata_dir = "testdata"
tmp_dir = "tmp"

[build]
bin = "./tmp/gbox run -config ./Caddyfile -watch"
bin = "./tmp/gbox run -config ./Caddyfile.dist -watch"
cmd = "go build -o ./tmp/gbox ./cmd"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_dir = ["assets", "tmp", "vendor", "testdata", "internal/testserver"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion admin/generated/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions gqlgen.yml → admin/gqlgen.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Where are all the schema files located? globs are supported eg src/**/*.graphqls
schema:
- admin/*.graphqls
- './*.graphqls'

# Where should the generated server code go?
exec:
filename: admin/generated/generated.go
filename: generated/generated.go
package: generated

#
Expand All @@ -16,7 +16,7 @@ exec:
# Where should the resolver implementations go?
resolver:
layout: follow-schema
dir: admin
dir: .
package: admin

# Optional: turn on use `gqlgen:"fieldName"` tags in your models
Expand Down
4 changes: 2 additions & 2 deletions caching.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ type (

const (
CachingStatusPass CachingStatus = "PASS"
CachingStatusHit = "HIT"
CachingStatusMiss = "MISS"
CachingStatusHit CachingStatus = "HIT"
CachingStatusMiss CachingStatus = "MISS"
)

type Caching struct {
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ services:
GBOX_ENABLED_METRICS: 'true'
GBOX_GLOBAL_DIRECTIVES: |
debug
admin off
redis:
image: redis
ports:
Expand Down
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/pkg/errors v0.9.1
github.com/pquerna/cachecontrol v0.1.0
github.com/prometheus/client_golang v1.12.1
github.com/stretchr/testify v1.7.1
github.com/vektah/gqlparser/v2 v2.4.0
go.uber.org/zap v1.19.0
)
Expand All @@ -30,6 +31,7 @@ require (
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/alecthomas/chroma v0.9.2 // indirect
github.com/antlr/antlr4 v0.0.0-20200503195918-621b933c7a7f // indirect
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect
github.com/buger/jsonparser v1.1.1 // indirect
Expand All @@ -41,6 +43,7 @@ require (
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgraph-io/badger v1.6.2 // indirect
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
github.com/dgraph-io/ristretto v0.1.0 // indirect
Expand Down Expand Up @@ -100,6 +103,7 @@ require (
github.com/nxadm/tail v1.4.8 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/pegasus-kv/thrift v0.13.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.33.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
Expand Down Expand Up @@ -150,6 +154,7 @@ require (
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
howett.net/plist v0.0.0-20181124034731-591f970eefbb // indirect
k8s.io/apimachinery v0.23.5 // indirect
nhooyr.io/websocket v1.8.7 // indirect
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw=
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
Expand Down
5 changes: 1 addition & 4 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,7 @@ func (h Handler) CaddyModule() caddy.ModuleInfo {
func (h *Handler) Provision(ctx caddy.Context) (err error) {
h.metrics = metrics
h.logger = ctx.Logger(h)

if err = h.initRouter(); err != nil {
return err
}
h.initRouter()

var m interface{}

Expand Down
255 changes: 255 additions & 0 deletions handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
package gbox

import (
"context"
"fmt"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/caddyserver/caddy/v2/caddytest"
"github.com/gbox-proxy/gbox/internal/testserver"
"github.com/gbox-proxy/gbox/internal/testserver/generated"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"io"
"io/ioutil"
"net/http"
"strings"
"testing"
"time"
)

const (
caddyfilePattern = `
{
http_port 9090
https_port 9443
}
localhost:9090 {
route {
gbox {
upstream http://localhost:9091
%s
}
}
}
`
)

type IntegrationTestSuite struct {
suite.Suite
upstreamMockServer *http.Server
}

func (s *IntegrationTestSuite) BeforeTest(suiteName, testName string) {
gqlServer := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &testserver.Resolver{}}))
s.upstreamMockServer = &http.Server{
Addr: "localhost:9091",
Handler: gqlServer,
}

go func() {
s.upstreamMockServer.ListenAndServe()
}()

<-time.After(time.Millisecond * 10)
}

func (s *IntegrationTestSuite) AfterTest(suiteName, testName string) {
s.NoError(s.upstreamMockServer.Shutdown(context.Background()))
s.upstreamMockServer = nil
}

func (s *IntegrationTestSuite) TestComplexity() {
testCases := map[string]struct {
extraConfig string
payload string
expectedBody string
}{
"enabled": {
extraConfig: `
complexity {
max_depth 1
node_count_limit 1
max_complexity 1
}
`,
payload: `{"query": "query { users { books { title } } }"}`,
expectedBody: `{"errors":[{"message":"query max depth is 1, current 3"},{"message":"query node count limit is 1, current 2"},{"message":"max query complexity allow is 1, current 2"}]}`,
},
"disabled": {
extraConfig: `
complexity {
enabled false
max_depth 1
}
`,
payload: `{"query": "query { users { name } }"}`,
expectedBody: `{"data":{"users":[{"name":"A"},{"name":"B"},{"name":"C"}]}}`,
},
}

for name, testCase := range testCases {
tester := caddytest.NewTester(s.T())
tester.InitServer(fmt.Sprintf(caddyfilePattern, testCase.extraConfig), "caddyfile")

r, _ := http.NewRequest(
"POST",
"http://localhost:9090/graphql",
strings.NewReader(testCase.payload),
)
r.Header.Add("content-type", "application/json")

resp := tester.AssertResponseCode(r, http.StatusOK)
respBody, _ := ioutil.ReadAll(resp.Body)

require.Equalf(s.T(), testCase.expectedBody, string(respBody), "case: %s", name)
resp.Body.Close()
}
}

func (s *IntegrationTestSuite) TestIntrospection() {
testCases := map[string]struct {
extraConfig string
payload string
expectedBody string
}{
"enabled": {
extraConfig: "disabled_introspection false",
payload: `{"query": "query { __schema { queryType { name } } }"}`,
expectedBody: `{"data":{"__schema":{"queryType":{"name":"QueryTest"}}}}`,
},
"disabled": {
extraConfig: "disabled_introspection true",
payload: `{"query": "query { __schema { queryType { name } } }"}`,
expectedBody: `{"errors":[{"message":"introspection queries are not allowed"}]}`,
},
}

for name, testCase := range testCases {
tester := caddytest.NewTester(s.T())
tester.InitServer(fmt.Sprintf(caddyfilePattern, testCase.extraConfig), "caddyfile")

r, _ := http.NewRequest(
"POST",
"http://localhost:9090/graphql",
strings.NewReader(testCase.payload),
)
r.Header.Add("content-type", "application/json")

resp := tester.AssertResponseCode(r, http.StatusOK)
respBody, _ := io.ReadAll(resp.Body)

require.Equalf(s.T(), testCase.expectedBody, string(respBody), "case: %s", name)
resp.Body.Close()
}
}

func (s *IntegrationTestSuite) TestCachingStatus() {
const payload = `{"query": "query { users { name } }"}`

testCases := map[string]struct {
extraConfig string
expectedCachingStatus CachingStatus
expectedBody string
}{
"pass_when_empty_rules": {
extraConfig: `
caching {
}
`,
expectedCachingStatus: CachingStatusPass,
expectedBody: `{"data":{"users":[{"name":"A"},{"name":"B"},{"name":"C"}]}}`,
},
"pass_when_not_match_type": {
extraConfig: `
caching {
rules {
book {
max_age 5m
types {
BookTest
}
}
}
}
`,
expectedCachingStatus: CachingStatusPass,
expectedBody: `{"data":{"users":[{"name":"A"},{"name":"B"},{"name":"C"}]}}`,
},
"pass_when_not_match_field": {
extraConfig: `
caching {
rules {
user {
max_age 5m
types {
UserTest {
id
}
}
}
}
}
`,
expectedCachingStatus: CachingStatusPass,
expectedBody: `{"data":{"users":[{"name":"A"},{"name":"B"},{"name":"C"}]}}`,
},
"miss_on_first_time": {
extraConfig: `
caching {
rules {
user {
max_age 5m
types {
UserTest
}
}
}
}
`,
expectedCachingStatus: CachingStatusMiss,
expectedBody: `{"data":{"users":[{"name":"A"},{"name":"B"},{"name":"C"}]}}`,
},
"hit_on_second_time": {
extraConfig: `
caching {
rules {
user {
max_age 5m
types {
UserTest
}
}
}
}
`,
expectedCachingStatus: CachingStatusHit,
expectedBody: `{"data":{"users":[{"name":"A"},{"name":"B"},{"name":"C"}]}}`,
},
}

for name, testCase := range testCases {
tester := caddytest.NewTester(s.T())
tester.InitServer(fmt.Sprintf(caddyfilePattern, testCase.extraConfig), "caddyfile")

r, _ := http.NewRequest(
"POST",
"http://localhost:9090/graphql",
strings.NewReader(payload),
)
r.Header.Add("content-type", "application/json")

resp := tester.AssertResponseCode(r, http.StatusOK)
respBody, _ := io.ReadAll(resp.Body)
actualStatus := resp.Header.Get("x-cache")

require.Equalf(s.T(), testCase.expectedBody, string(respBody), "case %s: unexpected payload", name)
require.Equalf(s.T(), string(testCase.expectedCachingStatus), actualStatus, "case %s: unexpected status", name)

resp.Body.Close()
}
}

func TestIntegration(t *testing.T) {
s := new(IntegrationTestSuite)
suite.Run(t, s)
}
Loading