github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/repo/list_pageable_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/DATA-DOG/go-sqlmock" 12 "github.com/jmoiron/sqlx" 13 "github.com/kyma-incubator/compass/components/director/internal/repo" 14 "github.com/kyma-incubator/compass/components/director/internal/repo/testdb" 15 "github.com/kyma-incubator/compass/components/director/pkg/apperrors" 16 "github.com/kyma-incubator/compass/components/director/pkg/persistence" 17 "github.com/kyma-incubator/compass/components/director/pkg/resource" 18 "github.com/stretchr/testify/assert" 19 "github.com/stretchr/testify/require" 20 ) 21 22 func TestListPageable(t *testing.T) { 23 sut := repo.NewPageableQuerier(appTableName, appColumns) 24 resourceType := resource.Application 25 m2mTable, ok := resourceType.TenantAccessTable() 26 require.True(t, ok) 27 28 t.Run("returns first page and there are no more pages", func(t *testing.T) { 29 db, mock := testdb.MockDatabase(t) 30 defer mock.AssertExpectations(t) 31 32 rows := sqlmock.NewRows(appColumns). 33 AddRow(appID, appName, appDescription). 34 AddRow(appID2, appName2, appDescription2) 35 mock.ExpectQuery(regexp.QuoteMeta(fmt.Sprintf("SELECT id, name, description FROM %s WHERE %s ORDER BY id LIMIT 10 OFFSET 0", appTableName, fmt.Sprintf(tenantIsolationConditionWithoutOwnerCheckFmt, m2mTable, "$1")))). 36 WithArgs(tenantID).WillReturnRows(rows) 37 mock.ExpectQuery(regexp.QuoteMeta(fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE %s", appTableName, fmt.Sprintf(tenantIsolationConditionWithoutOwnerCheckFmt, m2mTable, "$1")))). 38 WithArgs(tenantID).WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(2)) 39 ctx := persistence.SaveToContext(context.TODO(), db) 40 var dest AppCollection 41 42 actualPage, actualTotal, err := sut.List(ctx, resourceType, tenantID, 10, "", "id", &dest) 43 require.NoError(t, err) 44 assert.Equal(t, 2, actualTotal) 45 assert.Len(t, dest, 2) 46 assert.Equal(t, *fixApp, dest[0]) 47 assert.Equal(t, *fixApp2, dest[1]) 48 assert.False(t, actualPage.HasNextPage) 49 }) 50 51 t.Run("returns full page and has next page", func(t *testing.T) { 52 db, mock := testdb.MockDatabase(t) 53 defer mock.AssertExpectations(t) 54 55 rows := sqlmock.NewRows(appColumns). 56 AddRow(appID, appName, appDescription). 57 AddRow(appID2, appName2, appDescription2) 58 mock.ExpectQuery(regexp.QuoteMeta(fmt.Sprintf("SELECT id, name, description FROM %s WHERE %s ORDER BY id LIMIT 2 OFFSET 0", appTableName, fmt.Sprintf(tenantIsolationConditionWithoutOwnerCheckFmt, m2mTable, "$1")))). 59 WithArgs(tenantID).WillReturnRows(rows) 60 mock.ExpectQuery(regexp.QuoteMeta(fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE %s", appTableName, fmt.Sprintf(tenantIsolationConditionWithoutOwnerCheckFmt, m2mTable, "$1")))). 61 WithArgs(tenantID).WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(100)) 62 ctx := persistence.SaveToContext(context.TODO(), db) 63 var dest AppCollection 64 65 actualPage, actualTotal, err := sut.List(ctx, resourceType, tenantID, 2, "", "id", &dest) 66 require.NoError(t, err) 67 assert.Equal(t, 100, actualTotal) 68 assert.Len(t, dest, 2) 69 assert.True(t, actualPage.HasNextPage) 70 assert.NotEmpty(t, actualPage.EndCursor) 71 }) 72 73 t.Run("returns many pages and I can traverse it using cursor", func(t *testing.T) { 74 db, mock := testdb.MockDatabase(t) 75 defer mock.AssertExpectations(t) 76 77 rowsForPage1 := sqlmock.NewRows(appColumns). 78 AddRow(appID, appName, appDescription) 79 rowsForPage2 := sqlmock.NewRows(appColumns). 80 AddRow(appID2, appName2, appDescription2) 81 82 mock.ExpectQuery(regexp.QuoteMeta(fmt.Sprintf("SELECT id, name, description FROM %s WHERE %s ORDER BY id LIMIT 1 OFFSET 0", appTableName, fmt.Sprintf(tenantIsolationConditionWithoutOwnerCheckFmt, m2mTable, "$1")))). 83 WithArgs(tenantID).WillReturnRows(rowsForPage1) 84 mock.ExpectQuery(regexp.QuoteMeta(fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE %s", appTableName, fmt.Sprintf(tenantIsolationConditionWithoutOwnerCheckFmt, m2mTable, "$1")))). 85 WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(100)) 86 mock.ExpectQuery(regexp.QuoteMeta(fmt.Sprintf("SELECT id, name, description FROM %s WHERE %s ORDER BY id LIMIT 1 OFFSET 1", appTableName, fmt.Sprintf(tenantIsolationConditionWithoutOwnerCheckFmt, m2mTable, "$1")))). 87 WithArgs(tenantID).WillReturnRows(rowsForPage2) 88 mock.ExpectQuery(regexp.QuoteMeta(fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE %s", appTableName, fmt.Sprintf(tenantIsolationConditionWithoutOwnerCheckFmt, m2mTable, "$1")))). 89 WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(100)) 90 91 ctx := persistence.SaveToContext(context.TODO(), db) 92 var first AppCollection 93 94 actualFirstPage, actualTotal, err := sut.List(ctx, resourceType, tenantID, 1, "", "id", &first) 95 require.NoError(t, err) 96 assert.Equal(t, 100, actualTotal) 97 assert.Len(t, first, 1) 98 assert.True(t, actualFirstPage.HasNextPage) 99 assert.NotEmpty(t, actualFirstPage.EndCursor) 100 101 var second AppCollection 102 actualSecondPage, actualTotal, err := sut.List(ctx, resourceType, tenantID, 1, actualFirstPage.EndCursor, "id", &second) 103 require.NoError(t, err) 104 assert.Equal(t, 100, actualTotal) 105 assert.Len(t, second, 1) 106 assert.True(t, actualSecondPage.HasNextPage) 107 assert.NotEmpty(t, actualSecondPage.EndCursor) 108 }) 109 110 t.Run("returns page without conditions", func(t *testing.T) { 111 db, mock := testdb.MockDatabase(t) 112 defer mock.AssertExpectations(t) 113 114 rows := sqlmock.NewRows(appColumns). 115 AddRow(appID, appName, appDescription) 116 mock.ExpectQuery(regexp.QuoteMeta(fmt.Sprintf("SELECT id, name, description FROM %s WHERE %s ORDER BY id LIMIT 2 OFFSET 0", appTableName, fmt.Sprintf(tenantIsolationConditionWithoutOwnerCheckFmt, m2mTable, "$1")))). 117 WithArgs(tenantID).WillReturnRows(rows) 118 mock.ExpectQuery(regexp.QuoteMeta(fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE %s", appTableName, fmt.Sprintf(tenantIsolationConditionWithoutOwnerCheckFmt, m2mTable, "$1")))). 119 WithArgs(tenantID).WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(100)) 120 ctx := persistence.SaveToContext(context.TODO(), db) 121 var dest AppCollection 122 123 actualPage, actualTotal, err := sut.List(ctx, resourceType, tenantID, 2, "", "id", &dest) 124 require.NoError(t, err) 125 assert.Equal(t, 100, actualTotal) 126 assert.Len(t, dest, 1) 127 assert.True(t, actualPage.HasNextPage) 128 assert.NotEmpty(t, actualPage.EndCursor) 129 }) 130 131 t.Run("returns page with additional conditions", func(t *testing.T) { 132 db, mock := testdb.MockDatabase(t) 133 defer mock.AssertExpectations(t) 134 135 rows := sqlmock.NewRows(appColumns). 136 AddRow(appID, appName, appDescription) 137 mock.ExpectQuery(regexp.QuoteMeta(fmt.Sprintf("SELECT id, name, description FROM %s WHERE (name = $1 AND description != $2 AND %s) ORDER BY id LIMIT 2 OFFSET 0", appTableName, fmt.Sprintf(tenantIsolationConditionWithoutOwnerCheckFmt, m2mTable, "$3")))). 138 WithArgs(appName, appDescription2, tenantID). 139 WillReturnRows(rows) 140 mock.ExpectQuery(regexp.QuoteMeta(fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE (name = $1 AND description != $2 AND %s)", appTableName, fmt.Sprintf(tenantIsolationConditionWithoutOwnerCheckFmt, m2mTable, "$3")))). 141 WithArgs(appName, appDescription2, tenantID). 142 WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(100)) 143 ctx := persistence.SaveToContext(context.TODO(), db) 144 var dest AppCollection 145 146 conditions := repo.Conditions{ 147 repo.NewEqualCondition("name", appName), 148 repo.NewNotEqualCondition("description", appDescription2), 149 } 150 151 actualPage, actualTotal, err := sut.List(ctx, resourceType, tenantID, 2, "", "id", &dest, conditions...) 152 require.NoError(t, err) 153 assert.Equal(t, 100, actualTotal) 154 assert.Len(t, dest, 1) 155 assert.True(t, actualPage.HasNextPage) 156 assert.NotEmpty(t, actualPage.EndCursor) 157 }) 158 159 t.Run("returns empty page", func(t *testing.T) { 160 db, mock := testdb.MockDatabase(t) 161 defer mock.AssertExpectations(t) 162 163 rows := sqlmock.NewRows(appColumns) 164 mock.ExpectQuery(regexp.QuoteMeta(fmt.Sprintf("SELECT id, name, description FROM %s WHERE %s ORDER BY id LIMIT 2 OFFSET 0", appTableName, fmt.Sprintf(tenantIsolationConditionWithoutOwnerCheckFmt, m2mTable, "$1")))). 165 WithArgs(tenantID).WillReturnRows(rows) 166 mock.ExpectQuery(regexp.QuoteMeta(fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE %s", appTableName, fmt.Sprintf(tenantIsolationConditionWithoutOwnerCheckFmt, m2mTable, "$1")))). 167 WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(0)) 168 ctx := persistence.SaveToContext(context.TODO(), db) 169 var dest AppCollection 170 171 actualPage, actualTotal, err := sut.List(ctx, resourceType, tenantID, 2, "", "id", &dest) 172 require.NoError(t, err) 173 assert.Equal(t, 0, actualTotal) 174 assert.Empty(t, dest) 175 assert.False(t, actualPage.HasNextPage) 176 }) 177 178 t.Run("returns error if missing persistence context", func(t *testing.T) { 179 ctx := context.TODO() 180 _, _, err := sut.List(ctx, resourceType, tenantID, 2, "", "id", nil) 181 require.EqualError(t, err, apperrors.NewInternalError("unable to fetch database from context").Error()) 182 }) 183 184 t.Run("returns error if empty tenant", func(t *testing.T) { 185 ctx := context.TODO() 186 _, _, err := sut.List(ctx, resourceType, "", 2, "", "id", nil) 187 require.EqualError(t, err, apperrors.NewTenantRequiredError().Error()) 188 }) 189 190 t.Run("returns error if wrong cursor", func(t *testing.T) { 191 ctx := persistence.SaveToContext(context.TODO(), &sqlx.Tx{}) 192 _, _, err := sut.List(ctx, resourceType, tenantID, 2, "zzz", "", nil) 193 require.EqualError(t, err, "while decoding page cursor: cursor is not correct: illegal base64 data at input byte 0") 194 }) 195 196 t.Run("returns error if wrong pagination attributes", func(t *testing.T) { 197 ctx := persistence.SaveToContext(context.TODO(), &sqlx.Tx{}) 198 _, _, err := sut.List(ctx, resourceType, tenantID, -3, "", "id", nil) 199 require.EqualError(t, err, "while converting offset and limit to cursor: Invalid data [reason=page size cannot be smaller than 1]") 200 }) 201 202 t.Run("returns error on db operation", func(t *testing.T) { 203 db, mock := testdb.MockDatabase(t) 204 defer mock.AssertExpectations(t) 205 206 mock.ExpectQuery(`SELECT .*`).WillReturnError(someError()) 207 ctx := persistence.SaveToContext(context.TODO(), db) 208 var dest AppCollection 209 210 _, _, err := sut.List(ctx, resourceType, tenantID, 2, "", "id", &dest) 211 212 require.EqualError(t, err, "Internal Server Error: Unexpected error while executing SQL query") 213 }) 214 215 t.Run("returns error on calculating total count", func(t *testing.T) { 216 db, mock := testdb.MockDatabase(t) 217 defer mock.AssertExpectations(t) 218 219 rows := sqlmock.NewRows(appColumns) 220 mock.ExpectQuery(regexp.QuoteMeta(fmt.Sprintf("SELECT id, name, description FROM %s WHERE %s ORDER BY id LIMIT 2 OFFSET 0", appTableName, fmt.Sprintf(tenantIsolationConditionWithoutOwnerCheckFmt, m2mTable, "$1")))). 221 WillReturnRows(rows) 222 mock.ExpectQuery(regexp.QuoteMeta(fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE %s", appTableName, fmt.Sprintf(tenantIsolationConditionWithoutOwnerCheckFmt, m2mTable, "$1")))). 223 WillReturnError(someError()) 224 ctx := persistence.SaveToContext(context.TODO(), db) 225 var dest AppCollection 226 227 _, _, err := sut.List(ctx, resourceType, tenantID, 2, "", "id", &dest) 228 require.EqualError(t, err, "Internal Server Error: Unexpected error while executing SQL query") 229 }) 230 } 231 232 func TestListPageableWithEmbeddedTenant(t *testing.T) { 233 peterID := "peterID" 234 homerID := "homerID" 235 peter := User{FirstName: "Peter", LastName: "Griffin", Age: 40, ID: peterID} 236 peterRow := []driver.Value{peterID, "Peter", "Griffin", 40} 237 homer := User{FirstName: "Homer", LastName: "Simpson", Age: 55, ID: homerID} 238 homerRow := []driver.Value{homerID, "Homer", "Simpson", 55} 239 240 sut := repo.NewPageableQuerierWithEmbeddedTenant(userTableName, "tenant_id", []string{"id", "first_name", "last_name", "age"}) 241 242 t.Run("returns first page and there are no more pages", func(t *testing.T) { 243 db, mock := testdb.MockDatabase(t) 244 defer mock.AssertExpectations(t) 245 246 rows := sqlmock.NewRows([]string{"id", "first_name", "last_name", "age"}). 247 AddRow(peterRow...). 248 AddRow(homerRow...) 249 mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, first_name, last_name, age FROM users WHERE tenant_id = $1 ORDER BY id LIMIT 10 OFFSET 0`)).WithArgs(tenantID).WillReturnRows(rows) 250 mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM users WHERE tenant_id = $1`)).WithArgs(tenantID).WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(2)) 251 ctx := persistence.SaveToContext(context.TODO(), db) 252 var dest UserCollection 253 254 actualPage, actualTotal, err := sut.List(ctx, UserType, tenantID, 10, "", "id", &dest) 255 require.NoError(t, err) 256 assert.Equal(t, 2, actualTotal) 257 assert.Len(t, dest, 2) 258 assert.Equal(t, peter, dest[0]) 259 assert.Equal(t, homer, dest[1]) 260 assert.False(t, actualPage.HasNextPage) 261 }) 262 263 t.Run("returns full page and has next page", func(t *testing.T) { 264 db, mock := testdb.MockDatabase(t) 265 defer mock.AssertExpectations(t) 266 267 rows := sqlmock.NewRows([]string{"id", "first_name", "last_name", "age"}). 268 AddRow(peterRow...). 269 AddRow(homerRow...) 270 mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, first_name, last_name, age FROM users WHERE tenant_id = $1 ORDER BY id LIMIT 2 OFFSET 0`)).WithArgs(tenantID).WillReturnRows(rows) 271 mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM users WHERE tenant_id = $1`)).WithArgs(tenantID).WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(100)) 272 ctx := persistence.SaveToContext(context.TODO(), db) 273 var dest UserCollection 274 275 actualPage, actualTotal, err := sut.List(ctx, UserType, tenantID, 2, "", "id", &dest) 276 require.NoError(t, err) 277 assert.Equal(t, 100, actualTotal) 278 assert.Len(t, dest, 2) 279 assert.True(t, actualPage.HasNextPage) 280 assert.NotEmpty(t, actualPage.EndCursor) 281 }) 282 283 t.Run("returns many pages and I can traverse it using cursor", func(t *testing.T) { 284 db, mock := testdb.MockDatabase(t) 285 defer mock.AssertExpectations(t) 286 287 rowsForPage1 := sqlmock.NewRows([]string{"id", "first_name", "last_name", "age"}). 288 AddRow(peterRow...) 289 rowsForPage2 := sqlmock.NewRows([]string{"id", "first_name", "last_name", "age"}). 290 AddRow(homerRow...) 291 292 mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, first_name, last_name, age FROM users WHERE tenant_id = $1 ORDER BY id LIMIT 1 OFFSET 0`)).WithArgs(tenantID).WillReturnRows(rowsForPage1) 293 mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM users WHERE tenant_id = $1`)).WithArgs(tenantID).WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(100)) 294 mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, first_name, last_name, age FROM users WHERE tenant_id = $1 ORDER BY id LIMIT 1 OFFSET 1`)).WithArgs(tenantID).WillReturnRows(rowsForPage2) 295 mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM users WHERE tenant_id = $1`)).WithArgs(tenantID).WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(100)) 296 297 ctx := persistence.SaveToContext(context.TODO(), db) 298 var first UserCollection 299 300 actualFirstPage, actualTotal, err := sut.List(ctx, UserType, tenantID, 1, "", "id", &first) 301 require.NoError(t, err) 302 assert.Equal(t, 100, actualTotal) 303 assert.Len(t, first, 1) 304 assert.True(t, actualFirstPage.HasNextPage) 305 assert.NotEmpty(t, actualFirstPage.EndCursor) 306 307 var second UserCollection 308 actualSecondPage, actualTotal, err := sut.List(ctx, UserType, tenantID, 1, actualFirstPage.EndCursor, "id", &second) 309 require.NoError(t, err) 310 assert.Equal(t, 100, actualTotal) 311 assert.Len(t, second, 1) 312 assert.True(t, actualSecondPage.HasNextPage) 313 assert.NotEmpty(t, actualSecondPage.EndCursor) 314 }) 315 316 t.Run("returns page without conditions", func(t *testing.T) { 317 db, mock := testdb.MockDatabase(t) 318 defer mock.AssertExpectations(t) 319 320 rows := sqlmock.NewRows([]string{"id", "first_name", "last_name", "age"}). 321 AddRow(peterRow...) 322 mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, first_name, last_name, age FROM users WHERE tenant_id = $1 ORDER BY id LIMIT 2 OFFSET 0`)).WithArgs(tenantID).WillReturnRows(rows) 323 mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM users WHERE tenant_id = $1`)).WithArgs(tenantID).WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(100)) 324 ctx := persistence.SaveToContext(context.TODO(), db) 325 var dest UserCollection 326 327 actualPage, actualTotal, err := sut.List(ctx, UserType, tenantID, 2, "", "id", &dest) 328 require.NoError(t, err) 329 assert.Equal(t, 100, actualTotal) 330 assert.Len(t, dest, 1) 331 assert.True(t, actualPage.HasNextPage) 332 assert.NotEmpty(t, actualPage.EndCursor) 333 }) 334 335 t.Run("returns page with additional conditions", func(t *testing.T) { 336 db, mock := testdb.MockDatabase(t) 337 defer mock.AssertExpectations(t) 338 339 rows := sqlmock.NewRows([]string{"id", "first_name", "last_name", "age"}). 340 AddRow(peterRow...) 341 mock.ExpectQuery(regexp.QuoteMeta("SELECT id, first_name, last_name, age FROM users WHERE (tenant_id = $1 AND first_name = $2 AND age != $3) ORDER BY id LIMIT 2 OFFSET 0")). 342 WithArgs(tenantID, "Peter", 18). 343 WillReturnRows(rows) 344 mock.ExpectQuery(regexp.QuoteMeta("SELECT COUNT(*) FROM users WHERE (tenant_id = $1 AND first_name = $2 AND age != $3)")). 345 WithArgs(tenantID, "Peter", 18). 346 WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(100)) 347 ctx := persistence.SaveToContext(context.TODO(), db) 348 var dest UserCollection 349 350 conditions := repo.Conditions{ 351 repo.NewEqualCondition("first_name", "Peter"), 352 repo.NewNotEqualCondition("age", 18), 353 } 354 355 actualPage, actualTotal, err := sut.List(ctx, UserType, tenantID, 2, "", "id", &dest, conditions...) 356 require.NoError(t, err) 357 assert.Equal(t, 100, actualTotal) 358 assert.Len(t, dest, 1) 359 assert.True(t, actualPage.HasNextPage) 360 assert.NotEmpty(t, actualPage.EndCursor) 361 }) 362 363 t.Run("returns empty page", func(t *testing.T) { 364 db, mock := testdb.MockDatabase(t) 365 defer mock.AssertExpectations(t) 366 367 rows := sqlmock.NewRows([]string{"id", "first_name", "last_name", "age"}) 368 mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, first_name, last_name, age FROM users WHERE tenant_id = $1 ORDER BY id LIMIT 2 OFFSET 0`)).WithArgs(tenantID).WillReturnRows(rows) 369 mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM users WHERE tenant_id = $1`)).WithArgs(tenantID).WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(0)) 370 ctx := persistence.SaveToContext(context.TODO(), db) 371 var dest UserCollection 372 373 actualPage, actualTotal, err := sut.List(ctx, UserType, tenantID, 2, "", "id", &dest) 374 require.NoError(t, err) 375 assert.Equal(t, 0, actualTotal) 376 assert.Empty(t, dest) 377 assert.False(t, actualPage.HasNextPage) 378 }) 379 380 t.Run("returns error if missing persistence context", func(t *testing.T) { 381 ctx := context.TODO() 382 _, _, err := sut.List(ctx, UserType, tenantID, 2, "", "id", nil) 383 require.EqualError(t, err, apperrors.NewInternalError("unable to fetch database from context").Error()) 384 }) 385 386 t.Run("returns error if wrong cursor", func(t *testing.T) { 387 ctx := persistence.SaveToContext(context.TODO(), &sqlx.Tx{}) 388 _, _, err := sut.List(ctx, UserType, tenantID, 2, "zzz", "", nil) 389 require.EqualError(t, err, "while decoding page cursor: cursor is not correct: illegal base64 data at input byte 0") 390 }) 391 392 t.Run("returns error if wrong pagination attributes", func(t *testing.T) { 393 ctx := persistence.SaveToContext(context.TODO(), &sqlx.Tx{}) 394 _, _, err := sut.List(ctx, UserType, tenantID, -3, "", "id", nil) 395 require.EqualError(t, err, "while converting offset and limit to cursor: Invalid data [reason=page size cannot be smaller than 1]") 396 }) 397 398 t.Run("returns error on db operation", func(t *testing.T) { 399 db, mock := testdb.MockDatabase(t) 400 defer mock.AssertExpectations(t) 401 402 mock.ExpectQuery(`SELECT .*`).WillReturnError(someError()) 403 ctx := persistence.SaveToContext(context.TODO(), db) 404 var dest UserCollection 405 406 _, _, err := sut.List(ctx, UserType, tenantID, 2, "", "id", &dest) 407 408 require.EqualError(t, err, "Internal Server Error: Unexpected error while executing SQL query") 409 }) 410 411 t.Run("context properly canceled", func(t *testing.T) { 412 db, mock := testdb.MockDatabase(t) 413 defer mock.AssertExpectations(t) 414 415 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond) 416 defer cancel() 417 418 ctx = persistence.SaveToContext(ctx, db) 419 var dest UserCollection 420 421 _, _, err := sut.List(ctx, UserType, tenantID, 2, "", "id", &dest) 422 423 require.EqualError(t, err, "Internal Server Error: Maximum processing timeout reached") 424 }) 425 426 t.Run("returns error on calculating total count", func(t *testing.T) { 427 db, mock := testdb.MockDatabase(t) 428 defer mock.AssertExpectations(t) 429 430 rows := sqlmock.NewRows([]string{"id", "first_name", "last_name", "age"}) 431 mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, first_name, last_name, age FROM users WHERE tenant_id = $1 ORDER BY id LIMIT 2 OFFSET 0`)).WithArgs(tenantID).WillReturnRows(rows) 432 mock.ExpectQuery(`SELECT COUNT\(\*\).*`).WillReturnError(someError()) 433 ctx := persistence.SaveToContext(context.TODO(), db) 434 var dest UserCollection 435 436 _, _, err := sut.List(ctx, UserType, tenantID, 2, "", "id", &dest) 437 require.EqualError(t, err, "Internal Server Error: Unexpected error while executing SQL query") 438 }) 439 } 440 441 func TestListPageableGlobal(t *testing.T) { 442 peterID := "peterID" 443 homerID := "homerID" 444 peter := User{FirstName: "Peter", LastName: "Griffin", Age: 40, ID: peterID} 445 peterRow := []driver.Value{peterID, "Peter", "Griffin", 40} 446 homer := User{FirstName: "Homer", LastName: "Simpson", Age: 55, ID: homerID} 447 homerRow := []driver.Value{homerID, "Homer", "Simpson", 55} 448 449 sut := repo.NewPageableQuerierGlobal("UserType", "users", 450 []string{"id", "first_name", "last_name", "age"}) 451 452 t.Run("returns first page and there are no more pages", func(t *testing.T) { 453 db, mock := testdb.MockDatabase(t) 454 mockListOnePageDBSelect(peterRow, homerRow, mock, repo.NoLock) 455 ctx := persistence.SaveToContext(context.TODO(), db) 456 defer mock.AssertExpectations(t) 457 458 var dest UserCollection 459 actualPage, actualTotal, err := sut.ListGlobal(ctx, 10, "", "id", &dest) 460 require.NoError(t, err) 461 assert.Equal(t, 2, actualTotal) 462 assert.Len(t, dest, 2) 463 assert.Equal(t, peter, dest[0]) 464 assert.Equal(t, homer, dest[1]) 465 assert.False(t, actualPage.HasNextPage) 466 }) 467 468 t.Run("returns full page and has next page", func(t *testing.T) { 469 db, mock := testdb.MockDatabase(t) 470 mockListOnePageOfManyDBSelect(peterRow, homerRow, mock, repo.NoLock) 471 ctx := persistence.SaveToContext(context.TODO(), db) 472 defer mock.AssertExpectations(t) 473 474 var dest UserCollection 475 actualPage, actualTotal, err := sut.ListGlobal(ctx, 2, "", "id", &dest) 476 require.NoError(t, err) 477 assert.Equal(t, 100, actualTotal) 478 assert.Len(t, dest, 2) 479 assert.True(t, actualPage.HasNextPage) 480 assert.NotEmpty(t, actualPage.EndCursor) 481 }) 482 483 t.Run("returns many pages and I can traverse it using cursor", func(t *testing.T) { 484 db, mock := testdb.MockDatabase(t) 485 mockListManyPagesDBSelect(peterRow, homerRow, mock, repo.NoLock) 486 ctx := persistence.SaveToContext(context.TODO(), db) 487 defer mock.AssertExpectations(t) 488 489 var first UserCollection 490 actualFirstPage, actualTotal, err := sut.ListGlobal(ctx, 1, "", "id", &first) 491 require.NoError(t, err) 492 assert.Equal(t, 100, actualTotal) 493 assert.Len(t, first, 1) 494 assert.True(t, actualFirstPage.HasNextPage) 495 assert.NotEmpty(t, actualFirstPage.EndCursor) 496 497 var second UserCollection 498 actualSecondPage, actualTotal, err := sut.ListGlobal(ctx, 1, actualFirstPage.EndCursor, "id", &second) 499 require.NoError(t, err) 500 assert.Equal(t, 100, actualTotal) 501 assert.Len(t, second, 1) 502 assert.True(t, actualSecondPage.HasNextPage) 503 assert.NotEmpty(t, actualSecondPage.EndCursor) 504 }) 505 506 t.Run("returns page without conditions", func(t *testing.T) { 507 db, mock := testdb.MockDatabase(t) 508 mockListOnePageDBSelectWithoutConditions(peterRow, mock, repo.NoLock) 509 ctx := persistence.SaveToContext(context.TODO(), db) 510 defer mock.AssertExpectations(t) 511 512 var dest UserCollection 513 actualPage, actualTotal, err := sut.ListGlobal(ctx, 2, "", "id", &dest) 514 require.NoError(t, err) 515 assert.Equal(t, 100, actualTotal) 516 assert.Len(t, dest, 1) 517 assert.True(t, actualPage.HasNextPage) 518 assert.NotEmpty(t, actualPage.EndCursor) 519 }) 520 521 t.Run("returns page with additional conditions", func(t *testing.T) { 522 db, mock := testdb.MockDatabase(t) 523 mockListOnePageDBSelectWithConditions(peterRow, mock, repo.NoLock) 524 ctx := persistence.SaveToContext(context.TODO(), db) 525 defer mock.AssertExpectations(t) 526 527 var dest UserCollection 528 conditions := repo.Conditions{ 529 repo.NewEqualCondition("first_name", "Peter"), 530 repo.NewNotEqualCondition("age", 18), 531 } 532 533 actualPage, actualTotal, err := sut.ListGlobalWithAdditionalConditions(ctx, 2, "", "id", &dest, repo.And(repo.ConditionTreesFromConditions(conditions)...)) 534 require.NoError(t, err) 535 assert.Equal(t, 100, actualTotal) 536 assert.Len(t, dest, 1) 537 assert.True(t, actualPage.HasNextPage) 538 assert.NotEmpty(t, actualPage.EndCursor) 539 }) 540 541 t.Run("returns empty page", func(t *testing.T) { 542 db, mock := testdb.MockDatabase(t) 543 mockListNoPagesDBSelect(mock, repo.NoLock) 544 ctx := persistence.SaveToContext(context.TODO(), db) 545 defer mock.AssertExpectations(t) 546 547 var dest UserCollection 548 actualPage, actualTotal, err := sut.ListGlobal(ctx, 2, "", "id", &dest) 549 require.NoError(t, err) 550 assert.Equal(t, 0, actualTotal) 551 assert.Empty(t, dest) 552 assert.False(t, actualPage.HasNextPage) 553 }) 554 555 t.Run("returns error if missing persistence context", func(t *testing.T) { 556 ctx := context.TODO() 557 _, _, err := sut.ListGlobal(ctx, 2, "", "id", nil) 558 require.EqualError(t, err, apperrors.NewInternalError("unable to fetch database from context").Error()) 559 }) 560 561 t.Run("returns error if wrong cursor", func(t *testing.T) { 562 ctx := persistence.SaveToContext(context.TODO(), &sqlx.Tx{}) 563 _, _, err := sut.ListGlobal(ctx, 2, "zzz", "", nil) 564 require.EqualError(t, err, "while decoding page cursor: cursor is not correct: illegal base64 data at input byte 0") 565 }) 566 567 t.Run("returns error if wrong pagination attributes", func(t *testing.T) { 568 ctx := persistence.SaveToContext(context.TODO(), &sqlx.Tx{}) 569 _, _, err := sut.ListGlobal(ctx, -3, "", "id", nil) 570 require.EqualError(t, err, "while converting offset and limit to cursor: Invalid data [reason=page size cannot be smaller than 1]") 571 }) 572 573 t.Run("returns error on db operation", func(t *testing.T) { 574 db, mock := testdb.MockDatabase(t) 575 defer mock.AssertExpectations(t) 576 577 mock.ExpectQuery(`SELECT .*`).WillReturnError(someError()) 578 ctx := persistence.SaveToContext(context.TODO(), db) 579 var dest UserCollection 580 581 _, _, err := sut.ListGlobal(ctx, 2, "", "id", &dest) 582 583 require.EqualError(t, err, "Internal Server Error: Unexpected error while executing SQL query") 584 }) 585 586 t.Run("context properly canceled", func(t *testing.T) { 587 db, mock := testdb.MockDatabase(t) 588 defer mock.AssertExpectations(t) 589 590 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond) 591 defer cancel() 592 593 ctx = persistence.SaveToContext(ctx, db) 594 var dest UserCollection 595 596 _, _, err := sut.ListGlobal(ctx, 2, "", "id", &dest) 597 598 require.EqualError(t, err, "Internal Server Error: Maximum processing timeout reached") 599 }) 600 601 t.Run("returns error on calculating total count", func(t *testing.T) { 602 db, mock := testdb.MockDatabase(t) 603 mockListPageableDBSelectWithCountError(mock, db, repo.NoLock) 604 ctx := persistence.SaveToContext(context.TODO(), db) 605 defer mock.AssertExpectations(t) 606 607 var dest UserCollection 608 _, _, err := sut.ListGlobal(ctx, 2, "", "id", &dest) 609 require.EqualError(t, err, "Internal Server Error: Unexpected error while executing SQL query") 610 }) 611 } 612 613 func TestListPageableGlobalWithSelectForUpdate(t *testing.T) { 614 peterID := "peterID" 615 homerID := "homerID" 616 peter := User{FirstName: "Peter", LastName: "Griffin", Age: 40, ID: peterID} 617 peterRow := []driver.Value{peterID, "Peter", "Griffin", 40} 618 homer := User{FirstName: "Homer", LastName: "Simpson", Age: 55, ID: homerID} 619 homerRow := []driver.Value{homerID, "Homer", "Simpson", 55} 620 621 sut := repo.NewPageableQuerierGlobal("UserType", "users", 622 []string{"id", "first_name", "last_name", "age"}) 623 624 t.Run("returns first page and there are no more pages", func(t *testing.T) { 625 db, mock := testdb.MockDatabase(t) 626 mockListOnePageDBSelect(peterRow, homerRow, mock, repo.ForUpdateLock) 627 ctx := persistence.SaveToContext(context.TODO(), db) 628 defer mock.AssertExpectations(t) 629 630 var dest UserCollection 631 actualPage, actualTotal, err := sut.ListGlobalWithSelectForUpdate(ctx, 10, "", "id", &dest) 632 require.NoError(t, err) 633 assert.Equal(t, 2, actualTotal) 634 assert.Len(t, dest, 2) 635 assert.Equal(t, peter, dest[0]) 636 assert.Equal(t, homer, dest[1]) 637 assert.False(t, actualPage.HasNextPage) 638 }) 639 640 t.Run("returns full page and has next page", func(t *testing.T) { 641 db, mock := testdb.MockDatabase(t) 642 mockListOnePageOfManyDBSelect(peterRow, homerRow, mock, repo.ForUpdateLock) 643 ctx := persistence.SaveToContext(context.TODO(), db) 644 defer mock.AssertExpectations(t) 645 646 var dest UserCollection 647 actualPage, actualTotal, err := sut.ListGlobalWithSelectForUpdate(ctx, 2, "", "id", &dest) 648 require.NoError(t, err) 649 assert.Equal(t, 100, actualTotal) 650 assert.Len(t, dest, 2) 651 assert.True(t, actualPage.HasNextPage) 652 assert.NotEmpty(t, actualPage.EndCursor) 653 }) 654 655 t.Run("returns many pages and I can traverse it using cursor", func(t *testing.T) { 656 db, mock := testdb.MockDatabase(t) 657 mockListManyPagesDBSelect(peterRow, homerRow, mock, repo.ForUpdateLock) 658 ctx := persistence.SaveToContext(context.TODO(), db) 659 defer mock.AssertExpectations(t) 660 661 var first UserCollection 662 actualFirstPage, actualTotal, err := sut.ListGlobalWithSelectForUpdate(ctx, 1, "", "id", &first) 663 require.NoError(t, err) 664 assert.Equal(t, 100, actualTotal) 665 assert.Len(t, first, 1) 666 assert.True(t, actualFirstPage.HasNextPage) 667 assert.NotEmpty(t, actualFirstPage.EndCursor) 668 669 var second UserCollection 670 actualSecondPage, actualTotal, err := sut.ListGlobalWithSelectForUpdate(ctx, 1, actualFirstPage.EndCursor, "id", &second) 671 require.NoError(t, err) 672 assert.Equal(t, 100, actualTotal) 673 assert.Len(t, second, 1) 674 assert.True(t, actualSecondPage.HasNextPage) 675 assert.NotEmpty(t, actualSecondPage.EndCursor) 676 }) 677 678 t.Run("returns page without conditions", func(t *testing.T) { 679 db, mock := testdb.MockDatabase(t) 680 mockListOnePageDBSelectWithoutConditions(peterRow, mock, repo.ForUpdateLock) 681 ctx := persistence.SaveToContext(context.TODO(), db) 682 defer mock.AssertExpectations(t) 683 684 var dest UserCollection 685 actualPage, actualTotal, err := sut.ListGlobalWithSelectForUpdate(ctx, 2, "", "id", &dest) 686 require.NoError(t, err) 687 assert.Equal(t, 100, actualTotal) 688 assert.Len(t, dest, 1) 689 assert.True(t, actualPage.HasNextPage) 690 assert.NotEmpty(t, actualPage.EndCursor) 691 }) 692 693 t.Run("returns empty page", func(t *testing.T) { 694 db, mock := testdb.MockDatabase(t) 695 mockListNoPagesDBSelect(mock, repo.ForUpdateLock) 696 ctx := persistence.SaveToContext(context.TODO(), db) 697 defer mock.AssertExpectations(t) 698 699 var dest UserCollection 700 actualPage, actualTotal, err := sut.ListGlobalWithSelectForUpdate(ctx, 2, "", "id", &dest) 701 require.NoError(t, err) 702 assert.Equal(t, 0, actualTotal) 703 assert.Empty(t, dest) 704 assert.False(t, actualPage.HasNextPage) 705 }) 706 707 t.Run("returns error if missing persistence context", func(t *testing.T) { 708 ctx := context.TODO() 709 _, _, err := sut.ListGlobalWithSelectForUpdate(ctx, 2, "", "id", nil) 710 require.EqualError(t, err, apperrors.NewInternalError("unable to fetch database from context").Error()) 711 }) 712 713 t.Run("returns error if wrong cursor", func(t *testing.T) { 714 ctx := persistence.SaveToContext(context.TODO(), &sqlx.Tx{}) 715 _, _, err := sut.ListGlobalWithSelectForUpdate(ctx, 2, "zzz", "", nil) 716 require.EqualError(t, err, "while decoding page cursor: cursor is not correct: illegal base64 data at input byte 0") 717 }) 718 719 t.Run("returns error if wrong pagination attributes", func(t *testing.T) { 720 ctx := persistence.SaveToContext(context.TODO(), &sqlx.Tx{}) 721 _, _, err := sut.ListGlobalWithSelectForUpdate(ctx, -3, "", "id", nil) 722 require.EqualError(t, err, "while converting offset and limit to cursor: Invalid data [reason=page size cannot be smaller than 1]") 723 }) 724 725 t.Run("returns error on db operation", func(t *testing.T) { 726 db, mock := testdb.MockDatabase(t) 727 defer mock.AssertExpectations(t) 728 729 mock.ExpectQuery(`SELECT .*`).WillReturnError(someError()) 730 ctx := persistence.SaveToContext(context.TODO(), db) 731 var dest UserCollection 732 733 _, _, err := sut.ListGlobalWithSelectForUpdate(ctx, 2, "", "id", &dest) 734 735 require.EqualError(t, err, "Internal Server Error: Unexpected error while executing SQL query") 736 }) 737 738 t.Run("context properly canceled", func(t *testing.T) { 739 db, mock := testdb.MockDatabase(t) 740 defer mock.AssertExpectations(t) 741 742 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond) 743 defer cancel() 744 745 ctx = persistence.SaveToContext(ctx, db) 746 var dest UserCollection 747 748 _, _, err := sut.ListGlobalWithSelectForUpdate(ctx, 2, "", "id", &dest) 749 750 require.EqualError(t, err, "Internal Server Error: Maximum processing timeout reached") 751 }) 752 753 t.Run("returns error on calculating total count", func(t *testing.T) { 754 db, mock := testdb.MockDatabase(t) 755 mockListPageableDBSelectWithCountError(mock, db, repo.ForUpdateLock) 756 ctx := persistence.SaveToContext(context.TODO(), db) 757 defer mock.AssertExpectations(t) 758 759 var dest UserCollection 760 _, _, err := sut.ListGlobalWithSelectForUpdate(ctx, 2, "", "id", &dest) 761 require.EqualError(t, err, "Internal Server Error: Unexpected error while executing SQL query") 762 }) 763 } 764 765 func mockListOnePageDBSelect(peterRow []driver.Value, homerRow []driver.Value, mock testdb.DBMock, lockClause string) { 766 rows := sqlmock.NewRows([]string{"id", "first_name", "last_name", "age"}). 767 AddRow(peterRow...). 768 AddRow(homerRow...) 769 mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, first_name, last_name, age FROM users ORDER BY id LIMIT 10 OFFSET 0` + PrepareLockClause(lockClause))).WillReturnRows(rows) 770 mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM users`)).WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(2)) 771 } 772 773 func mockListOnePageOfManyDBSelect(peterRow []driver.Value, homerRow []driver.Value, mock testdb.DBMock, lockClause string) { 774 rows := sqlmock.NewRows([]string{"id", "first_name", "last_name", "age"}). 775 AddRow(peterRow...). 776 AddRow(homerRow...) 777 mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, first_name, last_name, age FROM users ORDER BY id LIMIT 2 OFFSET 0` + PrepareLockClause(lockClause))).WillReturnRows(rows) 778 mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM users`)).WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(100)) 779 } 780 781 func mockListManyPagesDBSelect(peterRow []driver.Value, homerRow []driver.Value, mock testdb.DBMock, lockClause string) { 782 rowsForPage1 := sqlmock.NewRows([]string{"id", "first_name", "last_name", "age"}). 783 AddRow(peterRow...) 784 rowsForPage2 := sqlmock.NewRows([]string{"id", "first_name", "last_name", "age"}). 785 AddRow(homerRow...) 786 787 mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, first_name, last_name, age FROM users ORDER BY id LIMIT 1 OFFSET 0` + PrepareLockClause(lockClause))).WillReturnRows(rowsForPage1) 788 mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM users`)).WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(100)) 789 mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, first_name, last_name, age FROM users ORDER BY id LIMIT 1 OFFSET 1` + PrepareLockClause(lockClause))).WillReturnRows(rowsForPage2) 790 mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM users`)).WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(100)) 791 } 792 793 func mockListOnePageDBSelectWithoutConditions(peterRow []driver.Value, mock testdb.DBMock, lockClause string) { 794 rows := sqlmock.NewRows([]string{"id", "first_name", "last_name", "age"}). 795 AddRow(peterRow...) 796 mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, first_name, last_name, age FROM users ORDER BY id LIMIT 2 OFFSET 0` + PrepareLockClause(lockClause))).WillReturnRows(rows) 797 mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM users`)).WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(100)) 798 } 799 800 func mockListOnePageDBSelectWithConditions(peterRow []driver.Value, mock testdb.DBMock, lockClause string) { 801 rows := sqlmock.NewRows([]string{"id", "first_name", "last_name", "age"}). 802 AddRow(peterRow...) 803 mock.ExpectQuery(regexp.QuoteMeta("SELECT id, first_name, last_name, age FROM users WHERE (first_name = $1 AND age != $2) ORDER BY id LIMIT 2 OFFSET 0"+PrepareLockClause(lockClause))). 804 WithArgs("Peter", 18). 805 WillReturnRows(rows) 806 mock.ExpectQuery(regexp.QuoteMeta("SELECT COUNT(*) FROM users WHERE (first_name = $1 AND age != $2)")). 807 WithArgs("Peter", 18). 808 WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(100)) 809 } 810 811 func mockListNoPagesDBSelect(mock testdb.DBMock, lockClause string) { 812 rows := sqlmock.NewRows([]string{"id", "first_name", "last_name", "age"}) 813 mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, first_name, last_name, age FROM users ORDER BY id LIMIT 2 OFFSET 0` + PrepareLockClause(lockClause))).WillReturnRows(rows) 814 mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM users`)).WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(0)) 815 } 816 817 func mockListPageableDBSelectWithCountError(mock testdb.DBMock, db *sqlx.DB, lockClause string) { 818 rows := sqlmock.NewRows([]string{"id", "first_name", "last_name", "age"}) 819 mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, first_name, last_name, age FROM users ORDER BY id LIMIT 2 OFFSET 0` + PrepareLockClause(lockClause))).WillReturnRows(rows) 820 mock.ExpectQuery(`SELECT COUNT\(\*\).*`).WillReturnError(someError()) 821 }