馃寠 A GraphQL implementation in Gleam
1/// Tests for GraphQL Executor
2///
3/// Tests query execution combining parser + schema + resolvers
4import birdie
5import gleam/dict
6import gleam/list
7import gleam/option.{None, Some}
8import gleam/string
9import gleeunit/should
10import swell/executor
11import swell/schema
12import swell/value
13
14// Helper to create a simple test schema
15fn test_schema() -> schema.Schema {
16 let query_type =
17 schema.object_type("Query", "Root query type", [
18 schema.field("hello", schema.string_type(), "Hello field", fn(_ctx) {
19 Ok(value.String("world"))
20 }),
21 schema.field("number", schema.int_type(), "Number field", fn(_ctx) {
22 Ok(value.Int(42))
23 }),
24 schema.field_with_args(
25 "greet",
26 schema.string_type(),
27 "Greet someone",
28 [schema.argument("name", schema.string_type(), "Name to greet", None)],
29 fn(_ctx) { Ok(value.String("Hello, Alice!")) },
30 ),
31 ])
32
33 schema.schema(query_type, None)
34}
35
36// Nested object schema for testing
37fn nested_schema() -> schema.Schema {
38 let user_type =
39 schema.object_type("User", "A user", [
40 schema.field("id", schema.id_type(), "User ID", fn(_ctx) {
41 Ok(value.String("123"))
42 }),
43 schema.field("name", schema.string_type(), "User name", fn(_ctx) {
44 Ok(value.String("Alice"))
45 }),
46 ])
47
48 let query_type =
49 schema.object_type("Query", "Root query type", [
50 schema.field("user", user_type, "Get user", fn(_ctx) {
51 Ok(
52 value.Object([
53 #("id", value.String("123")),
54 #("name", value.String("Alice")),
55 ]),
56 )
57 }),
58 ])
59
60 schema.schema(query_type, None)
61}
62
63pub fn execute_simple_query_test() {
64 let schema = test_schema()
65 let query = "{ hello }"
66
67 let result = executor.execute(query, schema, schema.context(None))
68
69 let response = case result {
70 Ok(r) -> r
71 Error(_) -> panic as "Execution failed"
72 }
73
74 birdie.snap(title: "Execute simple query", content: format_response(response))
75}
76
77pub fn execute_multiple_fields_test() {
78 let schema = test_schema()
79 let query = "{ hello number }"
80
81 let result = executor.execute(query, schema, schema.context(None))
82
83 should.be_ok(result)
84}
85
86pub fn execute_nested_query_test() {
87 let schema = nested_schema()
88 let query = "{ user { id name } }"
89
90 let result = executor.execute(query, schema, schema.context(None))
91
92 should.be_ok(result)
93}
94
95// Helper to format response for snapshots
96fn format_response(response: executor.Response) -> String {
97 string.inspect(response)
98}
99
100pub fn execute_field_with_arguments_test() {
101 let schema = test_schema()
102 let query = "{ greet(name: \"Alice\") }"
103
104 let result = executor.execute(query, schema, schema.context(None))
105
106 should.be_ok(result)
107}
108
109pub fn execute_invalid_query_returns_error_test() {
110 let schema = test_schema()
111 let query = "{ invalid }"
112
113 let result = executor.execute(query, schema, schema.context(None))
114
115 // Should return error since field doesn't exist
116 case result {
117 Ok(executor.Response(_, [_, ..])) -> should.be_true(True)
118 Error(_) -> should.be_true(True)
119 _ -> should.be_true(False)
120 }
121}
122
123pub fn execute_parse_error_returns_error_test() {
124 let schema = test_schema()
125 let query = "{ invalid syntax"
126
127 let result = executor.execute(query, schema, schema.context(None))
128
129 should.be_error(result)
130}
131
132pub fn execute_typename_introspection_test() {
133 let schema = test_schema()
134 let query = "{ __typename }"
135
136 let result = executor.execute(query, schema, schema.context(None))
137
138 let response = case result {
139 Ok(r) -> r
140 Error(_) -> panic as "Execution failed"
141 }
142
143 birdie.snap(
144 title: "Execute __typename introspection",
145 content: format_response(response),
146 )
147}
148
149pub fn execute_typename_with_regular_fields_test() {
150 let schema = test_schema()
151 let query = "{ __typename hello }"
152
153 let result = executor.execute(query, schema, schema.context(None))
154
155 let response = case result {
156 Ok(r) -> r
157 Error(_) -> panic as "Execution failed"
158 }
159
160 birdie.snap(
161 title: "Execute __typename with regular fields",
162 content: format_response(response),
163 )
164}
165
166pub fn execute_schema_introspection_query_type_test() {
167 let schema = test_schema()
168 let query = "{ __schema { queryType { name } } }"
169
170 let result = executor.execute(query, schema, schema.context(None))
171
172 let response = case result {
173 Ok(r) -> r
174 Error(_) -> panic as "Execution failed"
175 }
176
177 birdie.snap(
178 title: "Execute __schema introspection",
179 content: format_response(response),
180 )
181}
182
183// Fragment execution tests
184pub fn execute_simple_fragment_spread_test() {
185 let schema = nested_schema()
186 let query =
187 "
188 fragment UserFields on User {
189 id
190 name
191 }
192
193 { user { ...UserFields } }
194 "
195
196 let result = executor.execute(query, schema, schema.context(None))
197
198 let response = case result {
199 Ok(r) -> r
200 Error(_) -> panic as "Execution failed"
201 }
202
203 birdie.snap(
204 title: "Execute simple fragment spread",
205 content: format_response(response),
206 )
207}
208
209// Test for fragment spread on NonNull wrapped type
210pub fn execute_fragment_spread_on_non_null_type_test() {
211 // Create a schema where the user field returns a NonNull type
212 let user_type =
213 schema.object_type("User", "A user", [
214 schema.field("id", schema.id_type(), "User ID", fn(_ctx) {
215 Ok(value.String("123"))
216 }),
217 schema.field("name", schema.string_type(), "User name", fn(_ctx) {
218 Ok(value.String("Alice"))
219 }),
220 ])
221
222 let query_type =
223 schema.object_type("Query", "Root query type", [
224 // Wrap user_type in NonNull to test fragment type condition matching
225 schema.field("user", schema.non_null(user_type), "Get user", fn(_ctx) {
226 Ok(
227 value.Object([
228 #("id", value.String("123")),
229 #("name", value.String("Alice")),
230 ]),
231 )
232 }),
233 ])
234
235 let test_schema = schema.schema(query_type, None)
236
237 // Fragment is defined on "User" (not "User!") - this should still work
238 let query =
239 "
240 fragment UserFields on User {
241 id
242 name
243 }
244
245 { user { ...UserFields } }
246 "
247
248 let result = executor.execute(query, test_schema, schema.context(None))
249
250 let response = case result {
251 Ok(r) -> r
252 Error(_) -> panic as "Execution failed"
253 }
254
255 birdie.snap(
256 title: "Execute fragment spread on NonNull type",
257 content: format_response(response),
258 )
259}
260
261// Test for list fields with nested selections
262pub fn execute_list_with_nested_selections_test() {
263 // Create a schema with a list field
264 let user_type =
265 schema.object_type("User", "A user", [
266 schema.field("id", schema.id_type(), "User ID", fn(ctx) {
267 case ctx.data {
268 option.Some(value.Object(fields)) -> {
269 case list.key_find(fields, "id") {
270 Ok(id_val) -> Ok(id_val)
271 Error(_) -> Ok(value.Null)
272 }
273 }
274 _ -> Ok(value.Null)
275 }
276 }),
277 schema.field("name", schema.string_type(), "User name", fn(ctx) {
278 case ctx.data {
279 option.Some(value.Object(fields)) -> {
280 case list.key_find(fields, "name") {
281 Ok(name_val) -> Ok(name_val)
282 Error(_) -> Ok(value.Null)
283 }
284 }
285 _ -> Ok(value.Null)
286 }
287 }),
288 schema.field("email", schema.string_type(), "User email", fn(ctx) {
289 case ctx.data {
290 option.Some(value.Object(fields)) -> {
291 case list.key_find(fields, "email") {
292 Ok(email_val) -> Ok(email_val)
293 Error(_) -> Ok(value.Null)
294 }
295 }
296 _ -> Ok(value.Null)
297 }
298 }),
299 ])
300
301 let list_type = schema.list_type(user_type)
302
303 let query_type =
304 schema.object_type("Query", "Root query type", [
305 schema.field("users", list_type, "Get all users", fn(_ctx) {
306 // Return a list of user objects
307 Ok(
308 value.List([
309 value.Object([
310 #("id", value.String("1")),
311 #("name", value.String("Alice")),
312 #("email", value.String("alice@example.com")),
313 ]),
314 value.Object([
315 #("id", value.String("2")),
316 #("name", value.String("Bob")),
317 #("email", value.String("bob@example.com")),
318 ]),
319 ]),
320 )
321 }),
322 ])
323
324 let schema = schema.schema(query_type, None)
325
326 // Query with nested field selection - only request id and name, not email
327 let query = "{ users { id name } }"
328
329 let result = executor.execute(query, schema, schema.context(None))
330
331 let response = case result {
332 Ok(r) -> r
333 Error(_) -> panic as "Execution failed"
334 }
335
336 birdie.snap(
337 title: "Execute list with nested selections",
338 content: format_response(response),
339 )
340}
341
342// Test that arguments are actually passed to resolvers
343pub fn execute_field_receives_string_argument_test() {
344 let query_type =
345 schema.object_type("Query", "Root", [
346 schema.field_with_args(
347 "echo",
348 schema.string_type(),
349 "Echo the input",
350 [schema.argument("message", schema.string_type(), "Message", None)],
351 fn(ctx) {
352 // Extract the argument from context
353 case schema.get_argument(ctx, "message") {
354 Some(value.String(msg)) -> Ok(value.String("Echo: " <> msg))
355 _ -> Ok(value.String("No message"))
356 }
357 },
358 ),
359 ])
360
361 let test_schema = schema.schema(query_type, None)
362 let query = "{ echo(message: \"hello\") }"
363
364 let result = executor.execute(query, test_schema, schema.context(None))
365
366 let response = case result {
367 Ok(r) -> r
368 Error(_) -> panic as "Execution failed"
369 }
370
371 birdie.snap(
372 title: "Execute field with string argument",
373 content: format_response(response),
374 )
375}
376
377// Test list argument
378pub fn execute_field_receives_list_argument_test() {
379 let query_type =
380 schema.object_type("Query", "Root", [
381 schema.field_with_args(
382 "sum",
383 schema.int_type(),
384 "Sum numbers",
385 [
386 schema.argument(
387 "numbers",
388 schema.list_type(schema.int_type()),
389 "Numbers",
390 None,
391 ),
392 ],
393 fn(ctx) {
394 case schema.get_argument(ctx, "numbers") {
395 Some(value.List(_items)) -> Ok(value.String("got list"))
396 _ -> Ok(value.String("no list"))
397 }
398 },
399 ),
400 ])
401
402 let test_schema = schema.schema(query_type, None)
403 let query = "{ sum(numbers: [1, 2, 3]) }"
404
405 let result = executor.execute(query, test_schema, schema.context(None))
406
407 should.be_ok(result)
408 |> fn(response) {
409 case response {
410 executor.Response(
411 data: value.Object([#("sum", value.String("got list"))]),
412 errors: [],
413 ) -> True
414 _ -> False
415 }
416 }
417 |> should.be_true
418}
419
420// Test object argument (like sortBy)
421pub fn execute_field_receives_object_argument_test() {
422 let query_type =
423 schema.object_type("Query", "Root", [
424 schema.field_with_args(
425 "posts",
426 schema.list_type(schema.string_type()),
427 "Get posts",
428 [
429 schema.argument(
430 "sortBy",
431 schema.list_type(
432 schema.input_object_type("SortInput", "Sort", [
433 schema.input_field("field", schema.string_type(), "Field", None),
434 schema.input_field(
435 "direction",
436 schema.enum_type("Direction", "Direction", [
437 schema.enum_value("ASC", "Ascending"),
438 schema.enum_value("DESC", "Descending"),
439 ]),
440 "Direction",
441 None,
442 ),
443 ]),
444 ),
445 "Sort order",
446 None,
447 ),
448 ],
449 fn(ctx) {
450 case schema.get_argument(ctx, "sortBy") {
451 Some(value.List([value.Object(fields), ..])) -> {
452 case dict.from_list(fields) {
453 fields_dict -> {
454 case
455 dict.get(fields_dict, "field"),
456 dict.get(fields_dict, "direction")
457 {
458 Ok(value.String(field)), Ok(value.String(dir)) ->
459 Ok(value.String("Sorting by " <> field <> " " <> dir))
460 _, _ -> Ok(value.String("Invalid sort"))
461 }
462 }
463 }
464 }
465 _ -> Ok(value.String("No sort"))
466 }
467 },
468 ),
469 ])
470
471 let test_schema = schema.schema(query_type, None)
472 let query = "{ posts(sortBy: [{field: \"date\", direction: DESC}]) }"
473
474 let result = executor.execute(query, test_schema, schema.context(None))
475
476 let response = case result {
477 Ok(r) -> r
478 Error(_) -> panic as "Execution failed"
479 }
480
481 birdie.snap(
482 title: "Execute field with object argument",
483 content: format_response(response),
484 )
485}
486
487// Variable resolution tests
488pub fn execute_query_with_variable_string_test() {
489 let query_type =
490 schema.object_type("Query", "Root query type", [
491 schema.field_with_args(
492 "greet",
493 schema.string_type(),
494 "Greet someone",
495 [
496 schema.argument("name", schema.string_type(), "Name to greet", None),
497 ],
498 fn(ctx) {
499 case schema.get_argument(ctx, "name") {
500 Some(value.String(name)) ->
501 Ok(value.String("Hello, " <> name <> "!"))
502 _ -> Ok(value.String("Hello, stranger!"))
503 }
504 },
505 ),
506 ])
507
508 let test_schema = schema.schema(query_type, None)
509 let query = "query Test($name: String!) { greet(name: $name) }"
510
511 // Create context with variables
512 let variables = dict.from_list([#("name", value.String("Alice"))])
513 let ctx = schema.context_with_variables(None, variables)
514
515 let result = executor.execute(query, test_schema, ctx)
516
517 let response = case result {
518 Ok(r) -> r
519 Error(_) -> panic as "Execution failed"
520 }
521
522 birdie.snap(
523 title: "Execute query with string variable",
524 content: format_response(response),
525 )
526}
527
528pub fn execute_query_with_variable_int_test() {
529 let query_type =
530 schema.object_type("Query", "Root query type", [
531 schema.field_with_args(
532 "user",
533 schema.string_type(),
534 "Get user by ID",
535 [
536 schema.argument("id", schema.int_type(), "User ID", None),
537 ],
538 fn(ctx) {
539 case schema.get_argument(ctx, "id") {
540 Some(value.Int(id)) ->
541 Ok(value.String("User #" <> string.inspect(id)))
542 _ -> Ok(value.String("Unknown user"))
543 }
544 },
545 ),
546 ])
547
548 let test_schema = schema.schema(query_type, None)
549 let query = "query GetUser($userId: Int!) { user(id: $userId) }"
550
551 // Create context with variables
552 let variables = dict.from_list([#("userId", value.Int(42))])
553 let ctx = schema.context_with_variables(None, variables)
554
555 let result = executor.execute(query, test_schema, ctx)
556
557 let response = case result {
558 Ok(r) -> r
559 Error(_) -> panic as "Execution failed"
560 }
561
562 birdie.snap(
563 title: "Execute query with int variable",
564 content: format_response(response),
565 )
566}
567
568pub fn execute_query_with_multiple_variables_test() {
569 let query_type =
570 schema.object_type("Query", "Root query type", [
571 schema.field_with_args(
572 "search",
573 schema.string_type(),
574 "Search for something",
575 [
576 schema.argument("query", schema.string_type(), "Search query", None),
577 schema.argument("limit", schema.int_type(), "Max results", None),
578 ],
579 fn(ctx) {
580 case
581 schema.get_argument(ctx, "query"),
582 schema.get_argument(ctx, "limit")
583 {
584 Some(value.String(q)), Some(value.Int(l)) ->
585 Ok(value.String(
586 "Searching for '"
587 <> q
588 <> "' (limit: "
589 <> string.inspect(l)
590 <> ")",
591 ))
592 _, _ -> Ok(value.String("Invalid search"))
593 }
594 },
595 ),
596 ])
597
598 let test_schema = schema.schema(query_type, None)
599 let query =
600 "query Search($q: String!, $max: Int!) { search(query: $q, limit: $max) }"
601
602 // Create context with variables
603 let variables =
604 dict.from_list([
605 #("q", value.String("graphql")),
606 #("max", value.Int(10)),
607 ])
608 let ctx = schema.context_with_variables(None, variables)
609
610 let result = executor.execute(query, test_schema, ctx)
611
612 let response = case result {
613 Ok(r) -> r
614 Error(_) -> panic as "Execution failed"
615 }
616
617 birdie.snap(
618 title: "Execute query with multiple variables",
619 content: format_response(response),
620 )
621}
622
623// Union type execution tests
624pub fn execute_union_with_inline_fragment_test() {
625 // Create object types that will be part of the union
626 let post_type =
627 schema.object_type("Post", "A blog post", [
628 schema.field("title", schema.string_type(), "Post title", fn(ctx) {
629 case ctx.data {
630 option.Some(value.Object(fields)) -> {
631 case list.key_find(fields, "title") {
632 Ok(title_val) -> Ok(title_val)
633 Error(_) -> Ok(value.Null)
634 }
635 }
636 _ -> Ok(value.Null)
637 }
638 }),
639 schema.field("content", schema.string_type(), "Post content", fn(ctx) {
640 case ctx.data {
641 option.Some(value.Object(fields)) -> {
642 case list.key_find(fields, "content") {
643 Ok(content_val) -> Ok(content_val)
644 Error(_) -> Ok(value.Null)
645 }
646 }
647 _ -> Ok(value.Null)
648 }
649 }),
650 ])
651
652 let comment_type =
653 schema.object_type("Comment", "A comment", [
654 schema.field("text", schema.string_type(), "Comment text", fn(ctx) {
655 case ctx.data {
656 option.Some(value.Object(fields)) -> {
657 case list.key_find(fields, "text") {
658 Ok(text_val) -> Ok(text_val)
659 Error(_) -> Ok(value.Null)
660 }
661 }
662 _ -> Ok(value.Null)
663 }
664 }),
665 ])
666
667 // Type resolver that examines the __typename field
668 let type_resolver = fn(ctx: schema.Context) -> Result(String, String) {
669 case ctx.data {
670 option.Some(value.Object(fields)) -> {
671 case list.key_find(fields, "__typename") {
672 Ok(value.String(type_name)) -> Ok(type_name)
673 _ -> Error("No __typename field found")
674 }
675 }
676 _ -> Error("No data")
677 }
678 }
679
680 // Create union type
681 let search_result_union =
682 schema.union_type(
683 "SearchResult",
684 "A search result",
685 [post_type, comment_type],
686 type_resolver,
687 )
688
689 // Create query type with a field returning the union
690 let query_type =
691 schema.object_type("Query", "Root query type", [
692 schema.field(
693 "search",
694 search_result_union,
695 "Search for content",
696 fn(_ctx) {
697 // Return a Post
698 Ok(
699 value.Object([
700 #("__typename", value.String("Post")),
701 #("title", value.String("GraphQL is awesome")),
702 #("content", value.String("Learn all about GraphQL...")),
703 ]),
704 )
705 },
706 ),
707 ])
708
709 let test_schema = schema.schema(query_type, None)
710
711 // Query with inline fragment
712 let query =
713 "
714 {
715 search {
716 ... on Post {
717 title
718 content
719 }
720 ... on Comment {
721 text
722 }
723 }
724 }
725 "
726
727 let result = executor.execute(query, test_schema, schema.context(None))
728
729 let response = case result {
730 Ok(r) -> r
731 Error(_) -> panic as "Execution failed"
732 }
733
734 birdie.snap(
735 title: "Execute union with inline fragment",
736 content: format_response(response),
737 )
738}
739
740pub fn execute_union_list_with_inline_fragments_test() {
741 // Create object types
742 let post_type =
743 schema.object_type("Post", "A blog post", [
744 schema.field("title", schema.string_type(), "Post title", fn(ctx) {
745 case ctx.data {
746 option.Some(value.Object(fields)) -> {
747 case list.key_find(fields, "title") {
748 Ok(title_val) -> Ok(title_val)
749 Error(_) -> Ok(value.Null)
750 }
751 }
752 _ -> Ok(value.Null)
753 }
754 }),
755 ])
756
757 let comment_type =
758 schema.object_type("Comment", "A comment", [
759 schema.field("text", schema.string_type(), "Comment text", fn(ctx) {
760 case ctx.data {
761 option.Some(value.Object(fields)) -> {
762 case list.key_find(fields, "text") {
763 Ok(text_val) -> Ok(text_val)
764 Error(_) -> Ok(value.Null)
765 }
766 }
767 _ -> Ok(value.Null)
768 }
769 }),
770 ])
771
772 // Type resolver
773 let type_resolver = fn(ctx: schema.Context) -> Result(String, String) {
774 case ctx.data {
775 option.Some(value.Object(fields)) -> {
776 case list.key_find(fields, "__typename") {
777 Ok(value.String(type_name)) -> Ok(type_name)
778 _ -> Error("No __typename field found")
779 }
780 }
781 _ -> Error("No data")
782 }
783 }
784
785 // Create union type
786 let search_result_union =
787 schema.union_type(
788 "SearchResult",
789 "A search result",
790 [post_type, comment_type],
791 type_resolver,
792 )
793
794 // Create query type with a list of unions
795 let query_type =
796 schema.object_type("Query", "Root query type", [
797 schema.field(
798 "searchAll",
799 schema.list_type(search_result_union),
800 "Search for all content",
801 fn(_ctx) {
802 // Return a list with mixed types
803 Ok(
804 value.List([
805 value.Object([
806 #("__typename", value.String("Post")),
807 #("title", value.String("First Post")),
808 ]),
809 value.Object([
810 #("__typename", value.String("Comment")),
811 #("text", value.String("Great article!")),
812 ]),
813 value.Object([
814 #("__typename", value.String("Post")),
815 #("title", value.String("Second Post")),
816 ]),
817 ]),
818 )
819 },
820 ),
821 ])
822
823 let test_schema = schema.schema(query_type, None)
824
825 // Query with inline fragments on list items
826 let query =
827 "
828 {
829 searchAll {
830 ... on Post {
831 title
832 }
833 ... on Comment {
834 text
835 }
836 }
837 }
838 "
839
840 let result = executor.execute(query, test_schema, schema.context(None))
841
842 let response = case result {
843 Ok(r) -> r
844 Error(_) -> panic as "Execution failed"
845 }
846
847 birdie.snap(
848 title: "Execute union list with inline fragments",
849 content: format_response(response),
850 )
851}
852
853// Test field aliases
854pub fn execute_field_with_alias_test() {
855 let schema = test_schema()
856 let query = "{ greeting: hello }"
857
858 let result = executor.execute(query, schema, schema.context(None))
859
860 let response = case result {
861 Ok(r) -> r
862 Error(_) -> panic as "Execution failed"
863 }
864
865 // Response should contain "greeting" as the key, not "hello"
866 case response.data {
867 value.Object(fields) -> {
868 case list.key_find(fields, "greeting") {
869 Ok(_) -> should.be_true(True)
870 Error(_) -> {
871 // Check if it incorrectly used "hello" instead
872 case list.key_find(fields, "hello") {
873 Ok(_) ->
874 panic as "Alias not applied - used 'hello' instead of 'greeting'"
875 Error(_) ->
876 panic as "Neither 'greeting' nor 'hello' found in response"
877 }
878 }
879 }
880 }
881 _ -> panic as "Expected object response"
882 }
883}
884
885// Test multiple aliases
886pub fn execute_multiple_fields_with_aliases_test() {
887 let schema = test_schema()
888 let query = "{ greeting: hello num: number }"
889
890 let result = executor.execute(query, schema, schema.context(None))
891
892 let response = case result {
893 Ok(r) -> r
894 Error(_) -> panic as "Execution failed"
895 }
896
897 birdie.snap(
898 title: "Execute multiple fields with aliases",
899 content: format_response(response),
900 )
901}
902
903// Test mixed aliased and non-aliased fields
904pub fn execute_mixed_aliased_fields_test() {
905 let schema = test_schema()
906 let query = "{ greeting: hello number }"
907
908 let result = executor.execute(query, schema, schema.context(None))
909
910 let response = case result {
911 Ok(r) -> r
912 Error(_) -> panic as "Execution failed"
913 }
914
915 birdie.snap(
916 title: "Execute mixed aliased and non-aliased fields",
917 content: format_response(response),
918 )
919}
920
921pub fn execute_query_with_variable_default_value_test() {
922 let query_type =
923 schema.object_type("Query", "Root query type", [
924 schema.field_with_args(
925 "greet",
926 schema.string_type(),
927 "Greet someone",
928 [schema.argument("name", schema.string_type(), "Name to greet", None)],
929 fn(ctx) {
930 case schema.get_argument(ctx, "name") {
931 option.Some(value.String(name)) ->
932 Ok(value.String("Hello, " <> name <> "!"))
933 _ -> Ok(value.String("Hello, stranger!"))
934 }
935 },
936 ),
937 ])
938
939 let test_schema = schema.schema(query_type, None)
940 let query = "query Test($name: String = \"World\") { greet(name: $name) }"
941
942 // No variables provided - should use default
943 let ctx = schema.context_with_variables(None, dict.new())
944
945 let result = executor.execute(query, test_schema, ctx)
946
947 // Debug: print the actual result
948 let assert Ok(response) = result
949 let assert executor.Response(data: value.Object(fields), errors: _) = response
950 let assert Ok(greet_value) = list.key_find(fields, "greet")
951
952 // Should use default value "World" since no variable provided
953 greet_value
954 |> should.equal(value.String("Hello, World!"))
955}
956
957pub fn execute_query_with_variable_overriding_default_test() {
958 let query_type =
959 schema.object_type("Query", "Root query type", [
960 schema.field_with_args(
961 "greet",
962 schema.string_type(),
963 "Greet someone",
964 [schema.argument("name", schema.string_type(), "Name to greet", None)],
965 fn(ctx) {
966 case schema.get_argument(ctx, "name") {
967 option.Some(value.String(name)) ->
968 Ok(value.String("Hello, " <> name <> "!"))
969 _ -> Ok(value.String("Hello, stranger!"))
970 }
971 },
972 ),
973 ])
974
975 let test_schema = schema.schema(query_type, None)
976 let query = "query Test($name: String = \"World\") { greet(name: $name) }"
977
978 // Provide variable - should override default
979 let variables = dict.from_list([#("name", value.String("Alice"))])
980 let ctx = schema.context_with_variables(None, variables)
981
982 let result = executor.execute(query, test_schema, ctx)
983 result
984 |> should.be_ok
985 |> fn(response) {
986 case response {
987 executor.Response(data: value.Object(fields), errors: _) -> {
988 case list.key_find(fields, "greet") {
989 Ok(value.String("Hello, Alice!")) -> True
990 _ -> False
991 }
992 }
993 _ -> False
994 }
995 }
996 |> should.be_true
997}
998
999// Test: List argument rejects object value
1000pub fn list_argument_rejects_object_test() {
1001 // Create a schema with a field that has a list argument
1002 let list_arg_field =
1003 schema.field_with_args(
1004 "items",
1005 schema.string_type(),
1006 "Test field with list arg",
1007 [
1008 schema.argument(
1009 "ids",
1010 schema.list_type(schema.string_type()),
1011 "List of IDs",
1012 None,
1013 ),
1014 ],
1015 fn(_ctx) { Ok(value.String("test")) },
1016 )
1017
1018 let query_type = schema.object_type("Query", "Root", [list_arg_field])
1019 let s = schema.schema(query_type, None)
1020
1021 // Query with object instead of list should produce an error
1022 let query = "{ items(ids: {foo: \"bar\"}) }"
1023 let result = executor.execute(query, s, schema.context(None))
1024
1025 case result {
1026 Ok(executor.Response(_, errors)) -> {
1027 // Should have exactly one error
1028 list.length(errors)
1029 |> should.equal(1)
1030
1031 // Check error message mentions list vs object
1032 case list.first(errors) {
1033 Ok(executor.GraphQLError(message, _)) -> {
1034 string.contains(message, "expects a list")
1035 |> should.be_true
1036 string.contains(message, "not an object")
1037 |> should.be_true
1038 }
1039 Error(_) -> should.fail()
1040 }
1041 }
1042 Error(_) -> should.fail()
1043 }
1044}
1045
1046// Test: List argument accepts list value (sanity check)
1047pub fn list_argument_accepts_list_test() {
1048 // Create a schema with a field that has a list argument
1049 let list_arg_field =
1050 schema.field_with_args(
1051 "items",
1052 schema.string_type(),
1053 "Test field with list arg",
1054 [
1055 schema.argument(
1056 "ids",
1057 schema.list_type(schema.string_type()),
1058 "List of IDs",
1059 None,
1060 ),
1061 ],
1062 fn(_ctx) { Ok(value.String("success")) },
1063 )
1064
1065 let query_type = schema.object_type("Query", "Root", [list_arg_field])
1066 let s = schema.schema(query_type, None)
1067
1068 // Query with proper list should work
1069 let query = "{ items(ids: [\"a\", \"b\"]) }"
1070 let result = executor.execute(query, s, schema.context(None))
1071
1072 case result {
1073 Ok(executor.Response(value.Object(fields), errors)) -> {
1074 // Should have no errors
1075 list.length(errors)
1076 |> should.equal(0)
1077
1078 // Should return the value
1079 case list.key_find(fields, "items") {
1080 Ok(value.String("success")) -> should.be_true(True)
1081 _ -> should.fail()
1082 }
1083 }
1084 _ -> should.fail()
1085 }
1086}