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  }