1package graphql_test
2
3import (
4	"fmt"
5	"reflect"
6	"testing"
7
8	"github.com/graphql-go/graphql"
9	"github.com/graphql-go/graphql/testutil"
10)
11
12// TODO: have a separate package for other tests for eg `parser`
13// maybe for:
14// - tests that supposed to be black-boxed (no reason to access private identifiers)
15// - tests that create internal tests structs, we might not want to pollute the package with too many test structs
16
17type testPic struct {
18	Url    string `json:"url"`
19	Width  string `json:"width"`
20	Height string `json:"height"`
21}
22
23type testPicFn func(width, height string) *testPic
24
25type testAuthor struct {
26	Id            int          `json:"id"`
27	Name          string       `json:"name"`
28	Pic           testPicFn    `json:"pic"`
29	RecentArticle *testArticle `json:"recentArticle"`
30}
31type testArticle struct {
32	Id          string        `json:"id"`
33	IsPublished string        `json:"isPublished"`
34	Author      *testAuthor   `json:"author"`
35	Title       string        `json:"title"`
36	Body        string        `json:"body"`
37	Hidden      string        `json:"hidden"`
38	Keywords    []interface{} `json:"keywords"`
39}
40
41func getPic(id int, width, height string) *testPic {
42	return &testPic{
43		Url:    fmt.Sprintf("cdn://%v", id),
44		Width:  width,
45		Height: height,
46	}
47}
48
49var johnSmith *testAuthor
50
51func article(id interface{}) *testArticle {
52	return &testArticle{
53		Id:          fmt.Sprintf("%v", id),
54		IsPublished: "true",
55		Author:      johnSmith,
56		Title:       fmt.Sprintf("My Article %v", id),
57		Body:        "This is a post",
58		Hidden:      "This data is not exposed in the schema",
59		Keywords: []interface{}{
60			"foo", "bar", 1, true, nil,
61		},
62	}
63}
64
65func TestExecutesUsingAComplexSchema(t *testing.T) {
66
67	johnSmith = &testAuthor{
68		Id:   123,
69		Name: "John Smith",
70		Pic: func(width string, height string) *testPic {
71			return getPic(123, width, height)
72		},
73		RecentArticle: article("1"),
74	}
75
76	blogImage := graphql.NewObject(graphql.ObjectConfig{
77		Name: "Image",
78		Fields: graphql.Fields{
79			"url": &graphql.Field{
80				Type: graphql.String,
81			},
82			"width": &graphql.Field{
83				Type: graphql.Int,
84			},
85			"height": &graphql.Field{
86				Type: graphql.Int,
87			},
88		},
89	})
90	blogAuthor := graphql.NewObject(graphql.ObjectConfig{
91		Name: "Author",
92		Fields: graphql.Fields{
93			"id": &graphql.Field{
94				Type: graphql.String,
95			},
96			"name": &graphql.Field{
97				Type: graphql.String,
98			},
99			"pic": &graphql.Field{
100				Type: blogImage,
101				Args: graphql.FieldConfigArgument{
102					"width": &graphql.ArgumentConfig{
103						Type: graphql.Int,
104					},
105					"height": &graphql.ArgumentConfig{
106						Type: graphql.Int,
107					},
108				},
109				Resolve: func(p graphql.ResolveParams) (interface{}, error) {
110					if author, ok := p.Source.(*testAuthor); ok {
111						width := fmt.Sprintf("%v", p.Args["width"])
112						height := fmt.Sprintf("%v", p.Args["height"])
113						return author.Pic(width, height), nil
114					}
115					return nil, nil
116				},
117			},
118			"recentArticle": &graphql.Field{},
119		},
120	})
121	blogArticle := graphql.NewObject(graphql.ObjectConfig{
122		Name: "Article",
123		Fields: graphql.Fields{
124			"id": &graphql.Field{
125				Type: graphql.NewNonNull(graphql.String),
126			},
127			"isPublished": &graphql.Field{
128				Type: graphql.Boolean,
129			},
130			"author": &graphql.Field{
131				Type: blogAuthor,
132			},
133			"title": &graphql.Field{
134				Type: graphql.String,
135			},
136			"body": &graphql.Field{
137				Type: graphql.String,
138			},
139			"keywords": &graphql.Field{
140				Type: graphql.NewList(graphql.String),
141			},
142		},
143	})
144
145	blogAuthor.AddFieldConfig("recentArticle", &graphql.Field{
146		Type: blogArticle,
147	})
148
149	blogQuery := graphql.NewObject(graphql.ObjectConfig{
150		Name: "Query",
151		Fields: graphql.Fields{
152			"article": &graphql.Field{
153				Type: blogArticle,
154				Args: graphql.FieldConfigArgument{
155					"id": &graphql.ArgumentConfig{
156						Type: graphql.ID,
157					},
158				},
159				Resolve: func(p graphql.ResolveParams) (interface{}, error) {
160					id := p.Args["id"]
161					return article(id), nil
162				},
163			},
164			"feed": &graphql.Field{
165				Type: graphql.NewList(blogArticle),
166				Resolve: func(p graphql.ResolveParams) (interface{}, error) {
167					return []*testArticle{
168						article(1),
169						article(2),
170						article(3),
171						article(4),
172						article(5),
173						article(6),
174						article(7),
175						article(8),
176						article(9),
177						article(10),
178					}, nil
179				},
180			},
181		},
182	})
183
184	blogSchema, err := graphql.NewSchema(graphql.SchemaConfig{
185		Query: blogQuery,
186	})
187	if err != nil {
188		t.Fatalf("Error in schema %v", err.Error())
189	}
190
191	request := `
192      {
193        feed {
194          id,
195          title
196        },
197        article(id: "1") {
198          ...articleFields,
199          author {
200            id,
201            name,
202            pic(width: 640, height: 480) {
203              url,
204              width,
205              height
206            },
207            recentArticle {
208              ...articleFields,
209              keywords
210            }
211          }
212        }
213      }
214
215      fragment articleFields on Article {
216        id,
217        isPublished,
218        title,
219        body,
220        hidden,
221        notdefined
222      }
223	`
224
225	expected := &graphql.Result{
226		Data: map[string]interface{}{
227			"article": map[string]interface{}{
228				"title": "My Article 1",
229				"body":  "This is a post",
230				"author": map[string]interface{}{
231					"id":   "123",
232					"name": "John Smith",
233					"pic": map[string]interface{}{
234						"url":    "cdn://123",
235						"width":  640,
236						"height": 480,
237					},
238					"recentArticle": map[string]interface{}{
239						"id":          "1",
240						"isPublished": bool(true),
241						"title":       "My Article 1",
242						"body":        "This is a post",
243						"keywords": []interface{}{
244							"foo",
245							"bar",
246							"1",
247							"true",
248							nil,
249						},
250					},
251				},
252				"id":          "1",
253				"isPublished": bool(true),
254			},
255			"feed": []interface{}{
256				map[string]interface{}{
257					"id":    "1",
258					"title": "My Article 1",
259				},
260				map[string]interface{}{
261					"id":    "2",
262					"title": "My Article 2",
263				},
264				map[string]interface{}{
265					"id":    "3",
266					"title": "My Article 3",
267				},
268				map[string]interface{}{
269					"id":    "4",
270					"title": "My Article 4",
271				},
272				map[string]interface{}{
273					"id":    "5",
274					"title": "My Article 5",
275				},
276				map[string]interface{}{
277					"id":    "6",
278					"title": "My Article 6",
279				},
280				map[string]interface{}{
281					"id":    "7",
282					"title": "My Article 7",
283				},
284				map[string]interface{}{
285					"id":    "8",
286					"title": "My Article 8",
287				},
288				map[string]interface{}{
289					"id":    "9",
290					"title": "My Article 9",
291				},
292				map[string]interface{}{
293					"id":    "10",
294					"title": "My Article 10",
295				},
296			},
297		},
298	}
299
300	// parse query
301	ast := testutil.TestParse(t, request)
302
303	// execute
304	ep := graphql.ExecuteParams{
305		Schema: blogSchema,
306		AST:    ast,
307	}
308	result := testutil.TestExecute(t, ep)
309	if len(result.Errors) > 0 {
310		t.Fatalf("wrong result, unexpected errors: %v", result.Errors)
311	}
312	if !reflect.DeepEqual(expected, result) {
313		t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, result))
314	}
315}
316