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 }