github.com/dgraph-io/dgraph@v1.2.8/graphql/e2e/common/query.go (about) 1 /* 2 * Copyright 2019 Dgraph Labs, Inc. and Contributors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package common 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "math/rand" 23 "sort" 24 "strings" 25 "testing" 26 "time" 27 28 "github.com/dgraph-io/dgraph/testutil" 29 "github.com/dgraph-io/dgraph/x" 30 "github.com/google/go-cmp/cmp" 31 "github.com/stretchr/testify/require" 32 ) 33 34 func queryCountryByRegExp(t *testing.T, regexp string, expectedCountries []*country) { 35 getCountryParams := &GraphQLParams{ 36 Query: `query queryCountry($regexp: String!) { 37 queryCountry(filter: { name: { regexp: $regexp } }) { 38 name 39 } 40 }`, 41 Variables: map[string]interface{}{"regexp": regexp}, 42 } 43 44 gqlResponse := getCountryParams.ExecuteAsPost(t, graphqlURL) 45 requireNoGQLErrors(t, gqlResponse) 46 47 var expected, result struct { 48 QueryCountry []*country 49 } 50 expected.QueryCountry = expectedCountries 51 err := json.Unmarshal([]byte(gqlResponse.Data), &result) 52 require.NoError(t, err) 53 54 countrySort := func(i, j int) bool { 55 return result.QueryCountry[i].Name < result.QueryCountry[j].Name 56 } 57 sort.Slice(result.QueryCountry, countrySort) 58 59 if diff := cmp.Diff(expected, result); diff != "" { 60 t.Errorf("result mismatch (-want +got):\n%s", diff) 61 } 62 } 63 64 // This test checks that all the different combinations of 65 // request sending compressed / uncompressed query and receiving 66 // compressed / uncompressed result. 67 func gzipCompression(t *testing.T) { 68 r := []bool{false, true} 69 for _, acceptGzip := range r { 70 for _, gzipEncoding := range r { 71 t.Run(fmt.Sprintf("TestQueryByType acceptGzip=%t gzipEncoding=%t", 72 acceptGzip, gzipEncoding), func(t *testing.T) { 73 74 queryByTypeWithEncoding(t, acceptGzip, gzipEncoding) 75 }) 76 } 77 } 78 } 79 80 func queryByType(t *testing.T) { 81 queryByTypeWithEncoding(t, true, true) 82 } 83 84 func queryByTypeWithEncoding(t *testing.T, acceptGzip, gzipEncoding bool) { 85 queryCountry := &GraphQLParams{ 86 Query: `query { 87 queryCountry { 88 name 89 } 90 }`, 91 acceptGzip: acceptGzip, 92 gzipEncoding: gzipEncoding, 93 } 94 95 gqlResponse := queryCountry.ExecuteAsPost(t, graphqlURL) 96 requireNoGQLErrors(t, gqlResponse) 97 98 var expected, result struct { 99 QueryCountry []*country 100 } 101 expected.QueryCountry = []*country{ 102 &country{Name: "Angola"}, 103 &country{Name: "Bangladesh"}, 104 &country{Name: "Mozambique"}, 105 } 106 err := json.Unmarshal([]byte(gqlResponse.Data), &result) 107 require.NoError(t, err) 108 109 sort.Slice(result.QueryCountry, func(i, j int) bool { 110 return result.QueryCountry[i].Name < result.QueryCountry[j].Name 111 }) 112 113 if diff := cmp.Diff(expected, result); diff != "" { 114 t.Errorf("result mismatch (-want +got):\n%s", diff) 115 } 116 } 117 118 func uidAlias(t *testing.T) { 119 queryCountry := &GraphQLParams{ 120 Query: `query { 121 queryCountry(order: { asc: name }) { 122 uid: name 123 } 124 }`, 125 } 126 type countryUID struct { 127 UID string 128 } 129 130 gqlResponse := queryCountry.ExecuteAsPost(t, graphqlURL) 131 requireNoGQLErrors(t, gqlResponse) 132 133 var expected, result struct { 134 QueryCountry []*countryUID 135 } 136 expected.QueryCountry = []*countryUID{ 137 &countryUID{UID: "Angola"}, 138 &countryUID{UID: "Bangladesh"}, 139 &countryUID{UID: "Mozambique"}, 140 } 141 err := json.Unmarshal([]byte(gqlResponse.Data), &result) 142 require.NoError(t, err) 143 144 if diff := cmp.Diff(expected, result); diff != "" { 145 t.Errorf("result mismatch (-want +got):\n%s", diff) 146 } 147 } 148 149 func orderAtRoot(t *testing.T) { 150 queryCountry := &GraphQLParams{ 151 Query: `query { 152 queryCountry(order: { asc: name }) { 153 name 154 } 155 }`, 156 } 157 158 gqlResponse := queryCountry.ExecuteAsPost(t, graphqlURL) 159 requireNoGQLErrors(t, gqlResponse) 160 161 var expected, result struct { 162 QueryCountry []*country 163 } 164 expected.QueryCountry = []*country{ 165 &country{Name: "Angola"}, 166 &country{Name: "Bangladesh"}, 167 &country{Name: "Mozambique"}, 168 } 169 err := json.Unmarshal([]byte(gqlResponse.Data), &result) 170 require.NoError(t, err) 171 172 if diff := cmp.Diff(expected, result); diff != "" { 173 t.Errorf("result mismatch (-want +got):\n%s", diff) 174 } 175 } 176 177 func pageAtRoot(t *testing.T) { 178 queryCountry := &GraphQLParams{ 179 Query: `query { 180 queryCountry(order: { desc: name }, first: 2, offset: 1) { 181 name 182 } 183 }`, 184 } 185 186 gqlResponse := queryCountry.ExecuteAsPost(t, graphqlURL) 187 requireNoGQLErrors(t, gqlResponse) 188 189 var expected, result struct { 190 QueryCountry []*country 191 } 192 expected.QueryCountry = []*country{ 193 &country{Name: "Bangladesh"}, 194 &country{Name: "Angola"}, 195 } 196 err := json.Unmarshal([]byte(gqlResponse.Data), &result) 197 require.NoError(t, err) 198 199 if diff := cmp.Diff(expected, result); diff != "" { 200 t.Errorf("result mismatch (-want +got):\n%s", diff) 201 } 202 } 203 204 func regExp(t *testing.T) { 205 queryCountryByRegExp(t, "/[Aa]ng/", 206 []*country{ 207 &country{Name: "Angola"}, 208 &country{Name: "Bangladesh"}, 209 }) 210 } 211 212 func multipleSearchIndexes(t *testing.T) { 213 query := `query queryPost($filter: PostFilter){ 214 queryPost (filter: $filter) { 215 title 216 } 217 }` 218 219 testCases := []interface{}{ 220 map[string]interface{}{"title": map[string]interface{}{"anyofterms": "Introducing"}}, 221 map[string]interface{}{"title": map[string]interface { 222 }{"alloftext": "Introducing GraphQL in Dgraph"}}, 223 } 224 for _, filter := range testCases { 225 getCountryParams := &GraphQLParams{ 226 Query: query, 227 Variables: map[string]interface{}{"filter": filter}, 228 } 229 230 gqlResponse := getCountryParams.ExecuteAsPost(t, graphqlURL) 231 requireNoGQLErrors(t, gqlResponse) 232 233 var expected, result struct { 234 QueryPost []*post 235 } 236 237 expected.QueryPost = []*post{ 238 &post{Title: "Introducing GraphQL in Dgraph"}, 239 } 240 err := json.Unmarshal([]byte(gqlResponse.Data), &result) 241 require.NoError(t, err) 242 243 if diff := cmp.Diff(expected, result); diff != "" { 244 t.Errorf("result mismatch (-want +got):\n%s", diff) 245 } 246 } 247 } 248 249 func multipleSearchIndexesWrongField(t *testing.T) { 250 queryPostParams := &GraphQLParams{ 251 Query: `query { 252 queryPost (filter: {title : { regexp : "/Introducing.*$/" }} ) { 253 title 254 } 255 }`, 256 } 257 258 gqlResponse := queryPostParams.ExecuteAsPost(t, graphqlURL) 259 require.NotNil(t, gqlResponse.Errors) 260 261 expected := `Field "regexp" is not defined by type StringFullTextFilter_StringTermFilter` 262 require.Contains(t, gqlResponse.Errors[0].Error(), expected) 263 } 264 265 func hashSearch(t *testing.T) { 266 queryAuthorParams := &GraphQLParams{ 267 Query: `query { 268 queryAuthor(filter: { name: { eq: "Ann Author" } }) { 269 name 270 dob 271 } 272 }`, 273 } 274 275 gqlResponse := queryAuthorParams.ExecuteAsPost(t, graphqlURL) 276 requireNoGQLErrors(t, gqlResponse) 277 278 var expected, result struct { 279 QueryAuthor []*author 280 } 281 dob := time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC) 282 expected.QueryAuthor = []*author{{Name: "Ann Author", Dob: &dob}} 283 err := json.Unmarshal([]byte(gqlResponse.Data), &result) 284 require.NoError(t, err) 285 286 if diff := cmp.Diff(expected, result); diff != "" { 287 t.Errorf("result mismatch (-want +got):\n%s", diff) 288 } 289 } 290 291 func allPosts(t *testing.T) []*post { 292 queryPostParams := &GraphQLParams{ 293 Query: `query { 294 queryPost { 295 postID 296 title 297 text 298 tags 299 numLikes 300 isPublished 301 postType 302 } 303 }`, 304 } 305 gqlResponse := queryPostParams.ExecuteAsPost(t, graphqlURL) 306 requireNoGQLErrors(t, gqlResponse) 307 308 var result struct { 309 QueryPost []*post 310 } 311 err := json.Unmarshal([]byte(gqlResponse.Data), &result) 312 require.NoError(t, err) 313 314 require.Equal(t, 4, len(result.QueryPost)) 315 316 return result.QueryPost 317 } 318 319 func deepFilter(t *testing.T) { 320 getAuthorParams := &GraphQLParams{ 321 Query: `query { 322 queryAuthor(filter: { name: { eq: "Ann Other Author" } }) { 323 name 324 posts(filter: { title: { anyofterms: "GraphQL" } }) { 325 title 326 } 327 } 328 }`, 329 } 330 331 gqlResponse := getAuthorParams.ExecuteAsPost(t, graphqlURL) 332 requireNoGQLErrors(t, gqlResponse) 333 334 var result struct { 335 QueryAuthor []*author 336 } 337 err := json.Unmarshal([]byte(gqlResponse.Data), &result) 338 require.NoError(t, err) 339 require.Equal(t, 1, len(result.QueryAuthor)) 340 341 expected := &author{ 342 Name: "Ann Other Author", 343 Posts: []*post{{Title: "Learning GraphQL in Dgraph"}}, 344 } 345 346 if diff := cmp.Diff(expected, result.QueryAuthor[0]); diff != "" { 347 t.Errorf("result mismatch (-want +got):\n%s", diff) 348 } 349 } 350 351 // manyQueries runs multiple queries in the one block. Internally, the GraphQL 352 // server should run those concurrently, but the results should be returned in the 353 // requested order. This makes sure those many test runs are reassembled correctly. 354 func manyQueries(t *testing.T) { 355 posts := allPosts(t) 356 357 getPattern := `getPost(postID: "%s") { 358 postID 359 title 360 text 361 tags 362 isPublished 363 postType 364 numLikes 365 } 366 ` 367 368 var bld strings.Builder 369 x.Check2(bld.WriteString("query {\n")) 370 for idx, p := range posts { 371 x.Check2(bld.WriteString(fmt.Sprintf(" query%v : ", idx))) 372 x.Check2(bld.WriteString(fmt.Sprintf(getPattern, p.PostID))) 373 } 374 x.Check2(bld.WriteString("}")) 375 376 queryParams := &GraphQLParams{ 377 Query: bld.String(), 378 } 379 380 gqlResponse := queryParams.ExecuteAsPost(t, graphqlURL) 381 requireNoGQLErrors(t, gqlResponse) 382 383 var result map[string]*post 384 err := json.Unmarshal([]byte(gqlResponse.Data), &result) 385 require.NoError(t, err) 386 387 for idx, expectedPost := range posts { 388 resultPost := result[fmt.Sprintf("query%v", idx)] 389 if diff := cmp.Diff(expectedPost, resultPost); diff != "" { 390 t.Errorf("result mismatch (-want +got):\n%s", diff) 391 } 392 } 393 } 394 395 func queryOrderAtRoot(t *testing.T) { 396 posts := allPosts(t) 397 398 answers := make([]*post, 2) 399 for _, p := range posts { 400 if p.NumLikes == 77 { 401 answers[0] = p 402 } else if p.NumLikes == 100 { 403 answers[1] = p 404 } 405 } 406 407 filter := map[string]interface{}{ 408 "postID": []string{answers[0].PostID, answers[1].PostID}, 409 } 410 411 orderLikesDesc := map[string]interface{}{ 412 "desc": "numLikes", 413 } 414 415 orderLikesAsc := map[string]interface{}{ 416 "asc": "numLikes", 417 } 418 419 var result, expected struct { 420 QueryPost []*post 421 } 422 423 cases := map[string]struct { 424 Order map[string]interface{} 425 First int 426 Offset int 427 Expected []*post 428 }{ 429 "orderAsc": { 430 Order: orderLikesAsc, 431 First: 2, 432 Offset: 0, 433 Expected: []*post{answers[0], answers[1]}, 434 }, 435 "orderDesc": { 436 Order: orderLikesDesc, 437 First: 2, 438 Offset: 0, 439 Expected: []*post{answers[1], answers[0]}, 440 }, 441 "first": { 442 Order: orderLikesDesc, 443 First: 1, 444 Offset: 0, 445 Expected: []*post{answers[1]}, 446 }, 447 "offset": { 448 Order: orderLikesDesc, 449 First: 2, 450 Offset: 1, 451 Expected: []*post{answers[0]}, 452 }, 453 } 454 455 for name, test := range cases { 456 t.Run(name, func(t *testing.T) { 457 getParams := &GraphQLParams{ 458 Query: `query queryPost($filter: PostFilter, $order: PostOrder, 459 $first: Int, $offset: Int) { 460 queryPost( 461 filter: $filter, 462 order: $order, 463 first: $first, 464 offset: $offset) { 465 postID 466 title 467 text 468 tags 469 isPublished 470 postType 471 numLikes 472 } 473 } 474 `, 475 Variables: map[string]interface{}{ 476 "filter": filter, 477 "order": test.Order, 478 "first": test.First, 479 "offset": test.Offset, 480 }, 481 } 482 483 gqlResponse := getParams.ExecuteAsPost(t, graphqlURL) 484 requireNoGQLErrors(t, gqlResponse) 485 486 expected.QueryPost = test.Expected 487 err := json.Unmarshal([]byte(gqlResponse.Data), &result) 488 require.NoError(t, err) 489 490 require.Equal(t, len(result.QueryPost), len(expected.QueryPost)) 491 if diff := cmp.Diff(expected, result); diff != "" { 492 t.Errorf("result mismatch (-want +got):\n%s", diff) 493 } 494 }) 495 } 496 497 } 498 499 // queriesWithError runs multiple queries in the one block with 500 // an error. Internally, the GraphQL server should run those concurrently, and 501 // an error in one query should not affect the results of any others. 502 func queriesWithError(t *testing.T) { 503 posts := allPosts(t) 504 505 getPattern := `getPost(postID: "%s") { 506 postID 507 title 508 text 509 tags 510 isPublished 511 postType 512 numLikes 513 } 514 ` 515 516 // make one random query fail 517 shouldFail := rand.New(rand.NewSource(time.Now().UnixNano())).Intn(len(posts)) 518 519 var bld strings.Builder 520 x.Check2(bld.WriteString("query {\n")) 521 for idx, p := range posts { 522 x.Check2(bld.WriteString(fmt.Sprintf(" query%v : ", idx))) 523 if idx == shouldFail { 524 x.Check2(bld.WriteString(fmt.Sprintf(getPattern, "Not_An_ID"))) 525 } else { 526 x.Check2(bld.WriteString(fmt.Sprintf(getPattern, p.PostID))) 527 } 528 } 529 x.Check2(bld.WriteString("}")) 530 531 queryParams := &GraphQLParams{ 532 Query: bld.String(), 533 } 534 535 gqlResponse := queryParams.ExecuteAsPost(t, graphqlURL) 536 require.Len(t, gqlResponse.Errors, 1, "expected 1 error from malformed query") 537 538 var result map[string]*post 539 err := json.Unmarshal([]byte(gqlResponse.Data), &result) 540 require.NoError(t, err) 541 542 for idx, expectedPost := range posts { 543 resultPost := result[fmt.Sprintf("query%v", idx)] 544 if idx == shouldFail { 545 require.Nil(t, resultPost, "expected this query to fail and return nil") 546 } else { 547 if diff := cmp.Diff(expectedPost, resultPost); diff != "" { 548 t.Errorf("result mismatch (-want +got):\n%s", diff) 549 } 550 } 551 } 552 } 553 554 func dateFilters(t *testing.T) { 555 cases := map[string]struct { 556 Filter interface{} 557 Expected []*author 558 }{ 559 "less than": { 560 Filter: map[string]interface{}{"dob": map[string]interface{}{"lt": "2000-01-01"}}, 561 Expected: []*author{{Name: "Ann Other Author"}}}, 562 "less or equal": { 563 Filter: map[string]interface{}{"dob": map[string]interface{}{"le": "2000-01-01"}}, 564 Expected: []*author{{Name: "Ann Author"}, {Name: "Ann Other Author"}}}, 565 "equal": { 566 Filter: map[string]interface{}{"dob": map[string]interface{}{"eq": "2000-01-01"}}, 567 Expected: []*author{{Name: "Ann Author"}}}, 568 "greater or equal": { 569 Filter: map[string]interface{}{"dob": map[string]interface{}{"ge": "2000-01-01"}}, 570 Expected: []*author{{Name: "Ann Author"}, {Name: "Three Author"}}}, 571 "greater than": { 572 Filter: map[string]interface{}{"dob": map[string]interface{}{"gt": "2000-01-01"}}, 573 Expected: []*author{{Name: "Three Author"}}}, 574 } 575 576 for name, test := range cases { 577 t.Run(name, func(t *testing.T) { 578 authorTest(t, test.Filter, test.Expected) 579 }) 580 } 581 } 582 583 func floatFilters(t *testing.T) { 584 cases := map[string]struct { 585 Filter interface{} 586 Expected []*author 587 }{ 588 "less than": { 589 Filter: map[string]interface{}{"reputation": map[string]interface{}{"lt": 8.9}}, 590 Expected: []*author{{Name: "Ann Author"}}}, 591 "less or equal": { 592 Filter: map[string]interface{}{"reputation": map[string]interface{}{"le": 8.9}}, 593 Expected: []*author{{Name: "Ann Author"}, {Name: "Ann Other Author"}}}, 594 "equal": { 595 Filter: map[string]interface{}{"reputation": map[string]interface{}{"eq": 8.9}}, 596 Expected: []*author{{Name: "Ann Other Author"}}}, 597 "greater or equal": { 598 Filter: map[string]interface{}{"reputation": map[string]interface{}{"ge": 8.9}}, 599 Expected: []*author{{Name: "Ann Other Author"}, {Name: "Three Author"}}}, 600 "greater than": { 601 Filter: map[string]interface{}{"reputation": map[string]interface{}{"gt": 8.9}}, 602 Expected: []*author{{Name: "Three Author"}}}, 603 } 604 605 for name, test := range cases { 606 t.Run(name, func(t *testing.T) { 607 authorTest(t, test.Filter, test.Expected) 608 }) 609 } 610 } 611 612 func authorTest(t *testing.T, filter interface{}, expected []*author) { 613 queryParams := &GraphQLParams{ 614 Query: `query filterVariable($filter: AuthorFilter) { 615 queryAuthor(filter: $filter, order: { asc: name }) { 616 name 617 } 618 }`, 619 Variables: map[string]interface{}{"filter": filter}, 620 } 621 622 gqlResponse := queryParams.ExecuteAsPost(t, graphqlURL) 623 requireNoGQLErrors(t, gqlResponse) 624 625 var result struct { 626 QueryAuthor []*author 627 } 628 err := json.Unmarshal([]byte(gqlResponse.Data), &result) 629 require.NoError(t, err) 630 631 if diff := cmp.Diff(expected, result.QueryAuthor); diff != "" { 632 t.Errorf("result mismatch (-want +got):\n%s", diff) 633 } 634 } 635 636 func intFilters(t *testing.T) { 637 cases := map[string]struct { 638 Filter interface{} 639 Expected []*post 640 }{ 641 "less than": { 642 Filter: map[string]interface{}{"numLikes": map[string]interface{}{"lt": 87}}, 643 Expected: []*post{ 644 {Title: "GraphQL doco"}, 645 {Title: "Random post"}}}, 646 "less or equal": { 647 Filter: map[string]interface{}{"numLikes": map[string]interface{}{"le": 87}}, 648 Expected: []*post{ 649 {Title: "GraphQL doco"}, 650 {Title: "Learning GraphQL in Dgraph"}, 651 {Title: "Random post"}}}, 652 "equal": { 653 Filter: map[string]interface{}{"numLikes": map[string]interface{}{"eq": 87}}, 654 Expected: []*post{{Title: "Learning GraphQL in Dgraph"}}}, 655 "greater or equal": { 656 Filter: map[string]interface{}{"numLikes": map[string]interface{}{"ge": 87}}, 657 Expected: []*post{ 658 {Title: "Introducing GraphQL in Dgraph"}, 659 {Title: "Learning GraphQL in Dgraph"}}}, 660 "greater than": { 661 Filter: map[string]interface{}{"numLikes": map[string]interface{}{"gt": 87}}, 662 Expected: []*post{{Title: "Introducing GraphQL in Dgraph"}}}, 663 } 664 665 for name, test := range cases { 666 t.Run(name, func(t *testing.T) { 667 postTest(t, test.Filter, test.Expected) 668 }) 669 } 670 } 671 672 func booleanFilters(t *testing.T) { 673 cases := map[string]struct { 674 Filter interface{} 675 Expected []*post 676 }{ 677 "true": { 678 Filter: map[string]interface{}{"isPublished": true}, 679 Expected: []*post{ 680 {Title: "GraphQL doco"}, 681 {Title: "Introducing GraphQL in Dgraph"}, 682 {Title: "Learning GraphQL in Dgraph"}}}, 683 "false": { 684 Filter: map[string]interface{}{"isPublished": false}, 685 Expected: []*post{{Title: "Random post"}}}, 686 } 687 688 for name, test := range cases { 689 t.Run(name, func(t *testing.T) { 690 postTest(t, test.Filter, test.Expected) 691 }) 692 } 693 } 694 695 func termFilters(t *testing.T) { 696 cases := map[string]struct { 697 Filter interface{} 698 Expected []*post 699 }{ 700 "all of terms": { 701 Filter: map[string]interface{}{ 702 "title": map[string]interface{}{"allofterms": "GraphQL Dgraph"}}, 703 Expected: []*post{ 704 {Title: "Introducing GraphQL in Dgraph"}, 705 {Title: "Learning GraphQL in Dgraph"}}}, 706 "any of terms": { 707 Filter: map[string]interface{}{ 708 "title": map[string]interface{}{"anyofterms": "GraphQL Dgraph"}}, 709 Expected: []*post{ 710 {Title: "GraphQL doco"}, 711 {Title: "Introducing GraphQL in Dgraph"}, 712 {Title: "Learning GraphQL in Dgraph"}}}, 713 } 714 715 for name, test := range cases { 716 t.Run(name, func(t *testing.T) { 717 postTest(t, test.Filter, test.Expected) 718 }) 719 } 720 } 721 722 func fullTextFilters(t *testing.T) { 723 cases := map[string]struct { 724 Filter interface{} 725 Expected []*post 726 }{ 727 "all of text": { 728 Filter: map[string]interface{}{ 729 "text": map[string]interface{}{"alloftext": "learn GraphQL"}}, 730 Expected: []*post{ 731 {Title: "GraphQL doco"}, 732 {Title: "Learning GraphQL in Dgraph"}}}, 733 "any of text": { 734 Filter: map[string]interface{}{ 735 "text": map[string]interface{}{"anyoftext": "learn GraphQL"}}, 736 Expected: []*post{ 737 {Title: "GraphQL doco"}, 738 {Title: "Introducing GraphQL in Dgraph"}, 739 {Title: "Learning GraphQL in Dgraph"}}}, 740 } 741 742 for name, test := range cases { 743 t.Run(name, func(t *testing.T) { 744 postTest(t, test.Filter, test.Expected) 745 }) 746 } 747 } 748 749 func stringExactFilters(t *testing.T) { 750 cases := map[string]struct { 751 Filter interface{} 752 Expected []*post 753 }{ 754 "less than": { 755 Filter: map[string]interface{}{"topic": map[string]interface{}{"lt": "GraphQL"}}, 756 Expected: []*post{{Title: "GraphQL doco"}}}, 757 "less or equal": { 758 Filter: map[string]interface{}{"topic": map[string]interface{}{"le": "GraphQL"}}, 759 Expected: []*post{ 760 {Title: "GraphQL doco"}, 761 {Title: "Introducing GraphQL in Dgraph"}}}, 762 "equal": { 763 Filter: map[string]interface{}{"topic": map[string]interface{}{"eq": "GraphQL"}}, 764 Expected: []*post{{Title: "Introducing GraphQL in Dgraph"}}}, 765 "greater or equal": { 766 Filter: map[string]interface{}{"topic": map[string]interface{}{"ge": "GraphQL"}}, 767 Expected: []*post{ 768 {Title: "Introducing GraphQL in Dgraph"}, 769 {Title: "Learning GraphQL in Dgraph"}, 770 {Title: "Random post"}}}, 771 "greater than": { 772 Filter: map[string]interface{}{"topic": map[string]interface{}{"gt": "GraphQL"}}, 773 Expected: []*post{ 774 {Title: "Learning GraphQL in Dgraph"}, 775 {Title: "Random post"}}}, 776 } 777 778 for name, test := range cases { 779 t.Run(name, func(t *testing.T) { 780 postTest(t, test.Filter, test.Expected) 781 }) 782 } 783 } 784 785 func scalarListFilters(t *testing.T) { 786 787 // tags is a list of strings with @search(by: exact). So all the filters 788 // lt, le, ... mean "is there something in the list that's lt 'Dgraph'", etc. 789 790 cases := map[string]struct { 791 Filter interface{} 792 Expected []*post 793 }{ 794 "less than": { 795 Filter: map[string]interface{}{"tags": map[string]interface{}{"lt": "Dgraph"}}, 796 Expected: []*post{{Title: "Introducing GraphQL in Dgraph"}}}, 797 "less or equal": { 798 Filter: map[string]interface{}{"tags": map[string]interface{}{"le": "Dgraph"}}, 799 Expected: []*post{ 800 {Title: "GraphQL doco"}, 801 {Title: "Introducing GraphQL in Dgraph"}, 802 {Title: "Learning GraphQL in Dgraph"}}}, 803 "equal": { 804 Filter: map[string]interface{}{"tags": map[string]interface{}{"eq": "Database"}}, 805 Expected: []*post{{Title: "Introducing GraphQL in Dgraph"}}}, 806 "greater or equal": { 807 Filter: map[string]interface{}{"tags": map[string]interface{}{"ge": "Dgraph"}}, 808 Expected: []*post{ 809 {Title: "GraphQL doco"}, 810 {Title: "Introducing GraphQL in Dgraph"}, 811 {Title: "Learning GraphQL in Dgraph"}, 812 {Title: "Random post"}}}, 813 "greater than": { 814 Filter: map[string]interface{}{"tags": map[string]interface{}{"gt": "GraphQL"}}, 815 Expected: []*post{{Title: "Random post"}}}, 816 } 817 818 for name, test := range cases { 819 t.Run(name, func(t *testing.T) { 820 postTest(t, test.Filter, test.Expected) 821 }) 822 } 823 } 824 825 func postTest(t *testing.T, filter interface{}, expected []*post) { 826 queryParams := &GraphQLParams{ 827 Query: `query filterVariable($filter: PostFilter) { 828 queryPost(filter: $filter, order: { asc: title }) { 829 title 830 } 831 }`, 832 Variables: map[string]interface{}{"filter": filter}, 833 } 834 835 gqlResponse := queryParams.ExecuteAsPost(t, graphqlURL) 836 requireNoGQLErrors(t, gqlResponse) 837 838 var result struct { 839 QueryPost []*post 840 } 841 err := json.Unmarshal([]byte(gqlResponse.Data), &result) 842 require.NoError(t, err) 843 844 if diff := cmp.Diff(expected, result.QueryPost); diff != "" { 845 t.Errorf("result mismatch (-want +got):\n%s", diff) 846 } 847 } 848 849 func skipDirective(t *testing.T) { 850 getAuthorParams := &GraphQLParams{ 851 Query: `query ($skipPost: Boolean!, $skipName: Boolean!) { 852 queryAuthor(filter: { name: { eq: "Ann Other Author" } }) { 853 name @skip(if: $skipName) 854 dob 855 reputation 856 posts @skip(if: $skipPost) { 857 title 858 } 859 } 860 }`, 861 Variables: map[string]interface{}{ 862 "skipPost": true, 863 "skipName": false, 864 }, 865 } 866 867 gqlResponse := getAuthorParams.ExecuteAsPost(t, graphqlURL) 868 requireNoGQLErrors(t, gqlResponse) 869 870 expected := `{"queryAuthor":[{"name":"Ann Other Author", 871 "dob":"1988-01-01T00:00:00Z","reputation":8.9}]}` 872 require.JSONEq(t, expected, string(gqlResponse.Data)) 873 } 874 875 func includeDirective(t *testing.T) { 876 getAuthorParams := &GraphQLParams{ 877 Query: `query ($includeName: Boolean!, $includePost: Boolean!) { 878 queryAuthor(filter: { name: { eq: "Ann Other Author" } }) { 879 name @include(if: $includeName) 880 dob 881 posts @include(if: $includePost) { 882 title 883 } 884 } 885 }`, 886 Variables: map[string]interface{}{ 887 "includeName": true, 888 "includePost": false, 889 }, 890 } 891 892 gqlResponse := getAuthorParams.ExecuteAsPost(t, graphqlURL) 893 requireNoGQLErrors(t, gqlResponse) 894 895 expected := `{"queryAuthor":[{"name":"Ann Other Author","dob":"1988-01-01T00:00:00Z"}]}` 896 require.JSONEq(t, expected, string(gqlResponse.Data)) 897 } 898 899 func includeAndSkipDirective(t *testing.T) { 900 getAuthorParams := &GraphQLParams{ 901 Query: `query ($includeFalse: Boolean!, $skipTrue: Boolean!, $includeTrue: Boolean!, 902 $skipFalse: Boolean!) { 903 queryAuthor (filter: { name: { eq: "Ann Other Author" } }) { 904 dob @include(if: $includeFalse) @skip(if: $skipFalse) 905 reputation @include(if: $includeFalse) @skip(if: $skipTrue) 906 name @include(if: $includeTrue) @skip(if: $skipFalse) 907 posts(filter: { title: { anyofterms: "GraphQL" } }, first: 10) 908 @include(if: $includeTrue) @skip(if: $skipTrue) { 909 title 910 tags 911 } 912 } 913 }`, 914 Variables: map[string]interface{}{ 915 "includeFalse": false, 916 "includeTrue": true, 917 "skipFalse": false, 918 "skipTrue": true, 919 }, 920 } 921 922 gqlResponse := getAuthorParams.ExecuteAsPost(t, graphqlURL) 923 requireNoGQLErrors(t, gqlResponse) 924 925 expected := `{"queryAuthor":[{"name":"Ann Other Author"}]}` 926 require.JSONEq(t, expected, string(gqlResponse.Data)) 927 } 928 929 func queryByMultipleIds(t *testing.T) { 930 posts := allPosts(t) 931 ids := make([]string, 0, len(posts)) 932 for _, post := range posts { 933 ids = append(ids, post.PostID) 934 } 935 936 queryParams := &GraphQLParams{ 937 Query: `query queryPost($filter: PostFilter) { 938 queryPost(filter: $filter) { 939 postID 940 title 941 text 942 tags 943 numLikes 944 isPublished 945 postType 946 } 947 }`, 948 Variables: map[string]interface{}{"filter": map[string]interface{}{ 949 "postID": ids, 950 }}, 951 } 952 953 gqlResponse := queryParams.ExecuteAsPost(t, graphqlURL) 954 requireNoGQLErrors(t, gqlResponse) 955 956 var result struct { 957 QueryPost []*post 958 } 959 err := json.Unmarshal([]byte(gqlResponse.Data), &result) 960 require.NoError(t, err) 961 if diff := cmp.Diff(posts, result.QueryPost); diff != "" { 962 t.Errorf("result mismatch (-want +got):\n%s", diff) 963 } 964 } 965 966 func enumFilter(t *testing.T) { 967 posts := allPosts(t) 968 969 queryParams := &GraphQLParams{ 970 Query: `query queryPost($filter: PostFilter) { 971 queryPost(filter: $filter) { 972 postID 973 title 974 text 975 tags 976 numLikes 977 isPublished 978 postType 979 } 980 }`, 981 } 982 983 facts := make([]*post, 0, len(posts)) 984 questions := make([]*post, 0, len(posts)) 985 for _, post := range posts { 986 if post.PostType == "Fact" { 987 facts = append(facts, post) 988 } 989 if post.PostType == "Question" { 990 questions = append(questions, post) 991 } 992 } 993 994 cases := map[string]struct { 995 Filter interface{} 996 Expected []*post 997 }{ 998 "Hash Filter test": { 999 Filter: map[string]interface{}{ 1000 "postType": map[string]interface{}{ 1001 "eq": "Fact", 1002 }, 1003 }, 1004 Expected: facts, 1005 }, 1006 1007 "Regexp Filter test": { 1008 Filter: map[string]interface{}{ 1009 "postType": map[string]interface{}{ 1010 "regexp": "/(Fact)|(Question)/", 1011 }, 1012 }, 1013 Expected: append(questions, facts...), 1014 }, 1015 } 1016 1017 for name, test := range cases { 1018 t.Run(name, func(t *testing.T) { 1019 queryParams.Variables = map[string]interface{}{"filter": test.Filter} 1020 1021 gqlResponse := queryParams.ExecuteAsPost(t, graphqlURL) 1022 requireNoGQLErrors(t, gqlResponse) 1023 1024 var result struct { 1025 QueryPost []*post 1026 } 1027 1028 postSort := func(i, j int) bool { 1029 return result.QueryPost[i].Title < result.QueryPost[j].Title 1030 } 1031 testSort := func(i, j int) bool { 1032 return test.Expected[i].Title < test.Expected[j].Title 1033 } 1034 1035 err := json.Unmarshal([]byte(gqlResponse.Data), &result) 1036 sort.Slice(result.QueryPost, postSort) 1037 sort.Slice(test.Expected, testSort) 1038 1039 require.NoError(t, err) 1040 if diff := cmp.Diff(test.Expected, result.QueryPost); diff != "" { 1041 t.Errorf("result mismatch (-want +got):\n%s", diff) 1042 } 1043 }) 1044 1045 } 1046 } 1047 1048 func queryTypename(t *testing.T) { 1049 getCountryParams := &GraphQLParams{ 1050 Query: `query queryCountry { 1051 queryCountry { 1052 name 1053 __typename 1054 } 1055 }`, 1056 } 1057 1058 gqlResponse := getCountryParams.ExecuteAsPost(t, graphqlURL) 1059 requireNoGQLErrors(t, gqlResponse) 1060 1061 expected := `{ 1062 "queryCountry": [ 1063 { 1064 "name": "Angola", 1065 "__typename": "Country" 1066 }, 1067 { 1068 "name": "Bangladesh", 1069 "__typename": "Country" 1070 }, 1071 { 1072 "name": "Mozambique", 1073 "__typename": "Country" 1074 } 1075 ] 1076 }` 1077 testutil.CompareJSON(t, expected, string(gqlResponse.Data)) 1078 1079 } 1080 1081 func queryNestedTypename(t *testing.T) { 1082 getCountryParams := &GraphQLParams{ 1083 Query: `query { 1084 queryAuthor(filter: { name: { eq: "Ann Author" } }) { 1085 name 1086 dob 1087 posts { 1088 title 1089 __typename 1090 } 1091 } 1092 }`, 1093 } 1094 1095 gqlResponse := getCountryParams.ExecuteAsPost(t, graphqlURL) 1096 requireNoGQLErrors(t, gqlResponse) 1097 1098 expected := `{ 1099 "queryAuthor": [ 1100 { 1101 "name": "Ann Author", 1102 "dob": "2000-01-01T00:00:00Z", 1103 "posts": [ 1104 { 1105 "title": "Introducing GraphQL in Dgraph", 1106 "__typename": "Post" 1107 }, 1108 { 1109 "title": "GraphQL doco", 1110 "__typename": "Post" 1111 } 1112 ] 1113 } 1114 ] 1115 }` 1116 testutil.CompareJSON(t, expected, string(gqlResponse.Data)) 1117 } 1118 1119 func typenameForInterface(t *testing.T) { 1120 newStarship := addStarship(t) 1121 humanID := addHuman(t, newStarship.ID) 1122 droidID := addDroid(t) 1123 updateCharacter(t, humanID) 1124 1125 t.Run("test __typename for interface types", func(t *testing.T) { 1126 queryCharacterParams := &GraphQLParams{ 1127 Query: `query { 1128 queryCharacter (filter: { 1129 appearsIn: { 1130 eq: [EMPIRE] 1131 } 1132 }) { 1133 name 1134 __typename 1135 ... on Human { 1136 totalCredits 1137 } 1138 ... on Droid { 1139 primaryFunction 1140 } 1141 } 1142 }`, 1143 } 1144 1145 expected := `{ 1146 "queryCharacter": [ 1147 { 1148 "name":"Han Solo", 1149 "__typename": "Human", 1150 "totalCredits": 10 1151 }, 1152 { 1153 "name": "R2-D2", 1154 "__typename": "Droid", 1155 "primaryFunction": "Robot" 1156 } 1157 ] 1158 }` 1159 1160 gqlResponse := queryCharacterParams.ExecuteAsPost(t, graphqlURL) 1161 requireNoGQLErrors(t, gqlResponse) 1162 testutil.CompareJSON(t, expected, string(gqlResponse.Data)) 1163 }) 1164 1165 cleanupStarwars(t, newStarship.ID, humanID, droidID) 1166 } 1167 1168 func defaultEnumFilter(t *testing.T) { 1169 newStarship := addStarship(t) 1170 humanID := addHuman(t, newStarship.ID) 1171 droidID := addDroid(t) 1172 updateCharacter(t, humanID) 1173 1174 t.Run("test query enum default index on appearsIn", func(t *testing.T) { 1175 queryCharacterParams := &GraphQLParams{ 1176 Query: `query { 1177 queryCharacter (filter: { 1178 appearsIn: { 1179 eq: [EMPIRE] 1180 } 1181 }) { 1182 name 1183 appearsIn 1184 } 1185 }`, 1186 } 1187 1188 gqlResponse := queryCharacterParams.ExecuteAsPost(t, graphqlURL) 1189 requireNoGQLErrors(t, gqlResponse) 1190 1191 expected := `{ 1192 "queryCharacter": [ 1193 { 1194 "name":"Han Solo", 1195 "appearsIn": ["EMPIRE"] 1196 }, 1197 { 1198 "name": "R2-D2", 1199 "appearsIn": ["EMPIRE"] 1200 } 1201 ] 1202 }` 1203 testutil.CompareJSON(t, expected, string(gqlResponse.Data)) 1204 }) 1205 1206 cleanupStarwars(t, newStarship.ID, humanID, droidID) 1207 } 1208 1209 func queryByMultipleInvalidIds(t *testing.T) { 1210 queryParams := &GraphQLParams{ 1211 Query: `query queryPost($filter: PostFilter) { 1212 queryPost(filter: $filter) { 1213 postID 1214 title 1215 text 1216 tags 1217 numLikes 1218 isPublished 1219 postType 1220 } 1221 }`, 1222 Variables: map[string]interface{}{"filter": map[string]interface{}{ 1223 "postID": []string{"foo", "bar"}, 1224 }}, 1225 } 1226 // Since the ids are invalid and can't be converted to uint64, the query sent to Dgraph should 1227 // have func: uid() at root and should return 0 results. 1228 1229 gqlResponse := queryParams.ExecuteAsPost(t, graphqlURL) 1230 requireNoGQLErrors(t, gqlResponse) 1231 1232 require.Equal(t, `{"queryPost":[]}`, string(gqlResponse.Data)) 1233 var result struct { 1234 QueryPost []*post 1235 } 1236 err := json.Unmarshal([]byte(gqlResponse.Data), &result) 1237 require.NoError(t, err) 1238 require.Equal(t, 0, len(result.QueryPost)) 1239 } 1240 1241 func getStateByXid(t *testing.T) { 1242 getStateParams := &GraphQLParams{ 1243 Query: `{ 1244 getState(xcode: "nsw") { 1245 name 1246 } 1247 }`, 1248 } 1249 1250 gqlResponse := getStateParams.ExecuteAsPost(t, graphqlURL) 1251 requireNoGQLErrors(t, gqlResponse) 1252 require.Equal(t, `{"getState":{"name":"NSW"}}`, string(gqlResponse.Data)) 1253 } 1254 1255 func getStateWithoutArgs(t *testing.T) { 1256 getStateParams := &GraphQLParams{ 1257 Query: `{ 1258 getState { 1259 name 1260 } 1261 }`, 1262 } 1263 1264 gqlResponse := getStateParams.ExecuteAsPost(t, graphqlURL) 1265 requireNoGQLErrors(t, gqlResponse) 1266 require.JSONEq(t, `{"getState":null}`, string(gqlResponse.Data)) 1267 } 1268 1269 func getStateByBothXidAndUid(t *testing.T) { 1270 getStateParams := &GraphQLParams{ 1271 Query: `{ 1272 getState(xcode: "nsw", id: "0x1") { 1273 name 1274 } 1275 }`, 1276 } 1277 1278 gqlResponse := getStateParams.ExecuteAsPost(t, graphqlURL) 1279 requireNoGQLErrors(t, gqlResponse) 1280 require.JSONEq(t, `{"getState":null}`, string(gqlResponse.Data)) 1281 } 1282 1283 func queryStateByXid(t *testing.T) { 1284 getStateParams := &GraphQLParams{ 1285 Query: `{ 1286 queryState(filter: { xcode: { eq: "nsw"}}) { 1287 name 1288 } 1289 }`, 1290 } 1291 1292 gqlResponse := getStateParams.ExecuteAsPost(t, graphqlURL) 1293 requireNoGQLErrors(t, gqlResponse) 1294 require.Equal(t, `{"queryState":[{"name":"NSW"}]}`, string(gqlResponse.Data)) 1295 } 1296 1297 func queryStateByXidRegex(t *testing.T) { 1298 getStateParams := &GraphQLParams{ 1299 Query: `{ 1300 queryState(filter: { xcode: { regexp: "/n/"}}) { 1301 name 1302 } 1303 }`, 1304 } 1305 1306 gqlResponse := getStateParams.ExecuteAsPost(t, graphqlURL) 1307 requireNoGQLErrors(t, gqlResponse) 1308 testutil.CompareJSON(t, `{"queryState":[{"name":"Nusa"},{"name": "NSW"}]}`, 1309 string(gqlResponse.Data)) 1310 } 1311 1312 func multipleOperations(t *testing.T) { 1313 params := &GraphQLParams{ 1314 Query: `query sortCountryByNameDesc { 1315 queryCountry(order: { desc: name }, first: 1) { 1316 name 1317 } 1318 } 1319 1320 query sortCountryByNameAsc { 1321 queryCountry(order: { asc: name }, first: 1) { 1322 name 1323 } 1324 } 1325 `, 1326 OperationName: "sortCountryByNameAsc", 1327 } 1328 1329 cases := []struct { 1330 name string 1331 operationName string 1332 expectedError string 1333 expected []*country 1334 }{ 1335 { 1336 "second query name as operation name", 1337 "sortCountryByNameAsc", 1338 "", 1339 []*country{{Name: "Angola"}}, 1340 }, 1341 { 1342 "first query name as operation name", 1343 "sortCountryByNameDesc", 1344 "", 1345 []*country{{Name: "Mozambique"}}, 1346 }, 1347 { 1348 "operation name doesn't exist", 1349 "sortCountryByName", 1350 "Supplied operation name sortCountryByName isn't present in the request.", 1351 nil, 1352 }, 1353 { 1354 "operation name is empty", 1355 "", 1356 "Operation name must by supplied when query has more than 1 operation.", 1357 nil, 1358 }, 1359 } 1360 1361 for _, test := range cases { 1362 t.Run(test.name, func(t *testing.T) { 1363 params.OperationName = test.operationName 1364 gqlResponse := params.ExecuteAsPost(t, graphqlURL) 1365 if test.expectedError != "" { 1366 require.NotNil(t, gqlResponse.Errors) 1367 require.Equal(t, test.expectedError, gqlResponse.Errors[0].Error()) 1368 return 1369 } 1370 requireNoGQLErrors(t, gqlResponse) 1371 1372 var expected, result struct { 1373 QueryCountry []*country 1374 } 1375 expected.QueryCountry = test.expected 1376 err := json.Unmarshal([]byte(gqlResponse.Data), &result) 1377 require.NoError(t, err) 1378 1379 if diff := cmp.Diff(expected, result); diff != "" { 1380 t.Errorf("result mismatch (-want +got):\n%s", diff) 1381 } 1382 }) 1383 } 1384 } 1385 1386 func queryPostWithAuthor(t *testing.T) { 1387 queryPostParams := &GraphQLParams{ 1388 Query: `query { 1389 queryPost (filter: {title : { anyofterms : "Introducing" }} ) { 1390 title 1391 author { 1392 name 1393 } 1394 } 1395 }`, 1396 } 1397 1398 gqlResponse := queryPostParams.ExecuteAsPost(t, graphqlURL) 1399 requireNoGQLErrors(t, gqlResponse) 1400 testutil.CompareJSON(t, 1401 `{"queryPost":[{"title":"Introducing GraphQL in Dgraph","author":{"name":"Ann Author"}}]}`, 1402 string(gqlResponse.Data)) 1403 }