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