馃寠 A GraphQL implementation in Gleam
1/// Tests for GraphQL Parser (AST building)
2///
3/// GraphQL spec Section 2 - Language
4/// Parse tokens into Abstract Syntax Tree
5import gleam/list
6import gleam/option.{None}
7import gleeunit/should
8import swell/parser
9
10// Simple query tests
11pub fn parse_empty_query_test() {
12 "{ }"
13 |> parser.parse
14 |> should.be_ok
15}
16
17pub fn parse_anonymous_query_with_keyword_test() {
18 "query { user }"
19 |> parser.parse
20 |> should.be_ok
21 |> fn(doc) {
22 case doc {
23 parser.Document([
24 parser.Query(parser.SelectionSet([parser.Field("user", None, [], [])])),
25 ]) -> True
26 _ -> False
27 }
28 }
29 |> should.be_true
30}
31
32pub fn parse_single_field_test() {
33 "{ user }"
34 |> parser.parse
35 |> should.be_ok
36 |> fn(doc) {
37 case doc {
38 parser.Document([
39 parser.Query(parser.SelectionSet([
40 parser.Field(name: "user", alias: None, arguments: [], selections: []),
41 ])),
42 ]) -> True
43 _ -> False
44 }
45 }
46 |> should.be_true
47}
48
49pub fn parse_nested_fields_test() {
50 "{ user { name } }"
51 |> parser.parse
52 |> should.be_ok
53 |> fn(doc) {
54 case doc {
55 parser.Document([
56 parser.Query(parser.SelectionSet([
57 parser.Field(
58 name: "user",
59 alias: None,
60 arguments: [],
61 selections: [parser.Field("name", None, [], [])],
62 ),
63 ])),
64 ]) -> True
65 _ -> False
66 }
67 }
68 |> should.be_true
69}
70
71pub fn parse_multiple_fields_test() {
72 "{ user posts }"
73 |> parser.parse
74 |> should.be_ok
75 |> fn(doc) {
76 case doc {
77 parser.Document([
78 parser.Query(parser.SelectionSet([
79 parser.Field(name: "user", alias: None, arguments: [], selections: []),
80 parser.Field(
81 name: "posts",
82 alias: None,
83 arguments: [],
84 selections: [],
85 ),
86 ])),
87 ]) -> True
88 _ -> False
89 }
90 }
91 |> should.be_true
92}
93
94// Arguments tests
95pub fn parse_field_with_int_argument_test() {
96 "{ user(id: 42) }"
97 |> parser.parse
98 |> should.be_ok
99 |> fn(doc) {
100 case doc {
101 parser.Document([
102 parser.Query(parser.SelectionSet([
103 parser.Field(
104 name: "user",
105 alias: None,
106 arguments: [parser.Argument("id", parser.IntValue("42"))],
107 selections: [],
108 ),
109 ])),
110 ]) -> True
111 _ -> False
112 }
113 }
114 |> should.be_true
115}
116
117pub fn parse_field_with_string_argument_test() {
118 "{ user(name: \"Alice\") }"
119 |> parser.parse
120 |> should.be_ok
121 |> fn(doc) {
122 case doc {
123 parser.Document([
124 parser.Query(parser.SelectionSet([
125 parser.Field(
126 name: "user",
127 alias: None,
128 arguments: [parser.Argument("name", parser.StringValue("Alice"))],
129 selections: [],
130 ),
131 ])),
132 ]) -> True
133 _ -> False
134 }
135 }
136 |> should.be_true
137}
138
139pub fn parse_field_with_multiple_arguments_test() {
140 "{ user(id: 42, name: \"Alice\") }"
141 |> parser.parse
142 |> should.be_ok
143 |> fn(doc) {
144 case doc {
145 parser.Document([
146 parser.Query(parser.SelectionSet([
147 parser.Field(
148 name: "user",
149 alias: None,
150 arguments: [
151 parser.Argument("id", parser.IntValue("42")),
152 parser.Argument("name", parser.StringValue("Alice")),
153 ],
154 selections: [],
155 ),
156 ])),
157 ]) -> True
158 _ -> False
159 }
160 }
161 |> should.be_true
162}
163
164// Named operation tests
165pub fn parse_named_query_test() {
166 "query GetUser { user }"
167 |> parser.parse
168 |> should.be_ok
169 |> fn(doc) {
170 case doc {
171 parser.Document([
172 parser.NamedQuery(
173 name: "GetUser",
174 variables: [],
175 selections: parser.SelectionSet([parser.Field("user", None, [], [])]),
176 ),
177 ]) -> True
178 _ -> False
179 }
180 }
181 |> should.be_true
182}
183
184// Complex query test
185pub fn parse_complex_query_test() {
186 "
187 query GetUserPosts {
188 user(id: 1) {
189 name
190 posts {
191 title
192 content
193 }
194 }
195 }
196 "
197 |> parser.parse
198 |> should.be_ok
199}
200
201// Error cases
202pub fn parse_invalid_syntax_test() {
203 "{ user"
204 |> parser.parse
205 |> should.be_error
206}
207
208pub fn parse_empty_string_test() {
209 ""
210 |> parser.parse
211 |> should.be_error
212}
213
214pub fn parse_invalid_field_name_test() {
215 "{ 123 }"
216 |> parser.parse
217 |> should.be_error
218}
219
220// Fragment tests
221pub fn parse_fragment_definition_test() {
222 "
223 fragment UserFields on User {
224 id
225 name
226 }
227 { user { ...UserFields } }
228 "
229 |> parser.parse
230 |> should.be_ok
231 |> fn(doc) {
232 case doc {
233 parser.Document([
234 parser.FragmentDefinition(
235 name: "UserFields",
236 type_condition: "User",
237 selections: parser.SelectionSet([
238 parser.Field("id", None, [], []),
239 parser.Field("name", None, [], []),
240 ]),
241 ),
242 parser.Query(parser.SelectionSet([
243 parser.Field(
244 name: "user",
245 alias: None,
246 arguments: [],
247 selections: [parser.FragmentSpread("UserFields")],
248 ),
249 ])),
250 ]) -> True
251 _ -> False
252 }
253 }
254 |> should.be_true
255}
256
257pub fn parse_fragment_single_line_test() {
258 // The multiline version works - let's try it
259 "
260 { __type(name: \"Query\") { ...TypeFrag } }
261 fragment TypeFrag on __Type { name kind }
262 "
263 |> parser.parse
264 |> should.be_ok
265 |> fn(doc) {
266 case doc {
267 parser.Document(operations) -> list.length(operations) == 2
268 }
269 }
270 |> should.be_true
271}
272
273pub fn parse_fragment_truly_single_line_test() {
274 // This is the problematic single-line version
275 "{ __type(name: \"Query\") { ...TypeFrag } } fragment TypeFrag on __Type { name kind }"
276 |> parser.parse
277 |> should.be_ok
278 |> fn(doc) {
279 case doc {
280 parser.Document(operations) -> {
281 // If we only got 1 operation, the parser stopped after the query
282 case operations {
283 [parser.Query(_)] ->
284 panic as "Only got Query - fragment was not parsed"
285 _ -> list.length(operations) == 2
286 }
287 }
288 }
289 }
290 |> should.be_true
291}
292
293pub fn parse_inline_fragment_test() {
294 "
295 { user { ... on User { name } } }
296 "
297 |> parser.parse
298 |> should.be_ok
299}
300
301// List value tests
302pub fn parse_empty_list_argument_test() {
303 "{ user(tags: []) }"
304 |> parser.parse
305 |> should.be_ok
306 |> fn(doc) {
307 case doc {
308 parser.Document([
309 parser.Query(parser.SelectionSet([
310 parser.Field(
311 name: "user",
312 alias: None,
313 arguments: [parser.Argument("tags", parser.ListValue([]))],
314 selections: [],
315 ),
316 ])),
317 ]) -> True
318 _ -> False
319 }
320 }
321 |> should.be_true
322}
323
324pub fn parse_list_of_ints_test() {
325 "{ user(ids: [1, 2, 3]) }"
326 |> parser.parse
327 |> should.be_ok
328 |> fn(doc) {
329 case doc {
330 parser.Document([
331 parser.Query(parser.SelectionSet([
332 parser.Field(
333 name: "user",
334 alias: None,
335 arguments: [
336 parser.Argument(
337 "ids",
338 parser.ListValue([
339 parser.IntValue("1"),
340 parser.IntValue("2"),
341 parser.IntValue("3"),
342 ]),
343 ),
344 ],
345 selections: [],
346 ),
347 ])),
348 ]) -> True
349 _ -> False
350 }
351 }
352 |> should.be_true
353}
354
355pub fn parse_list_of_strings_test() {
356 "{ user(tags: [\"foo\", \"bar\"]) }"
357 |> parser.parse
358 |> should.be_ok
359 |> fn(doc) {
360 case doc {
361 parser.Document([
362 parser.Query(parser.SelectionSet([
363 parser.Field(
364 name: "user",
365 alias: None,
366 arguments: [
367 parser.Argument(
368 "tags",
369 parser.ListValue([
370 parser.StringValue("foo"),
371 parser.StringValue("bar"),
372 ]),
373 ),
374 ],
375 selections: [],
376 ),
377 ])),
378 ]) -> True
379 _ -> False
380 }
381 }
382 |> should.be_true
383}
384
385// Object value tests
386pub fn parse_empty_object_argument_test() {
387 "{ user(filter: {}) }"
388 |> parser.parse
389 |> should.be_ok
390 |> fn(doc) {
391 case doc {
392 parser.Document([
393 parser.Query(parser.SelectionSet([
394 parser.Field(
395 name: "user",
396 alias: None,
397 arguments: [parser.Argument("filter", parser.ObjectValue([]))],
398 selections: [],
399 ),
400 ])),
401 ]) -> True
402 _ -> False
403 }
404 }
405 |> should.be_true
406}
407
408pub fn parse_object_with_fields_test() {
409 "{ user(filter: {name: \"Alice\", age: 30}) }"
410 |> parser.parse
411 |> should.be_ok
412 |> fn(doc) {
413 case doc {
414 parser.Document([
415 parser.Query(parser.SelectionSet([
416 parser.Field(
417 name: "user",
418 alias: None,
419 arguments: [
420 parser.Argument(
421 "filter",
422 parser.ObjectValue([
423 #("name", parser.StringValue("Alice")),
424 #("age", parser.IntValue("30")),
425 ]),
426 ),
427 ],
428 selections: [],
429 ),
430 ])),
431 ]) -> True
432 _ -> False
433 }
434 }
435 |> should.be_true
436}
437
438// Nested structures
439pub fn parse_list_of_objects_test() {
440 "{ posts(sortBy: [{field: \"date\", direction: DESC}]) }"
441 |> parser.parse
442 |> should.be_ok
443 |> fn(doc) {
444 case doc {
445 parser.Document([
446 parser.Query(parser.SelectionSet([
447 parser.Field(
448 name: "posts",
449 alias: None,
450 arguments: [
451 parser.Argument(
452 "sortBy",
453 parser.ListValue([
454 parser.ObjectValue([
455 #("field", parser.StringValue("date")),
456 #("direction", parser.EnumValue("DESC")),
457 ]),
458 ]),
459 ),
460 ],
461 selections: [],
462 ),
463 ])),
464 ]) -> True
465 _ -> False
466 }
467 }
468 |> should.be_true
469}
470
471pub fn parse_object_with_nested_list_test() {
472 "{ user(filter: {tags: [\"a\", \"b\"]}) }"
473 |> parser.parse
474 |> should.be_ok
475}
476
477// Variable definition tests
478pub fn parse_query_with_one_variable_test() {
479 "query Test($name: String!) { user }"
480 |> parser.parse
481 |> should.be_ok
482 |> fn(doc) {
483 case doc {
484 parser.Document([
485 parser.NamedQuery(
486 name: "Test",
487 variables: [parser.Variable("name", "String!", None)],
488 selections: parser.SelectionSet([parser.Field("user", None, [], [])]),
489 ),
490 ]) -> True
491 _ -> False
492 }
493 }
494 |> should.be_true
495}
496
497pub fn parse_query_with_multiple_variables_test() {
498 "query Test($name: String!, $age: Int) { user }"
499 |> parser.parse
500 |> should.be_ok
501 |> fn(doc) {
502 case doc {
503 parser.Document([
504 parser.NamedQuery(
505 name: "Test",
506 variables: [
507 parser.Variable("name", "String!", None),
508 parser.Variable("age", "Int", None),
509 ],
510 selections: parser.SelectionSet([parser.Field("user", None, [], [])]),
511 ),
512 ]) -> True
513 _ -> False
514 }
515 }
516 |> should.be_true
517}
518
519pub fn parse_mutation_with_variables_test() {
520 "mutation CreateUser($name: String!, $email: String!) { createUser }"
521 |> parser.parse
522 |> should.be_ok
523 |> fn(doc) {
524 case doc {
525 parser.Document([
526 parser.NamedMutation(
527 name: "CreateUser",
528 variables: [
529 parser.Variable("name", "String!", None),
530 parser.Variable("email", "String!", None),
531 ],
532 selections: parser.SelectionSet([
533 parser.Field("createUser", None, [], []),
534 ]),
535 ),
536 ]) -> True
537 _ -> False
538 }
539 }
540 |> should.be_true
541}
542
543pub fn parse_variable_value_in_argument_test() {
544 "{ user(name: $userName) }"
545 |> parser.parse
546 |> should.be_ok
547 |> fn(doc) {
548 case doc {
549 parser.Document([
550 parser.Query(parser.SelectionSet([
551 parser.Field(
552 name: "user",
553 alias: None,
554 arguments: [
555 parser.Argument("name", parser.VariableValue("userName")),
556 ],
557 selections: [],
558 ),
559 ])),
560 ]) -> True
561 _ -> False
562 }
563 }
564 |> should.be_true
565}
566
567// Subscription tests
568pub fn parse_anonymous_subscription_with_keyword_test() {
569 "subscription { messageAdded }"
570 |> parser.parse
571 |> should.be_ok
572 |> fn(doc) {
573 case doc {
574 parser.Document([
575 parser.Subscription(parser.SelectionSet([
576 parser.Field("messageAdded", None, [], []),
577 ])),
578 ]) -> True
579 _ -> False
580 }
581 }
582 |> should.be_true
583}
584
585pub fn parse_named_subscription_test() {
586 "subscription OnMessage { messageAdded { content } }"
587 |> parser.parse
588 |> should.be_ok
589 |> fn(doc) {
590 case doc {
591 parser.Document([
592 parser.NamedSubscription(
593 "OnMessage",
594 [],
595 parser.SelectionSet([
596 parser.Field(
597 name: "messageAdded",
598 alias: None,
599 arguments: [],
600 selections: [parser.Field("content", None, [], [])],
601 ),
602 ]),
603 ),
604 ]) -> True
605 _ -> False
606 }
607 }
608 |> should.be_true
609}
610
611pub fn parse_subscription_with_nested_fields_test() {
612 "subscription { postCreated { id title author { name } } }"
613 |> parser.parse
614 |> should.be_ok
615 |> fn(doc) {
616 case doc {
617 parser.Document([
618 parser.Subscription(parser.SelectionSet([
619 parser.Field(
620 name: "postCreated",
621 alias: None,
622 arguments: [],
623 selections: [
624 parser.Field("id", None, [], []),
625 parser.Field("title", None, [], []),
626 parser.Field(
627 name: "author",
628 alias: None,
629 arguments: [],
630 selections: [parser.Field("name", None, [], [])],
631 ),
632 ],
633 ),
634 ])),
635 ]) -> True
636 _ -> False
637 }
638 }
639 |> should.be_true
640}
641
642// List type variable tests
643pub fn parse_variable_with_list_type_test() {
644 "query Test($ids: [Int]) { users }"
645 |> parser.parse
646 |> should.be_ok
647 |> fn(doc) {
648 case doc {
649 parser.Document([
650 parser.NamedQuery(
651 name: "Test",
652 variables: [parser.Variable("ids", "[Int]", None)],
653 selections: parser.SelectionSet([parser.Field("users", None, [], [])]),
654 ),
655 ]) -> True
656 _ -> False
657 }
658 }
659 |> should.be_true
660}
661
662pub fn parse_variable_with_non_null_list_type_test() {
663 "query Test($ids: [Int]!) { users }"
664 |> parser.parse
665 |> should.be_ok
666 |> fn(doc) {
667 case doc {
668 parser.Document([
669 parser.NamedQuery(
670 name: "Test",
671 variables: [parser.Variable("ids", "[Int]!", None)],
672 selections: parser.SelectionSet([parser.Field("users", None, [], [])]),
673 ),
674 ]) -> True
675 _ -> False
676 }
677 }
678 |> should.be_true
679}
680
681pub fn parse_variable_with_list_of_non_null_type_test() {
682 "query Test($ids: [Int!]) { users }"
683 |> parser.parse
684 |> should.be_ok
685 |> fn(doc) {
686 case doc {
687 parser.Document([
688 parser.NamedQuery(
689 name: "Test",
690 variables: [parser.Variable("ids", "[Int!]", None)],
691 selections: parser.SelectionSet([parser.Field("users", None, [], [])]),
692 ),
693 ]) -> True
694 _ -> False
695 }
696 }
697 |> should.be_true
698}
699
700pub fn parse_variable_with_non_null_list_of_non_null_type_test() {
701 "query Test($ids: [Int!]!) { users }"
702 |> parser.parse
703 |> should.be_ok
704 |> fn(doc) {
705 case doc {
706 parser.Document([
707 parser.NamedQuery(
708 name: "Test",
709 variables: [parser.Variable("ids", "[Int!]!", None)],
710 selections: parser.SelectionSet([parser.Field("users", None, [], [])]),
711 ),
712 ]) -> True
713 _ -> False
714 }
715 }
716 |> should.be_true
717}
718
719pub fn parse_mutation_with_list_variable_test() {
720 "mutation AddTags($tags: [String!]!) { addTags }"
721 |> parser.parse
722 |> should.be_ok
723 |> fn(doc) {
724 case doc {
725 parser.Document([
726 parser.NamedMutation(
727 name: "AddTags",
728 variables: [parser.Variable("tags", "[String!]!", None)],
729 selections: parser.SelectionSet([
730 parser.Field("addTags", None, [], []),
731 ]),
732 ),
733 ]) -> True
734 _ -> False
735 }
736 }
737 |> should.be_true
738}
739
740pub fn parse_variable_with_default_int_value_test() {
741 "query Test($count: Int = 20) { users }"
742 |> parser.parse
743 |> should.be_ok
744 |> fn(doc) {
745 case doc {
746 parser.Document([
747 parser.NamedQuery(
748 name: "Test",
749 variables: [
750 parser.Variable("count", "Int", option.Some(parser.IntValue("20"))),
751 ],
752 selections: parser.SelectionSet([parser.Field("users", None, [], [])]),
753 ),
754 ]) -> True
755 _ -> False
756 }
757 }
758 |> should.be_true
759}
760
761// Additional default value type tests
762pub fn parse_variable_with_default_string_value_test() {
763 "query Test($name: String = \"Alice\") { users }"
764 |> parser.parse
765 |> should.be_ok
766 |> fn(doc) {
767 case doc {
768 parser.Document([
769 parser.NamedQuery(
770 name: "Test",
771 variables: [
772 parser.Variable(
773 "name",
774 "String",
775 option.Some(parser.StringValue("Alice")),
776 ),
777 ],
778 selections: parser.SelectionSet([parser.Field("users", None, [], [])]),
779 ),
780 ]) -> True
781 _ -> False
782 }
783 }
784 |> should.be_true
785}
786
787pub fn parse_variable_with_default_boolean_value_test() {
788 "query Test($active: Boolean = true) { users }"
789 |> parser.parse
790 |> should.be_ok
791 |> fn(doc) {
792 case doc {
793 parser.Document([
794 parser.NamedQuery(
795 name: "Test",
796 variables: [
797 parser.Variable(
798 "active",
799 "Boolean",
800 option.Some(parser.BooleanValue(True)),
801 ),
802 ],
803 selections: parser.SelectionSet([parser.Field("users", None, [], [])]),
804 ),
805 ]) -> True
806 _ -> False
807 }
808 }
809 |> should.be_true
810}
811
812pub fn parse_variable_with_default_null_value_test() {
813 "query Test($filter: String = null) { users }"
814 |> parser.parse
815 |> should.be_ok
816 |> fn(doc) {
817 case doc {
818 parser.Document([
819 parser.NamedQuery(
820 name: "Test",
821 variables: [
822 parser.Variable("filter", "String", option.Some(parser.NullValue)),
823 ],
824 selections: parser.SelectionSet([parser.Field("users", None, [], [])]),
825 ),
826 ]) -> True
827 _ -> False
828 }
829 }
830 |> should.be_true
831}
832
833pub fn parse_variable_with_default_enum_value_test() {
834 "query Test($sort: SortOrder = DESC) { users }"
835 |> parser.parse
836 |> should.be_ok
837 |> fn(doc) {
838 case doc {
839 parser.Document([
840 parser.NamedQuery(
841 name: "Test",
842 variables: [
843 parser.Variable(
844 "sort",
845 "SortOrder",
846 option.Some(parser.EnumValue("DESC")),
847 ),
848 ],
849 selections: parser.SelectionSet([parser.Field("users", None, [], [])]),
850 ),
851 ]) -> True
852 _ -> False
853 }
854 }
855 |> should.be_true
856}
857
858pub fn parse_variable_with_mixed_defaults_test() {
859 "query Test($name: String!, $limit: Int = 10, $offset: Int) { users }"
860 |> parser.parse
861 |> should.be_ok
862 |> fn(doc) {
863 case doc {
864 parser.Document([
865 parser.NamedQuery(
866 name: "Test",
867 variables: [
868 parser.Variable("name", "String!", None),
869 parser.Variable("limit", "Int", option.Some(parser.IntValue("10"))),
870 parser.Variable("offset", "Int", None),
871 ],
872 selections: parser.SelectionSet([parser.Field("users", None, [], [])]),
873 ),
874 ]) -> True
875 _ -> False
876 }
877 }
878 |> should.be_true
879}