github.com/weaviate/weaviate@v1.24.6/usecases/objects/add_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  	"strings"
    17  	"testing"
    18  
    19  	"github.com/go-openapi/strfmt"
    20  	"github.com/google/uuid"
    21  	"github.com/sirupsen/logrus/hooks/test"
    22  	"github.com/stretchr/testify/assert"
    23  	"github.com/stretchr/testify/mock"
    24  	"github.com/stretchr/testify/require"
    25  	"github.com/weaviate/weaviate/entities/models"
    26  	"github.com/weaviate/weaviate/entities/schema"
    27  	"github.com/weaviate/weaviate/entities/vectorindex/hnsw"
    28  	"github.com/weaviate/weaviate/usecases/config"
    29  )
    30  
    31  func Test_Add_Object_WithNoVectorizerModule(t *testing.T) {
    32  	var (
    33  		vectorRepo      *fakeVectorRepo
    34  		modulesProvider *fakeModulesProvider
    35  		manager         *Manager
    36  	)
    37  
    38  	sch := schema.Schema{
    39  		Objects: &models.Schema{
    40  			Classes: []*models.Class{
    41  				{
    42  					Class:             "Foo",
    43  					Vectorizer:        config.VectorizerModuleNone,
    44  					VectorIndexConfig: hnsw.UserConfig{},
    45  				},
    46  				{
    47  					Class:      "FooSkipped",
    48  					Vectorizer: config.VectorizerModuleNone,
    49  					VectorIndexConfig: hnsw.UserConfig{
    50  						Skip: true,
    51  					},
    52  				},
    53  			},
    54  		},
    55  	}
    56  
    57  	resetAutoSchema := func(autoSchemaEnabled bool) {
    58  		vectorRepo = &fakeVectorRepo{}
    59  		vectorRepo.On("PutObject", mock.Anything, mock.Anything).Return(nil).Once()
    60  		schemaManager := &fakeSchemaManager{
    61  			GetSchemaResponse: sch,
    62  		}
    63  		locks := &fakeLocks{}
    64  		cfg := &config.WeaviateConfig{
    65  			Config: config.Config{
    66  				AutoSchema: config.AutoSchema{
    67  					Enabled:       autoSchemaEnabled,
    68  					DefaultString: schema.DataTypeText.String(),
    69  				},
    70  			},
    71  		}
    72  		authorizer := &fakeAuthorizer{}
    73  		logger, _ := test.NewNullLogger()
    74  		modulesProvider = getFakeModulesProvider()
    75  		metrics := &fakeMetrics{}
    76  		manager = NewManager(locks, schemaManager, cfg, logger, authorizer,
    77  			vectorRepo, modulesProvider, metrics)
    78  	}
    79  
    80  	reset := func() {
    81  		resetAutoSchema(false)
    82  	}
    83  
    84  	t.Run("without an id set", func(t *testing.T) {
    85  		reset()
    86  
    87  		ctx := context.Background()
    88  		class := &models.Object{
    89  			Vector: []float32{0.1, 0.2, 0.3},
    90  			Class:  "Foo",
    91  		}
    92  
    93  		modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)).
    94  			Return(nil, nil)
    95  
    96  		res, err := manager.AddObject(ctx, nil, class, nil)
    97  		require.Nil(t, err)
    98  		uuidDuringCreation := vectorRepo.Mock.Calls[0].Arguments.Get(0).(*models.Object).ID
    99  
   100  		assert.Len(t, uuidDuringCreation, 36, "check that a uuid was assigned")
   101  		assert.Equal(t, uuidDuringCreation, res.ID, "check that connector add ID and user response match")
   102  	})
   103  
   104  	t.Run("with an explicit (correct) ID set", func(t *testing.T) {
   105  		reset()
   106  
   107  		ctx := context.Background()
   108  		id := strfmt.UUID("5a1cd361-1e0d-42ae-bd52-ee09cb5f31cc")
   109  		object := &models.Object{
   110  			Vector: []float32{0.1, 0.2, 0.3},
   111  			ID:     id,
   112  			Class:  "Foo",
   113  		}
   114  		vectorRepo.On("Exists", "Foo", id).Return(false, nil).Once()
   115  		modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)).
   116  			Return(nil, nil)
   117  
   118  		res, err := manager.AddObject(ctx, nil, object, nil)
   119  		require.Nil(t, err)
   120  		uuidDuringCreation := vectorRepo.Mock.Calls[1].Arguments.Get(0).(*models.Object).ID
   121  
   122  		assert.Equal(t, id, uuidDuringCreation, "check that a uuid is the user specified one")
   123  		assert.Equal(t, res.ID, uuidDuringCreation, "check that connector add ID and user response match")
   124  	})
   125  
   126  	t.Run("with an explicit (correct) uppercase id set", func(t *testing.T) {
   127  		reset()
   128  
   129  		ctx := context.Background()
   130  		id := strfmt.UUID("4A334D0B-6347-40A0-A5AE-339677B20EDE")
   131  		lowered := strfmt.UUID(strings.ToLower(id.String()))
   132  		object := &models.Object{
   133  			ID:     id,
   134  			Class:  "Foo",
   135  			Vector: []float32{0.1, 0.2, 0.3},
   136  		}
   137  		vectorRepo.On("Exists", "Foo", lowered).Return(false, nil).Once()
   138  		modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)).
   139  			Return(nil, nil)
   140  
   141  		res, err := manager.AddObject(ctx, nil, object, nil)
   142  		require.Nil(t, err)
   143  		assert.Equal(t, res.ID, lowered, "check that id was lowered and added")
   144  	})
   145  
   146  	t.Run("with an explicit (correct) ID set and a property that doesn't exist", func(t *testing.T) {
   147  		resetAutoSchema(true)
   148  
   149  		ctx := context.Background()
   150  		id := strfmt.UUID("5aaad361-1e0d-42ae-bb52-ee09cb5f31cc")
   151  		object := &models.Object{
   152  			Vector: []float32{0.1, 0.2, 0.3},
   153  			ID:     id,
   154  			Class:  "Foo",
   155  			Properties: map[string]interface{}{
   156  				"newProperty": "string value",
   157  			},
   158  		}
   159  		vectorRepo.On("Exists", "Foo", id).Return(false, nil).Once()
   160  		modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)).
   161  			Return(nil, nil)
   162  
   163  		res, err := manager.AddObject(ctx, nil, object, nil)
   164  		require.Nil(t, err)
   165  		uuidDuringCreation := vectorRepo.Mock.Calls[1].Arguments.Get(0).(*models.Object).ID
   166  
   167  		assert.Equal(t, id, uuidDuringCreation, "check that a uuid is the user specified one")
   168  		assert.Equal(t, res.ID, uuidDuringCreation, "check that connector add ID and user response match")
   169  	})
   170  
   171  	t.Run("with a uuid that's already taken", func(t *testing.T) {
   172  		reset()
   173  
   174  		ctx := context.Background()
   175  		id := strfmt.UUID("5a1cd361-1e0d-42ae-bd52-ee09cb5f31cc")
   176  		class := &models.Object{
   177  			ID:    id,
   178  			Class: "Foo",
   179  		}
   180  
   181  		vectorRepo.On("Exists", "Foo", id).Return(true, nil).Once()
   182  		modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)).
   183  			Return(nil, nil)
   184  
   185  		_, err := manager.AddObject(ctx, nil, class, nil)
   186  		assert.Equal(t, NewErrInvalidUserInput("id '%s' already exists", id), err)
   187  	})
   188  
   189  	t.Run("with a uuid that's malformed", func(t *testing.T) {
   190  		reset()
   191  
   192  		ctx := context.Background()
   193  		id := strfmt.UUID("5a1cd361-1e0d-4fooooooo2ae-bd52-ee09cb5f31cc")
   194  		class := &models.Object{
   195  			ID:    id,
   196  			Class: "Foo",
   197  		}
   198  
   199  		vectorRepo.On("Exists", "Foo", id).Return(false, nil).Once()
   200  
   201  		_, err := manager.AddObject(ctx, nil, class, nil)
   202  		assert.Equal(t, NewErrInvalidUserInput("invalid object: invalid UUID length: %d", len(id)), err)
   203  	})
   204  
   205  	t.Run("without a vector", func(t *testing.T) {
   206  		// Note that this was an invalid case before v1.10 which added this
   207  		// functionality, as part of
   208  		// https://github.com/weaviate/weaviate/issues/1800
   209  		reset()
   210  
   211  		ctx := context.Background()
   212  		class := &models.Object{
   213  			Class: "Foo",
   214  		}
   215  
   216  		modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)).
   217  			Return(nil, nil)
   218  
   219  		_, err := manager.AddObject(ctx, nil, class, nil)
   220  		assert.Nil(t, err)
   221  	})
   222  
   223  	t.Run("without a vector, but indexing skipped", func(t *testing.T) {
   224  		reset()
   225  
   226  		ctx := context.Background()
   227  		class := &models.Object{
   228  			Class: "FooSkipped",
   229  		}
   230  
   231  		modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)).
   232  			Return(nil, nil)
   233  
   234  		_, err := manager.AddObject(ctx, nil, class, nil)
   235  		assert.Nil(t, err)
   236  	})
   237  }
   238  
   239  func Test_Add_Object_WithExternalVectorizerModule(t *testing.T) {
   240  	var (
   241  		vectorRepo      *fakeVectorRepo
   242  		modulesProvider *fakeModulesProvider
   243  		manager         *Manager
   244  	)
   245  
   246  	schema := schema.Schema{
   247  		Objects: &models.Schema{
   248  			Classes: []*models.Class{
   249  				{
   250  					Class:             "Foo",
   251  					Vectorizer:        config.VectorizerModuleText2VecContextionary,
   252  					VectorIndexConfig: hnsw.UserConfig{},
   253  				},
   254  			},
   255  		},
   256  	}
   257  
   258  	reset := func() {
   259  		vectorRepo = &fakeVectorRepo{}
   260  		vectorRepo.On("PutObject", mock.Anything, mock.Anything).Return(nil).Once()
   261  		schemaManager := &fakeSchemaManager{
   262  			GetSchemaResponse: schema,
   263  		}
   264  		locks := &fakeLocks{}
   265  		cfg := &config.WeaviateConfig{}
   266  		authorizer := &fakeAuthorizer{}
   267  		logger, _ := test.NewNullLogger()
   268  		metrics := &fakeMetrics{}
   269  		modulesProvider = getFakeModulesProvider()
   270  		modulesProvider.On("UsingRef2Vec", mock.Anything).Return(false)
   271  		manager = NewManager(locks, schemaManager, cfg, logger, authorizer,
   272  			vectorRepo, modulesProvider, metrics)
   273  	}
   274  
   275  	t.Run("without an id set", func(t *testing.T) {
   276  		reset()
   277  
   278  		ctx := context.Background()
   279  		object := &models.Object{
   280  			Class: "Foo",
   281  		}
   282  
   283  		modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)).
   284  			Return(nil, nil)
   285  
   286  		res, err := manager.AddObject(ctx, nil, object, nil)
   287  		require.Nil(t, err)
   288  
   289  		uuidDuringCreation := vectorRepo.Mock.Calls[0].Arguments.Get(0).(*models.Object).ID
   290  
   291  		assert.Len(t, uuidDuringCreation, 36, "check that a uuid was assigned")
   292  		assert.Equal(t, uuidDuringCreation, res.ID, "check that connector add ID and user response match")
   293  	})
   294  
   295  	t.Run("with an explicit (correct) ID set", func(t *testing.T) {
   296  		reset()
   297  
   298  		ctx := context.Background()
   299  		id := strfmt.UUID("5a1cd361-1e0d-42ae-bd52-ee09cb5f31cc")
   300  		object := &models.Object{
   301  			ID:    id,
   302  			Class: "Foo",
   303  		}
   304  		vectorRepo.On("Exists", "Foo", id).Return(false, nil).Once()
   305  		modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)).
   306  			Return(nil, nil)
   307  
   308  		res, err := manager.AddObject(ctx, nil, object, nil)
   309  		uuidDuringCreation := vectorRepo.Mock.Calls[1].Arguments.Get(0).(*models.Object).ID
   310  
   311  		assert.Nil(t, err)
   312  		assert.Equal(t, id, uuidDuringCreation, "check that a uuid is the user specified one")
   313  		assert.Equal(t, res.ID, uuidDuringCreation, "check that connector add ID and user response match")
   314  	})
   315  
   316  	t.Run("with a uuid that's already taken", func(t *testing.T) {
   317  		reset()
   318  
   319  		ctx := context.Background()
   320  		id := strfmt.UUID("5a1cd361-1e0d-42ae-bd52-ee09cb5f31cc")
   321  		object := &models.Object{
   322  			ID:    id,
   323  			Class: "Foo",
   324  		}
   325  
   326  		vectorRepo.On("Exists", "Foo", id).Return(true, nil).Once()
   327  		modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)).
   328  			Return(nil, nil)
   329  
   330  		_, err := manager.AddObject(ctx, nil, object, nil)
   331  		assert.Equal(t, NewErrInvalidUserInput("id '%s' already exists", id), err)
   332  	})
   333  
   334  	t.Run("with a uuid that's malformed", func(t *testing.T) {
   335  		reset()
   336  
   337  		ctx := context.Background()
   338  		id := strfmt.UUID("5a1cd361-1e0d-4f00000002ae-bd52-ee09cb5f31cc")
   339  		object := &models.Object{
   340  			ID:    id,
   341  			Class: "Foo",
   342  		}
   343  
   344  		vectorRepo.On("Exists", "Foo", id).Return(false, nil).Once()
   345  		modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)).
   346  			Return(nil, nil)
   347  
   348  		_, err := manager.AddObject(ctx, nil, object, nil)
   349  		assert.Equal(t, NewErrInvalidUserInput("invalid object: invalid UUID length: %d", len(id)), err)
   350  	})
   351  }
   352  
   353  func Test_Add_Object_OverrideVectorizer(t *testing.T) {
   354  	var (
   355  		vectorRepo      *fakeVectorRepo
   356  		modulesProvider *fakeModulesProvider
   357  		manager         *Manager
   358  	)
   359  
   360  	schema := schema.Schema{
   361  		Objects: &models.Schema{
   362  			Classes: []*models.Class{
   363  				{
   364  					Class:             "FooOverride",
   365  					Vectorizer:        config.VectorizerModuleText2VecContextionary,
   366  					VectorIndexConfig: hnsw.UserConfig{},
   367  				},
   368  			},
   369  		},
   370  	}
   371  
   372  	reset := func() {
   373  		vectorRepo = &fakeVectorRepo{}
   374  		vectorRepo.On("PutObject", mock.Anything, mock.Anything).Return(nil).Once()
   375  		schemaManager := &fakeSchemaManager{
   376  			GetSchemaResponse: schema,
   377  		}
   378  		locks := &fakeLocks{}
   379  		cfg := &config.WeaviateConfig{}
   380  		authorizer := &fakeAuthorizer{}
   381  		logger, _ := test.NewNullLogger()
   382  		modulesProvider = getFakeModulesProvider()
   383  		metrics := &fakeMetrics{}
   384  		manager = NewManager(locks, schemaManager, cfg, logger,
   385  			authorizer, vectorRepo, modulesProvider, metrics)
   386  	}
   387  
   388  	t.Run("overriding the vector by explicitly specifying it", func(t *testing.T) {
   389  		reset()
   390  
   391  		ctx := context.Background()
   392  		object := &models.Object{
   393  			Class:  "FooOverride",
   394  			Vector: []float32{9, 9, 9},
   395  		}
   396  
   397  		modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)).
   398  			Return(object.Vector, nil)
   399  
   400  		_, err := manager.AddObject(ctx, nil, object, nil)
   401  		require.Nil(t, err)
   402  
   403  		vec := vectorRepo.Mock.Calls[0].Arguments.Get(1).([]float32)
   404  
   405  		assert.Equal(t, []float32{9, 9, 9}, vec, "check that vector was overridden")
   406  	})
   407  }
   408  
   409  func Test_AddObjectEmptyProperties(t *testing.T) {
   410  	var (
   411  		vectorRepo      *fakeVectorRepo
   412  		modulesProvider *fakeModulesProvider
   413  		manager         *Manager
   414  	)
   415  	schema := schema.Schema{
   416  		Objects: &models.Schema{
   417  			Classes: []*models.Class{
   418  				{
   419  					Class:             "TestClass",
   420  					VectorIndexConfig: hnsw.UserConfig{},
   421  
   422  					Properties: []*models.Property{
   423  						{
   424  							Name:         "strings",
   425  							DataType:     schema.DataTypeTextArray.PropString(),
   426  							Tokenization: models.PropertyTokenizationWhitespace,
   427  						},
   428  					},
   429  				},
   430  			},
   431  		},
   432  	}
   433  	reset := func() {
   434  		vectorRepo = &fakeVectorRepo{}
   435  		vectorRepo.On("PutObject", mock.Anything, mock.Anything).Return(nil).Once()
   436  		schemaManager := &fakeSchemaManager{
   437  			GetSchemaResponse: schema,
   438  		}
   439  		locks := &fakeLocks{}
   440  		cfg := &config.WeaviateConfig{}
   441  		authorizer := &fakeAuthorizer{}
   442  		logger, _ := test.NewNullLogger()
   443  		modulesProvider = getFakeModulesProvider()
   444  		metrics := &fakeMetrics{}
   445  		manager = NewManager(locks, schemaManager, cfg, logger,
   446  			authorizer, vectorRepo, modulesProvider, metrics)
   447  	}
   448  	reset()
   449  	ctx := context.Background()
   450  	object := &models.Object{
   451  		Class:  "TestClass",
   452  		Vector: []float32{9, 9, 9},
   453  	}
   454  	assert.Nil(t, object.Properties)
   455  	modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)).
   456  		Return(nil, nil)
   457  	addedObject, err := manager.AddObject(ctx, nil, object, nil)
   458  	assert.Nil(t, err)
   459  	assert.NotNil(t, addedObject.Properties)
   460  }
   461  
   462  func Test_AddObjectWithUUIDProps(t *testing.T) {
   463  	var (
   464  		vectorRepo      *fakeVectorRepo
   465  		modulesProvider *fakeModulesProvider
   466  		manager         *Manager
   467  	)
   468  	schema := schema.Schema{
   469  		Objects: &models.Schema{
   470  			Classes: []*models.Class{
   471  				{
   472  					Class:             "TestClass",
   473  					VectorIndexConfig: hnsw.UserConfig{},
   474  
   475  					Properties: []*models.Property{
   476  						{
   477  							Name:     "my_id",
   478  							DataType: []string{"uuid"},
   479  						},
   480  						{
   481  							Name:     "my_idz",
   482  							DataType: []string{"uuid[]"},
   483  						},
   484  					},
   485  				},
   486  			},
   487  		},
   488  	}
   489  	reset := func() {
   490  		vectorRepo = &fakeVectorRepo{}
   491  		vectorRepo.On("PutObject", mock.Anything, mock.Anything).Return(nil).Once()
   492  		schemaManager := &fakeSchemaManager{
   493  			GetSchemaResponse: schema,
   494  		}
   495  		locks := &fakeLocks{}
   496  		cfg := &config.WeaviateConfig{}
   497  		authorizer := &fakeAuthorizer{}
   498  		logger, _ := test.NewNullLogger()
   499  		modulesProvider = getFakeModulesProvider()
   500  		metrics := &fakeMetrics{}
   501  		manager = NewManager(locks, schemaManager, cfg, logger,
   502  			authorizer, vectorRepo, modulesProvider, metrics)
   503  	}
   504  	reset()
   505  	ctx := context.Background()
   506  	object := &models.Object{
   507  		Class:  "TestClass",
   508  		Vector: []float32{9, 9, 9},
   509  		Properties: map[string]interface{}{
   510  			"my_id":  "28bafa1e-7956-4c58-8a02-4499a9d15253",
   511  			"my_idz": []any{"28bafa1e-7956-4c58-8a02-4499a9d15253"},
   512  		},
   513  	}
   514  	modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)).
   515  		Return(nil, nil)
   516  	addedObject, err := manager.AddObject(ctx, nil, object, nil)
   517  	require.Nil(t, err)
   518  	require.NotNil(t, addedObject.Properties)
   519  
   520  	expectedID := uuid.MustParse("28bafa1e-7956-4c58-8a02-4499a9d15253")
   521  	expectedIDz := []uuid.UUID{uuid.MustParse("28bafa1e-7956-4c58-8a02-4499a9d15253")}
   522  
   523  	assert.Equal(t, expectedID, addedObject.Properties.(map[string]interface{})["my_id"])
   524  	assert.Equal(t, expectedIDz, addedObject.Properties.(map[string]interface{})["my_idz"])
   525  }