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 }