github.com/weaviate/weaviate@v1.24.6/usecases/objects/get_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 objects
    13  
    14  import (
    15  	"context"
    16  	"errors"
    17  	"testing"
    18  
    19  	"github.com/go-openapi/strfmt"
    20  	"github.com/sirupsen/logrus/hooks/test"
    21  	"github.com/stretchr/testify/assert"
    22  	"github.com/stretchr/testify/mock"
    23  	"github.com/stretchr/testify/require"
    24  	"github.com/weaviate/weaviate/entities/additional"
    25  	"github.com/weaviate/weaviate/entities/filters"
    26  	"github.com/weaviate/weaviate/entities/models"
    27  	"github.com/weaviate/weaviate/entities/schema"
    28  	"github.com/weaviate/weaviate/entities/search"
    29  	"github.com/weaviate/weaviate/usecases/config"
    30  )
    31  
    32  func Test_GetAction(t *testing.T) {
    33  	var (
    34  		vectorRepo    *fakeVectorRepo
    35  		manager       *Manager
    36  		extender      *fakeExtender
    37  		projectorFake *fakeProjector
    38  		metrics       *fakeMetrics
    39  	)
    40  
    41  	schema := schema.Schema{
    42  		Objects: &models.Schema{
    43  			Classes: []*models.Class{
    44  				{
    45  					Class: "ActionClass",
    46  				},
    47  			},
    48  		},
    49  	}
    50  
    51  	reset := func() {
    52  		vectorRepo = &fakeVectorRepo{}
    53  		schemaManager := &fakeSchemaManager{
    54  			GetSchemaResponse: schema,
    55  		}
    56  		locks := &fakeLocks{}
    57  		cfg := &config.WeaviateConfig{}
    58  		cfg.Config.QueryDefaults.Limit = 20
    59  		cfg.Config.QueryMaximumResults = 200
    60  		authorizer := &fakeAuthorizer{}
    61  		logger, _ := test.NewNullLogger()
    62  		extender = &fakeExtender{}
    63  		projectorFake = &fakeProjector{}
    64  		metrics = &fakeMetrics{}
    65  		manager = NewManager(locks, schemaManager, cfg, logger,
    66  			authorizer, vectorRepo,
    67  			getFakeModulesProviderWithCustomExtenders(extender, projectorFake), metrics)
    68  	}
    69  
    70  	t.Run("get non-existing action by id", func(t *testing.T) {
    71  		reset()
    72  		id := strfmt.UUID("99ee9968-22ec-416a-9032-cff80f2f7fdf")
    73  
    74  		vectorRepo.On("ObjectByID", id, mock.Anything, mock.Anything).Return((*search.Result)(nil), nil).Once()
    75  
    76  		_, err := manager.GetObject(context.Background(), &models.Principal{}, "",
    77  			id, additional.Properties{}, nil, "")
    78  		assert.Equal(t, NewErrNotFound("no object with id '99ee9968-22ec-416a-9032-cff80f2f7fdf'"), err)
    79  	})
    80  
    81  	t.Run("get existing action by id", func(t *testing.T) {
    82  		reset()
    83  		id := strfmt.UUID("99ee9968-22ec-416a-9032-cff80f2f7fdf")
    84  
    85  		result := &search.Result{
    86  			ID:        id,
    87  			ClassName: "ActionClass",
    88  			Schema:    map[string]interface{}{"foo": "bar"},
    89  		}
    90  		vectorRepo.On("ObjectByID", id, mock.Anything, mock.Anything).Return(result, nil).Once()
    91  
    92  		expected := &models.Object{
    93  			ID:            id,
    94  			Class:         "ActionClass",
    95  			Properties:    map[string]interface{}{"foo": "bar"},
    96  			VectorWeights: (map[string]string)(nil),
    97  		}
    98  
    99  		res, err := manager.GetObject(context.Background(), &models.Principal{}, "",
   100  			id, additional.Properties{}, nil, "")
   101  		require.Nil(t, err)
   102  		assert.Equal(t, expected, res)
   103  	})
   104  
   105  	t.Run("get existing object by id with vector without classname (deprecated)", func(t *testing.T) {
   106  		reset()
   107  		id := strfmt.UUID("99ee9968-22ec-416a-9032-cff80f2f7fdf")
   108  
   109  		result := &search.Result{
   110  			ID:        id,
   111  			ClassName: "ActionClass",
   112  			Schema:    map[string]interface{}{"foo": "bar"},
   113  			Vector:    []float32{1, 2, 3},
   114  			Dims:      3,
   115  		}
   116  		vectorRepo.On("ObjectByID", id, mock.Anything, mock.Anything).Return(result, nil).Once()
   117  
   118  		expected := &models.Object{
   119  			ID:            id,
   120  			Class:         "ActionClass",
   121  			Properties:    map[string]interface{}{"foo": "bar"},
   122  			VectorWeights: (map[string]string)(nil),
   123  			Vector:        []float32{1, 2, 3},
   124  		}
   125  
   126  		metrics.On("AddUsageDimensions", "ActionClass", "get_rest", "single_include_vector", 3)
   127  
   128  		res, err := manager.GetObject(context.Background(), &models.Principal{}, "",
   129  			id, additional.Properties{Vector: true}, nil, "")
   130  		require.Nil(t, err)
   131  		assert.Equal(t, expected, res)
   132  	})
   133  
   134  	t.Run("get existing object by id with vector with classname", func(t *testing.T) {
   135  		reset()
   136  		id := strfmt.UUID("99ee9968-22ec-416a-9032-cff80f2f7fdf")
   137  
   138  		result := &search.Result{
   139  			ID:        id,
   140  			ClassName: "ActionClass",
   141  			Schema:    map[string]interface{}{"foo": "bar"},
   142  			Vector:    []float32{1, 2, 3},
   143  			Dims:      3,
   144  		}
   145  		vectorRepo.On("Object", "ActionClass", id, mock.Anything, mock.Anything, "").
   146  			Return(result, nil).Once()
   147  
   148  		expected := &models.Object{
   149  			ID:            id,
   150  			Class:         "ActionClass",
   151  			Properties:    map[string]interface{}{"foo": "bar"},
   152  			VectorWeights: (map[string]string)(nil),
   153  			Vector:        []float32{1, 2, 3},
   154  		}
   155  
   156  		metrics.On("AddUsageDimensions", "ActionClass", "get_rest", "single_include_vector", 3)
   157  
   158  		res, err := manager.GetObject(context.Background(), &models.Principal{},
   159  			"ActionClass", id, additional.Properties{Vector: true}, nil, "")
   160  		require.Nil(t, err)
   161  		assert.Equal(t, expected, res)
   162  	})
   163  
   164  	t.Run("list all existing actions with all default pagination settings", func(t *testing.T) {
   165  		reset()
   166  		id := strfmt.UUID("99ee9968-22ec-416a-9032-cff80f2f7fdf")
   167  
   168  		results := []search.Result{
   169  			{
   170  				ID:        id,
   171  				ClassName: "ActionClass",
   172  				Schema:    map[string]interface{}{"foo": "bar"},
   173  			},
   174  		}
   175  		vectorRepo.On("ObjectSearch", 0, 20, mock.Anything, mock.Anything, mock.Anything,
   176  			mock.Anything).Return(results, nil).Once()
   177  
   178  		expected := []*models.Object{
   179  			{
   180  				ID:            id,
   181  				Class:         "ActionClass",
   182  				Properties:    map[string]interface{}{"foo": "bar"},
   183  				VectorWeights: (map[string]string)(nil),
   184  			},
   185  		}
   186  
   187  		res, err := manager.GetObjects(context.Background(), &models.Principal{}, nil, nil, nil, nil, nil, additional.Properties{}, "")
   188  		require.Nil(t, err)
   189  		assert.Equal(t, expected, res)
   190  	})
   191  
   192  	t.Run("list all existing objects with vectors", func(t *testing.T) {
   193  		reset()
   194  		id := strfmt.UUID("99ee9968-22ec-416a-9032-cff80f2f7fdf")
   195  
   196  		results := []search.Result{
   197  			{
   198  				ID:        id,
   199  				ClassName: "ActionClass",
   200  				Schema:    map[string]interface{}{"foo": "bar"},
   201  				Vector:    []float32{1, 2, 3},
   202  				Dims:      3,
   203  			},
   204  		}
   205  		vectorRepo.On("ObjectSearch", 0, 20, mock.Anything, mock.Anything, mock.Anything,
   206  			mock.Anything).Return(results, nil).Once()
   207  
   208  		metrics.On("AddUsageDimensions", "ActionClass", "get_rest", "list_include_vector", 3)
   209  
   210  		expected := []*models.Object{
   211  			{
   212  				ID:            id,
   213  				Class:         "ActionClass",
   214  				Properties:    map[string]interface{}{"foo": "bar"},
   215  				VectorWeights: (map[string]string)(nil),
   216  				Vector:        []float32{1, 2, 3},
   217  			},
   218  		}
   219  
   220  		res, err := manager.GetObjects(context.Background(), &models.Principal{}, nil, nil, nil, nil, nil, additional.Properties{Vector: true}, "")
   221  		require.Nil(t, err)
   222  		assert.Equal(t, expected, res)
   223  	})
   224  
   225  	t.Run("list all existing actions with all explicit offset and limit", func(t *testing.T) {
   226  		reset()
   227  		id := strfmt.UUID("99ee9968-22ec-416a-9032-cff80f2f7fdf")
   228  
   229  		results := []search.Result{
   230  			{
   231  				ID:        id,
   232  				ClassName: "ActionClass",
   233  				Schema:    map[string]interface{}{"foo": "bar"},
   234  			},
   235  		}
   236  		vectorRepo.On("ObjectSearch", 7, 2, mock.Anything, mock.Anything, mock.Anything,
   237  			mock.Anything).Return(results, nil).Once()
   238  
   239  		expected := []*models.Object{
   240  			{
   241  				ID:            id,
   242  				Class:         "ActionClass",
   243  				Properties:    map[string]interface{}{"foo": "bar"},
   244  				VectorWeights: (map[string]string)(nil),
   245  			},
   246  		}
   247  
   248  		res, err := manager.GetObjects(context.Background(), &models.Principal{}, ptInt64(7), ptInt64(2), nil, nil, nil, additional.Properties{}, "")
   249  		require.Nil(t, err)
   250  		assert.Equal(t, expected, res)
   251  	})
   252  
   253  	t.Run("with an offset greater than the maximum", func(t *testing.T) {
   254  		reset()
   255  
   256  		_, err := manager.GetObjects(context.Background(), &models.Principal{}, ptInt64(201), ptInt64(2), nil, nil, nil, additional.Properties{}, "")
   257  		require.NotNil(t, err)
   258  		assert.Contains(t, err.Error(), "query maximum results exceeded")
   259  	})
   260  
   261  	t.Run("with a limit greater than the minimum", func(t *testing.T) {
   262  		reset()
   263  
   264  		_, err := manager.GetObjects(context.Background(), &models.Principal{}, ptInt64(0), ptInt64(202), nil, nil, nil, additional.Properties{}, "")
   265  		require.NotNil(t, err)
   266  		assert.Contains(t, err.Error(), "query maximum results exceeded")
   267  	})
   268  
   269  	t.Run("with limit and offset individually smaller, but combined greater", func(t *testing.T) {
   270  		reset()
   271  
   272  		_, err := manager.GetObjects(context.Background(), &models.Principal{}, ptInt64(150), ptInt64(150), nil, nil, nil, additional.Properties{}, "")
   273  		require.NotNil(t, err)
   274  		assert.Contains(t, err.Error(), "query maximum results exceeded")
   275  	})
   276  
   277  	t.Run("additional props", func(t *testing.T) {
   278  		t.Run("on get single requests", func(t *testing.T) {
   279  			t.Run("feature projection", func(t *testing.T) {
   280  				reset()
   281  				id := strfmt.UUID("99ee9968-22ec-416a-9032-cff80f2f7fdf")
   282  
   283  				result := &search.Result{
   284  					ID:        id,
   285  					ClassName: "ActionClass",
   286  					Schema:    map[string]interface{}{"foo": "bar"},
   287  				}
   288  				vectorRepo.On("ObjectByID", id, mock.Anything, mock.Anything).Return(result, nil).Once()
   289  				_, err := manager.GetObject(context.Background(), &models.Principal{}, "",
   290  					id, additional.Properties{
   291  						ModuleParams: map[string]interface{}{
   292  							"featureProjection": getDefaultParam("featureProjection"),
   293  						},
   294  					}, nil, "")
   295  				assert.Equal(t, errors.New("get extend: unknown capability: featureProjection"), err)
   296  			})
   297  
   298  			t.Run("semantic path", func(t *testing.T) {
   299  				reset()
   300  				id := strfmt.UUID("99ee9968-22ec-416a-9032-cff80f2f7fdf")
   301  
   302  				result := &search.Result{
   303  					ID:        id,
   304  					ClassName: "ActionClass",
   305  					Schema:    map[string]interface{}{"foo": "bar"},
   306  				}
   307  				vectorRepo.On("ObjectByID", id, mock.Anything, mock.Anything).Return(result, nil).Once()
   308  				_, err := manager.GetObject(context.Background(), &models.Principal{}, "",
   309  					id, additional.Properties{
   310  						ModuleParams: map[string]interface{}{
   311  							"semanticPath": getDefaultParam("semanticPath"),
   312  						},
   313  					}, nil, "")
   314  				assert.Equal(t, errors.New("get extend: unknown capability: semanticPath"), err)
   315  			})
   316  
   317  			t.Run("nearest neighbors", func(t *testing.T) {
   318  				reset()
   319  				id := strfmt.UUID("99ee9968-22ec-416a-9032-cff80f2f7fdf")
   320  
   321  				result := &search.Result{
   322  					ID:        id,
   323  					ClassName: "ActionClass",
   324  					Schema:    map[string]interface{}{"foo": "bar"},
   325  				}
   326  				vectorRepo.On("ObjectByID", id, mock.Anything, mock.Anything).Return(result, nil).Once()
   327  				extender.multi = []search.Result{
   328  					{
   329  						ID:        id,
   330  						ClassName: "ActionClass",
   331  						Schema:    map[string]interface{}{"foo": "bar"},
   332  						AdditionalProperties: models.AdditionalProperties{
   333  							"nearestNeighbors": &NearestNeighbors{
   334  								Neighbors: []*NearestNeighbor{
   335  									{
   336  										Concept:  "foo",
   337  										Distance: 0.3,
   338  									},
   339  								},
   340  							},
   341  						},
   342  					},
   343  				}
   344  
   345  				expected := &models.Object{
   346  					ID:            id,
   347  					Class:         "ActionClass",
   348  					Properties:    map[string]interface{}{"foo": "bar"},
   349  					VectorWeights: (map[string]string)(nil),
   350  					Additional: models.AdditionalProperties{
   351  						"nearestNeighbors": &NearestNeighbors{
   352  							Neighbors: []*NearestNeighbor{
   353  								{
   354  									Concept:  "foo",
   355  									Distance: 0.3,
   356  								},
   357  							},
   358  						},
   359  					},
   360  				}
   361  
   362  				res, err := manager.GetObject(context.Background(), &models.Principal{}, "",
   363  					id, additional.Properties{
   364  						ModuleParams: map[string]interface{}{
   365  							"nearestNeighbors": true,
   366  						},
   367  					}, nil, "")
   368  				require.Nil(t, err)
   369  				assert.Equal(t, expected, res)
   370  			})
   371  		})
   372  
   373  		t.Run("on list requests", func(t *testing.T) {
   374  			t.Run("nearest neighbors", func(t *testing.T) {
   375  				reset()
   376  				id := strfmt.UUID("99ee9968-22ec-416a-9032-cff80f2f7fdf")
   377  
   378  				result := []search.Result{
   379  					{
   380  						ID:        id,
   381  						ClassName: "ActionClass",
   382  						Schema:    map[string]interface{}{"foo": "bar"},
   383  					},
   384  				}
   385  				vectorRepo.On("ObjectSearch", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything,
   386  					mock.Anything).Return(result, nil).Once()
   387  				extender.multi = []search.Result{
   388  					{
   389  						ID:        id,
   390  						ClassName: "ActionClass",
   391  						Schema:    map[string]interface{}{"foo": "bar"},
   392  						AdditionalProperties: models.AdditionalProperties{
   393  							"nearestNeighbors": &NearestNeighbors{
   394  								Neighbors: []*NearestNeighbor{
   395  									{
   396  										Concept:  "foo",
   397  										Distance: 0.3,
   398  									},
   399  								},
   400  							},
   401  						},
   402  					},
   403  				}
   404  
   405  				expected := []*models.Object{
   406  					{
   407  						ID:            id,
   408  						Class:         "ActionClass",
   409  						Properties:    map[string]interface{}{"foo": "bar"},
   410  						VectorWeights: (map[string]string)(nil),
   411  						Additional: models.AdditionalProperties{
   412  							"nearestNeighbors": &NearestNeighbors{
   413  								Neighbors: []*NearestNeighbor{
   414  									{
   415  										Concept:  "foo",
   416  										Distance: 0.3,
   417  									},
   418  								},
   419  							},
   420  						},
   421  					},
   422  				}
   423  
   424  				res, err := manager.GetObjects(context.Background(), &models.Principal{}, nil, ptInt64(10), nil, nil, nil, additional.Properties{
   425  					ModuleParams: map[string]interface{}{
   426  						"nearestNeighbors": true,
   427  					},
   428  				}, "")
   429  				require.Nil(t, err)
   430  				assert.Equal(t, expected, res)
   431  			})
   432  
   433  			t.Run("feature projection", func(t *testing.T) {
   434  				reset()
   435  				id := strfmt.UUID("99ee9968-22ec-416a-9032-cff80f2f7fdf")
   436  
   437  				result := []search.Result{
   438  					{
   439  						ID:        id,
   440  						ClassName: "ActionClass",
   441  						Schema:    map[string]interface{}{"foo": "bar"},
   442  					},
   443  				}
   444  				vectorRepo.On("ObjectSearch", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything,
   445  					mock.Anything).Return(result, nil).Once()
   446  				projectorFake.multi = []search.Result{
   447  					{
   448  						ID:        id,
   449  						ClassName: "ActionClass",
   450  						Schema:    map[string]interface{}{"foo": "bar"},
   451  						AdditionalProperties: models.AdditionalProperties{
   452  							"featureProjection": &FeatureProjection{
   453  								Vector: []float32{1, 2, 3},
   454  							},
   455  						},
   456  					},
   457  				}
   458  
   459  				expected := []*models.Object{
   460  					{
   461  						ID:            id,
   462  						Class:         "ActionClass",
   463  						Properties:    map[string]interface{}{"foo": "bar"},
   464  						VectorWeights: (map[string]string)(nil),
   465  						Additional: models.AdditionalProperties{
   466  							"featureProjection": &FeatureProjection{
   467  								Vector: []float32{1, 2, 3},
   468  							},
   469  						},
   470  					},
   471  				}
   472  
   473  				res, err := manager.GetObjects(context.Background(), &models.Principal{}, nil, ptInt64(10), nil, nil, nil, additional.Properties{
   474  					ModuleParams: map[string]interface{}{
   475  						"featureProjection": getDefaultParam("featureProjection"),
   476  					},
   477  				}, "")
   478  				require.Nil(t, err)
   479  				assert.Equal(t, expected, res)
   480  			})
   481  		})
   482  	})
   483  
   484  	t.Run("sort props", func(t *testing.T) {
   485  		t.Run("sort=foo,number&order=asc,desc", func(t *testing.T) {
   486  			reset()
   487  			id := strfmt.UUID("99ee9968-22ec-416a-9032-cff80f2f7fdf")
   488  			sort := "foo,number"
   489  			asc := "asc,desc"
   490  			expectedSort := []filters.Sort{
   491  				{Path: []string{"foo"}, Order: "asc"},
   492  				{Path: []string{"number"}, Order: "desc"},
   493  			}
   494  
   495  			result := []search.Result{
   496  				{
   497  					ID:        id,
   498  					ClassName: "ActionClass",
   499  					Schema: map[string]interface{}{
   500  						"foo":    "bar",
   501  						"number": float64(1),
   502  					},
   503  				},
   504  			}
   505  			vectorRepo.On("ObjectSearch", mock.AnythingOfType("int"), mock.AnythingOfType("int"), expectedSort,
   506  				mock.Anything, mock.Anything, mock.Anything).Return(result, nil).Once()
   507  			projectorFake.multi = []search.Result{
   508  				{
   509  					ID:        id,
   510  					ClassName: "ActionClass",
   511  					Schema: map[string]interface{}{
   512  						"foo":    "bar",
   513  						"number": float64(1),
   514  					},
   515  				},
   516  			}
   517  
   518  			expected := []*models.Object{
   519  				{
   520  					ID:    id,
   521  					Class: "ActionClass",
   522  					Properties: map[string]interface{}{
   523  						"foo":    "bar",
   524  						"number": float64(1),
   525  					},
   526  					VectorWeights: (map[string]string)(nil),
   527  				},
   528  			}
   529  
   530  			res, err := manager.GetObjects(context.Background(), &models.Principal{}, nil, ptInt64(10), &sort, &asc, nil, additional.Properties{}, "")
   531  			require.Nil(t, err)
   532  			assert.Equal(t, expected, res)
   533  		})
   534  
   535  		t.Run("sort=foo,number,prop1,prop2&order=desc", func(t *testing.T) {
   536  			reset()
   537  			id := strfmt.UUID("99ee9968-22ec-416a-9032-cff80f2f7fdf")
   538  			sort := "foo,number,prop1,prop2"
   539  			asc := "desc"
   540  			expectedSort := []filters.Sort{
   541  				{Path: []string{"foo"}, Order: "desc"},
   542  				{Path: []string{"number"}, Order: "asc"},
   543  				{Path: []string{"prop1"}, Order: "asc"},
   544  				{Path: []string{"prop2"}, Order: "asc"},
   545  			}
   546  
   547  			result := []search.Result{
   548  				{
   549  					ID:        id,
   550  					ClassName: "ActionClass",
   551  					Schema: map[string]interface{}{
   552  						"foo":    "bar",
   553  						"number": float64(1),
   554  					},
   555  				},
   556  			}
   557  			vectorRepo.On("ObjectSearch", mock.Anything, mock.Anything, expectedSort, mock.Anything, mock.Anything,
   558  				mock.Anything).Return(result, nil).Once()
   559  			projectorFake.multi = []search.Result{
   560  				{
   561  					ID:        id,
   562  					ClassName: "ActionClass",
   563  					Schema: map[string]interface{}{
   564  						"foo":    "bar",
   565  						"number": float64(1),
   566  					},
   567  				},
   568  			}
   569  
   570  			expected := []*models.Object{
   571  				{
   572  					ID:    id,
   573  					Class: "ActionClass",
   574  					Properties: map[string]interface{}{
   575  						"foo":    "bar",
   576  						"number": float64(1),
   577  					},
   578  					VectorWeights: (map[string]string)(nil),
   579  				},
   580  			}
   581  
   582  			res, err := manager.GetObjects(context.Background(), &models.Principal{}, nil, ptInt64(10), &sort, &asc, nil, additional.Properties{}, "")
   583  			require.Nil(t, err)
   584  			assert.Equal(t, expected, res)
   585  		})
   586  
   587  		t.Run("sort=foo,number", func(t *testing.T) {
   588  			reset()
   589  			sort := "foo,number"
   590  			expectedSort := []filters.Sort{
   591  				{Path: []string{"foo"}, Order: "asc"},
   592  				{Path: []string{"number"}, Order: "asc"},
   593  			}
   594  			result := []search.Result{
   595  				{
   596  					ID:        "uuid",
   597  					ClassName: "ActionClass",
   598  					Schema: map[string]interface{}{
   599  						"foo":    "bar",
   600  						"number": float64(1),
   601  					},
   602  				},
   603  			}
   604  
   605  			vectorRepo.On("ObjectSearch", mock.Anything, mock.Anything, expectedSort, mock.Anything, mock.Anything,
   606  				mock.Anything).Return(result, nil).Once()
   607  
   608  			_, err := manager.GetObjects(context.Background(), &models.Principal{}, nil, ptInt64(10), &sort, nil, nil, additional.Properties{}, "")
   609  			require.Nil(t, err)
   610  		})
   611  
   612  		t.Run("sort=foo,number,prop", func(t *testing.T) {
   613  			reset()
   614  			sort := "foo,number,prop"
   615  			expectedSort := []filters.Sort{
   616  				{Path: []string{"foo"}, Order: "asc"},
   617  				{Path: []string{"number"}, Order: "asc"},
   618  				{Path: []string{"prop"}, Order: "asc"},
   619  			}
   620  			result := []search.Result{
   621  				{
   622  					ID:        "uuid",
   623  					ClassName: "ActionClass",
   624  					Schema: map[string]interface{}{
   625  						"foo":    "bar",
   626  						"number": float64(1),
   627  					},
   628  				},
   629  			}
   630  
   631  			vectorRepo.On("ObjectSearch", mock.Anything, mock.Anything, expectedSort, mock.Anything, mock.Anything,
   632  				mock.Anything).Return(result, nil).Once()
   633  
   634  			_, err := manager.GetObjects(context.Background(), &models.Principal{}, nil, ptInt64(10), &sort, nil, nil, additional.Properties{}, "")
   635  			require.Nil(t, err)
   636  		})
   637  
   638  		t.Run("order=asc", func(t *testing.T) {
   639  			reset()
   640  			order := "asc"
   641  			var expectedSort []filters.Sort
   642  			result := []search.Result{
   643  				{
   644  					ID:        "uuid",
   645  					ClassName: "ActionClass",
   646  					Schema: map[string]interface{}{
   647  						"foo":    "bar",
   648  						"number": float64(1),
   649  					},
   650  				},
   651  			}
   652  
   653  			vectorRepo.On("ObjectSearch", mock.Anything, mock.Anything, expectedSort, mock.Anything, mock.Anything,
   654  				mock.Anything).Return(result, nil).Once()
   655  
   656  			_, err := manager.GetObjects(context.Background(), &models.Principal{}, nil, ptInt64(10), nil, &order, nil, additional.Properties{}, "")
   657  			require.Nil(t, err)
   658  		})
   659  	})
   660  }
   661  
   662  func Test_GetThing(t *testing.T) {
   663  	var (
   664  		vectorRepo    *fakeVectorRepo
   665  		manager       *Manager
   666  		extender      *fakeExtender
   667  		projectorFake *fakeProjector
   668  	)
   669  
   670  	schema := schema.Schema{
   671  		Objects: &models.Schema{
   672  			Classes: []*models.Class{
   673  				{
   674  					Class: "ThingClass",
   675  				},
   676  			},
   677  		},
   678  	}
   679  
   680  	reset := func() {
   681  		vectorRepo = &fakeVectorRepo{}
   682  		schemaManager := &fakeSchemaManager{
   683  			GetSchemaResponse: schema,
   684  		}
   685  		locks := &fakeLocks{}
   686  		cfg := &config.WeaviateConfig{}
   687  		cfg.Config.QueryDefaults.Limit = 20
   688  		cfg.Config.QueryMaximumResults = 200
   689  		authorizer := &fakeAuthorizer{}
   690  		logger, _ := test.NewNullLogger()
   691  		extender = &fakeExtender{}
   692  		projectorFake = &fakeProjector{}
   693  		metrics := &fakeMetrics{}
   694  		manager = NewManager(locks, schemaManager, cfg, logger,
   695  			authorizer, vectorRepo,
   696  			getFakeModulesProviderWithCustomExtenders(extender, projectorFake), metrics)
   697  	}
   698  
   699  	t.Run("get non-existing thing by id", func(t *testing.T) {
   700  		reset()
   701  		id := strfmt.UUID("99ee9968-22ec-416a-9032-cff80f2f7fdf")
   702  
   703  		vectorRepo.On("ObjectByID", id, mock.Anything, mock.Anything).Return((*search.Result)(nil), nil).Once()
   704  
   705  		_, err := manager.GetObject(context.Background(), &models.Principal{}, "", id,
   706  			additional.Properties{}, nil, "")
   707  		assert.Equal(t, NewErrNotFound("no object with id '99ee9968-22ec-416a-9032-cff80f2f7fdf'"), err)
   708  	})
   709  
   710  	t.Run("get existing thing by id", func(t *testing.T) {
   711  		reset()
   712  		id := strfmt.UUID("99ee9968-22ec-416a-9032-cff80f2f7fdf")
   713  
   714  		result := &search.Result{
   715  			ID:        id,
   716  			ClassName: "ThingClass",
   717  			Schema:    map[string]interface{}{"foo": "bar"},
   718  		}
   719  		vectorRepo.On("ObjectByID", id, mock.Anything, mock.Anything).Return(result, nil).Once()
   720  
   721  		expected := &models.Object{
   722  			ID:            id,
   723  			Class:         "ThingClass",
   724  			Properties:    map[string]interface{}{"foo": "bar"},
   725  			VectorWeights: (map[string]string)(nil),
   726  		}
   727  
   728  		res, err := manager.GetObject(context.Background(), &models.Principal{}, "", id,
   729  			additional.Properties{}, nil, "")
   730  		require.Nil(t, err)
   731  		assert.Equal(t, expected, res)
   732  	})
   733  
   734  	t.Run("list all existing things", func(t *testing.T) {
   735  		reset()
   736  		id := strfmt.UUID("99ee9968-22ec-416a-9032-cff80f2f7fdf")
   737  
   738  		results := []search.Result{
   739  			{
   740  				ID:        id,
   741  				ClassName: "ThingClass",
   742  				Schema:    map[string]interface{}{"foo": "bar"},
   743  			},
   744  		}
   745  		vectorRepo.On("ObjectSearch", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything,
   746  			mock.Anything).Return(results, nil).Once()
   747  
   748  		expected := []*models.Object{
   749  			{
   750  				ID:            id,
   751  				Class:         "ThingClass",
   752  				Properties:    map[string]interface{}{"foo": "bar"},
   753  				VectorWeights: (map[string]string)(nil),
   754  			},
   755  		}
   756  
   757  		res, err := manager.GetObjects(context.Background(), &models.Principal{}, nil, nil, nil, nil, nil, additional.Properties{}, "")
   758  		require.Nil(t, err)
   759  		assert.Equal(t, expected, res)
   760  	})
   761  
   762  	t.Run("additional props", func(t *testing.T) {
   763  		t.Run("on get single requests", func(t *testing.T) {
   764  			t.Run("feature projection", func(t *testing.T) {
   765  				reset()
   766  				id := strfmt.UUID("99ee9968-22ec-416a-9032-cff80f2f7fdf")
   767  
   768  				result := &search.Result{
   769  					ID:        id,
   770  					ClassName: "ThingClass",
   771  					Schema:    map[string]interface{}{"foo": "bar"},
   772  				}
   773  				vectorRepo.On("ObjectByID", id, mock.Anything, mock.Anything).Return(result, nil).Once()
   774  				_, err := manager.GetObject(context.Background(), &models.Principal{}, "",
   775  					id, additional.Properties{
   776  						ModuleParams: map[string]interface{}{
   777  							"featureProjection": getDefaultParam("featureProjection"),
   778  						},
   779  					}, nil, "")
   780  				assert.Equal(t, errors.New("get extend: unknown capability: featureProjection"), err)
   781  			})
   782  
   783  			t.Run("nearest neighbors", func(t *testing.T) {
   784  				reset()
   785  				id := strfmt.UUID("99ee9968-22ec-416a-9032-cff80f2f7fdf")
   786  
   787  				result := &search.Result{
   788  					ID:        id,
   789  					ClassName: "ThingClass",
   790  					Schema:    map[string]interface{}{"foo": "bar"},
   791  				}
   792  				vectorRepo.On("ObjectByID", id, mock.Anything, mock.Anything).Return(result, nil).Once()
   793  				extender.multi = []search.Result{
   794  					{
   795  						ID:        id,
   796  						ClassName: "ThingClass",
   797  						Schema:    map[string]interface{}{"foo": "bar"},
   798  						AdditionalProperties: models.AdditionalProperties{
   799  							"nearestNeighbors": &NearestNeighbors{
   800  								Neighbors: []*NearestNeighbor{
   801  									{
   802  										Concept:  "foo",
   803  										Distance: 0.3,
   804  									},
   805  								},
   806  							},
   807  						},
   808  					},
   809  				}
   810  
   811  				expected := &models.Object{
   812  					ID:            id,
   813  					Class:         "ThingClass",
   814  					Properties:    map[string]interface{}{"foo": "bar"},
   815  					VectorWeights: (map[string]string)(nil),
   816  					Additional: models.AdditionalProperties{
   817  						"nearestNeighbors": &NearestNeighbors{
   818  							Neighbors: []*NearestNeighbor{
   819  								{
   820  									Concept:  "foo",
   821  									Distance: 0.3,
   822  								},
   823  							},
   824  						},
   825  					},
   826  				}
   827  
   828  				res, err := manager.GetObject(context.Background(), &models.Principal{}, "",
   829  					id, additional.Properties{
   830  						ModuleParams: map[string]interface{}{
   831  							"nearestNeighbors": true,
   832  						},
   833  					}, nil, "")
   834  				require.Nil(t, err)
   835  				assert.Equal(t, expected, res)
   836  			})
   837  		})
   838  
   839  		t.Run("on list requests", func(t *testing.T) {
   840  			t.Run("nearest neighbors", func(t *testing.T) {
   841  				reset()
   842  				id := strfmt.UUID("99ee9968-22ec-416a-9032-cff80f2f7fdf")
   843  
   844  				result := []search.Result{
   845  					{
   846  						ID:        id,
   847  						ClassName: "ThingClass",
   848  						Schema:    map[string]interface{}{"foo": "bar"},
   849  					},
   850  				}
   851  				vectorRepo.On("ObjectSearch", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything,
   852  					mock.Anything).Return(result, nil).Once()
   853  				extender.multi = []search.Result{
   854  					{
   855  						ID:        id,
   856  						ClassName: "ThingClass",
   857  						Schema:    map[string]interface{}{"foo": "bar"},
   858  						AdditionalProperties: models.AdditionalProperties{
   859  							"nearestNeighbors": &NearestNeighbors{
   860  								Neighbors: []*NearestNeighbor{
   861  									{
   862  										Concept:  "foo",
   863  										Distance: 0.3,
   864  									},
   865  								},
   866  							},
   867  						},
   868  					},
   869  				}
   870  
   871  				expected := []*models.Object{
   872  					{
   873  						ID:            id,
   874  						Class:         "ThingClass",
   875  						Properties:    map[string]interface{}{"foo": "bar"},
   876  						VectorWeights: (map[string]string)(nil),
   877  						Additional: models.AdditionalProperties{
   878  							"nearestNeighbors": &NearestNeighbors{
   879  								Neighbors: []*NearestNeighbor{
   880  									{
   881  										Concept:  "foo",
   882  										Distance: 0.3,
   883  									},
   884  								},
   885  							},
   886  						},
   887  					},
   888  				}
   889  
   890  				res, err := manager.GetObjects(context.Background(), &models.Principal{}, nil, ptInt64(10), nil, nil, nil, additional.Properties{
   891  					ModuleParams: map[string]interface{}{
   892  						"nearestNeighbors": true,
   893  					},
   894  				}, "")
   895  				require.Nil(t, err)
   896  				assert.Equal(t, expected, res)
   897  			})
   898  
   899  			t.Run("feature projection", func(t *testing.T) {
   900  				reset()
   901  				id := strfmt.UUID("99ee9968-22ec-416a-9032-cff80f2f7fdf")
   902  
   903  				result := []search.Result{
   904  					{
   905  						ID:        id,
   906  						ClassName: "ThingClass",
   907  						Schema:    map[string]interface{}{"foo": "bar"},
   908  					},
   909  				}
   910  				vectorRepo.On("ObjectSearch", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything,
   911  					mock.Anything).Return(result, nil).Once()
   912  				projectorFake.multi = []search.Result{
   913  					{
   914  						ID:        id,
   915  						ClassName: "ThingClass",
   916  						Schema:    map[string]interface{}{"foo": "bar"},
   917  						AdditionalProperties: models.AdditionalProperties{
   918  							"featureProjection": &FeatureProjection{
   919  								Vector: []float32{1, 2, 3},
   920  							},
   921  						},
   922  					},
   923  				}
   924  
   925  				expected := []*models.Object{
   926  					{
   927  						ID:            id,
   928  						Class:         "ThingClass",
   929  						Properties:    map[string]interface{}{"foo": "bar"},
   930  						VectorWeights: (map[string]string)(nil),
   931  						Additional: models.AdditionalProperties{
   932  							"featureProjection": &FeatureProjection{
   933  								Vector: []float32{1, 2, 3},
   934  							},
   935  						},
   936  					},
   937  				}
   938  
   939  				res, err := manager.GetObjects(context.Background(), &models.Principal{}, nil, ptInt64(10), nil, nil, nil, additional.Properties{
   940  					ModuleParams: map[string]interface{}{
   941  						"featureProjection": getDefaultParam("featureProjection"),
   942  					},
   943  				}, "")
   944  				require.Nil(t, err)
   945  				assert.Equal(t, expected, res)
   946  			})
   947  		})
   948  	})
   949  }
   950  
   951  func Test_GetObject(t *testing.T) {
   952  	var (
   953  		principal = models.Principal{}
   954  		adds      = additional.Properties{}
   955  		className = "MyClass"
   956  		id        = strfmt.UUID("99ee9968-22ec-416a-9032-cff80f2f7fdf")
   957  		schema    = schema.Schema{
   958  			Objects: &models.Schema{
   959  				Classes: []*models.Class{
   960  					{
   961  						Class: className,
   962  					},
   963  				},
   964  			},
   965  		}
   966  		result = &search.Result{
   967  			ID:        id,
   968  			ClassName: className,
   969  			Schema:    map[string]interface{}{"foo": "bar"},
   970  		}
   971  	)
   972  
   973  	t.Run("without projection", func(t *testing.T) {
   974  		m := newFakeGetManager(schema)
   975  		m.repo.On("Object", className, id, mock.Anything, mock.Anything, "").Return((*search.Result)(nil), nil).Once()
   976  		_, err := m.GetObject(context.Background(), &principal, className, id, adds, nil, "")
   977  		if err == nil {
   978  			t.Errorf("GetObject() must return an error for non existing object")
   979  		}
   980  
   981  		m.repo.On("Object", className, id, mock.Anything, mock.Anything, "").Return(result, nil).Once()
   982  		expected := &models.Object{
   983  			ID:            id,
   984  			Class:         className,
   985  			Properties:    map[string]interface{}{"foo": "bar"},
   986  			VectorWeights: (map[string]string)(nil),
   987  		}
   988  
   989  		got, err := m.GetObject(context.Background(), &principal, className, id, adds, nil, "")
   990  		require.Nil(t, err)
   991  		assert.Equal(t, expected, got)
   992  	})
   993  
   994  	t.Run("with projection", func(t *testing.T) {
   995  		m := newFakeGetManager(schema)
   996  		m.extender.multi = []search.Result{
   997  			{
   998  				ID:        id,
   999  				ClassName: className,
  1000  				Schema:    map[string]interface{}{"foo": "bar"},
  1001  				AdditionalProperties: models.AdditionalProperties{
  1002  					"nearestNeighbors": &NearestNeighbors{
  1003  						Neighbors: []*NearestNeighbor{
  1004  							{
  1005  								Concept:  "foo",
  1006  								Distance: 0.3,
  1007  							},
  1008  						},
  1009  					},
  1010  				},
  1011  			},
  1012  		}
  1013  		m.repo.On("Object", className, id, mock.Anything, mock.Anything, "").Return(result, nil).Once()
  1014  		_, err := m.GetObject(context.Background(), &principal, className, id,
  1015  			additional.Properties{
  1016  				ModuleParams: map[string]interface{}{
  1017  					"Unknown": getDefaultParam("Unknown"),
  1018  				},
  1019  			}, nil, "")
  1020  		if err == nil {
  1021  			t.Errorf("GetObject() must return unknown feature projection error")
  1022  		}
  1023  
  1024  		m.repo.On("Object", className, id, mock.Anything, mock.Anything, "").Return(result, nil).Once()
  1025  		expected := &models.Object{
  1026  			ID:            id,
  1027  			Class:         className,
  1028  			Properties:    map[string]interface{}{"foo": "bar"},
  1029  			VectorWeights: (map[string]string)(nil),
  1030  			Additional: models.AdditionalProperties{
  1031  				"nearestNeighbors": &NearestNeighbors{
  1032  					Neighbors: []*NearestNeighbor{
  1033  						{
  1034  							Concept:  "foo",
  1035  							Distance: 0.3,
  1036  						},
  1037  					},
  1038  				},
  1039  			},
  1040  		}
  1041  
  1042  		res, err := m.GetObject(context.Background(), &principal, className, id,
  1043  			additional.Properties{
  1044  				ModuleParams: map[string]interface{}{
  1045  					"nearestNeighbors": true,
  1046  				},
  1047  			}, nil, "")
  1048  		require.Nil(t, err)
  1049  		assert.Equal(t, expected, res)
  1050  	})
  1051  }
  1052  
  1053  func ptInt64(in int64) *int64 {
  1054  	return &in
  1055  }
  1056  
  1057  type fakeGetManager struct {
  1058  	*Manager
  1059  	repo            *fakeVectorRepo
  1060  	extender        *fakeExtender
  1061  	projector       *fakeProjector
  1062  	authorizer      *fakeAuthorizer
  1063  	locks           *fakeLocks
  1064  	metrics         *fakeMetrics
  1065  	modulesProvider *fakeModulesProvider
  1066  }
  1067  
  1068  func newFakeGetManager(schema schema.Schema, opts ...func(*fakeGetManager)) fakeGetManager {
  1069  	r := fakeGetManager{
  1070  		repo:            new(fakeVectorRepo),
  1071  		extender:        new(fakeExtender),
  1072  		projector:       new(fakeProjector),
  1073  		authorizer:      new(fakeAuthorizer),
  1074  		locks:           new(fakeLocks),
  1075  		metrics:         new(fakeMetrics),
  1076  		modulesProvider: new(fakeModulesProvider),
  1077  	}
  1078  
  1079  	for _, opt := range opts {
  1080  		opt(&r)
  1081  	}
  1082  
  1083  	schemaManager := &fakeSchemaManager{
  1084  		GetSchemaResponse: schema,
  1085  	}
  1086  	cfg := &config.WeaviateConfig{}
  1087  	cfg.Config.QueryDefaults.Limit = 20
  1088  	cfg.Config.QueryMaximumResults = 200
  1089  	cfg.Config.TrackVectorDimensions = true
  1090  	logger, _ := test.NewNullLogger()
  1091  	r.modulesProvider = getFakeModulesProviderWithCustomExtenders(r.extender, r.projector)
  1092  	r.Manager = NewManager(r.locks, schemaManager, cfg, logger,
  1093  		r.authorizer, r.repo, r.modulesProvider, r.metrics)
  1094  
  1095  	return r
  1096  }