github.com/weaviate/weaviate@v1.24.6/usecases/schema/manager_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 schema 13 14 import ( 15 "context" 16 "testing" 17 18 "github.com/sirupsen/logrus/hooks/test" 19 "github.com/stretchr/testify/assert" 20 "github.com/stretchr/testify/require" 21 "github.com/weaviate/weaviate/entities/models" 22 "github.com/weaviate/weaviate/entities/schema" 23 "github.com/weaviate/weaviate/usecases/cluster" 24 "github.com/weaviate/weaviate/usecases/config" 25 "github.com/weaviate/weaviate/usecases/scaler" 26 "github.com/weaviate/weaviate/usecases/schema/migrate" 27 "github.com/weaviate/weaviate/usecases/sharding" 28 ) 29 30 // TODO: These tests don't match the overall testing style in Weaviate. 31 // Refactor! 32 type NilMigrator struct{} 33 34 func (n *NilMigrator) AddClass(ctx context.Context, class *models.Class, 35 shardingState *sharding.State, 36 ) error { 37 return nil 38 } 39 40 func (n *NilMigrator) DropClass(ctx context.Context, className string) error { 41 return nil 42 } 43 44 func (n *NilMigrator) UpdateClass(ctx context.Context, className string, newClassName *string) error { 45 return nil 46 } 47 48 func (n *NilMigrator) GetShardsQueueSize(ctx context.Context, className, tenant string) (map[string]int64, error) { 49 return nil, nil 50 } 51 52 func (n *NilMigrator) GetShardsStatus(ctx context.Context, className, tenant string) (map[string]string, error) { 53 return nil, nil 54 } 55 56 func (n *NilMigrator) UpdateShardStatus(ctx context.Context, className, shardName, targetStatus string) error { 57 return nil 58 } 59 60 func (n *NilMigrator) AddProperty(ctx context.Context, className string, prop *models.Property) error { 61 return nil 62 } 63 64 func (n *NilMigrator) NewTenants(ctx context.Context, class *models.Class, creates []*migrate.CreateTenantPayload) (commit func(success bool), err error) { 65 return func(bool) {}, nil 66 } 67 68 func (n *NilMigrator) UpdateTenants(ctx context.Context, class *models.Class, updates []*migrate.UpdateTenantPayload) (commit func(success bool), err error) { 69 return func(bool) {}, nil 70 } 71 72 func (n *NilMigrator) DeleteTenants(ctx context.Context, class *models.Class, tenants []string) (commit func(success bool), err error) { 73 return func(bool) {}, nil 74 } 75 76 func (n *NilMigrator) UpdateProperty(ctx context.Context, className string, propName string, newName *string) error { 77 return nil 78 } 79 80 func (n *NilMigrator) UpdatePropertyAddDataType(ctx context.Context, className string, propName string, newDataType string) error { 81 return nil 82 } 83 84 func (n *NilMigrator) DropProperty(ctx context.Context, className string, propName string) error { 85 return nil 86 } 87 88 func (n *NilMigrator) ValidateVectorIndexConfigUpdate(ctx context.Context, 89 old, updated schema.VectorIndexConfig, 90 ) error { 91 return nil 92 } 93 94 func (n *NilMigrator) UpdateVectorIndexConfig(ctx context.Context, className string, 95 updated schema.VectorIndexConfig, 96 ) error { 97 return nil 98 } 99 100 func (n *NilMigrator) ValidateVectorIndexConfigsUpdate(ctx context.Context, 101 old, updated map[string]schema.VectorIndexConfig, 102 ) error { 103 return nil 104 } 105 106 func (n *NilMigrator) UpdateVectorIndexConfigs(ctx context.Context, className string, 107 updated map[string]schema.VectorIndexConfig, 108 ) error { 109 return nil 110 } 111 112 func (n *NilMigrator) ValidateInvertedIndexConfigUpdate(ctx context.Context, old, updated *models.InvertedIndexConfig) error { 113 return nil 114 } 115 116 func (n *NilMigrator) UpdateInvertedIndexConfig(ctx context.Context, className string, updated *models.InvertedIndexConfig) error { 117 return nil 118 } 119 120 func (n *NilMigrator) RecalculateVectorDimensions(ctx context.Context) error { 121 return nil 122 } 123 124 func (n *NilMigrator) InvertedReindex(ctx context.Context, taskNames ...string) error { 125 return nil 126 } 127 128 func (n *NilMigrator) AdjustFilterablePropSettings(ctx context.Context) error { 129 return nil 130 } 131 132 func (n *NilMigrator) RecountProperties(ctx context.Context) error { 133 return nil 134 } 135 136 var schemaTests = []struct { 137 name string 138 fn func(*testing.T, *Manager) 139 }{ 140 {name: "AddObjectClass", fn: testAddObjectClass}, 141 {name: "AddObjectClassWithExplicitVectorizer", fn: testAddObjectClassExplicitVectorizer}, 142 {name: "AddObjectClassWithImplicitVectorizer", fn: testAddObjectClassImplicitVectorizer}, 143 {name: "AddObjectClassWithWrongVectorizer", fn: testAddObjectClassWrongVectorizer}, 144 {name: "AddObjectClassWithWrongIndexType", fn: testAddObjectClassWrongIndexType}, 145 {name: "RemoveObjectClass", fn: testRemoveObjectClass}, 146 {name: "CantAddSameClassTwice", fn: testCantAddSameClassTwice}, 147 {name: "CantAddSameClassTwiceDifferentKind", fn: testCantAddSameClassTwiceDifferentKinds}, 148 {name: "AddPropertyDuringCreation", fn: testAddPropertyDuringCreation}, 149 {name: "AddInvalidPropertyDuringCreation", fn: testAddInvalidPropertyDuringCreation}, 150 {name: "AddInvalidPropertyWithEmptyDataTypeDuringCreation", fn: testAddInvalidPropertyWithEmptyDataTypeDuringCreation}, 151 {name: "DropProperty", fn: testDropProperty}, 152 } 153 154 func testAddObjectClass(t *testing.T, lsm *Manager) { 155 t.Parallel() 156 157 objectClassesNames := testGetClassNames(lsm) 158 assert.NotContains(t, objectClassesNames, "Car") 159 160 err := lsm.AddClass(context.Background(), nil, &models.Class{ 161 Class: "Car", 162 Properties: []*models.Property{{ 163 DataType: schema.DataTypeText.PropString(), 164 Tokenization: models.PropertyTokenizationWhitespace, 165 Name: "dummy", 166 }}, 167 VectorIndexConfig: map[string]interface{}{ 168 "dummy": "this should be parsed", 169 }, 170 }) 171 172 assert.Nil(t, err) 173 174 objectClassesNames = testGetClassNames(lsm) 175 assert.Contains(t, objectClassesNames, "Car") 176 177 objectClasses := testGetClasses(lsm) 178 require.Len(t, objectClasses, 1) 179 assert.Equal(t, config.VectorizerModuleNone, objectClasses[0].Vectorizer) 180 assert.Equal(t, fakeVectorConfig{ 181 raw: map[string]interface{}{ 182 "distance": "cosine", 183 "dummy": "this should be parsed", 184 }, 185 }, objectClasses[0].VectorIndexConfig) 186 assert.Equal(t, int64(60), objectClasses[0].InvertedIndexConfig.CleanupIntervalSeconds, 187 "the default was set") 188 } 189 190 func testAddObjectClassExplicitVectorizer(t *testing.T, lsm *Manager) { 191 t.Parallel() 192 193 objectClassesNames := testGetClassNames(lsm) 194 assert.NotContains(t, objectClassesNames, "Car") 195 196 err := lsm.AddClass(context.Background(), nil, &models.Class{ 197 Vectorizer: config.VectorizerModuleText2VecContextionary, 198 VectorIndexType: "hnsw", 199 Class: "Car", 200 Properties: []*models.Property{{ 201 DataType: schema.DataTypeText.PropString(), 202 Tokenization: models.PropertyTokenizationWhitespace, 203 Name: "dummy", 204 }}, 205 }) 206 207 assert.Nil(t, err) 208 209 objectClassesNames = testGetClassNames(lsm) 210 assert.Contains(t, objectClassesNames, "Car") 211 212 objectClasses := testGetClasses(lsm) 213 require.Len(t, objectClasses, 1) 214 assert.Equal(t, config.VectorizerModuleText2VecContextionary, objectClasses[0].Vectorizer) 215 assert.Equal(t, "hnsw", objectClasses[0].VectorIndexType) 216 } 217 218 func testAddObjectClassImplicitVectorizer(t *testing.T, lsm *Manager) { 219 t.Parallel() 220 lsm.config.DefaultVectorizerModule = config.VectorizerModuleText2VecContextionary 221 222 objectClassesNames := testGetClassNames(lsm) 223 assert.NotContains(t, objectClassesNames, "Car") 224 225 err := lsm.AddClass(context.Background(), nil, &models.Class{ 226 Class: "Car", 227 Properties: []*models.Property{{ 228 DataType: schema.DataTypeText.PropString(), 229 Tokenization: models.PropertyTokenizationWhitespace, 230 Name: "dummy", 231 }}, 232 }) 233 234 assert.Nil(t, err) 235 236 objectClassesNames = testGetClassNames(lsm) 237 assert.Contains(t, objectClassesNames, "Car") 238 239 objectClasses := testGetClasses(lsm) 240 require.Len(t, objectClasses, 1) 241 assert.Equal(t, config.VectorizerModuleText2VecContextionary, objectClasses[0].Vectorizer) 242 assert.Equal(t, "hnsw", objectClasses[0].VectorIndexType) 243 } 244 245 func testAddObjectClassWrongVectorizer(t *testing.T, lsm *Manager) { 246 t.Parallel() 247 248 objectClassesNames := testGetClassNames(lsm) 249 assert.NotContains(t, objectClassesNames, "Car") 250 251 err := lsm.AddClass(context.Background(), nil, &models.Class{ 252 Class: "Car", 253 Vectorizer: "vectorizer-5000000", 254 Properties: []*models.Property{{ 255 DataType: schema.DataTypeText.PropString(), 256 Tokenization: models.PropertyTokenizationWhitespace, 257 Name: "dummy", 258 }}, 259 }) 260 261 require.NotNil(t, err) 262 assert.Equal(t, "vectorizer: invalid vectorizer \"vectorizer-5000000\"", 263 err.Error()) 264 } 265 266 func testAddObjectClassWrongIndexType(t *testing.T, lsm *Manager) { 267 t.Parallel() 268 269 objectClassesNames := testGetClassNames(lsm) 270 assert.NotContains(t, objectClassesNames, "Car") 271 272 err := lsm.AddClass(context.Background(), nil, &models.Class{ 273 Class: "Car", 274 VectorIndexType: "vector-index-2-million", 275 Properties: []*models.Property{{ 276 DataType: schema.DataTypeText.PropString(), 277 Tokenization: models.PropertyTokenizationWhitespace, 278 Name: "dummy", 279 }}, 280 }) 281 282 require.NotNil(t, err) 283 assert.Equal(t, "unrecognized or unsupported vectorIndexType "+ 284 "\"vector-index-2-million\"", err.Error()) 285 } 286 287 func testRemoveObjectClass(t *testing.T, lsm *Manager) { 288 t.Parallel() 289 290 err := lsm.AddClass(context.Background(), nil, &models.Class{ 291 Class: "Car", 292 Vectorizer: "text2vec-contextionary", 293 ModuleConfig: map[string]interface{}{ 294 "text2vec-contextionary": map[string]interface{}{ 295 "vectorizeClassName": true, 296 }, 297 }, 298 }) 299 300 assert.Nil(t, err) 301 302 objectClasses := testGetClassNames(lsm) 303 assert.Contains(t, objectClasses, "Car") 304 305 // Now delete the class 306 err = lsm.DeleteClass(context.Background(), nil, "Car") 307 assert.Nil(t, err) 308 309 objectClasses = testGetClassNames(lsm) 310 assert.NotContains(t, objectClasses, "Car") 311 } 312 313 func testCantAddSameClassTwice(t *testing.T, lsm *Manager) { 314 t.Parallel() 315 316 err := lsm.AddClass(context.Background(), nil, &models.Class{ 317 Class: "Car", 318 Vectorizer: "text2vec-contextionary", 319 ModuleConfig: map[string]interface{}{ 320 "text2vec-contextionary": map[string]interface{}{ 321 "vectorizeClassName": true, 322 }, 323 }, 324 }) 325 326 assert.Nil(t, err) 327 328 // Add it again 329 err = lsm.AddClass(context.Background(), nil, &models.Class{ 330 Class: "Car", 331 Vectorizer: "text2vec-contextionary", 332 ModuleConfig: map[string]interface{}{ 333 "text2vec-contextionary": map[string]interface{}{ 334 "vectorizeClassName": true, 335 }, 336 }, 337 }) 338 339 assert.NotNil(t, err) 340 } 341 342 func testCantAddSameClassTwiceDifferentKinds(t *testing.T, lsm *Manager) { 343 t.Parallel() 344 345 err := lsm.AddClass(context.Background(), nil, &models.Class{ 346 Class: "Car", 347 Vectorizer: "text2vec-contextionary", 348 ModuleConfig: map[string]interface{}{ 349 "text2vec-contextionary": map[string]interface{}{ 350 "vectorizeClassName": true, 351 }, 352 }, 353 }) 354 355 assert.Nil(t, err) 356 357 // Add it again, but with a different kind. 358 err = lsm.AddClass(context.Background(), nil, &models.Class{ 359 ModuleConfig: map[string]interface{}{ 360 "text2vec-contextionary": map[string]interface{}{ 361 "vectorizeClassName": true, 362 }, 363 }, 364 Class: "Car", 365 Vectorizer: "text2vec-contextionary", 366 }) 367 368 assert.NotNil(t, err) 369 } 370 371 // TODO: parts of this test contain text2vec-contextionary logic, but parts are 372 // also general logic 373 func testAddPropertyDuringCreation(t *testing.T, lsm *Manager) { 374 t.Parallel() 375 376 vFalse := false 377 vTrue := true 378 379 var properties []*models.Property = []*models.Property{ 380 { 381 Name: "color", 382 DataType: schema.DataTypeText.PropString(), 383 Tokenization: models.PropertyTokenizationWhitespace, 384 ModuleConfig: map[string]interface{}{ 385 "text2vec-contextionary": map[string]interface{}{ 386 "vectorizePropertyName": true, 387 }, 388 }, 389 }, 390 { 391 Name: "colorRaw1", 392 DataType: schema.DataTypeText.PropString(), 393 Tokenization: models.PropertyTokenizationWhitespace, 394 IndexFilterable: &vFalse, 395 IndexSearchable: &vFalse, 396 ModuleConfig: map[string]interface{}{ 397 "text2vec-contextionary": map[string]interface{}{ 398 "skip": true, 399 }, 400 }, 401 }, 402 { 403 Name: "colorRaw2", 404 DataType: schema.DataTypeText.PropString(), 405 Tokenization: models.PropertyTokenizationWhitespace, 406 IndexFilterable: &vTrue, 407 IndexSearchable: &vFalse, 408 ModuleConfig: map[string]interface{}{ 409 "text2vec-contextionary": map[string]interface{}{ 410 "skip": true, 411 }, 412 }, 413 }, 414 { 415 Name: "colorRaw3", 416 DataType: schema.DataTypeText.PropString(), 417 Tokenization: models.PropertyTokenizationWhitespace, 418 IndexFilterable: &vFalse, 419 IndexSearchable: &vTrue, 420 ModuleConfig: map[string]interface{}{ 421 "text2vec-contextionary": map[string]interface{}{ 422 "skip": true, 423 }, 424 }, 425 }, 426 { 427 Name: "colorRaw4", 428 DataType: schema.DataTypeText.PropString(), 429 Tokenization: models.PropertyTokenizationWhitespace, 430 IndexFilterable: &vTrue, 431 IndexSearchable: &vTrue, 432 ModuleConfig: map[string]interface{}{ 433 "text2vec-contextionary": map[string]interface{}{ 434 "skip": true, 435 }, 436 }, 437 }, 438 { 439 Name: "content", 440 DataType: schema.DataTypeText.PropString(), 441 Tokenization: models.PropertyTokenizationWhitespace, 442 ModuleConfig: map[string]interface{}{ 443 "text2vec-contextionary": map[string]interface{}{ 444 "vectorizePropertyName": false, 445 }, 446 }, 447 }, 448 { 449 Name: "allDefault", 450 DataType: schema.DataTypeText.PropString(), 451 Tokenization: models.PropertyTokenizationWhitespace, 452 }, 453 } 454 455 err := lsm.AddClass(context.Background(), nil, &models.Class{ 456 Class: "Car", 457 Properties: properties, 458 }) 459 assert.Nil(t, err) 460 461 objectClasses := testGetClasses(lsm) 462 require.Len(t, objectClasses, 1) 463 require.Len(t, objectClasses[0].Properties, 7) 464 assert.Equal(t, objectClasses[0].Properties[0].Name, "color") 465 assert.Equal(t, objectClasses[0].Properties[0].DataType, schema.DataTypeText.PropString()) 466 467 assert.True(t, lsm.IndexedInverted("Car", "color"), "color should be indexed") 468 assert.False(t, lsm.IndexedInverted("Car", "colorRaw1"), "colorRaw1 should not be indexed") 469 assert.True(t, lsm.IndexedInverted("Car", "colorRaw2"), "colorRaw2 should be indexed") 470 assert.True(t, lsm.IndexedInverted("Car", "colorRaw3"), "colorRaw3 should be indexed") 471 assert.True(t, lsm.IndexedInverted("Car", "colorRaw4"), "colorRaw4 should be indexed") 472 assert.True(t, lsm.IndexedInverted("Car", "allDefault"), "allDefault should be indexed") 473 } 474 475 func testAddInvalidPropertyDuringCreation(t *testing.T, lsm *Manager) { 476 t.Parallel() 477 478 var properties []*models.Property = []*models.Property{ 479 {Name: "color", DataType: []string{"blurp"}}, 480 } 481 482 err := lsm.AddClass(context.Background(), nil, &models.Class{ 483 Class: "Car", 484 Properties: properties, 485 }) 486 assert.NotNil(t, err) 487 } 488 489 func testAddInvalidPropertyWithEmptyDataTypeDuringCreation(t *testing.T, lsm *Manager) { 490 t.Parallel() 491 492 var properties []*models.Property = []*models.Property{ 493 {Name: "color", DataType: []string{""}}, 494 } 495 496 err := lsm.AddClass(context.Background(), nil, &models.Class{ 497 Class: "Car", 498 Properties: properties, 499 }) 500 assert.NotNil(t, err) 501 } 502 503 func testDropProperty(t *testing.T, lsm *Manager) { 504 // TODO: https://github.com/weaviate/weaviate/issues/973 505 // Remove skip 506 507 t.Skip() 508 509 t.Parallel() 510 511 var properties []*models.Property = []*models.Property{ 512 {Name: "color", DataType: schema.DataTypeText.PropString(), Tokenization: models.PropertyTokenizationWhitespace}, 513 } 514 515 err := lsm.AddClass(context.Background(), nil, &models.Class{ 516 Class: "Car", 517 Properties: properties, 518 }) 519 assert.Nil(t, err) 520 521 objectClasses := testGetClasses(lsm) 522 require.Len(t, objectClasses, 1) 523 assert.Len(t, objectClasses[0].Properties, 1) 524 525 // Now drop the property 526 lsm.DeleteClassProperty(context.Background(), nil, "Car", "color") 527 528 objectClasses = testGetClasses(lsm) 529 require.Len(t, objectClasses, 1) 530 assert.Len(t, objectClasses[0].Properties, 0) 531 } 532 533 // This grant parent test setups up the temporary directory needed for the tests. 534 func TestSchema(t *testing.T) { 535 // We need this test here to make sure that we wait until all child tests 536 // (that can be run in parallel) have finished, before cleaning up the temp directory. 537 t.Run("group", func(t *testing.T) { 538 for _, testCase := range schemaTests { 539 // Create a test case, and inject the etcd schema manager in there 540 // to reduce boilerplate in each separate test. 541 t.Run(testCase.name, func(t *testing.T) { 542 sm := newSchemaManager() 543 sm.StartServing(context.Background()) // will also mark tx manager as ready 544 testCase.fn(t, sm) 545 }) 546 } 547 }) 548 } 549 550 // New Local Schema *Manager 551 func newSchemaManager() *Manager { 552 logger, _ := test.NewNullLogger() 553 vectorizerValidator := &fakeVectorizerValidator{ 554 valid: []string{"text2vec-contextionary", "model1", "model2"}, 555 } 556 dummyConfig := config.Config{ 557 DefaultVectorizerModule: config.VectorizerModuleNone, 558 DefaultVectorDistanceMetric: "cosine", 559 } 560 sm, err := NewManager(&NilMigrator{}, newFakeRepo(), logger, &fakeAuthorizer{}, 561 dummyConfig, dummyParseVectorConfig, // only option for now 562 vectorizerValidator, dummyValidateInvertedConfig, 563 &fakeModuleConfig{}, &fakeClusterState{hosts: []string{"node1"}}, 564 &fakeTxClient{}, &fakeTxPersistence{}, &fakeScaleOutManager{}, 565 ) 566 if err != nil { 567 panic(err.Error()) 568 } 569 570 sm.StartServing(context.Background()) // will also mark tx manager as ready 571 572 return sm 573 } 574 575 func testGetClasses(l *Manager) []*models.Class { 576 var classes []*models.Class 577 schema, _ := l.GetSchema(nil) 578 579 classes = append(classes, schema.SemanticSchemaFor().Classes...) 580 581 return classes 582 } 583 584 func testGetClassNames(l *Manager) []string { 585 var names []string 586 schema, _ := l.GetSchema(nil) 587 588 // Extract all names 589 for _, class := range schema.SemanticSchemaFor().Classes { 590 names = append(names, class.Class) 591 } 592 593 return names 594 } 595 596 func Test_ParseVectorConfigOnDiskLoad(t *testing.T) { 597 logger, _ := test.NewNullLogger() 598 599 repo := newFakeRepo() 600 repo.schema = State{ 601 ObjectSchema: &models.Schema{ 602 Classes: []*models.Class{{ 603 Class: "Foo", 604 VectorIndexConfig: "parse me, i should be in some sort of an object", 605 VectorIndexType: "hnsw", // will always be set when loading from disk 606 }}, 607 }, 608 } 609 sm, err := NewManager(&NilMigrator{}, repo, logger, &fakeAuthorizer{}, 610 config.Config{DefaultVectorizerModule: config.VectorizerModuleNone}, 611 dummyParseVectorConfig, // only option for now 612 &fakeVectorizerValidator{}, dummyValidateInvertedConfig, 613 &fakeModuleConfig{}, &fakeClusterState{hosts: []string{"node1"}}, 614 &fakeTxClient{}, &fakeTxPersistence{}, &fakeScaleOutManager{}, 615 ) 616 require.Nil(t, err) 617 618 classes := sm.GetSchemaSkipAuth().Objects.Classes 619 assert.Equal(t, fakeVectorConfig{ 620 raw: "parse me, i should be in some sort of an object", 621 }, classes[0].VectorIndexConfig) 622 } 623 624 func Test_ExtendSchemaWithExistingPropName(t *testing.T) { 625 logger, _ := test.NewNullLogger() 626 627 repo := newFakeRepo() 628 repo.schema = State{ 629 ObjectSchema: &models.Schema{ 630 Classes: []*models.Class{{ 631 Class: "Foo", 632 VectorIndexConfig: "parse me, i should be in some sort of an object", 633 VectorIndexType: "hnsw", // will always be set when loading from disk 634 Properties: []*models.Property{{ 635 Name: "my_prop", 636 DataType: schema.DataTypeText.PropString(), 637 Tokenization: models.PropertyTokenizationWhitespace, 638 }}, 639 }}, 640 }, 641 } 642 sm, err := NewManager(&NilMigrator{}, repo, logger, &fakeAuthorizer{}, 643 config.Config{DefaultVectorizerModule: config.VectorizerModuleNone}, 644 dummyParseVectorConfig, // only option for now 645 &fakeVectorizerValidator{}, dummyValidateInvertedConfig, 646 &fakeModuleConfig{}, &fakeClusterState{hosts: []string{"node1"}}, 647 &fakeTxClient{}, &fakeTxPersistence{}, &fakeScaleOutManager{}, 648 ) 649 require.Nil(t, err) 650 651 // exactly identical name 652 err = sm.AddClassProperty(context.Background(), nil, "Foo", &models.Property{ 653 Name: "my_prop", 654 DataType: []string{"int"}, 655 }) 656 657 require.NotNil(t, err) 658 assert.Contains(t, err.Error(), "conflict for property") 659 660 // identical if case insensitive 661 err = sm.AddClassProperty(context.Background(), nil, "Foo", &models.Property{ 662 Name: "mY_pROp", 663 DataType: []string{"int"}, 664 }) 665 666 require.NotNil(t, err) 667 assert.Contains(t, err.Error(), "conflict for property") 668 } 669 670 type fakeScaleOutManager struct{} 671 672 func (f *fakeScaleOutManager) Scale(ctx context.Context, 673 className string, updated sharding.Config, _, _ int64, 674 ) (*sharding.State, error) { 675 return nil, nil 676 } 677 678 func (f *fakeScaleOutManager) SetSchemaManager(sm scaler.SchemaManager) { 679 } 680 681 // does nothing as these do not involve crashes 682 type fakeTxPersistence struct{} 683 684 func (f *fakeTxPersistence) StoreTx(ctx context.Context, 685 tx *cluster.Transaction, 686 ) error { 687 return nil 688 } 689 690 func (f *fakeTxPersistence) DeleteTx(ctx context.Context, 691 txID string, 692 ) error { 693 return nil 694 } 695 696 func (f *fakeTxPersistence) IterateAll(ctx context.Context, 697 cb func(tx *cluster.Transaction), 698 ) error { 699 return nil 700 } 701 702 type fakeBroadcaster struct { 703 openErr error 704 commitErr error 705 abortErr error 706 abortCalledId string 707 } 708 709 func (f *fakeBroadcaster) BroadcastTransaction(ctx context.Context, 710 tx *cluster.Transaction, 711 ) error { 712 return f.openErr 713 } 714 715 func (f *fakeBroadcaster) BroadcastAbortTransaction(ctx context.Context, 716 tx *cluster.Transaction, 717 ) error { 718 f.abortCalledId = tx.ID 719 return f.abortErr 720 } 721 722 func (f *fakeBroadcaster) BroadcastCommitTransaction(ctx context.Context, 723 tx *cluster.Transaction, 724 ) error { 725 return f.commitErr 726 }