馃寠 A GraphQL implementation in Gleam
at v2.1.1 30 kB view raw
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}