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

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