Add tests for behavior on error (#83)

## Summary:
We guarantee that we never return a nil response, so you can safely do
```
resp, err := myQuery(...)
return resp.Field.SubField, err
```
And furthermore, if the error was a GraphQL error, `resp` may even be
nonzero; other, non-failing fields may be set.  (This depends on the
server, of course.) But we weren't testing either of those.  Now we do.

## Test plan:
make check


Author: benjaminjkraft

Reviewers: jvoll, aberkan, dnerdy, MiguelCastillo, mahtabsabet

Required Reviewers: 

Approved By: jvoll

Checks:  Test (1.17),  Test (1.16),  Test (1.15),  Test (1.14),  Lint,  Test (1.17),  Test (1.16),  Test (1.15),  Test (1.14),  Lint

Pull Request URL: https://github.com/Khan/genqlient/pull/83
This commit is contained in:
Ben Kraft
2021-09-10 15:45:34 -07:00
committed by GitHub
parent e88305ecbd
commit 9e1c98488e
5 changed files with 128 additions and 0 deletions
+35
View File
@@ -266,6 +266,17 @@ func (v *UserFields) UnmarshalJSON(b []byte) error {
return nil
}
// failingQueryMeUser includes the requested fields of the GraphQL type User.
type failingQueryMeUser struct {
Id string `json:"id"`
}
// failingQueryResponse is returned by failingQuery on success.
type failingQueryResponse struct {
Fail bool `json:"fail"`
Me failingQueryMeUser `json:"me"`
}
// queryWithFragmentsBeingsAnimal includes the requested fields of the GraphQL type Animal.
type queryWithFragmentsBeingsAnimal struct {
Typename string `json:"__typename"`
@@ -1074,6 +1085,30 @@ query simpleQuery {
return &retval, err
}
func failingQuery(
ctx context.Context,
client graphql.Client,
) (*failingQueryResponse, error) {
var err error
var retval failingQueryResponse
err = client.MakeRequest(
ctx,
"failingQuery",
`
query failingQuery {
fail
me {
id
}
}
`,
&retval,
nil,
)
return &retval, err
}
func queryWithVariables(
ctx context.Context,
client graphql.Client,
+33
View File
@@ -34,6 +34,39 @@ func TestSimpleQuery(t *testing.T) {
assert.Equal(t, 17, resp.Me.LuckyNumber)
}
func TestServerError(t *testing.T) {
_ = `# @genqlient
query failingQuery { fail me { id } }`
ctx := context.Background()
server := server.RunServer()
defer server.Close()
client := graphql.NewClient(server.URL, http.DefaultClient)
resp, err := failingQuery(ctx, client)
// As long as we get some response back, we should still return a full
// response -- and indeed in this case it should even have another field
// (which didn't err) set.
assert.Error(t, err)
assert.NotNil(t, resp)
assert.Equal(t, "1", resp.Me.Id)
}
func TestNetworkError(t *testing.T) {
ctx := context.Background()
client := graphql.NewClient("https://nothing.invalid/graphql", http.DefaultClient)
resp, err := failingQuery(ctx, client)
// As we guarantee in the README, even on network error you always get a
// non-nil response; this is so you can write e.g.
// resp, err := failingQuery(ctx)
// return resp.Me.Id, err
// without a bunch of extra ceremony.
assert.Error(t, err)
assert.NotNil(t, resp)
assert.Equal(t, new(failingQueryResponse), resp)
}
func TestVariables(t *testing.T) {
_ = `# @genqlient
query queryWithVariables($id: ID!) { user(id: $id) { id name luckyNumber } }`
+1
View File
@@ -4,6 +4,7 @@ type Query {
being(id: ID!): Being
beings(ids: [ID!]!): [Being]!
lotteryWinner(number: Int!): Lucky
fail: Boolean
}
type User implements Being & Lucky {
@@ -61,6 +61,7 @@ type ComplexityRoot struct {
Query struct {
Being func(childComplexity int, id string) int
Beings func(childComplexity int, ids []string) int
Fail func(childComplexity int) int
LotteryWinner func(childComplexity int, number int) int
Me func(childComplexity int) int
User func(childComplexity int, id string) int
@@ -80,6 +81,7 @@ type QueryResolver interface {
Being(ctx context.Context, id string) (Being, error)
Beings(ctx context.Context, ids []string) ([]Being, error)
LotteryWinner(ctx context.Context, number int) (Lucky, error)
Fail(ctx context.Context) (*bool, error)
}
type executableSchema struct {
@@ -170,6 +172,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Query.Beings(childComplexity, args["ids"].([]string)), true
case "Query.fail":
if e.complexity.Query.Fail == nil {
break
}
return e.complexity.Query.Fail(childComplexity), true
case "Query.lotteryWinner":
if e.complexity.Query.LotteryWinner == nil {
break
@@ -285,6 +294,7 @@ var sources = []*ast.Source{
being(id: ID!): Being
beings(ids: [ID!]!): [Being]!
lotteryWinner(number: Int!): Lucky
fail: Boolean
}
type User implements Being & Lucky {
@@ -867,6 +877,38 @@ func (ec *executionContext) _Query_lotteryWinner(ctx context.Context, field grap
return ec.marshalOLucky2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐLucky(ctx, field.Selections, res)
}
func (ec *executionContext) _Query_fail(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Query",
Field: field,
Args: nil,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Query().Fail(rctx)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*bool)
fc.Result = res
return ec.marshalOBoolean2ᚖbool(ctx, field.Selections, res)
}
func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -2371,6 +2413,17 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
res = ec._Query_lotteryWinner(ctx, field)
return res
})
case "fail":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Query_fail(ctx, field)
return res
})
case "__type":
out.Values[i] = ec._Query___type(ctx, field)
case "__schema":
+6
View File
@@ -2,6 +2,7 @@ package server
import (
"context"
"fmt"
"net/http/httptest"
"github.com/99designs/gqlgen/graphql/handler"
@@ -82,6 +83,11 @@ func (r *queryResolver) LotteryWinner(ctx context.Context, number int) (Lucky, e
return nil, nil
}
func (r *queryResolver) Fail(ctx context.Context) (*bool, error) {
f := true
return &f, fmt.Errorf("oh no")
}
func RunServer() *httptest.Server {
gqlgenServer := handler.New(NewExecutableSchema(Config{Resolvers: &resolver{}}))
gqlgenServer.AddTransport(transport.POST{})