github.com/weaviate/weaviate@v1.24.6/adapters/handlers/graphql/local/explore/concepts_test.go (about) 1 // _ _ 2 // __ _____ __ ___ ___ __ _| |_ ___ 3 // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ 4 // \ V V / __/ (_| |\ V /| | (_| | || __/ 5 // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| 6 // 7 // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. 8 // 9 // CONTACT: hello@weaviate.io 10 // 11 12 package explore 13 14 import ( 15 "testing" 16 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/require" 19 "github.com/weaviate/weaviate/entities/search" 20 "github.com/weaviate/weaviate/entities/searchparams" 21 helper "github.com/weaviate/weaviate/test/helper" 22 "github.com/weaviate/weaviate/usecases/traverser" 23 ) 24 25 type testCase struct { 26 name string 27 query string 28 expectedParamsToTraverser traverser.ExploreParams 29 resolverReturn []search.Result 30 expectedResults []result 31 } 32 33 type testCases []testCase 34 35 type result struct { 36 pathToField []string 37 expectedValue interface{} 38 } 39 40 func Test_ResolveExplore(t *testing.T) { 41 t.Parallel() 42 43 testsNearText := testCases{ 44 testCase{ 45 name: "Resolve Explore with nearCustomText", 46 query: ` 47 { 48 Explore(nearCustomText: {concepts: ["car", "best brand"]}) { 49 beacon className certainty distance 50 } 51 }`, 52 expectedParamsToTraverser: traverser.ExploreParams{ 53 ModuleParams: map[string]interface{}{ 54 "nearCustomText": extractNearCustomTextParam(map[string]interface{}{ 55 "concepts": []interface{}{"car", "best brand"}, 56 }), 57 }, 58 WithCertaintyProp: true, 59 }, 60 resolverReturn: []search.Result{ 61 { 62 Beacon: "weaviate://localhost/some-uuid", 63 ClassName: "bestClass", 64 Certainty: 0.7, 65 Dist: helper.CertaintyToDist(t, 0.7), 66 }, 67 }, 68 expectedResults: []result{{ 69 pathToField: []string{"Explore"}, 70 expectedValue: []interface{}{ 71 map[string]interface{}{ 72 "beacon": "weaviate://localhost/some-uuid", 73 "className": "bestClass", 74 "certainty": float32(0.7), 75 "distance": helper.CertaintyToDist(t, 0.7), 76 }, 77 }, 78 }}, 79 }, 80 81 testCase{ 82 name: "with nearCustomText with optional limit and distance set", 83 query: ` 84 { 85 Explore( 86 nearCustomText: {concepts: ["car", "best brand"], distance: 0.4}, limit: 17 87 ){ 88 beacon className 89 } 90 }`, 91 expectedParamsToTraverser: traverser.ExploreParams{ 92 ModuleParams: map[string]interface{}{ 93 "nearCustomText": extractNearCustomTextParam(map[string]interface{}{ 94 "concepts": []interface{}{"car", "best brand"}, 95 "distance": float64(0.4), 96 }), 97 }, 98 Limit: 17, 99 }, 100 resolverReturn: []search.Result{ 101 { 102 Beacon: "weaviate://localhost/some-uuid", 103 ClassName: "bestClass", 104 }, 105 }, 106 expectedResults: []result{{ 107 pathToField: []string{"Explore"}, 108 expectedValue: []interface{}{ 109 map[string]interface{}{ 110 "beacon": "weaviate://localhost/some-uuid", 111 "className": "bestClass", 112 }, 113 }, 114 }}, 115 }, 116 117 testCase{ 118 name: "with nearCustomText with optional limit and certainty set", 119 query: ` 120 { 121 Explore( 122 nearCustomText: {concepts: ["car", "best brand"], certainty: 0.6}, limit: 17 123 ){ 124 beacon className 125 } 126 }`, 127 expectedParamsToTraverser: traverser.ExploreParams{ 128 ModuleParams: map[string]interface{}{ 129 "nearCustomText": extractNearCustomTextParam(map[string]interface{}{ 130 "concepts": []interface{}{"car", "best brand"}, 131 "certainty": float64(0.6), 132 }), 133 }, 134 Limit: 17, 135 }, 136 resolverReturn: []search.Result{ 137 { 138 Beacon: "weaviate://localhost/some-uuid", 139 ClassName: "bestClass", 140 }, 141 }, 142 expectedResults: []result{{ 143 pathToField: []string{"Explore"}, 144 expectedValue: []interface{}{ 145 map[string]interface{}{ 146 "beacon": "weaviate://localhost/some-uuid", 147 "className": "bestClass", 148 }, 149 }, 150 }}, 151 }, 152 153 testCase{ 154 name: "with moveTo set", 155 query: ` 156 { 157 Explore( 158 limit: 17 159 nearCustomText: { 160 concepts: ["car", "best brand"] 161 moveTo: { 162 concepts: ["mercedes"] 163 force: 0.7 164 } 165 } 166 ) { 167 beacon className 168 } 169 }`, 170 expectedParamsToTraverser: traverser.ExploreParams{ 171 Limit: 17, 172 ModuleParams: map[string]interface{}{ 173 "nearCustomText": extractNearCustomTextParam(map[string]interface{}{ 174 "concepts": []interface{}{"car", "best brand"}, 175 "moveTo": map[string]interface{}{ 176 "concepts": []interface{}{"mercedes"}, 177 "force": float64(0.7), 178 }, 179 }), 180 }, 181 }, 182 resolverReturn: []search.Result{ 183 { 184 Beacon: "weaviate://localhost/some-uuid", 185 ClassName: "bestClass", 186 }, 187 }, 188 expectedResults: []result{{ 189 pathToField: []string{"Explore"}, 190 expectedValue: []interface{}{ 191 map[string]interface{}{ 192 "beacon": "weaviate://localhost/some-uuid", 193 "className": "bestClass", 194 }, 195 }, 196 }}, 197 }, 198 199 testCase{ 200 name: "with moveTo and moveAwayFrom set", 201 query: ` 202 { 203 Explore( 204 limit: 17 205 nearCustomText: { 206 concepts: ["car", "best brand"] 207 moveTo: { 208 concepts: ["mercedes"] 209 force: 0.7 210 } 211 moveAwayFrom: { 212 concepts: ["van"] 213 force: 0.7 214 } 215 } 216 ) { 217 beacon className 218 } 219 }`, 220 expectedParamsToTraverser: traverser.ExploreParams{ 221 Limit: 17, 222 ModuleParams: map[string]interface{}{ 223 "nearCustomText": extractNearCustomTextParam(map[string]interface{}{ 224 "concepts": []interface{}{"car", "best brand"}, 225 "moveTo": map[string]interface{}{ 226 "concepts": []interface{}{"mercedes"}, 227 "force": float64(0.7), 228 }, 229 "moveAwayFrom": map[string]interface{}{ 230 "concepts": []interface{}{"van"}, 231 "force": float64(0.7), 232 }, 233 }), 234 }, 235 }, 236 resolverReturn: []search.Result{ 237 { 238 Beacon: "weaviate://localhost/some-uuid", 239 ClassName: "bestClass", 240 }, 241 }, 242 expectedResults: []result{{ 243 pathToField: []string{"Explore"}, 244 expectedValue: []interface{}{ 245 map[string]interface{}{ 246 "beacon": "weaviate://localhost/some-uuid", 247 "className": "bestClass", 248 }, 249 }, 250 }}, 251 }, 252 253 testCase{ 254 name: "with moveTo and objects set", 255 query: ` 256 { 257 Explore( 258 limit: 17 259 nearCustomText: { 260 concepts: ["car", "best brand"] 261 moveTo: { 262 concepts: ["mercedes"] 263 force: 0.7 264 objects: [ 265 {id: "moveto-uuid"}, 266 {beacon: "weaviate://localhost/other-moveto-uuid"}, 267 ] 268 } 269 } 270 ) { 271 beacon className 272 } 273 }`, 274 expectedParamsToTraverser: traverser.ExploreParams{ 275 Limit: 17, 276 ModuleParams: map[string]interface{}{ 277 "nearCustomText": extractNearCustomTextParam(map[string]interface{}{ 278 "concepts": []interface{}{"car", "best brand"}, 279 "moveTo": map[string]interface{}{ 280 "concepts": []interface{}{"mercedes"}, 281 "force": float64(0.7), 282 "objects": []interface{}{ 283 map[string]interface{}{ 284 "id": "moveto-uuid", 285 }, 286 map[string]interface{}{ 287 "beacon": "weaviate://localhost/other-moveto-uuid", 288 }, 289 }, 290 }, 291 }), 292 }, 293 }, 294 resolverReturn: []search.Result{ 295 { 296 Beacon: "weaviate://localhost/some-uuid", 297 ClassName: "bestClass", 298 }, 299 }, 300 expectedResults: []result{{ 301 pathToField: []string{"Explore"}, 302 expectedValue: []interface{}{ 303 map[string]interface{}{ 304 "beacon": "weaviate://localhost/some-uuid", 305 "className": "bestClass", 306 }, 307 }, 308 }}, 309 }, 310 311 testCase{ 312 name: "with moveTo and objects set", 313 query: ` 314 { 315 Explore( 316 limit: 17 317 nearCustomText: { 318 concepts: ["car", "best brand"] 319 moveTo: { 320 concepts: ["mercedes"] 321 force: 0.7 322 objects: [ 323 {id: "moveto-uuid1"}, 324 {beacon: "weaviate://localhost/moveto-uuid2"}, 325 ] 326 } 327 moveAwayFrom: { 328 concepts: ["van"] 329 force: 0.7 330 objects: [ 331 {id: "moveAway-uuid1"}, 332 {beacon: "weaviate://localhost/moveAway-uuid2"}, 333 {id: "moveAway-uuid3"}, 334 {id: "moveAway-uuid4"}, 335 ] 336 } 337 } 338 ) { 339 beacon className 340 } 341 }`, 342 expectedParamsToTraverser: traverser.ExploreParams{ 343 Limit: 17, 344 ModuleParams: map[string]interface{}{ 345 "nearCustomText": extractNearCustomTextParam(map[string]interface{}{ 346 "concepts": []interface{}{"car", "best brand"}, 347 "moveTo": map[string]interface{}{ 348 "concepts": []interface{}{"mercedes"}, 349 "force": float64(0.7), 350 "objects": []interface{}{ 351 map[string]interface{}{ 352 "id": "moveto-uuid1", 353 }, 354 map[string]interface{}{ 355 "beacon": "weaviate://localhost/moveto-uuid2", 356 }, 357 }, 358 }, 359 "moveAwayFrom": map[string]interface{}{ 360 "concepts": []interface{}{"van"}, 361 "force": float64(0.7), 362 "objects": []interface{}{ 363 map[string]interface{}{ 364 "id": "moveAway-uuid1", 365 }, 366 map[string]interface{}{ 367 "beacon": "weaviate://localhost/moveAway-uuid2", 368 }, 369 map[string]interface{}{ 370 "id": "moveAway-uuid3", 371 }, 372 map[string]interface{}{ 373 "id": "moveAway-uuid4", 374 }, 375 }, 376 }, 377 }), 378 }, 379 }, 380 resolverReturn: []search.Result{ 381 { 382 Beacon: "weaviate://localhost/some-uuid", 383 ClassName: "bestClass", 384 }, 385 }, 386 expectedResults: []result{{ 387 pathToField: []string{"Explore"}, 388 expectedValue: []interface{}{ 389 map[string]interface{}{ 390 "beacon": "weaviate://localhost/some-uuid", 391 "className": "bestClass", 392 }, 393 }, 394 }}, 395 }, 396 } 397 398 tests := testCases{ 399 testCase{ 400 name: "Resolve Explore with nearVector", 401 query: ` 402 { 403 Explore(nearVector: {vector: [0, 1, 0.8]}) { 404 beacon className certainty distance 405 } 406 }`, 407 expectedParamsToTraverser: traverser.ExploreParams{ 408 NearVector: &searchparams.NearVector{ 409 Vector: []float32{0, 1, 0.8}, 410 }, 411 WithCertaintyProp: true, 412 }, 413 resolverReturn: []search.Result{ 414 { 415 Beacon: "weaviate://localhost/some-uuid", 416 ClassName: "bestClass", 417 Certainty: 0.7, 418 Dist: helper.CertaintyToDist(t, 0.7), 419 }, 420 }, 421 expectedResults: []result{{ 422 pathToField: []string{"Explore"}, 423 expectedValue: []interface{}{ 424 map[string]interface{}{ 425 "beacon": "weaviate://localhost/some-uuid", 426 "className": "bestClass", 427 "certainty": float32(0.7), 428 "distance": helper.CertaintyToDist(t, 0.7), 429 }, 430 }, 431 }}, 432 }, 433 434 testCase{ 435 name: "with nearVector with optional limit", 436 query: ` 437 { 438 Explore(limit: 17, nearVector: {vector: [0, 1, 0.8]}) { 439 beacon className certainty distance 440 } 441 }`, 442 expectedParamsToTraverser: traverser.ExploreParams{ 443 NearVector: &searchparams.NearVector{ 444 Vector: []float32{0, 1, 0.8}, 445 }, 446 Limit: 17, 447 WithCertaintyProp: true, 448 }, 449 resolverReturn: []search.Result{ 450 { 451 Beacon: "weaviate://localhost/some-uuid", 452 ClassName: "bestClass", 453 Certainty: 0.7, 454 Dist: helper.CertaintyToDist(t, 0.7), 455 }, 456 }, 457 expectedResults: []result{{ 458 pathToField: []string{"Explore"}, 459 expectedValue: []interface{}{ 460 map[string]interface{}{ 461 "beacon": "weaviate://localhost/some-uuid", 462 "className": "bestClass", 463 "certainty": float32(0.7), 464 "distance": helper.CertaintyToDist(t, 0.7), 465 }, 466 }, 467 }}, 468 }, 469 470 testCase{ 471 name: "Resolve Explore with nearObject, distance, and beacon set", 472 query: ` 473 { 474 Explore( 475 nearObject: { 476 beacon: "weaviate://localhost/27b5213d-e152-4fea-bd63-2063d529024d" 477 distance: 0.3 478 }) { 479 beacon className certainty distance 480 } 481 }`, 482 expectedParamsToTraverser: traverser.ExploreParams{ 483 NearObject: &searchparams.NearObject{ 484 Beacon: "weaviate://localhost/27b5213d-e152-4fea-bd63-2063d529024d", 485 Distance: float64(0.3), 486 WithDistance: true, 487 }, 488 WithCertaintyProp: true, 489 }, 490 resolverReturn: []search.Result{ 491 { 492 Beacon: "weaviate://localhost/27b5213d-e152-4fea-bd63-2063d529024d", 493 ClassName: "bestClass", 494 Certainty: 0.7, 495 Dist: helper.CertaintyToDist(t, 0.7), 496 }, 497 }, 498 expectedResults: []result{{ 499 pathToField: []string{"Explore"}, 500 expectedValue: []interface{}{ 501 map[string]interface{}{ 502 "beacon": "weaviate://localhost/27b5213d-e152-4fea-bd63-2063d529024d", 503 "className": "bestClass", 504 "certainty": float32(0.7), 505 "distance": helper.CertaintyToDist(t, 0.7), 506 }, 507 }, 508 }}, 509 }, 510 511 testCase{ 512 name: "Resolve Explore with nearObject, certainty, and beacon set", 513 query: ` 514 { 515 Explore( 516 nearObject: { 517 beacon: "weaviate://localhost/27b5213d-e152-4fea-bd63-2063d529024d" 518 certainty: 0.7 519 }) { 520 beacon className certainty distance 521 } 522 }`, 523 expectedParamsToTraverser: traverser.ExploreParams{ 524 NearObject: &searchparams.NearObject{ 525 Beacon: "weaviate://localhost/27b5213d-e152-4fea-bd63-2063d529024d", 526 Certainty: 0.7, 527 }, 528 WithCertaintyProp: true, 529 }, 530 resolverReturn: []search.Result{ 531 { 532 Beacon: "weaviate://localhost/27b5213d-e152-4fea-bd63-2063d529024d", 533 ClassName: "bestClass", 534 Certainty: 0.7, 535 Dist: helper.CertaintyToDist(t, 0.7), 536 }, 537 }, 538 expectedResults: []result{{ 539 pathToField: []string{"Explore"}, 540 expectedValue: []interface{}{ 541 map[string]interface{}{ 542 "beacon": "weaviate://localhost/27b5213d-e152-4fea-bd63-2063d529024d", 543 "className": "bestClass", 544 "certainty": float32(0.7), 545 "distance": helper.CertaintyToDist(t, 0.7), 546 }, 547 }, 548 }}, 549 }, 550 551 testCase{ 552 name: "Resolve Explore with nearObject, distance and id set", 553 query: ` 554 { 555 Explore( 556 limit: 17 557 nearObject: { 558 id: "27b5213d-e152-4fea-bd63-2063d529024d" 559 distance: 0.3 560 } 561 ) { 562 beacon className 563 } 564 }`, 565 expectedParamsToTraverser: traverser.ExploreParams{ 566 Limit: 17, 567 NearObject: &searchparams.NearObject{ 568 ID: "27b5213d-e152-4fea-bd63-2063d529024d", 569 Distance: 0.3, 570 WithDistance: true, 571 }, 572 }, 573 resolverReturn: []search.Result{ 574 { 575 Beacon: "weaviate://localhost/some-uuid", 576 ClassName: "bestClass", 577 }, 578 }, 579 expectedResults: []result{{ 580 pathToField: []string{"Explore"}, 581 expectedValue: []interface{}{ 582 map[string]interface{}{ 583 "beacon": "weaviate://localhost/some-uuid", 584 "className": "bestClass", 585 }, 586 }, 587 }}, 588 }, 589 590 testCase{ 591 name: "Resolve Explore with nearObject, certainty and id set", 592 query: ` 593 { 594 Explore( 595 limit: 17 596 nearObject: { 597 id: "27b5213d-e152-4fea-bd63-2063d529024d" 598 certainty: 0.7 599 } 600 ) { 601 beacon className 602 } 603 }`, 604 expectedParamsToTraverser: traverser.ExploreParams{ 605 Limit: 17, 606 NearObject: &searchparams.NearObject{ 607 ID: "27b5213d-e152-4fea-bd63-2063d529024d", 608 Certainty: 0.7, 609 }, 610 }, 611 resolverReturn: []search.Result{ 612 { 613 Beacon: "weaviate://localhost/some-uuid", 614 ClassName: "bestClass", 615 }, 616 }, 617 expectedResults: []result{{ 618 pathToField: []string{"Explore"}, 619 expectedValue: []interface{}{ 620 map[string]interface{}{ 621 "beacon": "weaviate://localhost/some-uuid", 622 "className": "bestClass", 623 }, 624 }, 625 }}, 626 }, 627 } 628 629 tests.AssertExtraction(t, newMockResolver()) 630 testsNearText.AssertExtraction(t, newMockResolver()) 631 tests.AssertExtraction(t, newMockResolverNoModules()) 632 } 633 634 func Test_ExploreWithNoText2VecClasses(t *testing.T) { 635 t.Run("with distance", func(t *testing.T) { 636 resolver := newMockResolverEmptySchema() 637 query := ` 638 { 639 Explore( 640 nearCustomText: {concepts: ["car", "best brand"], distance: 0.6}, limit: 17 641 ){ 642 beacon className 643 } 644 }` 645 res := resolver.Resolve(query) 646 require.Len(t, res.Errors, 1) 647 assert.Contains(t, res.Errors[0].Message, "Unknown argument \"nearCustomText\" on field \"Explore\"") 648 }) 649 650 t.Run("with certainty", func(t *testing.T) { 651 resolver := newMockResolverEmptySchema() 652 query := ` 653 { 654 Explore( 655 nearCustomText: {concepts: ["car", "best brand"], certainty: 0.6}, limit: 17 656 ){ 657 beacon className 658 } 659 }` 660 res := resolver.Resolve(query) 661 require.Len(t, res.Errors, 1) 662 assert.Contains(t, res.Errors[0].Message, "Unknown argument \"nearCustomText\" on field \"Explore\"") 663 }) 664 } 665 666 func Test_ExploreWithNoModules(t *testing.T) { 667 t.Run("with distance", func(t *testing.T) { 668 resolver := newMockResolverNoModules() 669 query := ` 670 { 671 Explore( 672 nearCustomText: {concepts: ["car", "best brand"], distance: 0.6}, limit: 17 673 ){ 674 beacon className 675 } 676 }` 677 res := resolver.Resolve(query) 678 require.Len(t, res.Errors, 1) 679 assert.Contains(t, res.Errors[0].Message, "Unknown argument \"nearCustomText\" on field \"Explore\"") 680 }) 681 682 t.Run("with certainty", func(t *testing.T) { 683 resolver := newMockResolverNoModules() 684 query := ` 685 { 686 Explore( 687 nearCustomText: {concepts: ["car", "best brand"], certainty: 0.6}, limit: 17 688 ){ 689 beacon className 690 } 691 }` 692 res := resolver.Resolve(query) 693 require.Len(t, res.Errors, 1) 694 assert.Contains(t, res.Errors[0].Message, "Unknown argument \"nearCustomText\" on field \"Explore\"") 695 }) 696 } 697 698 func (tests testCases) AssertExtraction(t *testing.T, resolver *mockResolver) { 699 for _, testCase := range tests { 700 t.Run(testCase.name, func(t *testing.T) { 701 resolver.On("Explore", testCase.expectedParamsToTraverser). 702 Return(testCase.resolverReturn, nil).Once() 703 704 result := resolver.AssertResolve(t, testCase.query) 705 706 for _, expectedResult := range testCase.expectedResults { 707 value := result.Get(expectedResult.pathToField...).Result 708 709 assert.Equal(t, expectedResult.expectedValue, value) 710 } 711 }) 712 } 713 }