github.com/CiscoM31/godata@v1.0.10/url_parser_test.go (about) 1 package godata 2 3 import ( 4 "context" 5 "fmt" 6 "net/url" 7 "regexp" 8 "testing" 9 ) 10 11 func TestUrlParser(t *testing.T) { 12 testUrl := "Employees(1)/Sales.Manager?$expand=DirectReports%28$select%3DFirstName%2CLastName%3B$levels%3D4%29" 13 parsedUrl, err := url.Parse(testUrl) 14 15 if err != nil { 16 t.Error(err) 17 return 18 } 19 ctx := context.Background() 20 request, err := ParseRequest(ctx, parsedUrl.Path, parsedUrl.Query()) 21 22 if err != nil { 23 t.Error(err) 24 return 25 } 26 27 if request.FirstSegment.Name != "Employees" { 28 t.Error("First segment is '" + request.FirstSegment.Name + "' not Employees") 29 return 30 } 31 if request.FirstSegment.Identifier.Get() != "1" { 32 t.Error("Employee identifier not found") 33 return 34 } 35 if request.FirstSegment.Next.Name != "Sales.Manager" { 36 t.Error("Second segment is not Sales.Manager") 37 return 38 } 39 } 40 41 func TestUrlParserStrictValidation(t *testing.T) { 42 testUrl := "Employees(1)/Sales.Manager?$expand=DirectReports%28$select%3DFirstName%2CLastName%3B$levels%3D4%29" 43 parsedUrl, err := url.Parse(testUrl) 44 if err != nil { 45 t.Error(err) 46 return 47 } 48 ctx := context.Background() 49 _, err = ParseRequest(ctx, parsedUrl.Path, parsedUrl.Query()) 50 if err != nil { 51 t.Error(err) 52 return 53 } 54 55 testUrl = "Employees(1)/Sales.Manager?$filter=FirstName eq 'Bob'" 56 parsedUrl, err = url.Parse(testUrl) 57 if err != nil { 58 t.Error(err) 59 return 60 } 61 _, err = ParseRequest(ctx, parsedUrl.Path, parsedUrl.Query()) 62 if err != nil { 63 t.Error(err) 64 return 65 } 66 67 // Wrong filter with an extraneous single quote 68 testUrl = "Employees(1)/Sales.Manager?$filter=FirstName eq' 'Bob'" 69 parsedUrl, err = url.Parse(testUrl) 70 if err != nil { 71 t.Error(err) 72 return 73 } 74 _, err = ParseRequest(ctx, parsedUrl.Path, parsedUrl.Query()) 75 if err == nil { 76 t.Errorf("Parser should have returned invalid filter error: %s", testUrl) 77 return 78 } 79 80 // Valid query with two parameters: 81 // $filter=FirstName eq 'Bob' 82 // at=Version eq '123' 83 testUrl = "Employees(1)/Sales.Manager?$filter=FirstName eq 'Bob'&at=Version eq '123'" 84 parsedUrl, err = url.Parse(testUrl) 85 if err != nil { 86 t.Error(err) 87 return 88 } 89 _, err = ParseRequest(ctx, parsedUrl.Path, parsedUrl.Query()) 90 if err != nil { 91 t.Error(err) 92 return 93 } 94 95 // Invalid query: 96 // $filter=FirstName eq' 'Bob' has extraneous single quote. 97 // at=Version eq '123' is valid 98 testUrl = "Employees(1)/Sales.Manager?$filter=FirstName eq' 'Bob'&at=Version eq '123'" 99 parsedUrl, err = url.Parse(testUrl) 100 if err != nil { 101 t.Error(err) 102 return 103 } 104 _, err = ParseRequest(ctx, parsedUrl.Path, parsedUrl.Query()) 105 if err == nil { 106 t.Errorf("Parser should have returned invalid filter error: %s", testUrl) 107 return 108 } 109 110 testUrl = "Employees(1)/Sales.Manager?$select=3DFirstName" 111 parsedUrl, err = url.Parse(testUrl) 112 if err != nil { 113 t.Error(err) 114 return 115 } 116 _, err = ParseRequest(ctx, parsedUrl.Path, parsedUrl.Query()) 117 if err != nil { 118 t.Error(err) 119 return 120 } 121 122 testUrl = "Employees(1)/Sales.Manager?$filter=Name in ('Bob','Alice')&$select=Name,Address&$expand=Address($select=City)" 123 parsedUrl, err = url.Parse(testUrl) 124 if err != nil { 125 t.Error(err) 126 return 127 } 128 _, err = ParseRequest(ctx, parsedUrl.Path, parsedUrl.Query()) 129 if err != nil { 130 t.Errorf("Unexpected parsing error: %v", err) 131 return 132 } 133 134 // A $select option cannot be wrapped with parenthesis. This is not legal ODATA. 135 136 /* 137 queryOptions = queryOption *( "&" queryOption ) 138 queryOption = systemQueryOption 139 / aliasAndValue 140 / nameAndValue 141 / customQueryOption 142 systemQueryOption = compute 143 / deltatoken 144 / expand 145 / filter 146 / format 147 / id 148 / inlinecount 149 / orderby 150 / schemaversion 151 / search 152 / select 153 / skip 154 / skiptoken 155 / top 156 / index 157 select = ( "$select" / "select" ) EQ selectItem *( COMMA selectItem ) 158 */ 159 testUrl = "Employees(1)/Sales.Manager?$filter=Name in ('Bob','Alice')&($select=Name,Address%3B$expand=Address($select=City))" 160 parsedUrl, err = url.Parse(testUrl) 161 if err != nil { 162 t.Error(err) 163 return 164 } 165 _, err = ParseRequest(ctx, parsedUrl.Path, parsedUrl.Query()) 166 if err == nil { 167 t.Errorf("Parser should have raised error") 168 return 169 } 170 171 // Duplicate keyword: '$select' is present twice. 172 testUrl = "Employees(1)/Sales.Manager?$select=3DFirstName&$select=3DFirstName" 173 parsedUrl, err = url.Parse(testUrl) 174 if err != nil { 175 t.Error(err) 176 return 177 } 178 // In lenient mode, do not return an error when there is a duplicate keyword. 179 lenientContext := WithOdataComplianceConfig(ctx, ComplianceIgnoreAll) 180 _, err = ParseRequest(lenientContext, parsedUrl.Path, parsedUrl.Query()) 181 if err != nil { 182 t.Error(err) 183 return 184 } 185 // In strict mode, return an error when there is a duplicate keyword. 186 _, err = ParseRequest(ctx, parsedUrl.Path, parsedUrl.Query()) 187 if err == nil { 188 t.Error("Parser should have returned duplicate keyword error") 189 return 190 } 191 192 // Unsupported keywords 193 testUrl = "Employees(1)/Sales.Manager?orderby=FirstName" 194 parsedUrl, err = url.Parse(testUrl) 195 if err != nil { 196 t.Error(err) 197 return 198 } 199 _, err = ParseRequest(lenientContext, parsedUrl.Path, parsedUrl.Query()) 200 if err != nil { 201 t.Error(err) 202 return 203 } 204 _, err = ParseRequest(ctx, parsedUrl.Path, parsedUrl.Query()) 205 if err == nil { 206 t.Error("Parser should have returned unsupported keyword error") 207 return 208 } 209 210 testUrl = "Employees(1)/Sales.Manager?$select=LastName&$expand=Address" 211 parsedUrl, err = url.Parse(testUrl) 212 if err != nil { 213 t.Error(err) 214 return 215 } 216 _, err = ParseRequest(ctx, parsedUrl.Path, parsedUrl.Query()) 217 if err != nil { 218 t.Error(err) 219 return 220 } 221 222 testUrl = "Employees(1)/Sales.Manager?$select=FirstName,LastName&$expand=Address" 223 parsedUrl, err = url.Parse(testUrl) 224 if err != nil { 225 t.Error(err) 226 return 227 } 228 _, err = ParseRequest(ctx, parsedUrl.Path, parsedUrl.Query()) 229 if err != nil { 230 t.Error(err) 231 return 232 } 233 234 } 235 236 // TestUnescapeStringTokens tests string encoding rules specified in the ODATA ABNF: 237 // http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_URLSyntax 238 func TestUnescapeStringTokens(t *testing.T) { 239 240 testCases := []struct { 241 url string // The test URL 242 // Set to nil if no error is expected. 243 // If error is expected, it is used to match err.Error() 244 errRegex *regexp.Regexp 245 246 expectedFilterTree []expectedParseNode 247 expectedOrderBy []OrderByItem 248 expectedCompute []ComputeItem 249 }{ 250 { 251 // Unescaped single quotes. 252 // This is not a valid filter because: 253 // 1. there are two consecutive literal values, 'ab' and 'c, 254 // 2. 'c is not terminated with a quote. 255 url: "/Books?$filter=Description eq 'ab'c'", 256 errRegex: regexp.MustCompile("Token ''' is invalid"), 257 }, 258 { 259 // Simple string with special characters. 260 url: "/Books?$filter=Description eq 'abc'", 261 errRegex: nil, 262 expectedFilterTree: []expectedParseNode{ 263 {Value: "eq", Depth: 0, Type: ExpressionTokenLogical}, 264 {Value: "Description", Depth: 1, Type: ExpressionTokenLiteral}, 265 {Value: "'abc'", Depth: 1, Type: ExpressionTokenString}, 266 }, 267 }, 268 { 269 // Two consecutive single quotes. 270 // One of the URL syntax rules for ODATA is that single quotes within string 271 // literals are represented as two consecutive single quotes. 272 // This is done to make the input strings in the ABNF test cases more readable. 273 url: "/Books?$filter=Description eq 'ab''c'", 274 errRegex: nil, 275 expectedFilterTree: []expectedParseNode{ 276 {Value: "eq", Depth: 0, Type: ExpressionTokenLogical}, 277 {Value: "Description", Depth: 1, Type: ExpressionTokenLiteral}, 278 // Note below two consecutive single-quotes are the encoding of one single quote, 279 // so after the tokenization it is unescaped to one single quote. 280 {Value: "'ab'c'", Depth: 1, Type: ExpressionTokenString}, 281 }, 282 }, 283 { 284 // Test single quotes escaped as %27. 285 url: "/Books?$filter=Description eq 'O%27%27Neil'", 286 errRegex: nil, 287 expectedFilterTree: []expectedParseNode{ 288 {Value: "eq", Depth: 0, Type: ExpressionTokenLogical}, 289 {Value: "Description", Depth: 1, Type: ExpressionTokenLiteral}, 290 // Percent-encoded character %27 must be decoded to single quote. 291 {Value: "'O'Neil'", Depth: 1, Type: ExpressionTokenString}, 292 }, 293 }, 294 { 295 // Test single quotes escaped as %27. 296 // This time all single quotes are percent-encoded, including the outer single-quotes. 297 url: "/Books?$filter=Description eq %27O%27%27Neil%27", 298 errRegex: nil, 299 expectedFilterTree: []expectedParseNode{ 300 {Value: "eq", Depth: 0, Type: ExpressionTokenLogical}, 301 {Value: "Description", Depth: 1, Type: ExpressionTokenLiteral}, 302 // Percent-encoded character %27 must be decoded to single quote. 303 {Value: "'O'Neil'", Depth: 1, Type: ExpressionTokenString}, 304 }, 305 }, 306 { 307 // According to RFC 1738, URLs should not include UTF-8 characters, 308 // but the string tokens are parsed anyway. 309 url: "/Books?$filter=Description eq '♺⛺⛵⚡'", 310 errRegex: nil, 311 expectedFilterTree: []expectedParseNode{ 312 {Value: "eq", Depth: 0, Type: ExpressionTokenLogical}, 313 {Value: "Description", Depth: 1, Type: ExpressionTokenLiteral}, 314 // Percent-encoded character %27 must be decoded to single quote. 315 {Value: "'♺⛺⛵⚡'", Depth: 1, Type: ExpressionTokenString}, 316 }, 317 }, 318 { 319 // Strings with percent encoding 320 url: "/Books?$filter=Description eq '%34%35%36'", 321 errRegex: nil, 322 expectedFilterTree: []expectedParseNode{ 323 {Value: "eq", Depth: 0, Type: ExpressionTokenLogical}, 324 {Value: "Description", Depth: 1, Type: ExpressionTokenLiteral}, 325 {Value: "'456'", Depth: 1, Type: ExpressionTokenString}, 326 }, 327 }, 328 { 329 url: "/Books?$filter=Description eq 'abc'&$orderby=Title", 330 errRegex: nil, 331 expectedFilterTree: []expectedParseNode{ 332 {Value: "eq", Depth: 0, Type: ExpressionTokenLogical}, 333 {Value: "Description", Depth: 1, Type: ExpressionTokenLiteral}, 334 {Value: "'abc'", Depth: 1, Type: ExpressionTokenString}, 335 }, 336 expectedOrderBy: []OrderByItem{ 337 {Field: &Token{Value: "Title"}, Order: "asc"}, 338 }, 339 }, 340 { 341 url: "/Books?$filter=Description eq 'abc'&$orderby=Title asc", 342 errRegex: nil, 343 expectedFilterTree: []expectedParseNode{ 344 {Value: "eq", Depth: 0, Type: ExpressionTokenLogical}, 345 {Value: "Description", Depth: 1, Type: ExpressionTokenLiteral}, 346 {Value: "'abc'", Depth: 1, Type: ExpressionTokenString}, 347 }, 348 expectedOrderBy: []OrderByItem{ 349 {Field: &Token{Value: "Title"}, Order: "asc"}, 350 }, 351 }, 352 { 353 url: "/Books?$filter=Description eq 'abc'&$orderby=Title desc", 354 errRegex: nil, 355 expectedFilterTree: []expectedParseNode{ 356 {Value: "eq", Depth: 0, Type: ExpressionTokenLogical}, 357 {Value: "Description", Depth: 1, Type: ExpressionTokenLiteral}, 358 {Value: "'abc'", Depth: 1, Type: ExpressionTokenString}, 359 }, 360 expectedOrderBy: []OrderByItem{ 361 {Field: &Token{Value: "Title"}, Order: "desc"}, 362 }, 363 }, 364 { 365 url: "/Books?$filter=Description eq 'abc'&$orderby=Author asc,Title desc", 366 errRegex: nil, 367 expectedFilterTree: []expectedParseNode{ 368 {Value: "eq", Depth: 0, Type: ExpressionTokenLogical}, 369 {Value: "Description", Depth: 1, Type: ExpressionTokenLiteral}, 370 {Value: "'abc'", Depth: 1, Type: ExpressionTokenString}, 371 }, 372 expectedOrderBy: []OrderByItem{ 373 {Field: &Token{Value: "Author"}, Order: "asc"}, 374 {Field: &Token{Value: "Title"}, Order: "desc"}, 375 }, 376 }, 377 { 378 url: "/Books?$filter=Description eq 'abc'&$orderby=Author asc,Title DESC", 379 errRegex: nil, 380 expectedFilterTree: []expectedParseNode{ 381 {Value: "eq", Depth: 0, Type: ExpressionTokenLogical}, 382 {Value: "Description", Depth: 1, Type: ExpressionTokenLiteral}, 383 {Value: "'abc'", Depth: 1, Type: ExpressionTokenString}, 384 }, 385 expectedOrderBy: []OrderByItem{ 386 {Field: &Token{Value: "Author"}, Order: "asc"}, 387 {Field: &Token{Value: "Title"}, Order: "desc"}, 388 }, 389 }, 390 { 391 url: "/Products?$orderby=Asc", 392 errRegex: nil, 393 expectedFilterTree: nil, 394 expectedOrderBy: []OrderByItem{ 395 {Field: &Token{Value: "Asc"}, Order: "asc"}, 396 }, 397 }, 398 { 399 url: "/Products?$orderby=Asc Asc", 400 errRegex: nil, 401 expectedFilterTree: nil, 402 expectedOrderBy: []OrderByItem{ 403 {Field: &Token{Value: "Asc"}, Order: "asc"}, 404 }, 405 }, 406 { 407 url: "/Products?$orderby=Desc Asc", 408 errRegex: nil, 409 expectedFilterTree: nil, 410 expectedOrderBy: []OrderByItem{ 411 {Field: &Token{Value: "Desc"}, Order: "asc"}, 412 }, 413 }, 414 { 415 url: "/Products?$orderby=Asc Desc", 416 errRegex: nil, 417 expectedFilterTree: nil, 418 expectedOrderBy: []OrderByItem{ 419 {Field: &Token{Value: "Asc"}, Order: "desc"}, 420 }, 421 }, 422 { 423 url: "/Products?$orderby=ProductDesc", 424 errRegex: nil, 425 expectedFilterTree: nil, 426 expectedOrderBy: []OrderByItem{ 427 {Field: &Token{Value: "ProductDesc"}, Order: "asc"}, 428 }, 429 }, 430 431 /* 432 TODO: this is not supported yet. 433 { 434 // return all Categories ordered by the number of Products within each category. 435 url: "Categories?$orderby=Products/$count", 436 errRegex: nil, 437 expectedFilterTree: nil, 438 expectedOrderBy: []OrderByItem{ 439 { 440 Field: &Token{Value: "Products/$count"}, 441 Order: "asc", 442 }, 443 }, 444 }, 445 */ 446 { 447 url: "/Product?$filter=Description eq 'abc'&$orderby=part_x0020_number asc", 448 errRegex: nil, 449 expectedFilterTree: []expectedParseNode{ 450 {Value: "eq", Depth: 0, Type: ExpressionTokenLogical}, 451 {Value: "Description", Depth: 1, Type: ExpressionTokenLiteral}, 452 {Value: "'abc'", Depth: 1, Type: ExpressionTokenString}, 453 }, 454 expectedOrderBy: []OrderByItem{ 455 { 456 Field: &Token{Value: "part number"}, 457 Order: "asc", 458 }, 459 }, 460 }, 461 { 462 url: "/Product?$orderby=Tags(Key='Environment')/Value desc", 463 errRegex: nil, 464 expectedFilterTree: nil, 465 expectedOrderBy: []OrderByItem{ 466 { 467 Field: &Token{Value: "Tags(Key='Environment')/Value"}, 468 Order: "desc", 469 }, 470 }, 471 }, 472 { 473 url: "/Product?$orderby=Tags(Key='Sku Number')/Value", 474 errRegex: nil, 475 expectedFilterTree: nil, 476 expectedOrderBy: []OrderByItem{ 477 { 478 Field: &Token{Value: "Tags(Key='Sku Number')/Value"}, 479 Order: "asc", 480 }, 481 }, 482 }, 483 { 484 // Disallow $orderby=+Name 485 // Query string uses %2B which is the escape for +. The + character is itself a url escape for space, see https://www.w3schools.com/tags/ref_urlencode.asp. 486 url: "/Product?$orderby=%2BName", 487 errRegex: regexp.MustCompile(`.*Token '\+Name' is invalid.*`), 488 expectedFilterTree: nil, 489 expectedOrderBy: nil, 490 }, 491 { 492 url: "/Product?$orderby=-Name", 493 errRegex: regexp.MustCompile(".*Token '-Name' is invalid.*"), 494 expectedFilterTree: nil, 495 expectedOrderBy: nil, 496 }, 497 { 498 url: "/Product?$orderby=Name,", 499 errRegex: regexp.MustCompile(`Extra comma in \$orderby\.`), 500 expectedFilterTree: nil, 501 expectedOrderBy: nil, 502 }, 503 { 504 url: "/Product?$orderby=Name,,Count", 505 errRegex: regexp.MustCompile(`Extra comma in \$orderby\.`), 506 expectedFilterTree: nil, 507 expectedOrderBy: nil, 508 }, 509 { 510 url: "/Product?$orderby=,Name", 511 errRegex: regexp.MustCompile(`Extra comma in \$orderby\.`), 512 expectedFilterTree: nil, 513 expectedOrderBy: nil, 514 }, 515 { 516 url: "/Product?$select=Name,", 517 errRegex: regexp.MustCompile(`Extra comma in \$select\.`), 518 expectedFilterTree: nil, 519 expectedOrderBy: nil, 520 }, 521 { 522 url: "/Product?$select=Name,,Count", 523 errRegex: regexp.MustCompile(`Extra comma in \$select\.`), 524 expectedFilterTree: nil, 525 expectedOrderBy: nil, 526 }, 527 { 528 url: "/Product?$select=,Name", 529 errRegex: regexp.MustCompile(`Extra comma in \$select\.`), 530 expectedFilterTree: nil, 531 expectedOrderBy: nil, 532 }, 533 { 534 url: "/Product?$select=$select=Name", 535 errRegex: regexp.MustCompile(`Invalid \$select\ value.`), 536 expectedFilterTree: nil, 537 expectedOrderBy: nil, 538 }, 539 { 540 url: "/Product?$select=$Name", 541 errRegex: regexp.MustCompile(`Invalid \$select\ value.`), 542 expectedFilterTree: nil, 543 expectedOrderBy: nil, 544 }, 545 { 546 url: "/Product?$compute=Price mul Quantity as TotalPrice", 547 errRegex: nil, 548 expectedCompute: []ComputeItem{ 549 { 550 Field: "TotalPrice", 551 }, 552 }, 553 }, 554 { 555 url: "/Product?$compute=Price mul Quantity as Extra/TotalPrice", 556 errRegex: nil, 557 expectedCompute: []ComputeItem{ 558 { 559 Field: "Extra/TotalPrice", 560 }, 561 }, 562 }, 563 { 564 url: "/Product?$compute=Price mul Quantity as TotalPrice,A add B as C", 565 expectedCompute: []ComputeItem{ 566 { 567 Field: "TotalPrice", 568 }, 569 { 570 Field: "C", 571 }, 572 }, 573 }, 574 { 575 url: "/Product?$expand=Details($compute=Price mul Quantity as TotalPrice)", 576 // todo: enhance fixture to handle $expand with embedded $compute and add assertions 577 }, 578 { 579 url: "/Product?$compute=discount(Item/Price) as SalePrice", 580 expectedCompute: []ComputeItem{ 581 { 582 Field: "SalePrice", 583 }, 584 }, 585 }, 586 { 587 url: "/Product?$compute=Price mul Quantity", 588 errRegex: regexp.MustCompile(`Invalid \$compute query option`), 589 }, 590 { 591 url: "/Product?$compute=Price bad Quantity as TotalPrice", 592 errRegex: regexp.MustCompile(`Invalid \$compute query option`), 593 }, 594 { 595 url: "/Product?$compute=Price mul Quantity as as TotalPrice", 596 errRegex: regexp.MustCompile(`Invalid \$compute query option`), 597 }, 598 { 599 url: "/Product?$compute=Price mul Quantity as TotalPrice as TotalPrice2", 600 errRegex: regexp.MustCompile(`Invalid \$compute query option`), 601 }, 602 { 603 url: "/Product?$compute=TotalPrice as Price mul Quantity", 604 errRegex: regexp.MustCompile(`Invalid \$compute query option`), 605 }, 606 } 607 err := DefineCustomFunctions([]CustomFunctionInput{{ 608 Name: "discount", 609 NumParams: []int{1}, 610 }}) 611 if err != nil { 612 t.Errorf("Failed to add custom function: %v", err) 613 t.FailNow() 614 } 615 616 for _, testCase := range testCases { 617 var parsedUrl *url.URL 618 parsedUrl, err = url.Parse(testCase.url) 619 if err != nil { 620 t.Errorf("Test case '%s' failed: %v", testCase.url, err) 621 continue 622 } 623 t.Logf("Running test case %s", testCase.url) 624 625 urlQuery := parsedUrl.Query() 626 ctx := context.Background() 627 var request *GoDataRequest 628 request, err = ParseRequest(ctx, parsedUrl.Path, urlQuery) 629 if testCase.errRegex == nil && err != nil { 630 t.Errorf("Test case '%s' failed: %v", testCase.url, err) 631 continue 632 } else if testCase.errRegex != nil && err == nil { 633 t.Errorf("Test case '%s' failed. Expected error but obtained nil error", testCase.url) 634 continue 635 } else if err != nil && !testCase.errRegex.MatchString(err.Error()) { 636 t.Errorf("Test case '%s' failed. Obtained error [%v] does not match expected regex [%v]", 637 testCase.url, err, testCase.errRegex) 638 continue 639 } 640 if err == nil { 641 filter := request.Query.Filter 642 if filter == nil { 643 if testCase.expectedFilterTree != nil { 644 t.Errorf("Test case '%s' failed. Parsed filter is nil", testCase.url) 645 } 646 } else { 647 pos := 0 648 err = CompareTree(filter.Tree, testCase.expectedFilterTree, &pos, 0) 649 if err != nil { 650 t.Errorf("Tree representation does not match expected value. error: %s", err.Error()) 651 } 652 } 653 654 err = compareOrderBy(request.Query.OrderBy, testCase.expectedOrderBy) 655 if err != nil { 656 t.Errorf("orderby does not match expected value. error: %s", err.Error()) 657 } 658 659 err = compareCompute(request.Query.Compute, testCase.expectedCompute) 660 if err != nil { 661 t.Errorf("compute does not match expected value. error: %s", err.Error()) 662 } 663 // t.Log(request.Query.Compute.ComputeItems[0]) 664 } 665 } 666 } 667 668 func compareOrderBy(obtained *GoDataOrderByQuery, expected []OrderByItem) error { 669 if len(expected) == 0 && (obtained == nil || obtained.OrderByItems == nil) { 670 return nil 671 } 672 if len(expected) > 0 && (obtained == nil || obtained.OrderByItems == nil) { 673 return fmt.Errorf("Unexpected number of $orderby fields. Got nil, expected %d", 674 len(expected)) 675 } 676 if len(obtained.OrderByItems) != len(expected) { 677 return fmt.Errorf("Unexpected number of $orderby fields. Got %d, expected %d", 678 len(obtained.OrderByItems), len(expected)) 679 } 680 for i, v := range expected { 681 if v.Field.Value != obtained.OrderByItems[i].Field.Value { 682 return fmt.Errorf("Unexpected $orderby field at index %d. Got '%s', expected '%s'", 683 i, obtained.OrderByItems[i].Field.Value, v.Field.Value) 684 } 685 if v.Order != obtained.OrderByItems[i].Order { 686 return fmt.Errorf("Unexpected $orderby at index %d. Got '%s', expected '%s'", 687 i, obtained.OrderByItems[i].Order, v.Order) 688 } 689 } 690 return nil 691 } 692 693 func compareCompute(obtained *GoDataComputeQuery, expected []ComputeItem) error { 694 if len(expected) == 0 && (obtained == nil || obtained.ComputeItems == nil) { 695 return nil 696 } 697 if len(expected) > 0 && (obtained == nil || obtained.ComputeItems == nil) { 698 return fmt.Errorf("Unexpected number of $compute fields. Got nil, expected %d", 699 len(expected)) 700 } 701 if len(obtained.ComputeItems) != len(expected) { 702 return fmt.Errorf("Unexpected number of $compute fields. Got %d, expected %d", 703 len(obtained.ComputeItems), len(expected)) 704 } 705 for i, v := range expected { 706 if obtained.ComputeItems[i].Field != v.Field { 707 return fmt.Errorf("Expected $compute field %d with name '%s'. Got '%s'.", i, v.Field, obtained.ComputeItems[i].Field) 708 } 709 } 710 return nil 711 }