馃寠 A GraphQL implementation in Gleam
at main 20 kB view raw
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}