1 package followschema
2
3 import (
4 "context"
5 "errors"
6 "testing"
7
8 "github.com/stretchr/testify/require"
9
10 "github.com/99designs/gqlgen/client"
11 "github.com/99designs/gqlgen/graphql/handler"
12 )
13
14 func TestNullBubbling(t *testing.T) {
15 resolvers := &Stub{}
16 resolvers.QueryResolver.Valid = func(ctx context.Context) (s string, e error) {
17 return "Ok", nil
18 }
19 resolvers.QueryResolver.Invalid = func(ctx context.Context) (s string, e error) {
20 return "Ok", errors.New("ERROR")
21 }
22 resolvers.QueryResolver.Errors = func(ctx context.Context) (errors *Errors, e error) {
23 return &Errors{}, nil
24 }
25 resolvers.QueryResolver.ErrorBubble = func(ctx context.Context) (i *Error, e error) {
26 return &Error{ID: "E1234"}, nil
27 }
28 resolvers.QueryResolver.ErrorBubbleList = func(ctx context.Context) (i []*Error, e error) {
29 return []*Error{{ID: "1"}, nil, nil}, nil
30 }
31 resolvers.QueryResolver.ErrorList = func(ctx context.Context) (i []*Error, e error) {
32 return []*Error{nil}, nil
33 }
34
35 c := client.New(handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: resolvers})))
36
37 t.Run("when function errors on non required field", func(t *testing.T) {
38 var resp struct {
39 Valid string
40 ErrorBubble *struct {
41 Id string
42 ErrorOnNonRequiredField *string
43 }
44 }
45 err := c.Post(`query { valid, errorBubble { id, errorOnNonRequiredField } }`, &resp)
46
47 require.EqualError(t, err, `[{"message":"boom","path":["errorBubble","errorOnNonRequiredField"]}]`)
48 require.Equal(t, "E1234", resp.ErrorBubble.Id)
49 require.Nil(t, resp.ErrorBubble.ErrorOnNonRequiredField)
50 require.Equal(t, "Ok", resp.Valid)
51 })
52
53 t.Run("when function errors", func(t *testing.T) {
54 var resp struct {
55 Valid string
56 ErrorBubble *struct {
57 NilOnRequiredField string
58 }
59 }
60 err := c.Post(`query { valid, errorBubble { id, errorOnRequiredField } }`, &resp)
61
62 require.EqualError(t, err, `[{"message":"boom","path":["errorBubble","errorOnRequiredField"]}]`)
63 require.Nil(t, resp.ErrorBubble)
64 require.Equal(t, "Ok", resp.Valid)
65 })
66
67 t.Run("when user returns null on required field", func(t *testing.T) {
68 var resp struct {
69 Valid string
70 ErrorBubble *struct {
71 NilOnRequiredField string
72 }
73 }
74 err := c.Post(`query { valid, errorBubble { id, nilOnRequiredField } }`, &resp)
75
76 require.EqualError(t, err, `[{"message":"the requested element is null which the schema does not allow","path":["errorBubble","nilOnRequiredField"]}]`)
77 require.Nil(t, resp.ErrorBubble)
78 require.Equal(t, "Ok", resp.Valid)
79 })
80
81 t.Run("when list element is null", func(t *testing.T) {
82 var resp struct {
83 Valid string
84 ErrorList []*struct{}
85 }
86 err := c.Post(`query { valid, errorList { id } }`, &resp)
87
88 require.Nil(t, err)
89 require.Equal(t, len(resp.ErrorList), 1)
90 require.Nil(t, resp.ErrorList[0])
91 require.Equal(t, "Ok", resp.Valid)
92 })
93
94 t.Run("when non-null list element is null", func(t *testing.T) {
95 var resp struct {
96 Valid string
97 ErrorBubbleList []*struct{}
98 }
99 err := c.Post(`query { valid, errorBubbleList { id } }`, &resp)
100
101 require.Contains(t, err.Error(), `{"message":"the requested element is null which the schema does not allow","path":["errorBubbleList",2]}`)
102 require.Contains(t, err.Error(), `{"message":"the requested element is null which the schema does not allow","path":["errorBubbleList",1]}`)
103 require.Nil(t, resp.ErrorBubbleList)
104 require.Equal(t, "Ok", resp.Valid)
105 })
106
107 t.Run("null args", func(t *testing.T) {
108 var resp struct {
109 NullableArg *string
110 }
111 resolvers.QueryResolver.NullableArg = func(ctx context.Context, arg *int) (i *string, e error) {
112 v := "Ok"
113 return &v, nil
114 }
115
116 err := c.Post(`query { nullableArg(arg: null) }`, &resp)
117 require.Nil(t, err)
118 require.Equal(t, "Ok", *resp.NullableArg)
119 })
120
121 t.Run("concurrent null detection", func(t *testing.T) {
122 var resp interface{}
123 resolvers.ErrorsResolver.A = func(ctx context.Context, obj *Errors) (i *Error, e error) { return nil, nil }
124 resolvers.ErrorsResolver.B = func(ctx context.Context, obj *Errors) (i *Error, e error) { return nil, nil }
125 resolvers.ErrorsResolver.C = func(ctx context.Context, obj *Errors) (i *Error, e error) { return nil, nil }
126 resolvers.ErrorsResolver.D = func(ctx context.Context, obj *Errors) (i *Error, e error) { return nil, nil }
127 resolvers.ErrorsResolver.E = func(ctx context.Context, obj *Errors) (i *Error, e error) { return nil, nil }
128
129 err := c.Post(`{ errors {
130 a { id },
131 b { id },
132 c { id },
133 d { id },
134 e { id },
135 } }`, &resp)
136
137 require.Error(t, err)
138 require.Contains(t, err.Error(), "the requested element is null which the schema does not allow")
139 })
140
141 t.Run("when non-nullable field returns content while error occurred", func(t *testing.T) {
142 var resp any
143 err := c.Post(`query { invalid }`, &resp)
144 require.Nil(t, resp)
145 require.NotNil(t, err)
146 require.Contains(t, err.Error(), `{"message":"ERROR","path":["invalid"]}`)
147 })
148 }
149
View as plain text