github.com/weaviate/weaviate@v1.24.6/usecases/objects/update_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 "time" 19 20 "github.com/go-openapi/strfmt" 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/search" 28 "github.com/weaviate/weaviate/usecases/config" 29 30 enthnsw "github.com/weaviate/weaviate/entities/vectorindex/hnsw" 31 ) 32 33 func Test_UpdateAction(t *testing.T) { 34 var ( 35 db *fakeVectorRepo 36 modulesProvider *fakeModulesProvider 37 manager *Manager 38 extender *fakeExtender 39 projectorFake *fakeProjector 40 ) 41 42 schema := schema.Schema{ 43 Objects: &models.Schema{ 44 Classes: []*models.Class{ 45 { 46 Class: "ActionClass", 47 VectorIndexConfig: enthnsw.NewDefaultUserConfig(), 48 Properties: []*models.Property{ 49 { 50 DataType: schema.DataTypeText.PropString(), 51 Tokenization: models.PropertyTokenizationWhitespace, 52 Name: "foo", 53 }, 54 }, 55 }, 56 }, 57 }, 58 } 59 60 reset := func() { 61 db = &fakeVectorRepo{} 62 schemaManager := &fakeSchemaManager{ 63 GetSchemaResponse: schema, 64 } 65 locks := &fakeLocks{} 66 cfg := &config.WeaviateConfig{} 67 cfg.Config.QueryDefaults.Limit = 20 68 cfg.Config.QueryMaximumResults = 200 69 authorizer := &fakeAuthorizer{} 70 logger, _ := test.NewNullLogger() 71 extender = &fakeExtender{} 72 projectorFake = &fakeProjector{} 73 metrics := &fakeMetrics{} 74 modulesProvider = getFakeModulesProviderWithCustomExtenders(extender, projectorFake) 75 manager = NewManager(locks, schemaManager, cfg, 76 logger, authorizer, db, modulesProvider, metrics) 77 } 78 79 t.Run("ensure creation timestamp persists", func(t *testing.T) { 80 reset() 81 82 beforeUpdate := time.Now().UnixNano() / int64(time.Millisecond) 83 id := strfmt.UUID("34e9df15-0c3b-468d-ab99-f929662834c7") 84 vec := []float32{0, 1, 2} 85 86 result := &search.Result{ 87 ID: id, 88 ClassName: "ActionClass", 89 Schema: map[string]interface{}{"foo": "bar"}, 90 Created: beforeUpdate, 91 Updated: beforeUpdate, 92 } 93 db.On("ObjectByID", id, mock.Anything, mock.Anything).Return(result, nil).Once() 94 modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)). 95 Return(vec, nil) 96 db.On("PutObject", mock.Anything, mock.Anything).Return(nil).Once() 97 98 payload := &models.Object{ 99 Class: "ActionClass", 100 ID: id, 101 Properties: map[string]interface{}{"foo": "baz"}, 102 } 103 res, err := manager.UpdateObject(context.Background(), &models.Principal{}, "", id, payload, nil) 104 require.Nil(t, err) 105 expected := &models.Object{ 106 Class: "ActionClass", 107 ID: id, 108 Properties: map[string]interface{}{"foo": "baz"}, 109 CreationTimeUnix: beforeUpdate, 110 } 111 112 afterUpdate := time.Now().UnixNano() / int64(time.Millisecond) 113 114 assert.Equal(t, expected.Class, res.Class) 115 assert.Equal(t, expected.ID, res.ID) 116 assert.Equal(t, expected.Properties, res.Properties) 117 assert.Equal(t, expected.CreationTimeUnix, res.CreationTimeUnix) 118 assert.GreaterOrEqual(t, res.LastUpdateTimeUnix, beforeUpdate) 119 assert.LessOrEqual(t, res.LastUpdateTimeUnix, afterUpdate) 120 }) 121 } 122 123 func Test_UpdateObject(t *testing.T) { 124 var ( 125 cls = "MyClass" 126 id = strfmt.UUID("34e9df15-0c3b-468d-ab99-f929662834c7") 127 beforeUpdate = (time.Now().UnixNano() - 2*int64(time.Millisecond)) / int64(time.Millisecond) 128 vec = []float32{0, 1, 2} 129 anyErr = errors.New("any error") 130 ) 131 132 schema := schema.Schema{ 133 Objects: &models.Schema{ 134 Classes: []*models.Class{ 135 { 136 Class: cls, 137 VectorIndexConfig: enthnsw.NewDefaultUserConfig(), 138 Properties: []*models.Property{ 139 { 140 DataType: schema.DataTypeText.PropString(), 141 Tokenization: models.PropertyTokenizationWhitespace, 142 Name: "foo", 143 }, 144 }, 145 }, 146 }, 147 }, 148 } 149 150 m := newFakeGetManager(schema) 151 payload := &models.Object{ 152 Class: cls, 153 ID: id, 154 Properties: map[string]interface{}{"foo": "baz"}, 155 } 156 // the object might not exist 157 m.repo.On("Object", cls, id, mock.Anything, mock.Anything, "").Return(nil, anyErr).Once() 158 _, err := m.UpdateObject(context.Background(), &models.Principal{}, cls, id, payload, nil) 159 if err == nil { 160 t.Fatalf("must return an error if object() fails") 161 } 162 163 result := &search.Result{ 164 ID: id, 165 ClassName: cls, 166 Schema: map[string]interface{}{"foo": "bar"}, 167 Created: beforeUpdate, 168 Updated: beforeUpdate, 169 } 170 m.repo.On("Object", cls, id, mock.Anything, mock.Anything, "").Return(result, nil).Once() 171 m.modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)). 172 Return(vec, nil) 173 m.repo.On("PutObject", mock.Anything, mock.Anything).Return(nil).Once() 174 175 expected := &models.Object{ 176 Class: cls, 177 ID: id, 178 Properties: map[string]interface{}{"foo": "baz"}, 179 CreationTimeUnix: beforeUpdate, 180 Vector: vec, 181 } 182 res, err := m.UpdateObject(context.Background(), &models.Principal{}, cls, id, payload, nil) 183 require.Nil(t, err) 184 if res.LastUpdateTimeUnix <= beforeUpdate { 185 t.Error("time after update must be greater than time before update ") 186 } 187 res.LastUpdateTimeUnix = 0 // to allow for equality 188 assert.Equal(t, expected, res) 189 }