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

     1  package repo_test
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"regexp"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/DATA-DOG/go-sqlmock"
    11  	"github.com/kyma-incubator/compass/components/director/internal/repo"
    12  	"github.com/kyma-incubator/compass/components/director/internal/repo/testdb"
    13  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
    14  	"github.com/kyma-incubator/compass/components/director/pkg/persistence"
    15  	"github.com/kyma-incubator/compass/components/director/pkg/resource"
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  )
    19  
    20  type methodToTest = func(ctx context.Context, resourceType resource.Type, tenant string, conditions repo.Conditions) error
    21  type methodToTestWithoutTenant = func(ctx context.Context, conditions repo.Conditions) error
    22  
    23  func TestDelete(t *testing.T) {
    24  	deleter := repo.NewDeleter(bundlesTableName)
    25  	resourceType := resource.Bundle
    26  	m2mTable, ok := resourceType.TenantAccessTable()
    27  	require.True(t, ok)
    28  
    29  	tc := map[string]methodToTest{
    30  		"DeleteMany": deleter.DeleteMany,
    31  		"DeleteOne":  deleter.DeleteOne,
    32  	}
    33  	for tn, testedMethod := range tc {
    34  		t.Run(fmt.Sprintf("[%s] success", tn), func(t *testing.T) {
    35  			// GIVEN
    36  			db, mock := testdb.MockDatabase(t)
    37  			ctx := persistence.SaveToContext(context.TODO(), db)
    38  			defer mock.AssertExpectations(t)
    39  
    40  			mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("DELETE FROM %s WHERE id = $1 AND %s", bundlesTableName, fmt.Sprintf(tenantIsolationConditionWithOwnerCheckFmt, m2mTable, "$2")))).
    41  				WithArgs(bundleID, tenantID).WillReturnResult(sqlmock.NewResult(-1, 1))
    42  
    43  			// WHEN
    44  			err := testedMethod(ctx, resourceType, tenantID, repo.Conditions{repo.NewEqualCondition("id", bundleID)})
    45  			// THEN
    46  			require.NoError(t, err)
    47  		})
    48  
    49  		t.Run(fmt.Sprintf("[%s] success when no conditions", tn), func(t *testing.T) {
    50  			// GIVEN
    51  			db, mock := testdb.MockDatabase(t)
    52  			ctx := persistence.SaveToContext(context.TODO(), db)
    53  			defer mock.AssertExpectations(t)
    54  
    55  			mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("DELETE FROM %s WHERE %s", bundlesTableName, fmt.Sprintf(tenantIsolationConditionWithOwnerCheckFmt, m2mTable, "$1")))).
    56  				WithArgs(tenantID).WillReturnResult(sqlmock.NewResult(-1, 1))
    57  
    58  			// WHEN
    59  			err := testedMethod(ctx, resourceType, tenantID, repo.Conditions{})
    60  			// THEN
    61  			require.NoError(t, err)
    62  		})
    63  
    64  		t.Run(fmt.Sprintf("[%s] success when more conditions", tn), func(t *testing.T) {
    65  			// GIVEN
    66  			db, mock := testdb.MockDatabase(t)
    67  			ctx := persistence.SaveToContext(context.TODO(), db)
    68  			defer mock.AssertExpectations(t)
    69  
    70  			mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("DELETE FROM %s WHERE description = $1 AND name = $2 AND %s", bundlesTableName, fmt.Sprintf(tenantIsolationConditionWithOwnerCheckFmt, m2mTable, "$3")))).
    71  				WithArgs(bundleDescription, bundleName, tenantID).WillReturnResult(sqlmock.NewResult(-1, 1))
    72  
    73  			// WHEN
    74  			err := testedMethod(ctx, resourceType, tenantID, repo.Conditions{repo.NewEqualCondition("description", bundleDescription), repo.NewEqualCondition("name", bundleName)})
    75  			// THEN
    76  			require.NoError(t, err)
    77  		})
    78  
    79  		t.Run(fmt.Sprintf("[%s] fail when 0 entities match conditions", tn), func(t *testing.T) {
    80  			// GIVEN
    81  			db, mock := testdb.MockDatabase(t)
    82  			ctx := persistence.SaveToContext(context.TODO(), db)
    83  			defer mock.AssertExpectations(t)
    84  
    85  			mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("DELETE FROM %s WHERE description = $1 AND name = $2 AND %s", bundlesTableName, fmt.Sprintf(tenantIsolationConditionWithOwnerCheckFmt, m2mTable, "$3")))).
    86  				WithArgs(bundleDescription, bundleName, tenantID).WillReturnResult(sqlmock.NewResult(-1, 0))
    87  
    88  			// WHEN
    89  			err := testedMethod(ctx, resourceType, tenantID, repo.Conditions{repo.NewEqualCondition("description", bundleDescription), repo.NewEqualCondition("name", bundleName)})
    90  			// THEN
    91  			if tn == "DeleteMany" {
    92  				require.NoError(t, err)
    93  			} else {
    94  				require.Error(t, err)
    95  				require.Contains(t, err.Error(), apperrors.ShouldBeOwnerMsg)
    96  			}
    97  		})
    98  
    99  		t.Run(fmt.Sprintf("[%s] returns error when delete operation returns error", tn), func(t *testing.T) {
   100  			// GIVEN
   101  			db, mock := testdb.MockDatabase(t)
   102  			ctx := persistence.SaveToContext(context.TODO(), db)
   103  			defer mock.AssertExpectations(t)
   104  
   105  			mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("DELETE FROM %s WHERE description = $1 AND name = $2 AND %s", bundlesTableName, fmt.Sprintf(tenantIsolationConditionWithOwnerCheckFmt, m2mTable, "$3")))).
   106  				WithArgs(bundleDescription, bundleName, tenantID).WillReturnError(someError())
   107  
   108  			// WHEN
   109  			err := testedMethod(ctx, resourceType, tenantID, repo.Conditions{repo.NewEqualCondition("description", bundleDescription), repo.NewEqualCondition("name", bundleName)})
   110  			// THEN
   111  			require.Error(t, err)
   112  			// THEN
   113  			require.EqualError(t, err, "Internal Server Error: Unexpected error while executing SQL query")
   114  		})
   115  
   116  		t.Run(fmt.Sprintf("[%s] context properly canceled", tn), func(t *testing.T) {
   117  			db, mock := testdb.MockDatabase(t)
   118  			defer mock.AssertExpectations(t)
   119  
   120  			ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
   121  			defer cancel()
   122  
   123  			ctx = persistence.SaveToContext(ctx, db)
   124  
   125  			err := testedMethod(ctx, resourceType, tenantID, repo.Conditions{repo.NewEqualCondition("id", bundleID)})
   126  
   127  			require.EqualError(t, err, "Internal Server Error: Maximum processing timeout reached")
   128  		})
   129  
   130  		t.Run(fmt.Sprintf("[%s] returns error if missing persistence context", tn), func(t *testing.T) {
   131  			ctx := context.TODO()
   132  			err := testedMethod(ctx, resourceType, tenantID, repo.Conditions{repo.NewEqualCondition("id", bundleID)})
   133  			require.EqualError(t, err, apperrors.NewInternalError("unable to fetch database from context").Error())
   134  		})
   135  
   136  		t.Run(fmt.Sprintf("[%s] returns error if missing tenant id", tn), func(t *testing.T) {
   137  			ctx := context.TODO()
   138  			err := testedMethod(ctx, resourceType, "", repo.Conditions{repo.NewEqualCondition("id", bundleID)})
   139  			require.EqualError(t, err, apperrors.NewTenantRequiredError().Error())
   140  		})
   141  
   142  		t.Run(fmt.Sprintf("[%s] returns error if entity does not have access table", tn), func(t *testing.T) {
   143  			db, mock := testdb.MockDatabase(t)
   144  			ctx := persistence.SaveToContext(context.TODO(), db)
   145  			defer mock.AssertExpectations(t)
   146  
   147  			err := testedMethod(ctx, resource.Type("unknown"), tenantID, repo.Conditions{repo.NewEqualCondition("id", bundleID)})
   148  
   149  			require.Error(t, err)
   150  			assert.Contains(t, err.Error(), "entity unknown does not have access table")
   151  		})
   152  	}
   153  
   154  	t.Run("BIA", func(t *testing.T) {
   155  		deleter := repo.NewDeleter(biaTableName)
   156  		resourceType := resource.BundleInstanceAuth
   157  		m2mTable, ok := resourceType.TenantAccessTable()
   158  		require.True(t, ok)
   159  
   160  		tc := map[string]methodToTest{
   161  			"DeleteMany": deleter.DeleteMany,
   162  			"DeleteOne":  deleter.DeleteOne,
   163  		}
   164  		for tn, testedMethod := range tc {
   165  			t.Run(fmt.Sprintf("[%s] success", tn), func(t *testing.T) {
   166  				// GIVEN
   167  				db, mock := testdb.MockDatabase(t)
   168  				ctx := persistence.SaveToContext(context.TODO(), db)
   169  				defer mock.AssertExpectations(t)
   170  
   171  				mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("DELETE FROM %s WHERE id = $1 AND %s", biaTableName, fmt.Sprintf(tenantIsolationConditionForBIA, m2mTable, "$2", "$3")))).
   172  					WithArgs(biaID, tenantID, tenantID).WillReturnResult(sqlmock.NewResult(-1, 1))
   173  
   174  				// WHEN
   175  				err := testedMethod(ctx, resourceType, tenantID, repo.Conditions{repo.NewEqualCondition("id", biaID)})
   176  				// THEN
   177  				require.NoError(t, err)
   178  			})
   179  		}
   180  	})
   181  
   182  	t.Run("[DeleteMany] success when more than one resource matches conditions", func(t *testing.T) {
   183  		// GIVEN
   184  		db, mock := testdb.MockDatabase(t)
   185  		ctx := persistence.SaveToContext(context.TODO(), db)
   186  		defer mock.AssertExpectations(t)
   187  
   188  		mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("DELETE FROM %s WHERE description = $1 AND name = $2 AND %s", bundlesTableName, fmt.Sprintf(tenantIsolationConditionWithOwnerCheckFmt, m2mTable, "$3")))).
   189  			WithArgs(bundleDescription, bundleName, tenantID).WillReturnResult(sqlmock.NewResult(-1, 2))
   190  
   191  		// WHEN
   192  		err := deleter.DeleteMany(ctx, resourceType, tenantID, repo.Conditions{repo.NewEqualCondition("description", bundleDescription), repo.NewEqualCondition("name", bundleName)})
   193  		// THEN
   194  		require.NoError(t, err)
   195  	})
   196  
   197  	t.Run("[DeleteOne] fail when more than one resource matches conditions", func(t *testing.T) {
   198  		// GIVEN
   199  		db, mock := testdb.MockDatabase(t)
   200  		ctx := persistence.SaveToContext(context.TODO(), db)
   201  		defer mock.AssertExpectations(t)
   202  
   203  		mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("DELETE FROM %s WHERE description = $1 AND name = $2 AND %s", bundlesTableName, fmt.Sprintf(tenantIsolationConditionWithOwnerCheckFmt, m2mTable, "$3")))).
   204  			WithArgs(bundleDescription, bundleName, tenantID).WillReturnResult(sqlmock.NewResult(-1, 2))
   205  
   206  		// WHEN
   207  		err := deleter.DeleteOne(ctx, resourceType, tenantID, repo.Conditions{repo.NewEqualCondition("description", bundleDescription), repo.NewEqualCondition("name", bundleName)})
   208  		// THEN
   209  		require.Error(t, err)
   210  		require.Contains(t, err.Error(), "delete should remove single row, but removed 2 rows")
   211  	})
   212  }
   213  
   214  func TestDeleteGlobal(t *testing.T) {
   215  	givenID := "id"
   216  	sut := repo.NewDeleterGlobal(UserType, userTableName)
   217  
   218  	tc := map[string]methodToTestWithoutTenant{
   219  		"DeleteMany": sut.DeleteManyGlobal,
   220  		"DeleteOne":  sut.DeleteOneGlobal,
   221  	}
   222  	for tn, testedMethod := range tc {
   223  		t.Run(fmt.Sprintf("[%s] success", tn), func(t *testing.T) {
   224  			// GIVEN
   225  			expectedQuery := regexp.QuoteMeta(fmt.Sprintf("DELETE FROM %s WHERE id = $1", userTableName))
   226  			db, mock := testdb.MockDatabase(t)
   227  			ctx := persistence.SaveToContext(context.TODO(), db)
   228  			defer mock.AssertExpectations(t)
   229  			mock.ExpectExec(expectedQuery).WithArgs(givenID).WillReturnResult(sqlmock.NewResult(-1, 1))
   230  			// WHEN
   231  			err := testedMethod(ctx, repo.Conditions{repo.NewEqualCondition("id", givenID)})
   232  			// THEN
   233  			require.NoError(t, err)
   234  		})
   235  
   236  		t.Run(fmt.Sprintf("[%s] success when no conditions", tn), func(t *testing.T) {
   237  			// GIVEN
   238  			expectedQuery := regexp.QuoteMeta(fmt.Sprintf("DELETE FROM %s", userTableName))
   239  			db, mock := testdb.MockDatabase(t)
   240  			ctx := persistence.SaveToContext(context.TODO(), db)
   241  			defer mock.AssertExpectations(t)
   242  			mock.ExpectExec(expectedQuery).WillReturnResult(sqlmock.NewResult(-1, 1))
   243  			// WHEN
   244  			err := testedMethod(ctx, repo.Conditions{})
   245  			// THEN
   246  			require.NoError(t, err)
   247  		})
   248  
   249  		t.Run(fmt.Sprintf("[%s] success when more conditions", tn), func(t *testing.T) {
   250  			// GIVEN
   251  			expectedQuery := regexp.QuoteMeta(fmt.Sprintf("DELETE FROM %s WHERE first_name = $1 AND last_name = $2", userTableName))
   252  			db, mock := testdb.MockDatabase(t)
   253  			ctx := persistence.SaveToContext(context.TODO(), db)
   254  			defer mock.AssertExpectations(t)
   255  			mock.ExpectExec(expectedQuery).WithArgs("john", "doe").WillReturnResult(sqlmock.NewResult(-1, 1))
   256  			// WHEN
   257  			err := testedMethod(ctx, repo.Conditions{repo.NewEqualCondition("first_name", "john"), repo.NewEqualCondition("last_name", "doe")})
   258  			// THEN
   259  			require.NoError(t, err)
   260  		})
   261  
   262  		t.Run(fmt.Sprintf("[%s] returns error on db operation", tn), func(t *testing.T) {
   263  			// GIVEN
   264  			expectedQuery := regexp.QuoteMeta(fmt.Sprintf("DELETE FROM %s WHERE id = $1", userTableName))
   265  			db, mock := testdb.MockDatabase(t)
   266  			ctx := persistence.SaveToContext(context.TODO(), db)
   267  			defer mock.AssertExpectations(t)
   268  			mock.ExpectExec(expectedQuery).WithArgs(givenID).WillReturnError(someError())
   269  			// WHEN
   270  			err := testedMethod(ctx, repo.Conditions{repo.NewEqualCondition("id", givenID)})
   271  			// THEN
   272  			require.EqualError(t, err, "Internal Server Error: Unexpected error while executing SQL query")
   273  		})
   274  
   275  		t.Run("context properly canceled", func(t *testing.T) {
   276  			db, mock := testdb.MockDatabase(t)
   277  			defer mock.AssertExpectations(t)
   278  
   279  			ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
   280  			defer cancel()
   281  
   282  			ctx = persistence.SaveToContext(ctx, db)
   283  
   284  			err := testedMethod(ctx, repo.Conditions{repo.NewEqualCondition("id", givenID)})
   285  
   286  			require.EqualError(t, err, "Internal Server Error: Maximum processing timeout reached")
   287  		})
   288  
   289  		t.Run(fmt.Sprintf("[%s] returns error if missing persistence context", tn), func(t *testing.T) {
   290  			ctx := context.TODO()
   291  			err := testedMethod(ctx, repo.Conditions{repo.NewEqualCondition("id", givenID)})
   292  			require.EqualError(t, err, apperrors.NewInternalError("unable to fetch database from context").Error())
   293  		})
   294  	}
   295  }
   296  
   297  func TestDeleteWithEmbeddedTenant(t *testing.T) {
   298  	givenID := "id"
   299  	sut := repo.NewDeleterWithEmbeddedTenant(userTableName, "tenant_id")
   300  
   301  	tc := map[string]methodToTest{
   302  		"DeleteMany": sut.DeleteMany,
   303  		"DeleteOne":  sut.DeleteOne,
   304  	}
   305  	for tn, testedMethod := range tc {
   306  		t.Run(fmt.Sprintf("[%s] success", tn), func(t *testing.T) {
   307  			// GIVEN
   308  			expectedQuery := regexp.QuoteMeta(fmt.Sprintf("DELETE FROM %s WHERE tenant_id = $1 AND id = $2", userTableName))
   309  			db, mock := testdb.MockDatabase(t)
   310  			ctx := persistence.SaveToContext(context.TODO(), db)
   311  			defer mock.AssertExpectations(t)
   312  			mock.ExpectExec(expectedQuery).WithArgs(tenantID, givenID).WillReturnResult(sqlmock.NewResult(-1, 1))
   313  			// WHEN
   314  			err := testedMethod(ctx, resource.AutomaticScenarioAssigment, tenantID, repo.Conditions{repo.NewEqualCondition("id", givenID)})
   315  			// THEN
   316  			require.NoError(t, err)
   317  		})
   318  
   319  		t.Run(fmt.Sprintf("[%s] success when no conditions", tn), func(t *testing.T) {
   320  			// GIVEN
   321  			expectedQuery := regexp.QuoteMeta(fmt.Sprintf("DELETE FROM %s WHERE tenant_id = $1", userTableName))
   322  			db, mock := testdb.MockDatabase(t)
   323  			ctx := persistence.SaveToContext(context.TODO(), db)
   324  			defer mock.AssertExpectations(t)
   325  			mock.ExpectExec(expectedQuery).WithArgs(tenantID).WillReturnResult(sqlmock.NewResult(-1, 1))
   326  			// WHEN
   327  			err := testedMethod(ctx, resource.AutomaticScenarioAssigment, tenantID, repo.Conditions{})
   328  			// THEN
   329  			require.NoError(t, err)
   330  		})
   331  
   332  		t.Run(fmt.Sprintf("[%s] success when more conditions", tn), func(t *testing.T) {
   333  			// GIVEN
   334  			expectedQuery := regexp.QuoteMeta(fmt.Sprintf("DELETE FROM %s WHERE tenant_id = $1 AND first_name = $2 AND last_name = $3", userTableName))
   335  			db, mock := testdb.MockDatabase(t)
   336  			ctx := persistence.SaveToContext(context.TODO(), db)
   337  			defer mock.AssertExpectations(t)
   338  			mock.ExpectExec(expectedQuery).WithArgs(tenantID, "john", "doe").WillReturnResult(sqlmock.NewResult(-1, 1))
   339  			// WHEN
   340  			err := testedMethod(ctx, resource.AutomaticScenarioAssigment, tenantID, repo.Conditions{repo.NewEqualCondition("first_name", "john"), repo.NewEqualCondition("last_name", "doe")})
   341  			// THEN
   342  			require.NoError(t, err)
   343  		})
   344  
   345  		t.Run(fmt.Sprintf("[%s] returns error on db operation", tn), func(t *testing.T) {
   346  			// GIVEN
   347  			expectedQuery := regexp.QuoteMeta(fmt.Sprintf("DELETE FROM %s WHERE tenant_id = $1 AND id = $2", userTableName))
   348  			db, mock := testdb.MockDatabase(t)
   349  			ctx := persistence.SaveToContext(context.TODO(), db)
   350  			defer mock.AssertExpectations(t)
   351  			mock.ExpectExec(expectedQuery).WithArgs(tenantID, givenID).WillReturnError(someError())
   352  			// WHEN
   353  			err := testedMethod(ctx, resource.AutomaticScenarioAssigment, tenantID, repo.Conditions{repo.NewEqualCondition("id", givenID)})
   354  			// THEN
   355  			require.EqualError(t, err, "Internal Server Error: Unexpected error while executing SQL query")
   356  		})
   357  
   358  		t.Run("context properly canceled", func(t *testing.T) {
   359  			db, mock := testdb.MockDatabase(t)
   360  			defer mock.AssertExpectations(t)
   361  
   362  			ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
   363  			defer cancel()
   364  
   365  			ctx = persistence.SaveToContext(ctx, db)
   366  
   367  			err := testedMethod(ctx, resource.AutomaticScenarioAssigment, tenantID, repo.Conditions{repo.NewEqualCondition("id", givenID)})
   368  
   369  			require.EqualError(t, err, "Internal Server Error: Maximum processing timeout reached")
   370  		})
   371  
   372  		t.Run(fmt.Sprintf("[%s] returns error if missing persistence context", tn), func(t *testing.T) {
   373  			ctx := context.TODO()
   374  			err := testedMethod(ctx, resource.AutomaticScenarioAssigment, tenantID, repo.Conditions{repo.NewEqualCondition("id", givenID)})
   375  			require.EqualError(t, err, apperrors.NewInternalError("unable to fetch database from context").Error())
   376  		})
   377  	}
   378  }
   379  
   380  func TestDeleterConditionTreeWithEmbeddedTenant(t *testing.T) {
   381  	sut := repo.NewDeleterConditionTreeWithEmbeddedTenant(userTableName, "tenant_id")
   382  
   383  	t.Run("deletes all items successfully", func(t *testing.T) {
   384  		db, mock := testdb.MockDatabase(t)
   385  		defer mock.AssertExpectations(t)
   386  
   387  		mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM users WHERE (tenant_id = $1 AND first_name IN ($2, $3))`)).
   388  			WithArgs(tenantID, "Joe", "Smith").WillReturnResult(sqlmock.NewResult(0, 2))
   389  		ctx := persistence.SaveToContext(context.TODO(), db)
   390  
   391  		err := sut.DeleteConditionTree(ctx, UserType, tenantID, &repo.ConditionTree{Operand: repo.NewInConditionForStringValues("first_name", []string{"Joe", "Smith"})})
   392  		require.NoError(t, err)
   393  	})
   394  
   395  	t.Run("deletes all items successfully with additional parameters", func(t *testing.T) {
   396  		db, mock := testdb.MockDatabase(t)
   397  		defer mock.AssertExpectations(t)
   398  
   399  		mock.ExpectExec(regexp.QuoteMeta("DELETE FROM users WHERE (tenant_id = $1 AND (first_name = $2 OR age != $3))")).
   400  			WithArgs(tenantID, "Joe", 18).WillReturnResult(sqlmock.NewResult(0, 1))
   401  		ctx := persistence.SaveToContext(context.TODO(), db)
   402  
   403  		conditions := repo.Or(repo.ConditionTreesFromConditions([]repo.Condition{
   404  			repo.NewEqualCondition("first_name", "Joe"),
   405  			repo.NewNotEqualCondition("age", 18),
   406  		})...)
   407  
   408  		err := sut.DeleteConditionTree(ctx, UserType, tenantID, conditions)
   409  		require.NoError(t, err)
   410  	})
   411  
   412  	t.Run("returns error if missing persistence context", func(t *testing.T) {
   413  		ctx := context.TODO()
   414  		err := sut.DeleteConditionTree(ctx, UserType, tenantID, nil)
   415  		require.EqualError(t, err, apperrors.NewInternalError("unable to fetch database from context").Error())
   416  	})
   417  
   418  	t.Run("returns error on db operation", func(t *testing.T) {
   419  		db, mock := testdb.MockDatabase(t)
   420  		defer mock.AssertExpectations(t)
   421  
   422  		mock.ExpectExec(`DELETE .*`).WillReturnError(someError())
   423  		ctx := persistence.SaveToContext(context.TODO(), db)
   424  
   425  		err := sut.DeleteConditionTree(ctx, UserType, tenantID, &repo.ConditionTree{Operand: repo.NewInConditionForStringValues("first_name", []string{"Peter", "Homer"})})
   426  		require.EqualError(t, err, "Internal Server Error: Unexpected error while executing SQL query")
   427  	})
   428  
   429  	t.Run("context properly canceled", func(t *testing.T) {
   430  		db, mock := testdb.MockDatabase(t)
   431  		defer mock.AssertExpectations(t)
   432  
   433  		ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
   434  		defer cancel()
   435  
   436  		ctx = persistence.SaveToContext(ctx, db)
   437  		err := sut.DeleteConditionTree(ctx, UserType, tenantID, &repo.ConditionTree{Operand: repo.NewInConditionForStringValues("first_name", []string{"Peter", "Homer"})})
   438  		require.EqualError(t, err, "Internal Server Error: Maximum processing timeout reached")
   439  	})
   440  }
   441  
   442  func TestDeleterConditionTree(t *testing.T) {
   443  	sut := repo.NewDeleterConditionTree(biaTableName)
   444  	resourceType := resource.BundleInstanceAuth
   445  	m2mTable, ok := resourceType.TenantAccessTable()
   446  	require.True(t, ok)
   447  
   448  	t.Run("deletes all items successfully", func(t *testing.T) {
   449  		db, mock := testdb.MockDatabase(t)
   450  		defer mock.AssertExpectations(t)
   451  
   452  		mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("DELETE FROM %s WHERE (%s AND name IN ($3, $4))", biaTableName, fmt.Sprintf(tenantIsolationConditionForBIA, m2mTable, "$1", "$2")))).
   453  			WithArgs(tenantID, tenantID, "bundle1", "bundle2").WillReturnResult(sqlmock.NewResult(-1, 1))
   454  		ctx := persistence.SaveToContext(context.TODO(), db)
   455  
   456  		err := sut.DeleteConditionTree(ctx, resource.BundleInstanceAuth, tenantID, &repo.ConditionTree{Operand: repo.NewInConditionForStringValues("name", []string{"bundle1", "bundle2"})})
   457  		require.NoError(t, err)
   458  	})
   459  
   460  	t.Run("deletes all items successfully with additional parameters", func(t *testing.T) {
   461  		db, mock := testdb.MockDatabase(t)
   462  		defer mock.AssertExpectations(t)
   463  
   464  		mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("DELETE FROM %s WHERE (%s AND (name = $3 OR id != $4))", biaTableName, fmt.Sprintf(tenantIsolationConditionForBIA, m2mTable, "$1", "$2")))).
   465  			WithArgs(tenantID, tenantID, "bundle1", bundleID).WillReturnResult(sqlmock.NewResult(-1, 1))
   466  		ctx := persistence.SaveToContext(context.TODO(), db)
   467  
   468  		conditions := repo.Or(repo.ConditionTreesFromConditions([]repo.Condition{
   469  			repo.NewEqualCondition("name", "bundle1"),
   470  			repo.NewNotEqualCondition("id", bundleID),
   471  		})...)
   472  
   473  		err := sut.DeleteConditionTree(ctx, resource.BundleInstanceAuth, tenantID, conditions)
   474  		require.NoError(t, err)
   475  	})
   476  
   477  	t.Run("returns error if missing persistence context", func(t *testing.T) {
   478  		ctx := context.TODO()
   479  		err := sut.DeleteConditionTree(ctx, resource.BundleInstanceAuth, tenantID, nil)
   480  		require.EqualError(t, err, apperrors.NewInternalError("unable to fetch database from context").Error())
   481  	})
   482  	t.Run("returns error if resource type does not have access table", func(t *testing.T) {
   483  		ctx := context.TODO()
   484  		err := sut.DeleteConditionTree(ctx, userTableName, tenantID, nil)
   485  		require.EqualError(t, err, fmt.Sprintf("entity %s does not have access table", userTableName))
   486  	})
   487  	t.Run("returns error if tenant is not provided", func(t *testing.T) {
   488  		ctx := context.TODO()
   489  		err := sut.DeleteConditionTree(ctx, userTableName, "", nil)
   490  		require.EqualError(t, err, apperrors.NewTenantRequiredError().Error())
   491  	})
   492  	t.Run("returns error on db operation", func(t *testing.T) {
   493  		db, mock := testdb.MockDatabase(t)
   494  		defer mock.AssertExpectations(t)
   495  
   496  		mock.ExpectExec(`DELETE .*`).WillReturnError(someError())
   497  		ctx := persistence.SaveToContext(context.TODO(), db)
   498  
   499  		err := sut.DeleteConditionTree(ctx, resource.BundleInstanceAuth, tenantID, &repo.ConditionTree{Operand: repo.NewInConditionForStringValues("first_name", []string{"Peter", "Homer"})})
   500  		require.EqualError(t, err, "Internal Server Error: Unexpected error while executing SQL query")
   501  	})
   502  
   503  	t.Run("context properly canceled", func(t *testing.T) {
   504  		db, mock := testdb.MockDatabase(t)
   505  		defer mock.AssertExpectations(t)
   506  
   507  		ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
   508  		defer cancel()
   509  
   510  		ctx = persistence.SaveToContext(ctx, db)
   511  		err := sut.DeleteConditionTree(ctx, resource.BundleInstanceAuth, tenantID, &repo.ConditionTree{Operand: repo.NewInConditionForStringValues("first_name", []string{"Peter", "Homer"})})
   512  		require.EqualError(t, err, "Internal Server Error: Maximum processing timeout reached")
   513  	})
   514  }
   515  
   516  func TestDeleteGlobalReactsOnNumberOfRemovedObjects(t *testing.T) {
   517  	givenID := "id"
   518  	sut := repo.NewDeleterGlobal(UserType, userTableName)
   519  
   520  	cases := map[string]struct {
   521  		methodToTest      methodToTestWithoutTenant
   522  		givenRowsAffected int64
   523  		expectedErrString string
   524  	}{
   525  		"[DeleteOne] returns error when removed more than one object": {
   526  			methodToTest:      sut.DeleteOneGlobal,
   527  			givenRowsAffected: 154,
   528  			expectedErrString: "Internal Server Error: delete should remove single row, but removed 154 rows",
   529  		},
   530  		"[DeleteOne] returns error when object not found": {
   531  			methodToTest:      sut.DeleteOneGlobal,
   532  			givenRowsAffected: 0,
   533  			expectedErrString: "Internal Server Error: delete should remove single row, but removed 0 rows",
   534  		},
   535  		"[Delete Many] success when removed more than one object": {
   536  			methodToTest:      sut.DeleteManyGlobal,
   537  			givenRowsAffected: 154,
   538  			expectedErrString: "",
   539  		},
   540  		"[Delete Many] success when not found objects to remove": {
   541  			methodToTest:      sut.DeleteManyGlobal,
   542  			givenRowsAffected: 0,
   543  			expectedErrString: "",
   544  		},
   545  	}
   546  
   547  	for tn, tc := range cases {
   548  		t.Run(tn, func(t *testing.T) {
   549  			// GIVEN
   550  			expectedQuery := regexp.QuoteMeta(fmt.Sprintf("DELETE FROM %s WHERE id = $1", userTableName))
   551  			db, mock := testdb.MockDatabase(t)
   552  			ctx := persistence.SaveToContext(context.TODO(), db)
   553  			defer mock.AssertExpectations(t)
   554  			mock.ExpectExec(expectedQuery).WithArgs(givenID).WillReturnResult(sqlmock.NewResult(0, tc.givenRowsAffected))
   555  			// WHEN
   556  			err := tc.methodToTest(ctx, repo.Conditions{repo.NewEqualCondition("id", givenID)})
   557  			// THEN
   558  			if tc.expectedErrString != "" {
   559  				require.EqualError(t, err, tc.expectedErrString)
   560  			}
   561  		})
   562  	}
   563  }