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  }