github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/repo/testdb/generic_repo_update_tests.go (about) 1 package testdb 2 3 import ( 4 "context" 5 "fmt" 6 "reflect" 7 "testing" 8 9 "github.com/kyma-incubator/compass/components/director/pkg/apperrors" 10 "github.com/kyma-incubator/compass/components/director/pkg/persistence" 11 "github.com/pkg/errors" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 ) 15 16 // RepoUpdateTestSuite represents a generic test suite for repository Update and UpdateWithVersion methods of any global entity or entity that has externally managed tenants in m2m table/view. 17 type RepoUpdateTestSuite struct { 18 Name string 19 SQLQueryDetails []SQLQueryDetails 20 ConverterMockProvider func() Mock 21 RepoConstructorFunc interface{} 22 ModelEntity interface{} 23 DBEntity interface{} 24 NilModelEntity interface{} 25 TenantID string 26 DisableConverterErrorTest bool 27 UpdateMethodName string 28 IsGlobal bool 29 } 30 31 // Run runs the generic repo update test suite 32 func (suite *RepoUpdateTestSuite) Run(t *testing.T) bool { 33 if len(suite.UpdateMethodName) == 0 { 34 suite.UpdateMethodName = "Update" 35 } 36 37 return t.Run(suite.Name, func(t *testing.T) { 38 testErr := errors.New("test error") 39 40 t.Run("success", func(t *testing.T) { 41 sqlxDB, sqlMock := MockDatabase(t) 42 ctx := persistence.SaveToContext(context.TODO(), sqlxDB) 43 44 configureValidSQLQueries(sqlMock, suite.SQLQueryDetails) 45 46 convMock := suite.ConverterMockProvider() 47 convMock.On("ToEntity", suite.ModelEntity).Return(suite.DBEntity, nil).Once() 48 pgRepository := createRepo(suite.RepoConstructorFunc, convMock) 49 // WHEN 50 err := callUpdate(pgRepository, ctx, suite.TenantID, suite.ModelEntity, suite.UpdateMethodName) 51 // THEN 52 require.NoError(t, err) 53 sqlMock.AssertExpectations(t) 54 convMock.AssertExpectations(t) 55 }) 56 57 for i := range suite.SQLQueryDetails { 58 t.Run(fmt.Sprintf("error if SQL query %d fail", i), func(t *testing.T) { 59 sqlxDB, sqlMock := MockDatabase(t) 60 ctx := persistence.SaveToContext(context.TODO(), sqlxDB) 61 62 configureFailureForSQLQueryOnIndex(sqlMock, suite.SQLQueryDetails, i, testErr) 63 64 convMock := suite.ConverterMockProvider() 65 convMock.On("ToEntity", suite.ModelEntity).Return(suite.DBEntity, nil).Once() 66 pgRepository := createRepo(suite.RepoConstructorFunc, convMock) 67 // WHEN 68 err := callUpdate(pgRepository, ctx, suite.TenantID, suite.ModelEntity, suite.UpdateMethodName) 69 // THEN 70 require.Error(t, err) 71 require.Equal(t, apperrors.InternalError, apperrors.ErrorCode(err)) 72 require.Contains(t, err.Error(), "Internal Server Error: Unexpected error while executing SQL query") 73 74 sqlMock.AssertExpectations(t) 75 convMock.AssertExpectations(t) 76 }) 77 } 78 79 if suite.UpdateMethodName == "UpdateWithVersion" { // We have an exists check 80 t.Run("error when entity is missing", func(t *testing.T) { 81 sqlxDB, sqlMock := MockDatabase(t) 82 ctx := persistence.SaveToContext(context.TODO(), sqlxDB) 83 84 configureInvalidSelect(sqlMock, suite.SQLQueryDetails) 85 86 convMock := suite.ConverterMockProvider() 87 convMock.On("ToEntity", suite.ModelEntity).Return(suite.DBEntity, nil).Once() 88 pgRepository := createRepo(suite.RepoConstructorFunc, convMock) 89 // WHEN 90 err := callUpdate(pgRepository, ctx, suite.TenantID, suite.ModelEntity, suite.UpdateMethodName) 91 // THEN 92 require.Error(t, err) 93 require.Equal(t, apperrors.InvalidOperation, apperrors.ErrorCode(err)) 94 require.Contains(t, err.Error(), "entity does not exist or caller tenant does not have owner access") 95 96 sqlMock.AssertExpectations(t) 97 convMock.AssertExpectations(t) 98 }) 99 } 100 101 if !suite.DisableConverterErrorTest { 102 t.Run("error when conversion fail", func(t *testing.T) { 103 sqlxDB, sqlMock := MockDatabase(t) 104 ctx := persistence.SaveToContext(context.TODO(), sqlxDB) 105 106 convMock := suite.ConverterMockProvider() 107 convMock.On("ToEntity", suite.ModelEntity).Return(nil, testErr).Once() 108 pgRepository := createRepo(suite.RepoConstructorFunc, convMock) 109 // WHEN 110 err := callUpdate(pgRepository, ctx, suite.TenantID, suite.ModelEntity, suite.UpdateMethodName) 111 // THEN 112 require.Error(t, err) 113 require.Contains(t, err.Error(), testErr.Error()) 114 115 sqlMock.AssertExpectations(t) 116 convMock.AssertExpectations(t) 117 }) 118 } 119 120 t.Run("error if 0 affected rows", func(t *testing.T) { 121 sqlxDB, sqlMock := MockDatabase(t) 122 ctx := persistence.SaveToContext(context.TODO(), sqlxDB) 123 124 configureInvalidUpdateSQLQuery(sqlMock, suite.SQLQueryDetails) 125 126 convMock := suite.ConverterMockProvider() 127 convMock.On("ToEntity", suite.ModelEntity).Return(suite.DBEntity, nil).Once() 128 pgRepository := createRepo(suite.RepoConstructorFunc, convMock) 129 // WHEN 130 err := callUpdate(pgRepository, ctx, suite.TenantID, suite.ModelEntity, suite.UpdateMethodName) 131 // THEN 132 require.Error(t, err) 133 if suite.UpdateMethodName == "UpdateWithVersion" { 134 require.Equal(t, apperrors.ConcurrentUpdate, apperrors.ErrorCode(err)) 135 require.Contains(t, err.Error(), apperrors.ConcurrentUpdateMsg) 136 } else if suite.IsGlobal { 137 require.Equal(t, apperrors.InternalError, apperrors.ErrorCode(err)) 138 require.Contains(t, err.Error(), fmt.Sprintf(apperrors.ShouldUpdateSingleRowButUpdatedMsgF, 0)) 139 } else { 140 require.Equal(t, apperrors.Unauthorized, apperrors.ErrorCode(err)) 141 require.Contains(t, err.Error(), apperrors.ShouldBeOwnerMsg) 142 } 143 144 sqlMock.AssertExpectations(t) 145 convMock.AssertExpectations(t) 146 }) 147 148 t.Run("returns error when item is nil", func(t *testing.T) { 149 ctx := context.TODO() 150 convMock := suite.ConverterMockProvider() 151 pgRepository := createRepo(suite.RepoConstructorFunc, convMock) 152 // WHEN 153 err := callUpdate(pgRepository, ctx, suite.TenantID, suite.NilModelEntity, suite.UpdateMethodName) 154 // THEN 155 require.Error(t, err) 156 assert.Contains(t, err.Error(), "Internal Server Error") 157 convMock.AssertExpectations(t) 158 }) 159 }) 160 } 161 162 func callUpdate(repo interface{}, ctx context.Context, tenant string, modelEntity interface{}, methodName string) error { 163 args := []reflect.Value{reflect.ValueOf(ctx)} 164 if len(tenant) > 0 { 165 args = append(args, reflect.ValueOf(tenant)) 166 } 167 args = append(args, reflect.ValueOf(modelEntity)) 168 results := reflect.ValueOf(repo).MethodByName(methodName).Call(args) 169 if len(results) != 1 { 170 panic("Update should return one argument") 171 } 172 result := results[0].Interface() 173 if result == nil { 174 return nil 175 } 176 err, ok := result.(error) 177 if !ok { 178 panic("Expected result to be an error") 179 } 180 return err 181 } 182 183 func configureInvalidUpdateSQLQuery(sqlMock DBMock, sqlQueryDetails []SQLQueryDetails) { 184 for _, sqlDetails := range sqlQueryDetails { 185 if sqlDetails.IsSelect { 186 sqlMock.ExpectQuery(sqlDetails.Query).WithArgs(sqlDetails.Args...).WillReturnRows(sqlDetails.ValidRowsProvider()...) 187 } else { 188 sqlMock.ExpectExec(sqlDetails.Query).WithArgs(sqlDetails.Args...).WillReturnResult(sqlDetails.InvalidResult) 189 } 190 } 191 }