github.com/weaviate/weaviate@v1.24.6/usecases/traverser/explorer_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 traverser 13 14 import ( 15 "context" 16 "testing" 17 "time" 18 19 "github.com/go-openapi/strfmt" 20 "github.com/pkg/errors" 21 "github.com/sirupsen/logrus/hooks/test" 22 "github.com/stretchr/testify/assert" 23 "github.com/stretchr/testify/require" 24 "github.com/weaviate/weaviate/entities/additional" 25 "github.com/weaviate/weaviate/entities/dto" 26 "github.com/weaviate/weaviate/entities/filters" 27 "github.com/weaviate/weaviate/entities/models" 28 "github.com/weaviate/weaviate/entities/modulecapabilities" 29 "github.com/weaviate/weaviate/entities/schema" 30 "github.com/weaviate/weaviate/entities/search" 31 "github.com/weaviate/weaviate/entities/searchparams" 32 "github.com/weaviate/weaviate/entities/vectorindex/hnsw" 33 "github.com/weaviate/weaviate/usecases/config" 34 ) 35 36 var defaultConfig = config.Config{ 37 QueryDefaults: config.QueryDefaults{ 38 Limit: 100, 39 }, 40 QueryMaximumResults: 100, 41 } 42 43 func Test_Explorer_GetClass(t *testing.T) { 44 t.Run("when an explore param is set for nearVector", func(t *testing.T) { 45 // TODO: this is a module specific test case, which relies on the 46 // text2vec-contextionary module 47 params := dto.GetParams{ 48 ClassName: "BestClass", 49 NearVector: &searchparams.NearVector{ 50 Vector: []float32{0.8, 0.2, 0.7}, 51 }, 52 Pagination: &filters.Pagination{Limit: 100}, 53 Filters: nil, 54 } 55 56 searchResults := []search.Result{ 57 { 58 ID: "id1", 59 Schema: map[string]interface{}{ 60 "name": "Foo", 61 }, 62 Dims: 128, 63 }, 64 { 65 ID: "id2", 66 Schema: map[string]interface{}{ 67 "age": 200, 68 }, 69 Dims: 128, 70 }, 71 } 72 73 search := &fakeVectorSearcher{} 74 metrics := &fakeMetrics{} 75 log, _ := test.NewNullLogger() 76 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 77 explorer.SetSchemaGetter(&fakeSchemaGetter{ 78 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 79 {Class: "BestClass"}, 80 }}}, 81 }) 82 expectedParamsToSearch := params 83 expectedParamsToSearch.SearchVector = []float32{0.8, 0.2, 0.7} 84 search. 85 On("VectorSearch", expectedParamsToSearch). 86 Return(searchResults, nil) 87 88 metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearVector", 128) 89 res, err := explorer.GetClass(context.Background(), params) 90 91 t.Run("vector search must be called with right params", func(t *testing.T) { 92 assert.Nil(t, err) 93 search.AssertExpectations(t) 94 }) 95 96 t.Run("response must contain concepts", func(t *testing.T) { 97 require.Len(t, res, 2) 98 assert.Equal(t, 99 map[string]interface{}{ 100 "name": "Foo", 101 }, res[0]) 102 assert.Equal(t, 103 map[string]interface{}{ 104 "age": 200, 105 }, res[1]) 106 }) 107 108 t.Run("usage must be tracked", func(t *testing.T) { 109 metrics.AssertExpectations(t) 110 }) 111 }) 112 113 t.Run("when an explore param is set for nearObject without id and beacon", func(t *testing.T) { 114 t.Run("with distance", func(t *testing.T) { 115 // TODO: this is a module specific test case, which relies on the 116 // text2vec-contextionary module 117 params := dto.GetParams{ 118 ClassName: "BestClass", 119 NearObject: &searchparams.NearObject{ 120 Distance: 0.1, 121 }, 122 Pagination: &filters.Pagination{Limit: 100}, 123 Filters: nil, 124 } 125 126 search := &fakeVectorSearcher{} 127 log, _ := test.NewNullLogger() 128 metrics := &fakeMetrics{} 129 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 130 explorer.SetSchemaGetter(&fakeSchemaGetter{ 131 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 132 {Class: "BestClass"}, 133 }}}, 134 }) 135 136 res, err := explorer.GetClass(context.Background(), params) 137 138 t.Run("vector search must be called with right params", func(t *testing.T) { 139 assert.NotNil(t, err) 140 assert.Nil(t, res) 141 assert.Contains(t, err.Error(), "explorer: get class: vectorize params: nearObject params: empty id and beacon") 142 }) 143 }) 144 145 t.Run("with certainty", func(t *testing.T) { 146 // TODO: this is a module specific test case, which relies on the 147 // text2vec-contextionary module 148 params := dto.GetParams{ 149 ClassName: "BestClass", 150 NearObject: &searchparams.NearObject{ 151 Certainty: 0.9, 152 }, 153 Pagination: &filters.Pagination{Limit: 100}, 154 Filters: nil, 155 } 156 157 search := &fakeVectorSearcher{} 158 log, _ := test.NewNullLogger() 159 metrics := &fakeMetrics{} 160 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 161 explorer.SetSchemaGetter(&fakeSchemaGetter{ 162 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 163 {Class: "BestClass"}, 164 }}}, 165 }) 166 167 res, err := explorer.GetClass(context.Background(), params) 168 169 t.Run("vector search must be called with right params", func(t *testing.T) { 170 assert.NotNil(t, err) 171 assert.Nil(t, res) 172 assert.Contains(t, err.Error(), "explorer: get class: vectorize params: nearObject params: empty id and beacon") 173 }) 174 }) 175 }) 176 177 t.Run("when an explore param is set for nearObject with beacon", func(t *testing.T) { 178 t.Run("with distance", func(t *testing.T) { 179 t.Run("with certainty", func(t *testing.T) { 180 // TODO: this is a module specific test case, which relies on the 181 // text2vec-contextionary module 182 params := dto.GetParams{ 183 ClassName: "BestClass", 184 NearObject: &searchparams.NearObject{ 185 Beacon: "weaviate://localhost/e9c12c22-766f-4bde-b140-d4cf8fd6e041", 186 Distance: 0.1, 187 }, 188 Pagination: &filters.Pagination{Limit: 100}, 189 Filters: nil, 190 } 191 192 searchRes := search.Result{ 193 ID: "e9c12c22-766f-4bde-b140-d4cf8fd6e041", 194 Schema: map[string]interface{}{ 195 "name": "Foo", 196 }, 197 } 198 199 searchResults := []search.Result{ 200 { 201 ID: "id1", 202 Schema: map[string]interface{}{ 203 "name": "Foo", 204 }, 205 Dims: 128, 206 }, 207 { 208 ID: "id2", 209 Schema: map[string]interface{}{ 210 "age": 200, 211 }, 212 Dims: 128, 213 }, 214 } 215 216 search := &fakeVectorSearcher{} 217 log, _ := test.NewNullLogger() 218 metrics := &fakeMetrics{} 219 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 220 explorer.SetSchemaGetter(&fakeSchemaGetter{ 221 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 222 {Class: "BestClass"}, 223 }}}, 224 }) 225 expectedParamsToSearch := params 226 search. 227 On("Object", "BestClass", strfmt.UUID("e9c12c22-766f-4bde-b140-d4cf8fd6e041")). 228 Return(&searchRes, nil) 229 search. 230 On("VectorSearch", expectedParamsToSearch). 231 Return(searchResults, nil) 232 metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearObject", 128) 233 234 res, err := explorer.GetClass(context.Background(), params) 235 236 t.Run("vector search must be called with right params", func(t *testing.T) { 237 assert.Nil(t, err) 238 search.AssertExpectations(t) 239 }) 240 241 t.Run("response must contain object", func(t *testing.T) { 242 require.Len(t, res, 2) 243 assert.Equal(t, 244 map[string]interface{}{ 245 "name": "Foo", 246 }, res[0]) 247 assert.Equal(t, 248 map[string]interface{}{ 249 "age": 200, 250 }, res[1]) 251 }) 252 }) 253 }) 254 255 t.Run("with certainty", func(t *testing.T) { 256 // TODO: this is a module specific test case, which relies on the 257 // text2vec-contextionary module 258 params := dto.GetParams{ 259 ClassName: "BestClass", 260 NearObject: &searchparams.NearObject{ 261 Beacon: "weaviate://localhost/e9c12c22-766f-4bde-b140-d4cf8fd6e041", 262 Certainty: 0.9, 263 }, 264 Pagination: &filters.Pagination{Limit: 100}, 265 Filters: nil, 266 } 267 268 searchRes := search.Result{ 269 ID: "e9c12c22-766f-4bde-b140-d4cf8fd6e041", 270 Schema: map[string]interface{}{ 271 "name": "Foo", 272 }, 273 } 274 275 searchResults := []search.Result{ 276 { 277 ID: "id1", 278 Schema: map[string]interface{}{ 279 "name": "Foo", 280 }, 281 Dims: 128, 282 }, 283 { 284 ID: "id2", 285 Schema: map[string]interface{}{ 286 "age": 200, 287 }, 288 Dims: 128, 289 }, 290 } 291 292 search := &fakeVectorSearcher{} 293 log, _ := test.NewNullLogger() 294 metrics := &fakeMetrics{} 295 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 296 explorer.SetSchemaGetter(&fakeSchemaGetter{ 297 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 298 {Class: "BestClass"}, 299 }}}, 300 }) 301 expectedParamsToSearch := params 302 search. 303 On("Object", "BestClass", strfmt.UUID("e9c12c22-766f-4bde-b140-d4cf8fd6e041")). 304 Return(&searchRes, nil) 305 search. 306 On("VectorSearch", expectedParamsToSearch). 307 Return(searchResults, nil) 308 metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearObject", 128) 309 310 res, err := explorer.GetClass(context.Background(), params) 311 312 t.Run("vector search must be called with right params", func(t *testing.T) { 313 assert.Nil(t, err) 314 search.AssertExpectations(t) 315 }) 316 317 t.Run("response must contain object", func(t *testing.T) { 318 require.Len(t, res, 2) 319 assert.Equal(t, 320 map[string]interface{}{ 321 "name": "Foo", 322 }, res[0]) 323 assert.Equal(t, 324 map[string]interface{}{ 325 "age": 200, 326 }, res[1]) 327 }) 328 }) 329 }) 330 331 t.Run("when an explore param is set for nearObject with id", func(t *testing.T) { 332 t.Run("with distance", func(t *testing.T) { 333 // TODO: this is a module specific test case, which relies on the 334 // text2vec-contextionary module 335 params := dto.GetParams{ 336 ClassName: "BestClass", 337 NearObject: &searchparams.NearObject{ 338 ID: "e9c12c22-766f-4bde-b140-d4cf8fd6e041", 339 Distance: 0.1, 340 }, 341 Pagination: &filters.Pagination{Limit: 100}, 342 Filters: nil, 343 } 344 345 searchRes := search.Result{ 346 ID: "e9c12c22-766f-4bde-b140-d4cf8fd6e041", 347 Schema: map[string]interface{}{ 348 "name": "Foo", 349 }, 350 } 351 352 searchResults := []search.Result{ 353 { 354 ID: "id1", 355 Schema: map[string]interface{}{ 356 "name": "Foo", 357 }, 358 Dims: 128, 359 }, 360 { 361 ID: "id2", 362 Schema: map[string]interface{}{ 363 "age": 200, 364 }, 365 Dims: 128, 366 }, 367 } 368 369 search := &fakeVectorSearcher{} 370 log, _ := test.NewNullLogger() 371 metrics := &fakeMetrics{} 372 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 373 explorer.SetSchemaGetter(&fakeSchemaGetter{ 374 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 375 {Class: "BestClass"}, 376 }}}, 377 }) 378 expectedParamsToSearch := params 379 search. 380 On("Object", "BestClass", strfmt.UUID("e9c12c22-766f-4bde-b140-d4cf8fd6e041")). 381 Return(&searchRes, nil) 382 search. 383 On("VectorSearch", expectedParamsToSearch). 384 Return(searchResults, nil) 385 metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearObject", 128) 386 387 res, err := explorer.GetClass(context.Background(), params) 388 389 t.Run("vector search must be called with right params", func(t *testing.T) { 390 assert.Nil(t, err) 391 search.AssertExpectations(t) 392 }) 393 394 t.Run("response must contain object", func(t *testing.T) { 395 require.Len(t, res, 2) 396 assert.Equal(t, 397 map[string]interface{}{ 398 "name": "Foo", 399 }, res[0]) 400 assert.Equal(t, 401 map[string]interface{}{ 402 "age": 200, 403 }, res[1]) 404 }) 405 }) 406 407 t.Run("with certainty", func(t *testing.T) { 408 // TODO: this is a module specific test case, which relies on the 409 // text2vec-contextionary module 410 params := dto.GetParams{ 411 ClassName: "BestClass", 412 NearObject: &searchparams.NearObject{ 413 ID: "e9c12c22-766f-4bde-b140-d4cf8fd6e041", 414 Certainty: 0.9, 415 }, 416 Pagination: &filters.Pagination{Limit: 100}, 417 Filters: nil, 418 } 419 420 searchRes := search.Result{ 421 ID: "e9c12c22-766f-4bde-b140-d4cf8fd6e041", 422 Schema: map[string]interface{}{ 423 "name": "Foo", 424 }, 425 } 426 427 searchResults := []search.Result{ 428 { 429 ID: "id1", 430 Schema: map[string]interface{}{ 431 "name": "Foo", 432 }, 433 Dims: 128, 434 }, 435 { 436 ID: "id2", 437 Schema: map[string]interface{}{ 438 "age": 200, 439 }, 440 Dims: 128, 441 }, 442 } 443 444 search := &fakeVectorSearcher{} 445 metrics := &fakeMetrics{} 446 log, _ := test.NewNullLogger() 447 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 448 explorer.SetSchemaGetter(&fakeSchemaGetter{ 449 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 450 {Class: "BestClass"}, 451 }}}, 452 }) 453 expectedParamsToSearch := params 454 search. 455 On("Object", "BestClass", strfmt.UUID("e9c12c22-766f-4bde-b140-d4cf8fd6e041")). 456 Return(&searchRes, nil) 457 search. 458 On("VectorSearch", expectedParamsToSearch). 459 Return(searchResults, nil) 460 metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearObject", 128) 461 462 res, err := explorer.GetClass(context.Background(), params) 463 464 t.Run("vector search must be called with right params", func(t *testing.T) { 465 assert.Nil(t, err) 466 search.AssertExpectations(t) 467 }) 468 469 t.Run("response must contain object", func(t *testing.T) { 470 require.Len(t, res, 2) 471 assert.Equal(t, 472 map[string]interface{}{ 473 "name": "Foo", 474 }, res[0]) 475 assert.Equal(t, 476 map[string]interface{}{ 477 "age": 200, 478 }, res[1]) 479 }) 480 }) 481 }) 482 483 t.Run("when an explore param is set for nearVector and the required distance not met", 484 func(t *testing.T) { 485 t.Run("with distance", func(t *testing.T) { 486 params := dto.GetParams{ 487 ClassName: "BestClass", 488 NearVector: &searchparams.NearVector{ 489 Vector: []float32{0.8, 0.2, 0.7}, 490 Distance: 0.4, 491 WithDistance: true, 492 }, 493 Pagination: &filters.Pagination{Limit: 100}, 494 Filters: nil, 495 } 496 497 searchResults := []search.Result{ 498 { 499 ID: "id1", 500 Dist: 2 * 0.69, 501 Dims: 128, 502 }, 503 { 504 ID: "id2", 505 Dist: 2 * 0.69, 506 Dims: 128, 507 }, 508 } 509 510 search := &fakeVectorSearcher{} 511 log, _ := test.NewNullLogger() 512 metrics := &fakeMetrics{} 513 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 514 explorer.SetSchemaGetter(&fakeSchemaGetter{ 515 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 516 {Class: "BestClass"}, 517 }}}, 518 }) 519 expectedParamsToSearch := params 520 expectedParamsToSearch.SearchVector = []float32{0.8, 0.2, 0.7} 521 search. 522 On("VectorSearch", expectedParamsToSearch). 523 Return(searchResults, nil) 524 metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearVector", 128) 525 526 res, err := explorer.GetClass(context.Background(), params) 527 528 t.Run("vector search must be called with right params", func(t *testing.T) { 529 assert.Nil(t, err) 530 search.AssertExpectations(t) 531 }) 532 533 t.Run("no concept met the required certainty", func(t *testing.T) { 534 assert.Len(t, res, 0) 535 }) 536 }) 537 538 t.Run("with certainty", func(t *testing.T) { 539 params := dto.GetParams{ 540 ClassName: "BestClass", 541 NearVector: &searchparams.NearVector{ 542 Vector: []float32{0.8, 0.2, 0.7}, 543 Certainty: 0.8, 544 }, 545 Pagination: &filters.Pagination{Limit: 100}, 546 Filters: nil, 547 } 548 549 searchResults := []search.Result{ 550 { 551 ID: "id1", 552 Dist: 2 * 0.69, 553 Dims: 128, 554 }, 555 { 556 ID: "id2", 557 Dist: 2 * 0.69, 558 Dims: 128, 559 }, 560 } 561 562 search := &fakeVectorSearcher{} 563 log, _ := test.NewNullLogger() 564 metrics := &fakeMetrics{} 565 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 566 explorer.SetSchemaGetter(&fakeSchemaGetter{ 567 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 568 {Class: "BestClass"}, 569 }}}, 570 }) 571 expectedParamsToSearch := params 572 expectedParamsToSearch.SearchVector = []float32{0.8, 0.2, 0.7} 573 search. 574 On("VectorSearch", expectedParamsToSearch). 575 Return(searchResults, nil) 576 metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearVector", 128) 577 578 res, err := explorer.GetClass(context.Background(), params) 579 580 t.Run("vector search must be called with right params", func(t *testing.T) { 581 assert.Nil(t, err) 582 search.AssertExpectations(t) 583 }) 584 585 t.Run("no concept met the required certainty", func(t *testing.T) { 586 assert.Len(t, res, 0) 587 }) 588 }) 589 }) 590 591 t.Run("when two conflicting (nearVector, nearObject) near searchers are set", func(t *testing.T) { 592 params := dto.GetParams{ 593 ClassName: "BestClass", 594 Pagination: &filters.Pagination{Limit: 100}, 595 Filters: nil, 596 NearVector: &searchparams.NearVector{ 597 Vector: []float32{0.8, 0.2, 0.7}, 598 }, 599 NearObject: &searchparams.NearObject{ 600 Beacon: "weaviate://localhost/e9c12c22-766f-4bde-b140-d4cf8fd6e041", 601 }, 602 } 603 604 search := &fakeVectorSearcher{} 605 log, _ := test.NewNullLogger() 606 metrics := &fakeMetrics{} 607 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 608 explorer.SetSchemaGetter(&fakeSchemaGetter{ 609 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 610 {Class: "BestClass"}, 611 }}}, 612 }) 613 _, err := explorer.GetClass(context.Background(), params) 614 require.NotNil(t, err) 615 assert.Contains(t, err.Error(), "parameters which are conflicting") 616 }) 617 618 t.Run("when no explore param is set", func(t *testing.T) { 619 params := dto.GetParams{ 620 ClassName: "BestClass", 621 Pagination: &filters.Pagination{Limit: 100}, 622 Filters: nil, 623 } 624 625 searchResults := []search.Result{ 626 { 627 ID: "id1", 628 Schema: map[string]interface{}{ 629 "name": "Foo", 630 }, 631 }, 632 { 633 ID: "id2", 634 Schema: map[string]interface{}{ 635 "age": 200, 636 }, 637 }, 638 } 639 640 search := &fakeVectorSearcher{} 641 log, _ := test.NewNullLogger() 642 metrics := &fakeMetrics{} 643 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 644 explorer.SetSchemaGetter(&fakeSchemaGetter{ 645 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 646 {Class: "BestClass"}, 647 }}}, 648 }) 649 expectedParamsToSearch := params 650 expectedParamsToSearch.SearchVector = nil 651 search. 652 On("Search", expectedParamsToSearch). 653 Return(searchResults, nil) 654 655 res, err := explorer.GetClass(context.Background(), params) 656 657 t.Run("class search must be called with right params", func(t *testing.T) { 658 assert.Nil(t, err) 659 search.AssertExpectations(t) 660 }) 661 662 t.Run("response must contain concepts", func(t *testing.T) { 663 require.Len(t, res, 2) 664 assert.Equal(t, 665 map[string]interface{}{ 666 "name": "Foo", 667 }, res[0]) 668 assert.Equal(t, 669 map[string]interface{}{ 670 "age": 200, 671 }, res[1]) 672 }) 673 }) 674 675 t.Run("near vector with group", func(t *testing.T) { 676 params := dto.GetParams{ 677 ClassName: "BestClass", 678 Pagination: &filters.Pagination{Limit: 100}, 679 Filters: nil, 680 NearVector: &searchparams.NearVector{ 681 Vector: []float32{0.8, 0.2, 0.7}, 682 }, 683 Group: &dto.GroupParams{ 684 Strategy: "closest", 685 Force: 1.0, 686 }, 687 } 688 689 searchResults := []search.Result{ 690 { 691 ID: "id1", 692 Schema: map[string]interface{}{ 693 "name": "Foo", 694 }, 695 Dims: 128, 696 }, 697 } 698 699 search := &fakeVectorSearcher{} 700 log, _ := test.NewNullLogger() 701 metrics := &fakeMetrics{} 702 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 703 explorer.SetSchemaGetter(&fakeSchemaGetter{ 704 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 705 {Class: "BestClass"}, 706 }}}, 707 }) 708 expectedParamsToSearch := params 709 expectedParamsToSearch.SearchVector = []float32{0.8, 0.2, 0.7} 710 expectedParamsToSearch.AdditionalProperties.Vector = true 711 search. 712 On("VectorSearch", expectedParamsToSearch). 713 Return(searchResults, nil) 714 metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearVector", 128) 715 716 res, err := explorer.GetClass(context.Background(), params) 717 718 t.Run("class search must be called with right params", func(t *testing.T) { 719 assert.Nil(t, err) 720 search.AssertExpectations(t) 721 }) 722 723 t.Run("response must contain concepts", func(t *testing.T) { 724 require.Len(t, res, 1) 725 }) 726 }) 727 728 t.Run("when the semanticPath prop is set but cannot be", func(t *testing.T) { 729 params := dto.GetParams{ 730 ClassName: "BestClass", 731 Pagination: &filters.Pagination{Limit: 100}, 732 Filters: nil, 733 AdditionalProperties: additional.Properties{ 734 ModuleParams: map[string]interface{}{ 735 "semanticPath": getDefaultParam("semanticPath"), 736 }, 737 }, 738 } 739 740 searchResults := []search.Result{ 741 { 742 ID: "id1", 743 Schema: map[string]interface{}{ 744 "name": "Foo", 745 }, 746 }, 747 { 748 ID: "id2", 749 Schema: map[string]interface{}{ 750 "age": 200, 751 }, 752 }, 753 } 754 755 search := &fakeVectorSearcher{} 756 log, _ := test.NewNullLogger() 757 metrics := &fakeMetrics{} 758 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 759 explorer.SetSchemaGetter(&fakeSchemaGetter{ 760 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 761 {Class: "BestClass"}, 762 }}}, 763 }) 764 expectedParamsToSearch := params 765 expectedParamsToSearch.SearchVector = nil 766 search. 767 On("Search", expectedParamsToSearch). 768 Return(searchResults, nil) 769 770 res, err := explorer.GetClass(context.Background(), params) 771 772 t.Run("error can't be nil", func(t *testing.T) { 773 assert.NotNil(t, err) 774 assert.Nil(t, res) 775 assert.Contains(t, err.Error(), "unknown capability: semanticPath") 776 }) 777 }) 778 779 t.Run("when the classification prop is set", func(t *testing.T) { 780 params := dto.GetParams{ 781 ClassName: "BestClass", 782 Pagination: &filters.Pagination{Limit: 100}, 783 Filters: nil, 784 AdditionalProperties: additional.Properties{ 785 Classification: true, 786 }, 787 } 788 789 searchResults := []search.Result{ 790 { 791 ID: "id1", 792 Schema: map[string]interface{}{ 793 "name": "Foo", 794 }, 795 AdditionalProperties: models.AdditionalProperties{ 796 "classification": nil, 797 }, 798 }, 799 { 800 ID: "id2", 801 Schema: map[string]interface{}{ 802 "age": 200, 803 }, 804 AdditionalProperties: models.AdditionalProperties{ 805 "classification": &additional.Classification{ 806 ID: "1234", 807 }, 808 }, 809 }, 810 } 811 812 search := &fakeVectorSearcher{} 813 log, _ := test.NewNullLogger() 814 metrics := &fakeMetrics{} 815 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 816 explorer.SetSchemaGetter(&fakeSchemaGetter{ 817 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 818 {Class: "BestClass"}, 819 }}}, 820 }) 821 expectedParamsToSearch := params 822 expectedParamsToSearch.SearchVector = nil 823 search. 824 On("Search", expectedParamsToSearch). 825 Return(searchResults, nil) 826 827 res, err := explorer.GetClass(context.Background(), params) 828 829 t.Run("class search must be called with right params", func(t *testing.T) { 830 assert.Nil(t, err) 831 search.AssertExpectations(t) 832 }) 833 834 t.Run("response must contain concepts", func(t *testing.T) { 835 require.Len(t, res, 2) 836 assert.Equal(t, 837 map[string]interface{}{ 838 "name": "Foo", 839 }, res[0]) 840 assert.Equal(t, 841 map[string]interface{}{ 842 "age": 200, 843 "_additional": map[string]interface{}{ 844 "classification": &additional.Classification{ 845 ID: "1234", 846 }, 847 }, 848 }, res[1]) 849 }) 850 }) 851 852 t.Run("when the interpretation prop is set", func(t *testing.T) { 853 params := dto.GetParams{ 854 ClassName: "BestClass", 855 Pagination: &filters.Pagination{Limit: 100}, 856 Filters: nil, 857 AdditionalProperties: additional.Properties{ 858 ModuleParams: map[string]interface{}{ 859 "interpretation": true, 860 }, 861 }, 862 } 863 864 searchResults := []search.Result{ 865 { 866 ID: "id1", 867 Schema: map[string]interface{}{ 868 "name": "Foo", 869 }, 870 AdditionalProperties: models.AdditionalProperties{ 871 "interpretation": nil, 872 }, 873 }, 874 { 875 ID: "id2", 876 Schema: map[string]interface{}{ 877 "age": 200, 878 }, 879 AdditionalProperties: models.AdditionalProperties{ 880 "interpretation": &Interpretation{ 881 Source: []*InterpretationSource{ 882 { 883 Concept: "foo", 884 Weight: 0.123, 885 Occurrence: 123, 886 }, 887 }, 888 }, 889 }, 890 }, 891 } 892 893 search := &fakeVectorSearcher{} 894 log, _ := test.NewNullLogger() 895 metrics := &fakeMetrics{} 896 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 897 explorer.SetSchemaGetter(&fakeSchemaGetter{ 898 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 899 {Class: "BestClass"}, 900 }}}, 901 }) 902 expectedParamsToSearch := params 903 expectedParamsToSearch.SearchVector = nil 904 search. 905 On("Search", expectedParamsToSearch). 906 Return(searchResults, nil) 907 908 res, err := explorer.GetClass(context.Background(), params) 909 910 t.Run("class search must be called with right params", func(t *testing.T) { 911 assert.Nil(t, err) 912 search.AssertExpectations(t) 913 }) 914 915 t.Run("response must contain concepts", func(t *testing.T) { 916 require.Len(t, res, 2) 917 assert.Equal(t, 918 map[string]interface{}{ 919 "name": "Foo", 920 }, res[0]) 921 assert.Equal(t, 922 map[string]interface{}{ 923 "age": 200, 924 "_additional": map[string]interface{}{ 925 "interpretation": &Interpretation{ 926 Source: []*InterpretationSource{ 927 { 928 Concept: "foo", 929 Weight: 0.123, 930 Occurrence: 123, 931 }, 932 }, 933 }, 934 }, 935 }, res[1]) 936 }) 937 }) 938 939 t.Run("when the vector _additional prop is set", func(t *testing.T) { 940 params := dto.GetParams{ 941 ClassName: "BestClass", 942 Pagination: &filters.Pagination{Limit: 100}, 943 Filters: nil, 944 AdditionalProperties: additional.Properties{ 945 Vector: true, 946 }, 947 } 948 949 searchResults := []search.Result{ 950 { 951 ID: "id1", 952 Schema: map[string]interface{}{ 953 "name": "Foo", 954 }, 955 Vector: []float32{0.1, -0.3}, 956 Dims: 128, 957 }, 958 } 959 960 search := &fakeVectorSearcher{} 961 log, _ := test.NewNullLogger() 962 metrics := &fakeMetrics{} 963 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 964 explorer.SetSchemaGetter(&fakeSchemaGetter{ 965 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 966 {Class: "BestClass"}, 967 }}}, 968 }) 969 expectedParamsToSearch := params 970 expectedParamsToSearch.SearchVector = nil 971 search. 972 On("Search", expectedParamsToSearch). 973 Return(searchResults, nil) 974 metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "_additional.vector", 128) 975 976 res, err := explorer.GetClass(context.Background(), params) 977 978 t.Run("class search must be called with right params", func(t *testing.T) { 979 assert.Nil(t, err) 980 search.AssertExpectations(t) 981 }) 982 983 t.Run("response must contain vector", func(t *testing.T) { 984 require.Len(t, res, 1) 985 assert.Equal(t, 986 map[string]interface{}{ 987 "name": "Foo", 988 "_additional": map[string]interface{}{ 989 "vector": []float32{0.1, -0.3}, 990 }, 991 }, res[0]) 992 }) 993 }) 994 995 t.Run("when the creationTimeUnix _additional prop is set", func(t *testing.T) { 996 params := dto.GetParams{ 997 ClassName: "BestClass", 998 Pagination: &filters.Pagination{Limit: 100}, 999 Filters: nil, 1000 AdditionalProperties: additional.Properties{ 1001 CreationTimeUnix: true, 1002 }, 1003 } 1004 1005 now := time.Now().UnixNano() / int64(time.Millisecond) 1006 1007 searchResults := []search.Result{ 1008 { 1009 ID: "id1", 1010 Schema: map[string]interface{}{ 1011 "name": "Foo", 1012 }, 1013 Created: now, 1014 }, 1015 } 1016 1017 search := &fakeVectorSearcher{} 1018 log, _ := test.NewNullLogger() 1019 metrics := &fakeMetrics{} 1020 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 1021 explorer.SetSchemaGetter(&fakeSchemaGetter{ 1022 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 1023 {Class: "BestClass"}, 1024 }}}, 1025 }) 1026 expectedParamsToSearch := params 1027 expectedParamsToSearch.SearchVector = nil 1028 search. 1029 On("Search", expectedParamsToSearch). 1030 Return(searchResults, nil) 1031 1032 res, err := explorer.GetClass(context.Background(), params) 1033 1034 t.Run("class search must be called with right params", func(t *testing.T) { 1035 assert.Nil(t, err) 1036 search.AssertExpectations(t) 1037 }) 1038 1039 t.Run("response must contain creationTimeUnix", func(t *testing.T) { 1040 require.Len(t, res, 1) 1041 assert.Equal(t, 1042 map[string]interface{}{ 1043 "name": "Foo", 1044 "_additional": map[string]interface{}{ 1045 "creationTimeUnix": now, 1046 }, 1047 }, res[0]) 1048 }) 1049 }) 1050 1051 t.Run("when the lastUpdateTimeUnix _additional prop is set", func(t *testing.T) { 1052 params := dto.GetParams{ 1053 ClassName: "BestClass", 1054 Pagination: &filters.Pagination{Limit: 100}, 1055 Filters: nil, 1056 AdditionalProperties: additional.Properties{ 1057 LastUpdateTimeUnix: true, 1058 }, 1059 } 1060 1061 now := time.Now().UnixNano() / int64(time.Millisecond) 1062 1063 searchResults := []search.Result{ 1064 { 1065 ID: "id1", 1066 Schema: map[string]interface{}{ 1067 "name": "Foo", 1068 }, 1069 Updated: now, 1070 }, 1071 } 1072 1073 search := &fakeVectorSearcher{} 1074 log, _ := test.NewNullLogger() 1075 metrics := &fakeMetrics{} 1076 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 1077 explorer.SetSchemaGetter(&fakeSchemaGetter{ 1078 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 1079 {Class: "BestClass"}, 1080 }}}, 1081 }) 1082 expectedParamsToSearch := params 1083 expectedParamsToSearch.SearchVector = nil 1084 search. 1085 On("Search", expectedParamsToSearch). 1086 Return(searchResults, nil) 1087 1088 res, err := explorer.GetClass(context.Background(), params) 1089 1090 t.Run("class search must be called with right params", func(t *testing.T) { 1091 assert.Nil(t, err) 1092 search.AssertExpectations(t) 1093 }) 1094 1095 t.Run("response must contain lastUpdateTimeUnix", func(t *testing.T) { 1096 require.Len(t, res, 1) 1097 assert.Equal(t, 1098 map[string]interface{}{ 1099 "name": "Foo", 1100 "_additional": map[string]interface{}{ 1101 "lastUpdateTimeUnix": now, 1102 }, 1103 }, res[0]) 1104 }) 1105 }) 1106 1107 t.Run("when the nearestNeighbors prop is set", func(t *testing.T) { 1108 params := dto.GetParams{ 1109 ClassName: "BestClass", 1110 Pagination: &filters.Pagination{Limit: 100}, 1111 Filters: nil, 1112 AdditionalProperties: additional.Properties{ 1113 ModuleParams: map[string]interface{}{ 1114 "nearestNeighbors": true, 1115 }, 1116 }, 1117 } 1118 1119 searchResults := []search.Result{ 1120 { 1121 ID: "id1", 1122 Schema: map[string]interface{}{ 1123 "name": "Foo", 1124 }, 1125 }, 1126 { 1127 ID: "id2", 1128 Schema: map[string]interface{}{ 1129 "name": "Bar", 1130 }, 1131 }, 1132 } 1133 1134 searcher := &fakeVectorSearcher{} 1135 log, _ := test.NewNullLogger() 1136 extender := &fakeExtender{ 1137 returnArgs: []search.Result{ 1138 { 1139 ID: "id1", 1140 Schema: map[string]interface{}{ 1141 "name": "Foo", 1142 }, 1143 AdditionalProperties: models.AdditionalProperties{ 1144 "nearestNeighbors": &NearestNeighbors{ 1145 Neighbors: []*NearestNeighbor{ 1146 { 1147 Concept: "foo", 1148 Distance: 0.1, 1149 }, 1150 }, 1151 }, 1152 }, 1153 }, 1154 { 1155 ID: "id2", 1156 Schema: map[string]interface{}{ 1157 "name": "Bar", 1158 }, 1159 AdditionalProperties: models.AdditionalProperties{ 1160 "nearestNeighbors": &NearestNeighbors{ 1161 Neighbors: []*NearestNeighbor{ 1162 { 1163 Concept: "bar", 1164 Distance: 0.1, 1165 }, 1166 }, 1167 }, 1168 }, 1169 }, 1170 }, 1171 } 1172 explorer := NewExplorer(searcher, log, getFakeModulesProviderWithCustomExtenders(extender, nil, nil), nil, defaultConfig) 1173 explorer.SetSchemaGetter(&fakeSchemaGetter{ 1174 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 1175 {Class: "BestClass"}, 1176 }}}, 1177 }) 1178 expectedParamsToSearch := params 1179 expectedParamsToSearch.SearchVector = nil 1180 searcher. 1181 On("Search", expectedParamsToSearch). 1182 Return(searchResults, nil) 1183 1184 res, err := explorer.GetClass(context.Background(), params) 1185 1186 t.Run("class search must be called with right params", func(t *testing.T) { 1187 assert.Nil(t, err) 1188 searcher.AssertExpectations(t) 1189 }) 1190 1191 t.Run("response must contain concepts", func(t *testing.T) { 1192 require.Len(t, res, 2) 1193 assert.Equal(t, 1194 map[string]interface{}{ 1195 "name": "Foo", 1196 "_additional": map[string]interface{}{ 1197 "nearestNeighbors": &NearestNeighbors{ 1198 Neighbors: []*NearestNeighbor{ 1199 { 1200 Concept: "foo", 1201 Distance: 0.1, 1202 }, 1203 }, 1204 }, 1205 }, 1206 }, res[0]) 1207 assert.Equal(t, 1208 map[string]interface{}{ 1209 "name": "Bar", 1210 "_additional": map[string]interface{}{ 1211 "nearestNeighbors": &NearestNeighbors{ 1212 Neighbors: []*NearestNeighbor{ 1213 { 1214 Concept: "bar", 1215 Distance: 0.1, 1216 }, 1217 }, 1218 }, 1219 }, 1220 }, res[1]) 1221 }) 1222 }) 1223 1224 t.Run("when the featureProjection prop is set", func(t *testing.T) { 1225 params := dto.GetParams{ 1226 ClassName: "BestClass", 1227 Pagination: &filters.Pagination{Limit: 100}, 1228 Filters: nil, 1229 AdditionalProperties: additional.Properties{ 1230 ModuleParams: map[string]interface{}{ 1231 "featureProjection": getDefaultParam("featureProjection"), 1232 }, 1233 }, 1234 } 1235 1236 searchResults := []search.Result{ 1237 { 1238 ID: "id1", 1239 Schema: map[string]interface{}{ 1240 "name": "Foo", 1241 }, 1242 }, 1243 { 1244 ID: "id2", 1245 Schema: map[string]interface{}{ 1246 "name": "Bar", 1247 }, 1248 }, 1249 } 1250 1251 searcher := &fakeVectorSearcher{} 1252 log, _ := test.NewNullLogger() 1253 projector := &fakeProjector{ 1254 returnArgs: []search.Result{ 1255 { 1256 ID: "id1", 1257 Schema: map[string]interface{}{ 1258 "name": "Foo", 1259 }, 1260 AdditionalProperties: models.AdditionalProperties{ 1261 "featureProjection": &FeatureProjection{ 1262 Vector: []float32{0, 1}, 1263 }, 1264 }, 1265 }, 1266 { 1267 ID: "id2", 1268 Schema: map[string]interface{}{ 1269 "name": "Bar", 1270 }, 1271 AdditionalProperties: models.AdditionalProperties{ 1272 "featureProjection": &FeatureProjection{ 1273 Vector: []float32{1, 0}, 1274 }, 1275 }, 1276 }, 1277 }, 1278 } 1279 explorer := NewExplorer(searcher, log, getFakeModulesProviderWithCustomExtenders(nil, projector, nil), nil, defaultConfig) 1280 explorer.SetSchemaGetter(&fakeSchemaGetter{ 1281 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 1282 {Class: "BestClass"}, 1283 }}}, 1284 }) 1285 expectedParamsToSearch := params 1286 expectedParamsToSearch.SearchVector = nil 1287 searcher. 1288 On("Search", expectedParamsToSearch). 1289 Return(searchResults, nil) 1290 1291 res, err := explorer.GetClass(context.Background(), params) 1292 1293 t.Run("class search must be called with right params", func(t *testing.T) { 1294 assert.Nil(t, err) 1295 searcher.AssertExpectations(t) 1296 }) 1297 1298 t.Run("response must contain concepts", func(t *testing.T) { 1299 require.Len(t, res, 2) 1300 assert.Equal(t, 1301 map[string]interface{}{ 1302 "name": "Foo", 1303 "_additional": map[string]interface{}{ 1304 "featureProjection": &FeatureProjection{ 1305 Vector: []float32{0, 1}, 1306 }, 1307 }, 1308 }, res[0]) 1309 assert.Equal(t, 1310 map[string]interface{}{ 1311 "name": "Bar", 1312 "_additional": map[string]interface{}{ 1313 "featureProjection": &FeatureProjection{ 1314 Vector: []float32{1, 0}, 1315 }, 1316 }, 1317 }, res[1]) 1318 }) 1319 }) 1320 1321 t.Run("when the _additional on ref prop is set", func(t *testing.T) { 1322 now := time.Now().UnixMilli() 1323 params := dto.GetParams{ 1324 ClassName: "BestClass", 1325 Pagination: &filters.Pagination{Limit: 100}, 1326 Filters: nil, 1327 Properties: []search.SelectProperty{ 1328 { 1329 Name: "ofBestRefClass", 1330 Refs: []search.SelectClass{ 1331 { 1332 ClassName: "BestRefClass", 1333 AdditionalProperties: additional.Properties{ 1334 ID: true, 1335 Vector: true, 1336 CreationTimeUnix: true, 1337 LastUpdateTimeUnix: true, 1338 }, 1339 }, 1340 }, 1341 }, 1342 }, 1343 } 1344 1345 searchResults := []search.Result{ 1346 { 1347 ID: "id1", 1348 Schema: map[string]interface{}{ 1349 "name": "Foo", 1350 "ofBestRefClass": []interface{}{ 1351 search.LocalRef{ 1352 Class: "BestRefClass", 1353 Fields: map[string]interface{}{ 1354 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cea", 1355 "vector": []float32{1, 0}, 1356 "creationTimeUnix": now, 1357 "lastUpdateTimeUnix": now, 1358 }, 1359 }, 1360 }, 1361 }, 1362 AdditionalProperties: models.AdditionalProperties{ 1363 "classification": nil, 1364 }, 1365 }, 1366 { 1367 ID: "id2", 1368 Schema: map[string]interface{}{ 1369 "age": 200, 1370 "ofBestRefClass": []interface{}{ 1371 search.LocalRef{ 1372 Class: "BestRefClass", 1373 Fields: map[string]interface{}{ 1374 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4ceb", 1375 }, 1376 }, 1377 }, 1378 }, 1379 AdditionalProperties: models.AdditionalProperties{ 1380 "classification": &additional.Classification{ 1381 ID: "1234", 1382 }, 1383 }, 1384 }, 1385 } 1386 1387 fakeSearch := &fakeVectorSearcher{} 1388 log, _ := test.NewNullLogger() 1389 metrics := &fakeMetrics{} 1390 explorer := NewExplorer(fakeSearch, log, getFakeModulesProvider(), metrics, defaultConfig) 1391 explorer.SetSchemaGetter(&fakeSchemaGetter{ 1392 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 1393 {Class: "BestClass"}, 1394 }}}, 1395 }) 1396 expectedParamsToSearch := params 1397 expectedParamsToSearch.SearchVector = nil 1398 fakeSearch. 1399 On("Search", expectedParamsToSearch). 1400 Return(searchResults, nil) 1401 1402 res, err := explorer.GetClass(context.Background(), params) 1403 1404 t.Run("class search must be called with right params", func(t *testing.T) { 1405 assert.Nil(t, err) 1406 fakeSearch.AssertExpectations(t) 1407 }) 1408 1409 t.Run("response must contain _additional id and vector params for ref prop", func(t *testing.T) { 1410 require.Len(t, res, 2) 1411 assert.Equal(t, 1412 map[string]interface{}{ 1413 "name": "Foo", 1414 "ofBestRefClass": []interface{}{ 1415 search.LocalRef{ 1416 Class: "BestRefClass", 1417 Fields: map[string]interface{}{ 1418 "_additional": map[string]interface{}{ 1419 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cea", 1420 "vector": []float32{1, 0}, 1421 "creationTimeUnix": now, 1422 "lastUpdateTimeUnix": now, 1423 }, 1424 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cea", 1425 "vector": []float32{1, 0}, 1426 "creationTimeUnix": now, 1427 "lastUpdateTimeUnix": now, 1428 }, 1429 }, 1430 }, 1431 }, res[0]) 1432 assert.Equal(t, 1433 map[string]interface{}{ 1434 "age": 200, 1435 "ofBestRefClass": []interface{}{ 1436 search.LocalRef{ 1437 Class: "BestRefClass", 1438 Fields: map[string]interface{}{ 1439 "_additional": map[string]interface{}{ 1440 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4ceb", 1441 "vector": nil, 1442 "creationTimeUnix": nil, 1443 "lastUpdateTimeUnix": nil, 1444 }, 1445 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4ceb", 1446 }, 1447 }, 1448 }, 1449 "_additional": map[string]interface{}{ 1450 "classification": &additional.Classification{ 1451 ID: "1234", 1452 }, 1453 }, 1454 }, res[1]) 1455 }) 1456 }) 1457 1458 t.Run("when the _additional on all refs prop is set", func(t *testing.T) { 1459 params := dto.GetParams{ 1460 ClassName: "BestClass", 1461 Pagination: &filters.Pagination{Limit: 100}, 1462 Filters: nil, 1463 Properties: []search.SelectProperty{ 1464 { 1465 Name: "ofBestRefClass", 1466 Refs: []search.SelectClass{ 1467 { 1468 ClassName: "BestRefClass", 1469 AdditionalProperties: additional.Properties{ 1470 ID: true, 1471 }, 1472 RefProperties: search.SelectProperties{ 1473 search.SelectProperty{ 1474 Name: "ofBestRefInnerClass", 1475 Refs: []search.SelectClass{ 1476 { 1477 ClassName: "BestRefInnerClass", 1478 AdditionalProperties: additional.Properties{ 1479 ID: true, 1480 }, 1481 }, 1482 }, 1483 }, 1484 }, 1485 }, 1486 }, 1487 }, 1488 }, 1489 } 1490 1491 searchResults := []search.Result{ 1492 { 1493 ID: "id1", 1494 Schema: map[string]interface{}{ 1495 "name": "Foo", 1496 "ofBestRefClass": []interface{}{ 1497 search.LocalRef{ 1498 Class: "BestRefClass", 1499 Fields: map[string]interface{}{ 1500 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cea", 1501 "ofBestRefInnerClass": []interface{}{ 1502 search.LocalRef{ 1503 Class: "BestRefInnerClass", 1504 Fields: map[string]interface{}{ 1505 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4caa", 1506 }, 1507 }, 1508 }, 1509 }, 1510 }, 1511 }, 1512 }, 1513 AdditionalProperties: models.AdditionalProperties{ 1514 "classification": nil, 1515 }, 1516 }, 1517 { 1518 ID: "id2", 1519 Schema: map[string]interface{}{ 1520 "age": 200, 1521 "ofBestRefClass": []interface{}{ 1522 search.LocalRef{ 1523 Class: "BestRefClass", 1524 Fields: map[string]interface{}{ 1525 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4ceb", 1526 "ofBestRefInnerClass": []interface{}{ 1527 search.LocalRef{ 1528 Class: "BestRefInnerClass", 1529 Fields: map[string]interface{}{ 1530 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cbb", 1531 }, 1532 }, 1533 }, 1534 }, 1535 }, 1536 }, 1537 }, 1538 AdditionalProperties: models.AdditionalProperties{ 1539 "classification": &additional.Classification{ 1540 ID: "1234", 1541 }, 1542 }, 1543 }, 1544 } 1545 1546 fakeSearch := &fakeVectorSearcher{} 1547 log, _ := test.NewNullLogger() 1548 metrics := &fakeMetrics{} 1549 explorer := NewExplorer(fakeSearch, log, getFakeModulesProvider(), metrics, defaultConfig) 1550 explorer.SetSchemaGetter(&fakeSchemaGetter{ 1551 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 1552 {Class: "BestClass"}, 1553 }}}, 1554 }) 1555 expectedParamsToSearch := params 1556 expectedParamsToSearch.SearchVector = nil 1557 fakeSearch. 1558 On("Search", expectedParamsToSearch). 1559 Return(searchResults, nil) 1560 1561 res, err := explorer.GetClass(context.Background(), params) 1562 1563 t.Run("class search must be called with right params", func(t *testing.T) { 1564 assert.Nil(t, err) 1565 fakeSearch.AssertExpectations(t) 1566 }) 1567 1568 t.Run("response must contain _additional id param for ref prop", func(t *testing.T) { 1569 require.Len(t, res, 2) 1570 assert.Equal(t, 1571 map[string]interface{}{ 1572 "name": "Foo", 1573 "ofBestRefClass": []interface{}{ 1574 search.LocalRef{ 1575 Class: "BestRefClass", 1576 Fields: map[string]interface{}{ 1577 "_additional": map[string]interface{}{ 1578 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cea", 1579 }, 1580 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cea", 1581 "ofBestRefInnerClass": []interface{}{ 1582 search.LocalRef{ 1583 Class: "BestRefInnerClass", 1584 Fields: map[string]interface{}{ 1585 "_additional": map[string]interface{}{ 1586 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4caa", 1587 }, 1588 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4caa", 1589 }, 1590 }, 1591 }, 1592 }, 1593 }, 1594 }, 1595 }, res[0]) 1596 assert.Equal(t, 1597 map[string]interface{}{ 1598 "age": 200, 1599 "ofBestRefClass": []interface{}{ 1600 search.LocalRef{ 1601 Class: "BestRefClass", 1602 Fields: map[string]interface{}{ 1603 "_additional": map[string]interface{}{ 1604 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4ceb", 1605 }, 1606 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4ceb", 1607 "ofBestRefInnerClass": []interface{}{ 1608 search.LocalRef{ 1609 Class: "BestRefInnerClass", 1610 Fields: map[string]interface{}{ 1611 "_additional": map[string]interface{}{ 1612 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cbb", 1613 }, 1614 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cbb", 1615 }, 1616 }, 1617 }, 1618 }, 1619 }, 1620 }, 1621 "_additional": map[string]interface{}{ 1622 "classification": &additional.Classification{ 1623 ID: "1234", 1624 }, 1625 }, 1626 }, res[1]) 1627 }) 1628 }) 1629 1630 t.Run("when the _additional on lots of refs prop is set", func(t *testing.T) { 1631 now := time.Now().UnixMilli() 1632 vec := []float32{1, 2, 3} 1633 params := dto.GetParams{ 1634 ClassName: "BestClass", 1635 Pagination: &filters.Pagination{Limit: 100}, 1636 Filters: nil, 1637 Properties: []search.SelectProperty{ 1638 { 1639 Name: "ofBestRefClass", 1640 Refs: []search.SelectClass{ 1641 { 1642 ClassName: "BestRefClass", 1643 AdditionalProperties: additional.Properties{ 1644 ID: true, 1645 Vector: true, 1646 CreationTimeUnix: true, 1647 LastUpdateTimeUnix: true, 1648 }, 1649 RefProperties: search.SelectProperties{ 1650 search.SelectProperty{ 1651 Name: "ofBestRefInnerClass", 1652 Refs: []search.SelectClass{ 1653 { 1654 ClassName: "BestRefInnerClass", 1655 AdditionalProperties: additional.Properties{ 1656 ID: true, 1657 Vector: true, 1658 CreationTimeUnix: true, 1659 LastUpdateTimeUnix: true, 1660 }, 1661 RefProperties: search.SelectProperties{ 1662 search.SelectProperty{ 1663 Name: "ofBestRefInnerInnerClass", 1664 Refs: []search.SelectClass{ 1665 { 1666 ClassName: "BestRefInnerInnerClass", 1667 AdditionalProperties: additional.Properties{ 1668 ID: true, 1669 Vector: true, 1670 CreationTimeUnix: true, 1671 LastUpdateTimeUnix: true, 1672 }, 1673 }, 1674 }, 1675 }, 1676 }, 1677 }, 1678 }, 1679 }, 1680 }, 1681 }, 1682 }, 1683 }, 1684 }, 1685 } 1686 1687 searchResults := []search.Result{ 1688 { 1689 ID: "id1", 1690 Schema: map[string]interface{}{ 1691 "name": "Foo", 1692 "ofBestRefClass": []interface{}{ 1693 search.LocalRef{ 1694 Class: "BestRefClass", 1695 Fields: map[string]interface{}{ 1696 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cea", 1697 "creationTimeUnix": now, 1698 "lastUpdateTimeUnix": now, 1699 "vector": vec, 1700 "ofBestRefInnerClass": []interface{}{ 1701 search.LocalRef{ 1702 Class: "BestRefInnerClass", 1703 Fields: map[string]interface{}{ 1704 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4caa", 1705 "creationTimeUnix": now, 1706 "lastUpdateTimeUnix": now, 1707 "vector": vec, 1708 "ofBestRefInnerInnerClass": []interface{}{ 1709 search.LocalRef{ 1710 Class: "BestRefInnerInnerClass", 1711 Fields: map[string]interface{}{ 1712 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4aaa", 1713 "creationTimeUnix": now, 1714 "lastUpdateTimeUnix": now, 1715 "vector": vec, 1716 }, 1717 }, 1718 }, 1719 }, 1720 }, 1721 }, 1722 }, 1723 }, 1724 }, 1725 }, 1726 AdditionalProperties: models.AdditionalProperties{ 1727 "classification": nil, 1728 }, 1729 }, 1730 { 1731 ID: "id2", 1732 Schema: map[string]interface{}{ 1733 "age": 200, 1734 "ofBestRefClass": []interface{}{ 1735 search.LocalRef{ 1736 Class: "BestRefClass", 1737 Fields: map[string]interface{}{ 1738 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4ceb", 1739 "creationTimeUnix": now, 1740 "lastUpdateTimeUnix": now, 1741 "vector": vec, 1742 "ofBestRefInnerClass": []interface{}{ 1743 search.LocalRef{ 1744 Class: "BestRefInnerClass", 1745 Fields: map[string]interface{}{ 1746 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cbb", 1747 "creationTimeUnix": now, 1748 "lastUpdateTimeUnix": now, 1749 "vector": vec, 1750 "ofBestRefInnerInnerClass": []interface{}{ 1751 search.LocalRef{ 1752 Class: "BestRefInnerInnerClass", 1753 Fields: map[string]interface{}{ 1754 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4bbb", 1755 "creationTimeUnix": now, 1756 "lastUpdateTimeUnix": now, 1757 "vector": vec, 1758 }, 1759 }, 1760 }, 1761 }, 1762 }, 1763 }, 1764 }, 1765 }, 1766 }, 1767 }, 1768 AdditionalProperties: models.AdditionalProperties{ 1769 "classification": &additional.Classification{ 1770 ID: "1234", 1771 }, 1772 }, 1773 }, 1774 } 1775 1776 fakeSearch := &fakeVectorSearcher{} 1777 log, _ := test.NewNullLogger() 1778 metrics := &fakeMetrics{} 1779 explorer := NewExplorer(fakeSearch, log, getFakeModulesProvider(), metrics, defaultConfig) 1780 explorer.SetSchemaGetter(&fakeSchemaGetter{ 1781 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 1782 {Class: "BestClass"}, 1783 }}}, 1784 }) 1785 expectedParamsToSearch := params 1786 expectedParamsToSearch.SearchVector = nil 1787 fakeSearch. 1788 On("Search", expectedParamsToSearch). 1789 Return(searchResults, nil) 1790 1791 res, err := explorer.GetClass(context.Background(), params) 1792 1793 t.Run("class search must be called with right params", func(t *testing.T) { 1794 assert.Nil(t, err) 1795 fakeSearch.AssertExpectations(t) 1796 }) 1797 1798 t.Run("response must contain _additional id param for ref prop", func(t *testing.T) { 1799 require.Len(t, res, 2) 1800 assert.Equal(t, 1801 map[string]interface{}{ 1802 "name": "Foo", 1803 "ofBestRefClass": []interface{}{ 1804 search.LocalRef{ 1805 Class: "BestRefClass", 1806 Fields: map[string]interface{}{ 1807 "_additional": map[string]interface{}{ 1808 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cea", 1809 "creationTimeUnix": now, 1810 "lastUpdateTimeUnix": now, 1811 "vector": vec, 1812 }, 1813 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cea", 1814 "creationTimeUnix": now, 1815 "lastUpdateTimeUnix": now, 1816 "vector": vec, 1817 "ofBestRefInnerClass": []interface{}{ 1818 search.LocalRef{ 1819 Class: "BestRefInnerClass", 1820 Fields: map[string]interface{}{ 1821 "_additional": map[string]interface{}{ 1822 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4caa", 1823 "creationTimeUnix": now, 1824 "lastUpdateTimeUnix": now, 1825 "vector": vec, 1826 }, 1827 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4caa", 1828 "creationTimeUnix": now, 1829 "lastUpdateTimeUnix": now, 1830 "vector": vec, 1831 "ofBestRefInnerInnerClass": []interface{}{ 1832 search.LocalRef{ 1833 Class: "BestRefInnerInnerClass", 1834 Fields: map[string]interface{}{ 1835 "_additional": map[string]interface{}{ 1836 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4aaa", 1837 "creationTimeUnix": now, 1838 "lastUpdateTimeUnix": now, 1839 "vector": vec, 1840 }, 1841 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4aaa", 1842 "creationTimeUnix": now, 1843 "lastUpdateTimeUnix": now, 1844 "vector": vec, 1845 }, 1846 }, 1847 }, 1848 }, 1849 }, 1850 }, 1851 }, 1852 }, 1853 }, 1854 }, res[0]) 1855 assert.Equal(t, 1856 map[string]interface{}{ 1857 "age": 200, 1858 "ofBestRefClass": []interface{}{ 1859 search.LocalRef{ 1860 Class: "BestRefClass", 1861 Fields: map[string]interface{}{ 1862 "_additional": map[string]interface{}{ 1863 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4ceb", 1864 "creationTimeUnix": now, 1865 "lastUpdateTimeUnix": now, 1866 "vector": vec, 1867 }, 1868 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4ceb", 1869 "creationTimeUnix": now, 1870 "lastUpdateTimeUnix": now, 1871 "vector": vec, 1872 "ofBestRefInnerClass": []interface{}{ 1873 search.LocalRef{ 1874 Class: "BestRefInnerClass", 1875 Fields: map[string]interface{}{ 1876 "_additional": map[string]interface{}{ 1877 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cbb", 1878 "creationTimeUnix": now, 1879 "lastUpdateTimeUnix": now, 1880 "vector": vec, 1881 }, 1882 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4cbb", 1883 "creationTimeUnix": now, 1884 "lastUpdateTimeUnix": now, 1885 "vector": vec, 1886 "ofBestRefInnerInnerClass": []interface{}{ 1887 search.LocalRef{ 1888 Class: "BestRefInnerInnerClass", 1889 Fields: map[string]interface{}{ 1890 "_additional": map[string]interface{}{ 1891 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4bbb", 1892 "creationTimeUnix": now, 1893 "lastUpdateTimeUnix": now, 1894 "vector": vec, 1895 }, 1896 "id": "2d68456c-73b4-4cfc-a6dc-718efc5b4bbb", 1897 "creationTimeUnix": now, 1898 "lastUpdateTimeUnix": now, 1899 "vector": vec, 1900 }, 1901 }, 1902 }, 1903 }, 1904 }, 1905 }, 1906 }, 1907 }, 1908 }, 1909 "_additional": map[string]interface{}{ 1910 "classification": &additional.Classification{ 1911 ID: "1234", 1912 }, 1913 }, 1914 }, res[1]) 1915 }) 1916 }) 1917 1918 t.Run("when the almost all _additional props set", func(t *testing.T) { 1919 params := dto.GetParams{ 1920 ClassName: "BestClass", 1921 Pagination: &filters.Pagination{Limit: 100}, 1922 Filters: nil, 1923 AdditionalProperties: additional.Properties{ 1924 ID: true, 1925 Classification: true, 1926 ModuleParams: map[string]interface{}{ 1927 "interpretation": true, 1928 "nearestNeighbors": true, 1929 }, 1930 }, 1931 } 1932 1933 searchResults := []search.Result{ 1934 { 1935 ID: "id1", 1936 Schema: map[string]interface{}{ 1937 "name": "Foo", 1938 }, 1939 AdditionalProperties: models.AdditionalProperties{ 1940 "classification": &additional.Classification{ 1941 ID: "1234", 1942 }, 1943 "nearestNeighbors": &NearestNeighbors{ 1944 Neighbors: []*NearestNeighbor{ 1945 { 1946 Concept: "foo", 1947 Distance: 0.1, 1948 }, 1949 }, 1950 }, 1951 }, 1952 }, 1953 { 1954 ID: "id2", 1955 Schema: map[string]interface{}{ 1956 "name": "Bar", 1957 }, 1958 AdditionalProperties: models.AdditionalProperties{ 1959 "classification": &additional.Classification{ 1960 ID: "5678", 1961 }, 1962 "nearestNeighbors": &NearestNeighbors{ 1963 Neighbors: []*NearestNeighbor{ 1964 { 1965 Concept: "bar", 1966 Distance: 0.1, 1967 }, 1968 }, 1969 }, 1970 }, 1971 }, 1972 } 1973 1974 searcher := &fakeVectorSearcher{} 1975 log, _ := test.NewNullLogger() 1976 extender := &fakeExtender{ 1977 returnArgs: []search.Result{ 1978 { 1979 ID: "id1", 1980 Schema: map[string]interface{}{ 1981 "name": "Foo", 1982 }, 1983 AdditionalProperties: models.AdditionalProperties{ 1984 "classification": &additional.Classification{ 1985 ID: "1234", 1986 }, 1987 "interpretation": &Interpretation{ 1988 Source: []*InterpretationSource{ 1989 { 1990 Concept: "foo", 1991 Weight: 0.123, 1992 Occurrence: 123, 1993 }, 1994 }, 1995 }, 1996 "nearestNeighbors": &NearestNeighbors{ 1997 Neighbors: []*NearestNeighbor{ 1998 { 1999 Concept: "foo", 2000 Distance: 0.1, 2001 }, 2002 }, 2003 }, 2004 }, 2005 }, 2006 { 2007 ID: "id2", 2008 Schema: map[string]interface{}{ 2009 "name": "Bar", 2010 }, 2011 AdditionalProperties: models.AdditionalProperties{ 2012 "classification": &additional.Classification{ 2013 ID: "5678", 2014 }, 2015 "interpretation": &Interpretation{ 2016 Source: []*InterpretationSource{ 2017 { 2018 Concept: "bar", 2019 Weight: 0.456, 2020 Occurrence: 456, 2021 }, 2022 }, 2023 }, 2024 "nearestNeighbors": &NearestNeighbors{ 2025 Neighbors: []*NearestNeighbor{ 2026 { 2027 Concept: "bar", 2028 Distance: 0.1, 2029 }, 2030 }, 2031 }, 2032 }, 2033 }, 2034 }, 2035 } 2036 explorer := NewExplorer(searcher, log, getFakeModulesProviderWithCustomExtenders(extender, nil, nil), nil, defaultConfig) 2037 explorer.SetSchemaGetter(&fakeSchemaGetter{ 2038 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 2039 {Class: "BestClass"}, 2040 }}}, 2041 }) 2042 expectedParamsToSearch := params 2043 expectedParamsToSearch.SearchVector = nil 2044 searcher. 2045 On("Search", expectedParamsToSearch). 2046 Return(searchResults, nil) 2047 2048 res, err := explorer.GetClass(context.Background(), params) 2049 2050 t.Run("class search must be called with right params", func(t *testing.T) { 2051 assert.Nil(t, err) 2052 searcher.AssertExpectations(t) 2053 }) 2054 2055 t.Run("response must contain concepts", func(t *testing.T) { 2056 require.Len(t, res, 2) 2057 assert.Equal(t, 2058 map[string]interface{}{ 2059 "name": "Foo", 2060 "_additional": map[string]interface{}{ 2061 "id": strfmt.UUID("id1"), 2062 "classification": &additional.Classification{ 2063 ID: "1234", 2064 }, 2065 "nearestNeighbors": &NearestNeighbors{ 2066 Neighbors: []*NearestNeighbor{ 2067 { 2068 Concept: "foo", 2069 Distance: 0.1, 2070 }, 2071 }, 2072 }, 2073 "interpretation": &Interpretation{ 2074 Source: []*InterpretationSource{ 2075 { 2076 Concept: "foo", 2077 Weight: 0.123, 2078 Occurrence: 123, 2079 }, 2080 }, 2081 }, 2082 }, 2083 }, res[0]) 2084 assert.Equal(t, 2085 map[string]interface{}{ 2086 "name": "Bar", 2087 "_additional": map[string]interface{}{ 2088 "id": strfmt.UUID("id2"), 2089 "classification": &additional.Classification{ 2090 ID: "5678", 2091 }, 2092 "nearestNeighbors": &NearestNeighbors{ 2093 Neighbors: []*NearestNeighbor{ 2094 { 2095 Concept: "bar", 2096 Distance: 0.1, 2097 }, 2098 }, 2099 }, 2100 "interpretation": &Interpretation{ 2101 Source: []*InterpretationSource{ 2102 { 2103 Concept: "bar", 2104 Weight: 0.456, 2105 Occurrence: 456, 2106 }, 2107 }, 2108 }, 2109 }, 2110 }, res[1]) 2111 }) 2112 }) 2113 } 2114 2115 func Test_Explorer_GetClass_With_Modules(t *testing.T) { 2116 t.Run("when an explore param is set for nearCustomText", func(t *testing.T) { 2117 params := dto.GetParams{ 2118 ClassName: "BestClass", 2119 ModuleParams: map[string]interface{}{ 2120 "nearCustomText": extractNearCustomTextParam(map[string]interface{}{ 2121 "concepts": []interface{}{"foo"}, 2122 }), 2123 }, 2124 Pagination: &filters.Pagination{Limit: 100}, 2125 Filters: nil, 2126 } 2127 2128 searchResults := []search.Result{ 2129 { 2130 ID: "id1", 2131 Schema: map[string]interface{}{ 2132 "name": "Foo", 2133 }, 2134 Dims: 128, 2135 }, 2136 { 2137 ID: "id2", 2138 Schema: map[string]interface{}{ 2139 "age": 200, 2140 }, 2141 Dims: 128, 2142 }, 2143 } 2144 2145 search := &fakeVectorSearcher{} 2146 log, _ := test.NewNullLogger() 2147 metrics := &fakeMetrics{} 2148 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 2149 explorer.SetSchemaGetter(&fakeSchemaGetter{ 2150 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 2151 {Class: "BestClass"}, 2152 }}}, 2153 }) 2154 expectedParamsToSearch := params 2155 expectedParamsToSearch.SearchVector = []float32{1, 2, 3} 2156 search. 2157 On("VectorSearch", expectedParamsToSearch). 2158 Return(searchResults, nil) 2159 metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearCustomText", 128) 2160 2161 res, err := explorer.GetClass(context.Background(), params) 2162 2163 t.Run("vector search must be called with right params", func(t *testing.T) { 2164 assert.Nil(t, err) 2165 search.AssertExpectations(t) 2166 }) 2167 2168 t.Run("response must contain concepts", func(t *testing.T) { 2169 require.Len(t, res, 2) 2170 assert.Equal(t, 2171 map[string]interface{}{ 2172 "name": "Foo", 2173 }, res[0]) 2174 assert.Equal(t, 2175 map[string]interface{}{ 2176 "age": 200, 2177 }, res[1]) 2178 }) 2179 }) 2180 2181 t.Run("when an explore param is set for nearCustomText and the required distance not met", 2182 func(t *testing.T) { 2183 params := dto.GetParams{ 2184 ClassName: "BestClass", 2185 ModuleParams: map[string]interface{}{ 2186 "nearCustomText": extractNearCustomTextParam(map[string]interface{}{ 2187 "concepts": []interface{}{"foo"}, 2188 "distance": float64(0.2), 2189 }), 2190 }, 2191 Pagination: &filters.Pagination{Limit: 100}, 2192 Filters: nil, 2193 } 2194 2195 searchResults := []search.Result{ 2196 { 2197 ID: "id1", 2198 Dist: 2 * 0.69, 2199 Dims: 128, 2200 }, 2201 { 2202 ID: "id2", 2203 Dist: 2 * 0.69, 2204 Dims: 128, 2205 }, 2206 } 2207 2208 search := &fakeVectorSearcher{} 2209 log, _ := test.NewNullLogger() 2210 metrics := &fakeMetrics{} 2211 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 2212 explorer.SetSchemaGetter(&fakeSchemaGetter{ 2213 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 2214 {Class: "BestClass"}, 2215 }}}, 2216 }) 2217 expectedParamsToSearch := params 2218 expectedParamsToSearch.SearchVector = []float32{1, 2, 3} 2219 search. 2220 On("VectorSearch", expectedParamsToSearch). 2221 Return(searchResults, nil) 2222 metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearCustomText", 128) 2223 2224 res, err := explorer.GetClass(context.Background(), params) 2225 2226 t.Run("vector search must be called with right params", func(t *testing.T) { 2227 assert.Nil(t, err) 2228 search.AssertExpectations(t) 2229 }) 2230 2231 t.Run("no object met the required distance", func(t *testing.T) { 2232 assert.Len(t, res, 0) 2233 }) 2234 }) 2235 2236 t.Run("when an explore param is set for nearCustomText and the required certainty not met", 2237 func(t *testing.T) { 2238 params := dto.GetParams{ 2239 ClassName: "BestClass", 2240 ModuleParams: map[string]interface{}{ 2241 "nearCustomText": extractNearCustomTextParam(map[string]interface{}{ 2242 "concepts": []interface{}{"foo"}, 2243 "certainty": float64(0.8), 2244 }), 2245 }, 2246 Pagination: &filters.Pagination{Limit: 100}, 2247 Filters: nil, 2248 } 2249 2250 searchResults := []search.Result{ 2251 { 2252 ID: "id1", 2253 Dist: 2 * 0.69, 2254 Dims: 128, 2255 }, 2256 { 2257 ID: "id2", 2258 Dist: 2 * 0.69, 2259 Dims: 128, 2260 }, 2261 } 2262 2263 search := &fakeVectorSearcher{} 2264 log, _ := test.NewNullLogger() 2265 metrics := &fakeMetrics{} 2266 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 2267 explorer.SetSchemaGetter(&fakeSchemaGetter{ 2268 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 2269 {Class: "BestClass"}, 2270 }}}, 2271 }) 2272 expectedParamsToSearch := params 2273 expectedParamsToSearch.SearchVector = []float32{1, 2, 3} 2274 search. 2275 On("VectorSearch", expectedParamsToSearch). 2276 Return(searchResults, nil) 2277 metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearCustomText", 128) 2278 2279 res, err := explorer.GetClass(context.Background(), params) 2280 2281 t.Run("vector search must be called with right params", func(t *testing.T) { 2282 assert.Nil(t, err) 2283 search.AssertExpectations(t) 2284 }) 2285 2286 t.Run("no object met the required certainty", func(t *testing.T) { 2287 assert.Len(t, res, 0) 2288 }) 2289 }) 2290 2291 t.Run("when two conflicting (nearVector, nearCustomText) near searchers are set", func(t *testing.T) { 2292 params := dto.GetParams{ 2293 ClassName: "BestClass", 2294 Pagination: &filters.Pagination{Limit: 100}, 2295 Filters: nil, 2296 NearVector: &searchparams.NearVector{ 2297 Vector: []float32{0.8, 0.2, 0.7}, 2298 }, 2299 ModuleParams: map[string]interface{}{ 2300 "nearCustomText": extractNearCustomTextParam(map[string]interface{}{ 2301 "concepts": []interface{}{"foo"}, 2302 }), 2303 }, 2304 } 2305 2306 search := &fakeVectorSearcher{} 2307 log, _ := test.NewNullLogger() 2308 metrics := &fakeMetrics{} 2309 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 2310 explorer.SetSchemaGetter(&fakeSchemaGetter{ 2311 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 2312 {Class: "BestClass"}, 2313 }}}, 2314 }) 2315 _, err := explorer.GetClass(context.Background(), params) 2316 require.NotNil(t, err) 2317 assert.Contains(t, err.Error(), "parameters which are conflicting") 2318 }) 2319 2320 t.Run("when two conflicting (nearCustomText, nearObject) near searchers are set", func(t *testing.T) { 2321 params := dto.GetParams{ 2322 ClassName: "BestClass", 2323 Pagination: &filters.Pagination{Limit: 100}, 2324 Filters: nil, 2325 NearObject: &searchparams.NearObject{ 2326 Beacon: "weaviate://localhost/e9c12c22-766f-4bde-b140-d4cf8fd6e041", 2327 }, 2328 ModuleParams: map[string]interface{}{ 2329 "nearCustomText": extractNearCustomTextParam(map[string]interface{}{ 2330 "concepts": []interface{}{"foo"}, 2331 }), 2332 }, 2333 } 2334 2335 search := &fakeVectorSearcher{} 2336 log, _ := test.NewNullLogger() 2337 metrics := &fakeMetrics{} 2338 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 2339 explorer.SetSchemaGetter(&fakeSchemaGetter{ 2340 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 2341 {Class: "BestClass"}, 2342 }}}, 2343 }) 2344 _, err := explorer.GetClass(context.Background(), params) 2345 require.NotNil(t, err) 2346 assert.Contains(t, err.Error(), "parameters which are conflicting") 2347 }) 2348 2349 t.Run("when three conflicting (nearCustomText, nearVector, nearObject) near searchers are set", func(t *testing.T) { 2350 params := dto.GetParams{ 2351 ClassName: "BestClass", 2352 Pagination: &filters.Pagination{Limit: 100}, 2353 Filters: nil, 2354 NearVector: &searchparams.NearVector{ 2355 Vector: []float32{0.8, 0.2, 0.7}, 2356 }, 2357 NearObject: &searchparams.NearObject{ 2358 Beacon: "weaviate://localhost/e9c12c22-766f-4bde-b140-d4cf8fd6e041", 2359 }, 2360 ModuleParams: map[string]interface{}{ 2361 "nearCustomText": extractNearCustomTextParam(map[string]interface{}{ 2362 "concepts": []interface{}{"foo"}, 2363 }), 2364 }, 2365 } 2366 2367 search := &fakeVectorSearcher{} 2368 log, _ := test.NewNullLogger() 2369 metrics := &fakeMetrics{} 2370 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 2371 explorer.SetSchemaGetter(&fakeSchemaGetter{ 2372 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 2373 {Class: "BestClass"}, 2374 }}}, 2375 }) 2376 _, err := explorer.GetClass(context.Background(), params) 2377 require.NotNil(t, err) 2378 assert.Contains(t, err.Error(), "parameters which are conflicting") 2379 }) 2380 2381 t.Run("when nearCustomText.moveTo has no concepts and objects defined", func(t *testing.T) { 2382 params := dto.GetParams{ 2383 ClassName: "BestClass", 2384 Pagination: &filters.Pagination{Limit: 100}, 2385 Filters: nil, 2386 ModuleParams: map[string]interface{}{ 2387 "nearCustomText": extractNearCustomTextParam(map[string]interface{}{ 2388 "concepts": []interface{}{"foo"}, 2389 "moveTo": map[string]interface{}{ 2390 "force": float64(0.1), 2391 }, 2392 }), 2393 }, 2394 } 2395 2396 search := &fakeVectorSearcher{} 2397 log, _ := test.NewNullLogger() 2398 metrics := &fakeMetrics{} 2399 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 2400 explorer.SetSchemaGetter(&fakeSchemaGetter{ 2401 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 2402 {Class: "BestClass"}, 2403 }}}, 2404 }) 2405 _, err := explorer.GetClass(context.Background(), params) 2406 require.NotNil(t, err) 2407 assert.Contains(t, err.Error(), "needs to have defined either 'concepts' or 'objects' fields") 2408 }) 2409 2410 t.Run("when nearCustomText.moveAwayFrom has no concepts and objects defined", func(t *testing.T) { 2411 params := dto.GetParams{ 2412 ClassName: "BestClass", 2413 Pagination: &filters.Pagination{Limit: 100}, 2414 Filters: nil, 2415 ModuleParams: map[string]interface{}{ 2416 "nearCustomText": extractNearCustomTextParam(map[string]interface{}{ 2417 "concepts": []interface{}{"foo"}, 2418 "moveAwayFrom": map[string]interface{}{ 2419 "force": float64(0.1), 2420 }, 2421 }), 2422 }, 2423 } 2424 2425 search := &fakeVectorSearcher{} 2426 log, _ := test.NewNullLogger() 2427 metrics := &fakeMetrics{} 2428 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 2429 explorer.SetSchemaGetter(&fakeSchemaGetter{ 2430 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 2431 {Class: "BestClass"}, 2432 }}}, 2433 }) 2434 explorer.SetSchemaGetter(&fakeSchemaGetter{ 2435 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 2436 {Class: "BestClass"}, 2437 }}}, 2438 }) 2439 _, err := explorer.GetClass(context.Background(), params) 2440 require.NotNil(t, err) 2441 assert.Contains(t, err.Error(), "needs to have defined either 'concepts' or 'objects' fields") 2442 }) 2443 2444 t.Run("when the distance prop is set", func(t *testing.T) { 2445 params := dto.GetParams{ 2446 Filters: nil, 2447 ClassName: "BestClass", 2448 Pagination: &filters.Pagination{Limit: 100}, 2449 SearchVector: []float32{1.0, 2.0, 3.0}, 2450 AdditionalProperties: additional.Properties{ 2451 Distance: true, 2452 }, 2453 ModuleParams: map[string]interface{}{ 2454 "nearCustomText": extractNearCustomTextParam(map[string]interface{}{ 2455 "concepts": []interface{}{"foobar"}, 2456 "limit": 100, 2457 "distance": float64(1.38), 2458 }), 2459 }, 2460 } 2461 2462 searchResults := []search.Result{ 2463 { 2464 ID: "id2", 2465 Schema: map[string]interface{}{ 2466 "age": 200, 2467 }, 2468 Vector: []float32{0.5, 1.5, 0.0}, 2469 Dist: 2 * 0.69, 2470 Dims: 128, 2471 }, 2472 } 2473 2474 search := &fakeVectorSearcher{} 2475 log, _ := test.NewNullLogger() 2476 metrics := &fakeMetrics{} 2477 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 2478 explorer.SetSchemaGetter(&fakeSchemaGetter{ 2479 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 2480 {Class: "BestClass"}, 2481 }}}, 2482 }) 2483 expectedParamsToSearch := params 2484 expectedParamsToSearch.SearchVector = []float32{1.0, 2.0, 3.0} 2485 // expectedParamsToSearch.SearchVector = nil 2486 search. 2487 On("VectorSearch", expectedParamsToSearch). 2488 Return(searchResults, nil) 2489 metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearCustomText", 128) 2490 2491 res, err := explorer.GetClass(context.Background(), params) 2492 2493 t.Run("class search must be called with right params", func(t *testing.T) { 2494 assert.Nil(t, err) 2495 search.AssertExpectations(t) 2496 }) 2497 2498 t.Run("response must contain concepts", func(t *testing.T) { 2499 require.Len(t, res, 1) 2500 2501 resMap := res[0].(map[string]interface{}) 2502 assert.Equal(t, 2, len(resMap)) 2503 assert.Contains(t, resMap, "age") 2504 assert.Equal(t, 200, resMap["age"]) 2505 additionalMap := resMap["_additional"] 2506 assert.Contains(t, additionalMap, "distance") 2507 assert.InEpsilon(t, 1.38, additionalMap.(map[string]interface{})["distance"].(float32), 0.000001) 2508 }) 2509 }) 2510 2511 t.Run("when the certainty prop is set", func(t *testing.T) { 2512 params := dto.GetParams{ 2513 Filters: nil, 2514 ClassName: "BestClass", 2515 Pagination: &filters.Pagination{Limit: 100}, 2516 SearchVector: []float32{1.0, 2.0, 3.0}, 2517 AdditionalProperties: additional.Properties{ 2518 Certainty: true, 2519 }, 2520 ModuleParams: map[string]interface{}{ 2521 "nearCustomText": extractNearCustomTextParam(map[string]interface{}{ 2522 "concepts": []interface{}{"foobar"}, 2523 "limit": 100, 2524 "certainty": float64(0.1), 2525 }), 2526 }, 2527 } 2528 2529 searchResults := []search.Result{ 2530 { 2531 ID: "id2", 2532 Schema: map[string]interface{}{ 2533 "age": 200, 2534 }, 2535 Vector: []float32{0.5, 1.5, 0.0}, 2536 Dist: 2 * 0.69, 2537 Dims: 128, 2538 }, 2539 } 2540 2541 search := &fakeVectorSearcher{} 2542 log, _ := test.NewNullLogger() 2543 metrics := &fakeMetrics{} 2544 explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig) 2545 explorer.SetSchemaGetter(&fakeSchemaGetter{ 2546 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 2547 {Class: "BestClass"}, 2548 }}}, 2549 }) 2550 schemaGetter := newFakeSchemaGetter("BestClass") 2551 schemaGetter.SetVectorIndexConfig(hnsw.UserConfig{Distance: "cosine"}) 2552 explorer.schemaGetter = schemaGetter 2553 expectedParamsToSearch := params 2554 expectedParamsToSearch.SearchVector = []float32{1.0, 2.0, 3.0} 2555 // expectedParamsToSearch.SearchVector = nil 2556 search. 2557 On("VectorSearch", expectedParamsToSearch). 2558 Return(searchResults, nil) 2559 metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearCustomText", 128) 2560 2561 res, err := explorer.GetClass(context.Background(), params) 2562 2563 t.Run("class search must be called with right params", func(t *testing.T) { 2564 assert.Nil(t, err) 2565 search.AssertExpectations(t) 2566 }) 2567 2568 t.Run("response must contain concepts", func(t *testing.T) { 2569 require.Len(t, res, 1) 2570 2571 resMap := res[0].(map[string]interface{}) 2572 assert.Equal(t, 2, len(resMap)) 2573 assert.Contains(t, resMap, "age") 2574 assert.Equal(t, 200, resMap["age"]) 2575 additionalMap := resMap["_additional"] 2576 assert.Contains(t, additionalMap, "certainty") 2577 // Certainty is fixed to 0.69 in this mock 2578 assert.InEpsilon(t, 0.31, additionalMap.(map[string]interface{})["certainty"], 0.000001) 2579 }) 2580 }) 2581 2582 t.Run("when the semanticPath prop is set", func(t *testing.T) { 2583 params := dto.GetParams{ 2584 ClassName: "BestClass", 2585 Pagination: &filters.Pagination{Limit: 100}, 2586 Filters: nil, 2587 AdditionalProperties: additional.Properties{ 2588 ModuleParams: map[string]interface{}{ 2589 "semanticPath": getDefaultParam("semanticPath"), 2590 }, 2591 }, 2592 ModuleParams: map[string]interface{}{ 2593 "nearCustomText": extractNearCustomTextParam(map[string]interface{}{ 2594 "concepts": []interface{}{"foobar"}, 2595 }), 2596 }, 2597 } 2598 2599 searchResults := []search.Result{ 2600 { 2601 ID: "id1", 2602 Schema: map[string]interface{}{ 2603 "name": "Foo", 2604 }, 2605 }, 2606 { 2607 ID: "id2", 2608 Schema: map[string]interface{}{ 2609 "name": "Bar", 2610 }, 2611 }, 2612 } 2613 2614 searcher := &fakeVectorSearcher{} 2615 log, _ := test.NewNullLogger() 2616 pathBuilder := &fakePathBuilder{ 2617 returnArgs: []search.Result{ 2618 { 2619 ID: "id1", 2620 Dims: 128, 2621 Schema: map[string]interface{}{ 2622 "name": "Foo", 2623 }, 2624 AdditionalProperties: models.AdditionalProperties{ 2625 "semanticPath": &SemanticPath{ 2626 Path: []*SemanticPathElement{ 2627 { 2628 Concept: "pathelem1", 2629 DistanceToQuery: 0, 2630 DistanceToResult: 2.1, 2631 DistanceToPrevious: nil, 2632 DistanceToNext: ptFloat32(0.5), 2633 }, 2634 { 2635 Concept: "pathelem2", 2636 DistanceToQuery: 2.1, 2637 DistanceToResult: 0, 2638 DistanceToPrevious: ptFloat32(0.5), 2639 DistanceToNext: nil, 2640 }, 2641 }, 2642 }, 2643 }, 2644 }, 2645 { 2646 ID: "id2", 2647 Dims: 128, 2648 Schema: map[string]interface{}{ 2649 "name": "Bar", 2650 }, 2651 AdditionalProperties: models.AdditionalProperties{ 2652 "semanticPath": &SemanticPath{ 2653 Path: []*SemanticPathElement{ 2654 { 2655 Concept: "pathelem1", 2656 DistanceToQuery: 0, 2657 DistanceToResult: 2.1, 2658 DistanceToPrevious: nil, 2659 DistanceToNext: ptFloat32(0.5), 2660 }, 2661 { 2662 Concept: "pathelem2", 2663 DistanceToQuery: 2.1, 2664 DistanceToResult: 0, 2665 DistanceToPrevious: ptFloat32(0.5), 2666 DistanceToNext: nil, 2667 }, 2668 }, 2669 }, 2670 }, 2671 }, 2672 }, 2673 } 2674 metrics := &fakeMetrics{} 2675 explorer := NewExplorer(searcher, log, getFakeModulesProviderWithCustomExtenders(nil, nil, pathBuilder), metrics, defaultConfig) 2676 explorer.SetSchemaGetter(&fakeSchemaGetter{ 2677 schema: schema.Schema{Objects: &models.Schema{Classes: []*models.Class{ 2678 {Class: "BestClass"}, 2679 }}}, 2680 }) 2681 expectedParamsToSearch := params 2682 expectedParamsToSearch.SearchVector = []float32{1, 2, 3} 2683 expectedParamsToSearch.AdditionalProperties.Vector = true // any custom additional params will trigger vector 2684 searcher. 2685 On("VectorSearch", expectedParamsToSearch). 2686 Return(searchResults, nil) 2687 metrics.On("AddUsageDimensions", "BestClass", "get_graphql", "nearCustomText", 128) 2688 2689 res, err := explorer.GetClass(context.Background(), params) 2690 2691 t.Run("class search must be called with right params", func(t *testing.T) { 2692 assert.Nil(t, err) 2693 searcher.AssertExpectations(t) 2694 }) 2695 2696 t.Run("response must contain concepts", func(t *testing.T) { 2697 require.Len(t, res, 2) 2698 assert.Equal(t, 2699 map[string]interface{}{ 2700 "name": "Foo", 2701 "_additional": map[string]interface{}{ 2702 "vector": []float32(nil), 2703 "semanticPath": &SemanticPath{ 2704 Path: []*SemanticPathElement{ 2705 { 2706 Concept: "pathelem1", 2707 DistanceToQuery: 0, 2708 DistanceToResult: 2.1, 2709 DistanceToPrevious: nil, 2710 DistanceToNext: ptFloat32(0.5), 2711 }, 2712 { 2713 Concept: "pathelem2", 2714 DistanceToQuery: 2.1, 2715 DistanceToResult: 0, 2716 DistanceToPrevious: ptFloat32(0.5), 2717 DistanceToNext: nil, 2718 }, 2719 }, 2720 }, 2721 }, 2722 }, res[0]) 2723 assert.Equal(t, 2724 map[string]interface{}{ 2725 "name": "Bar", 2726 "_additional": map[string]interface{}{ 2727 "vector": []float32(nil), 2728 "semanticPath": &SemanticPath{ 2729 Path: []*SemanticPathElement{ 2730 { 2731 Concept: "pathelem1", 2732 DistanceToQuery: 0, 2733 DistanceToResult: 2.1, 2734 DistanceToPrevious: nil, 2735 DistanceToNext: ptFloat32(0.5), 2736 }, 2737 { 2738 Concept: "pathelem2", 2739 DistanceToQuery: 2.1, 2740 DistanceToResult: 0, 2741 DistanceToPrevious: ptFloat32(0.5), 2742 DistanceToNext: nil, 2743 }, 2744 }, 2745 }, 2746 }, 2747 }, res[1]) 2748 }) 2749 }) 2750 } 2751 2752 func ptFloat32(in float32) *float32 { 2753 return &in 2754 } 2755 2756 type fakeModulesProvider struct { 2757 customC11yModule *fakeText2vecContextionaryModule 2758 } 2759 2760 func (p *fakeModulesProvider) VectorFromInput(ctx context.Context, className, input, targetVector string) ([]float32, error) { 2761 panic("not implemented") 2762 } 2763 2764 func (p *fakeModulesProvider) VectorFromSearchParam(ctx context.Context, className, 2765 param string, params interface{}, 2766 findVectorFn modulecapabilities.FindVectorFn, tenant string, 2767 ) ([]float32, string, error) { 2768 txt2vec := p.getFakeT2Vec() 2769 vectorForParams := txt2vec.VectorSearches()["nearCustomText"] 2770 targetVector := "" 2771 vec, err := vectorForParams(ctx, params, "", findVectorFn, nil) 2772 return vec, targetVector, err 2773 } 2774 2775 func (p *fakeModulesProvider) CrossClassVectorFromSearchParam(ctx context.Context, 2776 param string, params interface{}, 2777 findVectorFn modulecapabilities.FindVectorFn, 2778 ) ([]float32, string, error) { 2779 txt2vec := p.getFakeT2Vec() 2780 vectorForParams := txt2vec.VectorSearches()["nearCustomText"] 2781 targetVector := "" 2782 vec, err := vectorForParams(ctx, params, "", findVectorFn, nil) 2783 return vec, targetVector, err 2784 } 2785 2786 func (p *fakeModulesProvider) CrossClassValidateSearchParam(name string, value interface{}) error { 2787 return p.ValidateSearchParam(name, value, "") 2788 } 2789 2790 func (p *fakeModulesProvider) ValidateSearchParam(name string, value interface{}, className string) error { 2791 txt2vec := p.getFakeT2Vec() 2792 arg := txt2vec.Arguments()["nearCustomText"] 2793 return arg.ValidateFunction(value) 2794 } 2795 2796 func (p *fakeModulesProvider) GetExploreAdditionalExtend(ctx context.Context, in []search.Result, 2797 moduleParams map[string]interface{}, searchVector []float32, 2798 argumentModuleParams map[string]interface{}, 2799 ) ([]search.Result, error) { 2800 return p.additionalExtend(ctx, in, moduleParams, searchVector, "ExploreGet") 2801 } 2802 2803 func (p *fakeModulesProvider) ListExploreAdditionalExtend(ctx context.Context, in []search.Result, 2804 moduleParams map[string]interface{}, 2805 argumentModuleParams map[string]interface{}, 2806 ) ([]search.Result, error) { 2807 return p.additionalExtend(ctx, in, moduleParams, nil, "ExploreList") 2808 } 2809 2810 func (p *fakeModulesProvider) additionalExtend(ctx context.Context, 2811 in search.Results, moduleParams map[string]interface{}, 2812 searchVector []float32, capability string, 2813 ) (search.Results, error) { 2814 txt2vec := p.getFakeT2Vec() 2815 if additionalProperties := txt2vec.AdditionalProperties(); len(additionalProperties) > 0 { 2816 for name, value := range moduleParams { 2817 additionalPropertyFn := p.getAdditionalPropertyFn(additionalProperties[name], capability) 2818 if additionalPropertyFn != nil && value != nil { 2819 searchValue := value 2820 if searchVectorValue, ok := value.(modulecapabilities.AdditionalPropertyWithSearchVector); ok { 2821 searchVectorValue.SetSearchVector(searchVector) 2822 searchValue = searchVectorValue 2823 } 2824 resArray, err := additionalPropertyFn(ctx, in, searchValue, nil, nil, nil) 2825 if err != nil { 2826 return nil, err 2827 } 2828 in = resArray 2829 } else { 2830 return nil, errors.Errorf("unknown capability: %s", name) 2831 } 2832 } 2833 } 2834 return in, nil 2835 } 2836 2837 func (p *fakeModulesProvider) getAdditionalPropertyFn(additionalProperty modulecapabilities.AdditionalProperty, 2838 capability string, 2839 ) modulecapabilities.AdditionalPropertyFn { 2840 switch capability { 2841 case "ObjectGet": 2842 return additionalProperty.SearchFunctions.ObjectGet 2843 case "ObjectList": 2844 return additionalProperty.SearchFunctions.ObjectList 2845 case "ExploreGet": 2846 return additionalProperty.SearchFunctions.ExploreGet 2847 case "ExploreList": 2848 return additionalProperty.SearchFunctions.ExploreList 2849 default: 2850 return nil 2851 } 2852 } 2853 2854 func (p *fakeModulesProvider) getFakeT2Vec() *fakeText2vecContextionaryModule { 2855 if p.customC11yModule != nil { 2856 return p.customC11yModule 2857 } 2858 return &fakeText2vecContextionaryModule{} 2859 } 2860 2861 func extractNearCustomTextParam(param map[string]interface{}) interface{} { 2862 txt2vec := &fakeText2vecContextionaryModule{} 2863 argument := txt2vec.Arguments()["nearCustomText"] 2864 return argument.ExtractFunction(param) 2865 } 2866 2867 func getDefaultParam(name string) interface{} { 2868 switch name { 2869 case "featureProjection": 2870 return &fakeProjectorParams{} 2871 case "semanticPath": 2872 return &pathBuilderParams{} 2873 case "nearestNeighbors": 2874 return true 2875 default: 2876 return nil 2877 } 2878 } 2879 2880 func getFakeModulesProviderWithCustomExtenders( 2881 customExtender *fakeExtender, 2882 customProjector *fakeProjector, 2883 customPathBuilder *fakePathBuilder, 2884 ) ModulesProvider { 2885 return &fakeModulesProvider{ 2886 newFakeText2vecContextionaryModuleWithCustomExtender(customExtender, customProjector, customPathBuilder), 2887 } 2888 } 2889 2890 func getFakeModulesProvider() ModulesProvider { 2891 return &fakeModulesProvider{} 2892 }