1 module graphql.parsertests;
2 
3 import std.format : format;
4 import std.experimental.allocator;
5 import std.experimental.allocator.mallocator : Mallocator;
6 import std.stdio;
7 
8 import graphql.lexer;
9 import graphql.parser;
10 
11 private struct TestCase {
12 	int id;
13 	QueryParser qp;
14 	string str;
15 }
16 
17 unittest {
18 	TestCase[] tests;
19 	tests ~= TestCase(0, QueryParser.yes,
20 	`
21 mutation updateUser($userId: String! $name: String!) {
22   updateUser(id: $userId name: $name) {
23     name
24   }
25 }
26 `);
27 
28 	tests ~= TestCase(1, QueryParser.yes, `
29 query inlineFragmentNoType($expandedInfo: Boolean) {
30   user(handle: "zuck") {
31     id
32     name
33     ... @include(if: $expandedInfo) {
34       firstName
35       lastName
36       birthday
37     }
38   }
39 }
40 `);
41 
42 	tests ~= TestCase(2, QueryParser.yes,  `
43 query HeroForEpisode($ep: Episode!) {
44   hero(episode: $ep) {
45     name
46     ... on Droid {
47       primaryFunction
48     }
49     ... on Human {
50       height
51     }
52   }
53 }
54 `);
55 
56 	tests ~= TestCase(3, QueryParser.yes,  `
57 mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
58   createReview(episode: $ep, review: $review) {
59     stars
60     commentary
61   }
62 }
63 `);
64 
65 	tests ~= TestCase(4, QueryParser.yes,  `
66 query HeroNameAndFriends($episode: Episode = "JEDI") {
67   hero(episode: $episode) {
68     name
69     friends {
70       name
71     }
72   }
73 }`);
74 
75 	tests ~= TestCase(5, QueryParser.yes,  `
76 query hero {
77     name
78     # Queries can have comments!
79 
80     # Queries can have comments Another!
81     friends {
82       name
83     }
84 }`);
85 
86 	tests ~= TestCase(6, QueryParser.no,  `
87 enum DogCommand { SIT, DOWN, HEEL }
88 
89 type Dog implements Pet {
90   name: String!
91   nickname: String
92   barkVolume: Int
93   doesKnowCommand(dogCommand: DogCommand!): Boolean!
94   isHousetrained(atOtherHomes: Boolean): Boolean!
95   owner: Human
96 }
97 
98 interface Sentient {
99   name: String!
100 }
101 
102 interface Pet {
103   name: String!
104 }
105 
106 type Alien implements Sentient {
107   name: String!
108   homePlanet: String
109 }
110 
111 type Human implements Sentient {
112   name: String!
113 }
114 
115 enum CatCommand { JUMP }
116 
117 type Cat implements Pet {
118   name: String!
119   nickname: String
120   doesKnowCommand(catCommand: CatCommand!): Boolean!
121   meowVolume: Int
122 }
123 
124 union CatOrDog = Cat | Dog
125 union DogOrHuman = Dog | Human
126 union HumanOrAlien = Human | Alien
127 
128 type QueryRoot {
129   dog: Dog
130 }
131 `);
132 
133 	tests ~= TestCase(7, QueryParser.no,  `
134 union SearchResult = Photo | Person
135 union SearchResult = Photo Person
136 
137 type Person {
138   name: String
139   age: Int
140 }
141 
142 type Photo {
143   height: Int
144   width: Int
145 }
146 
147 type SearchQuery {
148   firstSearchResult: SearchResult
149 }`);
150 
151 	tests ~= TestCase(8, QueryParser.no,  `
152 interface NamedEntity {
153   name: String
154 }
155 
156 type Person implements NamedEntity {
157   name: String
158   age: Int
159 }
160 
161 type Business implements NamedEntity {
162   name: String
163   employeeCount: Int
164 }`);
165 
166 	tests ~= TestCase(9, QueryParser.no, `type Person {
167   name: String
168   age: Int
169   picture: Url
170 }`);
171 
172 	tests ~= TestCase(10, QueryParser.yes, `
173 query myQuery($someTest: Boolean) {
174   experimentalField @skip(if: $someTest)
175 }`);
176 
177 	tests ~= TestCase(11, QueryParser.no, `
178 subscription sub {
179   newMessage {
180     body
181     sender
182   }
183 }
184 
185 fragment newMessageFields on Message {
186   body
187   sender
188 }
189 
190 subscription sub {
191   newMessage {
192     ... newMessageFields
193   }
194 
195 }`);
196 
197 	tests ~= TestCase(11, QueryParser.yes, `
198 query HeroNameAndFriends($episode: Episode) {
199   hero(episode: $episode) {
200     name,
201     friends {
202       name
203     }
204   }
205 }`);
206 
207 	tests ~= TestCase(12, QueryParser.yes, `
208 query name {
209   leftComparison: hero(episode: $EMPIRE) {
210     ...comparisonFields
211   }
212   rightComparison: hero(episode: $JEDI) {
213     ...comparisonFields
214   }
215 }
216 fragment comparisonFields on Character {
217   name
218   appearsIn
219   friends {
220     name
221   }
222 }
223 
224 `);
225 
226 	tests ~= TestCase(13, QueryParser.yes, `
227 query name{
228  builds(first: 54) {
229 	all: number
230  }
231 }
232 `);
233 
234 	tests ~= TestCase(14, QueryParser.yes, `
235 		query foo {
236 			viewer {
237 				user {
238 					name
239 						builds(first: 1) {
240 							edges {
241 								node {
242 									number
243 										branch
244 										message
245 								}
246 							}
247 						}
248 				}
249 			}
250 		}
251 `);
252 
253 	tests ~= TestCase(15, QueryParser.yes, `
254 query foo {
255 	name
256     builds(first: 1) {
257 		abc
258   }
259  }`);
260 
261 	tests ~= TestCase(16, QueryParser.yes, `
262 query h {
263 	name
264     builds
265   }
266 `);
267 
268 	tests ~= TestCase(17, QueryParser.yes, `
269   query human($id: H, $limit: lim, $gender: Gen) {
270     name
271     height
272 	friends {
273 		id
274 		gender
275 		income
276 	}
277   }` );
278 
279 	tests ~= TestCase(18, QueryParser.yes, `
280   query human($id: Var) {
281     name
282     height
283   }`);
284 
285 	tests ~= TestCase(19, QueryParser.yes, `
286   query human() {
287     name
288     height
289   }`);
290 
291 	tests ~= TestCase(20, QueryParser.yes, `
292   query name {
293 	  foo
294 }`);
295 
296 	/*tests ~= TestCase(21, `{
297 		query n {
298   __type(name: "User") {
299     name
300     fields {
301       name
302       type {
303         name
304       }
305     }
306   }
307 }
308 }`);*/
309 
310 	tests ~= TestCase(21, QueryParser.yes, `{
311  user(id: 1) {
312    friends {
313      name
314    }
315  }
316 }`);
317 
318 	tests ~= TestCase(22, QueryParser.yes, `query IntrospectionQueryTypeQuery {
319           __schema {
320             queryType {
321               fields {
322                 name
323                 args {
324                   name
325                   description
326                   type {
327                     name
328                     kind
329                     ofType {
330                       name
331                       kind
332                     }
333                   }
334                   defaultValue
335                 }
336               }
337             }
338           }
339         }`);
340 
341 	tests ~= TestCase(23, QueryParser.yes, `query IntrospectionDroidFieldsQuery {
342           __type(name: "Droid") {
343             name
344             fields {
345               name
346               type {
347                 name
348                 kind
349               }
350             }
351           }
352         }`);
353 
354 	tests ~= TestCase(24, QueryParser.yes, `
355         query IntrospectionCharacterKindQuery {
356           __type(name: "Character") {
357             name
358             kind
359           }
360         }`);
361 
362 
363 	tests ~= TestCase(25, QueryParser.yes, `query IntrospectionDroidKindQuery {
364           __type(name: "Droid") {
365             name
366             kind
367           }
368         }`);
369 
370 	tests ~= TestCase(26, QueryParser.yes, `query IntrospectionDroidTypeQuery {
371           __type(name: "Droid") {
372             name
373           }
374         }`);
375 
376 	tests ~= TestCase(27, QueryParser.yes, `query IntrospectionQueryTypeQuery {
377           __schema {
378             queryType {
379               name
380             }
381           }
382         }`);
383 
384 	tests ~= TestCase(28, QueryParser.yes, `query IntrospectionTypeQuery {
385           __schema {
386             types {
387               name
388             }
389           }
390         }`);
391 
392 	tests ~= TestCase(29, QueryParser.yes,
393 			`query IntrospectionDroidDescriptionQuery {
394           __type(name: "Droid") {
395             name
396             description
397           }
398         }`);
399 
400 	foreach(test; tests) {
401 		auto l = Lexer(test.str, test.qp);
402 		auto p = Parser(l);
403 		try {
404 			auto d = p.parseDocument();
405 		} catch(Throwable e) {
406 			writeln(e.toString());
407 			while(e.next) {
408 				writeln(e.next.toString());
409 				e = e.next;
410 			}
411 			assert(false, format("Test %d", test.id));
412 		}
413 		assert(p.lex.empty, format("%d %s", test.id, p.lex.getRestOfInput()));
414 	}
415 }