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  }