github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/repo/testdb/generic_repo_delete_tests.go (about)

     1  package testdb
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"reflect"
     7  	"testing"
     8  
     9  	"github.com/DATA-DOG/go-sqlmock"
    10  
    11  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
    12  	"github.com/kyma-incubator/compass/components/director/pkg/persistence"
    13  	"github.com/pkg/errors"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  // RepoDeleteTestSuite represents a generic test suite for repository Delete method of any global entity or entity that has externally managed tenants in m2m table/view.
    18  type RepoDeleteTestSuite struct {
    19  	Name                  string
    20  	SQLQueryDetails       []SQLQueryDetails
    21  	ConverterMockProvider func() Mock
    22  	RepoConstructorFunc   interface{}
    23  	MethodName            string
    24  	MethodArgs            []interface{}
    25  	IsDeleteMany          bool
    26  	IsGlobal              bool
    27  }
    28  
    29  // Run runs the generic repo delete test suite
    30  func (suite *RepoDeleteTestSuite) Run(t *testing.T) bool {
    31  	if len(suite.MethodName) == 0 {
    32  		suite.MethodName = "Delete"
    33  	}
    34  
    35  	return t.Run(suite.Name, func(t *testing.T) {
    36  		testErr := errors.New("test error")
    37  
    38  		t.Run("success", func(t *testing.T) {
    39  			sqlxDB, sqlMock := MockDatabase(t)
    40  			ctx := persistence.SaveToContext(context.TODO(), sqlxDB)
    41  
    42  			configureValidSQLQueries(sqlMock, suite.SQLQueryDetails)
    43  
    44  			convMock := suite.ConverterMockProvider()
    45  			pgRepository := createRepo(suite.RepoConstructorFunc, convMock)
    46  			// WHEN
    47  			err := callDelete(pgRepository, ctx, suite.MethodName, suite.MethodArgs)
    48  			// THEN
    49  			require.NoError(t, err)
    50  
    51  			sqlMock.AssertExpectations(t)
    52  			convMock.AssertExpectations(t)
    53  		})
    54  
    55  		if !suite.IsDeleteMany { // Single delete requires exactly one row to be deleted
    56  			t.Run("returns error if no entity matches criteria", func(t *testing.T) {
    57  				sqlxDB, sqlMock := MockDatabase(t)
    58  				ctx := persistence.SaveToContext(context.TODO(), sqlxDB)
    59  
    60  				configureNoEntityDeleted(sqlMock, suite.SQLQueryDetails)
    61  
    62  				convMock := suite.ConverterMockProvider()
    63  				pgRepository := createRepo(suite.RepoConstructorFunc, convMock)
    64  				// WHEN
    65  				err := callDelete(pgRepository, ctx, suite.MethodName, suite.MethodArgs)
    66  				// THEN
    67  				require.Error(t, err)
    68  
    69  				if suite.IsGlobal {
    70  					require.Equal(t, apperrors.InternalError, apperrors.ErrorCode(err))
    71  					require.Contains(t, err.Error(), "delete should remove single row, but removed 0 rows")
    72  				} else {
    73  					require.Equal(t, apperrors.Unauthorized, apperrors.ErrorCode(err))
    74  					require.Contains(t, err.Error(), apperrors.ShouldBeOwnerMsg)
    75  				}
    76  
    77  				sqlMock.AssertExpectations(t)
    78  				convMock.AssertExpectations(t)
    79  			})
    80  
    81  			t.Run("returns error if more than one entity matches criteria", func(t *testing.T) {
    82  				sqlxDB, sqlMock := MockDatabase(t)
    83  				ctx := persistence.SaveToContext(context.TODO(), sqlxDB)
    84  
    85  				configureMoreThanOneEntityDeleted(sqlMock, suite.SQLQueryDetails)
    86  
    87  				convMock := suite.ConverterMockProvider()
    88  				pgRepository := createRepo(suite.RepoConstructorFunc, convMock)
    89  				// WHEN
    90  				err := callDelete(pgRepository, ctx, suite.MethodName, suite.MethodArgs)
    91  				// THEN
    92  				require.Error(t, err)
    93  				require.Equal(t, apperrors.InternalError, apperrors.ErrorCode(err))
    94  				require.Contains(t, err.Error(), "delete should remove single row, but removed")
    95  
    96  				sqlMock.AssertExpectations(t)
    97  				convMock.AssertExpectations(t)
    98  			})
    99  		}
   100  
   101  		for i := range suite.SQLQueryDetails {
   102  			t.Run(fmt.Sprintf("error if SQL query %d fail", i), func(t *testing.T) {
   103  				sqlxDB, sqlMock := MockDatabase(t)
   104  				ctx := persistence.SaveToContext(context.TODO(), sqlxDB)
   105  
   106  				configureFailureForSQLQueryOnIndex(sqlMock, suite.SQLQueryDetails, i, testErr)
   107  
   108  				convMock := suite.ConverterMockProvider()
   109  				pgRepository := createRepo(suite.RepoConstructorFunc, convMock)
   110  				// WHEN
   111  				err := callDelete(pgRepository, ctx, suite.MethodName, suite.MethodArgs)
   112  				// THEN
   113  				require.Error(t, err)
   114  				require.Equal(t, apperrors.InternalError, apperrors.ErrorCode(err))
   115  				require.Contains(t, err.Error(), "Internal Server Error: Unexpected error while executing SQL query")
   116  
   117  				sqlMock.AssertExpectations(t)
   118  				convMock.AssertExpectations(t)
   119  			})
   120  		}
   121  	})
   122  }
   123  
   124  func callDelete(repo interface{}, ctx context.Context, methodName string, args []interface{}) error {
   125  	argsVals := make([]reflect.Value, 1, len(args))
   126  	argsVals[0] = reflect.ValueOf(ctx)
   127  	for _, arg := range args {
   128  		argsVals = append(argsVals, reflect.ValueOf(arg))
   129  	}
   130  	results := reflect.ValueOf(repo).MethodByName(methodName).Call(argsVals)
   131  	if len(results) != 1 {
   132  		panic("Create should return one argument")
   133  	}
   134  	result := results[0].Interface()
   135  	if result == nil {
   136  		return nil
   137  	}
   138  	err, ok := result.(error)
   139  	if !ok {
   140  		panic("Expected result to be an error")
   141  	}
   142  	return err
   143  }
   144  
   145  func configureMoreThanOneEntityDeleted(sqlMock DBMock, sqlQueryDetails []SQLQueryDetails) {
   146  	for _, sqlDetails := range sqlQueryDetails {
   147  		if sqlDetails.IsSelect {
   148  			sqlMock.ExpectQuery(sqlDetails.Query).WithArgs(sqlDetails.Args...).WillReturnRows(sqlDetails.ValidRowsProvider()...)
   149  		} else {
   150  			sqlMock.ExpectExec(sqlDetails.Query).WithArgs(sqlDetails.Args...).WillReturnResult(sqlDetails.InvalidResult)
   151  			break
   152  		}
   153  	}
   154  }
   155  
   156  func configureNoEntityDeleted(sqlMock DBMock, sqlQueryDetails []SQLQueryDetails) {
   157  	for _, sqlDetails := range sqlQueryDetails {
   158  		if sqlDetails.IsSelect {
   159  			sqlMock.ExpectQuery(sqlDetails.Query).WithArgs(sqlDetails.Args...).WillReturnRows(sqlDetails.ValidRowsProvider()...)
   160  		} else {
   161  			sqlMock.ExpectExec(sqlDetails.Query).WithArgs(sqlDetails.Args...).WillReturnResult(sqlmock.NewResult(-1, 0))
   162  			break
   163  		}
   164  	}
   165  }