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 	// Issue 20
401 	tests ~= TestCase(30, QueryParser.yes,
402 			`# a stupid comment that crashed Steven's tests
403 			# more comments
404 			query IntrospectionDroidDescriptionQuery {
405 					__type(name: "Droid") {
406 						name
407 						description
408 					}
409 				}`);
410 
411 	tests ~= TestCase(31, QueryParser.yes,
412 `# Welcome to GraphiQL
413 #
414 # GraphiQL is an in-browser tool for writing, validating, and
415 # testing GraphQL queries.
416 #
417 # Type queries into this side of the screen, and you will see intelligent
418 # typeaheads aware of the current GraphQL type schema and live syntax and
419 # validation errors highlighted within the text.
420 #
421 # GraphQL queries typically start with a "{" character. Lines that starts
422 # with a # are ignored.
423 #
424 # An example GraphQL query might look like:
425 #
426 #		 {
427 #			 field(arg: "value") {
428 #				 subField
429 #			 }
430 #		 }
431 #
432 # Keyboard shortcuts:
433 #
434 #	Prettify Query:	Shift-Ctrl-P (or press the prettify button above)
435 #
436 #			 Run Query:	Ctrl-Enter (or press the play button above)
437 #
438 #	 Auto Complete:	Ctrl-Space (or just start typing)
439 #
440 
441 
442 {allEmployees
443 {
444 	info
445 	{
446 		addressInstance
447 		{
448 			line1
449 		}
450 	}
451 }}
452 			`);
453 
454 	foreach(test; tests) {
455 		auto l = Lexer(test.str, test.qp);
456 		auto p = Parser(l);
457 		try {
458 			auto d = p.parseDocument();
459 		} catch(Throwable e) {
460 			writeln(e.toString());
461 			while(e.next) {
462 				writeln(e.next.toString());
463 				e = e.next;
464 			}
465 			assert(false, format("Test %d", test.id));
466 		}
467 		assert(p.lex.empty, format("%d %s", test.id, p.lex.getRestOfInput()));
468 	}
469 }
470 
471 unittest {
472 	import std.file : readText;
473 	auto l = Lexer(readText("starwarsschemaparsetest.graphql"), QueryParser.no);
474 	auto p = Parser(l);
475 	auto a = p.parseDocument();
476 }