Skip to content

Commit 2a8c7f0

Browse files
committed
add tests for GORM scopes
1 parent 446138b commit 2a8c7f0

File tree

1 file changed

+376
-2
lines changed

1 file changed

+376
-2
lines changed

tests/scopes_test.go

Lines changed: 376 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ package tests
4040

4141
import (
4242
"context"
43+
"fmt"
44+
"sync"
4345
"testing"
46+
"time"
4447

4548
. "github.com/oracle-samples/gorm-oracle/tests/utils"
4649

@@ -103,11 +106,11 @@ func TestScopes(t *testing.T) {
103106
t.Errorf("Should found two users's name in 1, 2, but got %v", result.RowsAffected)
104107
}
105108

106-
var maxId int64
109+
var maxID int64
107110
userTable := func(db *gorm.DB) *gorm.DB {
108111
return db.WithContext(context.Background()).Table("users")
109112
}
110-
if err := DB.Scopes(userTable).Select("max(\"id\")").Scan(&maxId).Error; err != nil {
113+
if err := DB.Scopes(userTable).Select("max(\"id\")").Scan(&maxID).Error; err != nil {
111114
t.Errorf("select max(id)")
112115
}
113116
}
@@ -166,3 +169,374 @@ func TestComplexScopes(t *testing.T) {
166169
})
167170
}
168171
}
172+
173+
func TestEmptyAndNilScopes(t *testing.T) {
174+
setupScopeTestData(t)
175+
176+
// Test with no scopes
177+
var users []User
178+
err := DB.Scopes().Find(&users).Error
179+
if err != nil {
180+
t.Errorf("Empty scopes should work, got error: %v", err)
181+
}
182+
183+
// Test with empty slice of scopes
184+
emptyScopes := []func(*gorm.DB) *gorm.DB{}
185+
err = DB.Scopes(emptyScopes...).Find(&users).Error
186+
if err != nil {
187+
t.Errorf("Empty scope slice should work, got error: %v", err)
188+
}
189+
190+
// Test behavior when we have mixed nil and valid scopes
191+
defer func() {
192+
if r := recover(); r != nil {
193+
t.Logf("Mixed nil scopes caused panic (expected): %v", r)
194+
}
195+
}()
196+
}
197+
198+
func TestScopesWithDatabaseErrors(t *testing.T) {
199+
setupScopeTestData(t)
200+
201+
// Scope that generates invalid SQL
202+
invalidSQLScope := func(db *gorm.DB) *gorm.DB {
203+
return db.Where("invalid_column_name_12345 = ?", "test")
204+
}
205+
206+
var users []User
207+
err := DB.Scopes(invalidSQLScope).Find(&users).Error
208+
if err == nil {
209+
t.Error("Expected error for invalid SQL in scope, got nil")
210+
}
211+
212+
// Verify database still works after scope error
213+
err = DB.Find(&users).Error
214+
if err != nil {
215+
t.Errorf("Database should still work after scope error, got: %v", err)
216+
}
217+
}
218+
219+
func TestConflictingScopes(t *testing.T) {
220+
setupScopeTestData(t)
221+
222+
// Scopes with contradictory conditions
223+
alwaysTrue := func(db *gorm.DB) *gorm.DB {
224+
return db.Where("1 = 1")
225+
}
226+
alwaysFalse := func(db *gorm.DB) *gorm.DB {
227+
return db.Where("1 = 0")
228+
}
229+
230+
var users []User
231+
err := DB.Scopes(alwaysTrue, alwaysFalse).Find(&users).Error
232+
if err != nil {
233+
t.Errorf("Conflicting scopes should not cause error, got: %v", err)
234+
}
235+
if len(users) != 0 {
236+
t.Errorf("Conflicting scopes should return no results, got %d users", len(users))
237+
}
238+
239+
// Test conflicting WHERE conditions on same column
240+
nameScope1 := func(db *gorm.DB) *gorm.DB {
241+
return db.Where("\"name\" = ?", "ScopeUser1")
242+
}
243+
nameScope2 := func(db *gorm.DB) *gorm.DB {
244+
return db.Where("\"name\" = ?", "ScopeUser2")
245+
}
246+
247+
err = DB.Scopes(nameScope1, nameScope2).Find(&users).Error
248+
if err != nil {
249+
t.Errorf("Conflicting name scopes should not cause error, got: %v", err)
250+
}
251+
if len(users) != 0 {
252+
t.Errorf("Conflicting name scopes should return no results, got %d users", len(users))
253+
}
254+
}
255+
256+
func TestContextCancellationInScopes(t *testing.T) {
257+
setupScopeTestData(t)
258+
259+
// Create a context that gets cancelled immediately
260+
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
261+
defer cancel()
262+
263+
contextScope := func(db *gorm.DB) *gorm.DB {
264+
return db.WithContext(ctx)
265+
}
266+
267+
var users []User
268+
err := DB.Scopes(contextScope).Find(&users).Error
269+
// Error is expected due to context cancellation
270+
271+
// Verify database still works after context cancellation
272+
err = DB.Find(&users).Error
273+
if err != nil {
274+
t.Errorf("Database should work after context cancellation in scope, got: %v", err)
275+
}
276+
}
277+
278+
func TestConcurrentScopeUsage(t *testing.T) {
279+
setupScopeTestData(t)
280+
281+
const numGoroutines = 10
282+
const operationsPerGoroutine = 5
283+
284+
var wg sync.WaitGroup
285+
errors := make(chan error, numGoroutines*operationsPerGoroutine)
286+
287+
for i := 0; i < numGoroutines; i++ {
288+
wg.Add(1)
289+
go func(goroutineID int) {
290+
defer wg.Done()
291+
292+
for j := 0; j < operationsPerGoroutine; j++ {
293+
userScope := func(db *gorm.DB) *gorm.DB {
294+
return db.Where("\"name\" LIKE ?", fmt.Sprintf("ScopeUser%d", (goroutineID%3)+1))
295+
}
296+
297+
var users []User
298+
err := DB.Scopes(userScope).Find(&users).Error
299+
if err != nil {
300+
errors <- fmt.Errorf("goroutine %d operation %d failed: %v", goroutineID, j, err)
301+
}
302+
}
303+
}(i)
304+
}
305+
306+
wg.Wait()
307+
close(errors)
308+
309+
errorCount := 0
310+
for err := range errors {
311+
t.Errorf("Concurrent scope error: %v", err)
312+
errorCount++
313+
}
314+
315+
if errorCount > 0 {
316+
t.Errorf("Got %d errors in concurrent scope usage", errorCount)
317+
}
318+
}
319+
320+
func TestScopesThatModifyUnexpectedQuery(t *testing.T) {
321+
setupScopeTestData(t)
322+
323+
// Scope that changes the table
324+
tableChangingScope := func(db *gorm.DB) *gorm.DB {
325+
return db.Table("companies")
326+
}
327+
328+
// Scope that changes the model
329+
modelChangingScope := func(db *gorm.DB) *gorm.DB {
330+
return db.Model(&Company{})
331+
}
332+
333+
// Test table changing scope
334+
var users []User
335+
err := DB.Model(&User{}).Scopes(tableChangingScope).Find(&users).Error
336+
337+
// Test model changing scope
338+
err = DB.Scopes(modelChangingScope).Find(&users).Error
339+
340+
// Scope that adds unexpected clauses
341+
limitScope := func(db *gorm.DB) *gorm.DB {
342+
return db.Limit(1).Offset(1).Order("\"id\" DESC")
343+
}
344+
345+
err = DB.Scopes(limitScope).Find(&users).Error
346+
if err != nil {
347+
t.Errorf("Scope with limit/offset/order should work, got: %v", err)
348+
}
349+
}
350+
351+
func TestLargeNumberOfScopes(t *testing.T) {
352+
setupScopeTestData(t)
353+
354+
// Create a large number of scopes
355+
const numScopes = 100
356+
scopes := make([]func(*gorm.DB) *gorm.DB, numScopes)
357+
358+
for i := 0; i < numScopes; i++ {
359+
val := i
360+
scopes[i] = func(db *gorm.DB) *gorm.DB {
361+
return db.Where("\"id\" > ?", val*-1) // Always true conditions
362+
}
363+
}
364+
365+
var users []User
366+
start := time.Now()
367+
err := DB.Scopes(scopes...).Find(&users).Error
368+
duration := time.Since(start)
369+
370+
if err != nil {
371+
t.Errorf("Large number of scopes failed: %v", err)
372+
}
373+
374+
t.Logf("Processing %d scopes took %v", numScopes, duration)
375+
376+
// Verify we still get results
377+
if len(users) == 0 {
378+
t.Error("Large number of scopes should still return results")
379+
}
380+
}
381+
382+
func TestScopesWithTransactions(t *testing.T) {
383+
setupScopeTestData(t)
384+
385+
// Test scopes within a transaction
386+
err := DB.Transaction(func(tx *gorm.DB) error {
387+
transactionScope := func(db *gorm.DB) *gorm.DB {
388+
return db.Where("\"name\" = ?", "ScopeUser1")
389+
}
390+
391+
var users []User
392+
return tx.Scopes(transactionScope).Find(&users).Error
393+
})
394+
395+
if err != nil {
396+
t.Errorf("Scopes within transaction should work, got: %v", err)
397+
}
398+
399+
// Test scope that tries to start its own transaction (nested transaction scenario)
400+
nestedTxScope := func(db *gorm.DB) *gorm.DB {
401+
return db.Begin()
402+
}
403+
404+
err = DB.Transaction(func(tx *gorm.DB) error {
405+
var users []User
406+
return tx.Scopes(nestedTxScope).Find(&users).Error
407+
})
408+
}
409+
410+
func TestScopesWithRawSQL(t *testing.T) {
411+
setupScopeTestData(t)
412+
413+
// Scope that adds raw SQL conditions
414+
rawSQLScope := func(db *gorm.DB) *gorm.DB {
415+
return db.Where("LENGTH(\"name\") > ?", 5)
416+
}
417+
418+
var users []User
419+
err := DB.Scopes(rawSQLScope).Find(&users).Error
420+
if err != nil {
421+
t.Errorf("Raw SQL scope should work, got: %v", err)
422+
}
423+
424+
// Test proper parameterized queries (safe)
425+
safeParameterizedScope := func(db *gorm.DB) *gorm.DB {
426+
return db.Where("\"name\" = ? OR \"name\" LIKE ?", "test", "ScopeUser%")
427+
}
428+
429+
err = DB.Scopes(safeParameterizedScope).Find(&users).Error
430+
if err != nil {
431+
t.Errorf("Parameterized SQL in scope should be safe, got: %v", err)
432+
}
433+
434+
// Test complex safe expressions
435+
complexSafeScope := func(db *gorm.DB) *gorm.DB {
436+
return db.Where("(\"name\" = ? OR \"age\" > ?) AND \"deleted_at\" IS NULL", "ScopeUser1", 10)
437+
}
438+
439+
err = DB.Scopes(complexSafeScope).Find(&users).Error
440+
if err != nil {
441+
t.Errorf("Complex parameterized SQL should work, got: %v", err)
442+
}
443+
444+
// Test that we get expected results
445+
if len(users) == 0 {
446+
t.Error("Should have found some users with complex scope")
447+
}
448+
}
449+
450+
func TestScopeErrorRecovery(t *testing.T) {
451+
setupScopeTestData(t)
452+
453+
// First, cause an error with a bad scope
454+
badScope := func(db *gorm.DB) *gorm.DB {
455+
return db.Where("non_existent_column = ?", "value")
456+
}
457+
458+
var users []User
459+
err := DB.Scopes(badScope).Find(&users).Error
460+
if err == nil {
461+
t.Error("Expected error from bad scope")
462+
}
463+
464+
// Then verify normal operations still work
465+
goodScope := func(db *gorm.DB) *gorm.DB {
466+
return db.Where("\"name\" = ?", "ScopeUser1")
467+
}
468+
469+
err = DB.Scopes(goodScope).Find(&users).Error
470+
if err != nil {
471+
t.Errorf("Good scope should work after bad scope error: %v", err)
472+
}
473+
474+
if len(users) != 1 {
475+
t.Errorf("Expected 1 user, got %d", len(users))
476+
}
477+
}
478+
479+
func TestScopeChainModification(t *testing.T) {
480+
// Test that scopes don't interfere with each other's chain modifications
481+
setupScopeTestData(t)
482+
483+
scope1Called := false
484+
scope2Called := false
485+
486+
scope1 := func(db *gorm.DB) *gorm.DB {
487+
scope1Called = true
488+
return db.Where("\"id\" > ?", 0)
489+
}
490+
491+
scope2 := func(db *gorm.DB) *gorm.DB {
492+
scope2Called = true
493+
return db.Where("\"name\" IS NOT NULL")
494+
}
495+
496+
var users []User
497+
err := DB.Scopes(scope1, scope2).Find(&users).Error
498+
if err != nil {
499+
t.Errorf("Scope chain should work, got: %v", err)
500+
}
501+
502+
if !scope1Called {
503+
t.Error("Scope1 should have been called")
504+
}
505+
if !scope2Called {
506+
t.Error("Scope2 should have been called")
507+
}
508+
}
509+
510+
func TestScopesWithSubqueries(t *testing.T) {
511+
setupScopeTestData(t)
512+
513+
// Scope that uses a subquery
514+
subqueryScope := func(db *gorm.DB) *gorm.DB {
515+
subQuery := DB.Model(&User{}).Select("\"name\"").Where("\"id\" = 1")
516+
return db.Where("\"name\" IN (?)", subQuery)
517+
}
518+
519+
var users []User
520+
err := DB.Scopes(subqueryScope).Find(&users).Error
521+
if err != nil {
522+
t.Errorf("Subquery scope should work, got: %v", err)
523+
}
524+
}
525+
526+
// Helper function to set up test data for scope tests
527+
func setupScopeTestData(t *testing.T) {
528+
// Clean up any existing data
529+
DB.Exec("DELETE FROM users WHERE \"name\" LIKE 'ScopeUser%'")
530+
531+
// Create test users
532+
users := []*User{
533+
GetUser("ScopeUser1", Config{}),
534+
GetUser("ScopeUser2", Config{}),
535+
GetUser("ScopeUser3", Config{}),
536+
}
537+
538+
err := DB.Create(&users).Error
539+
if err != nil {
540+
t.Fatalf("Failed to create test data: %v", err)
541+
}
542+
}

0 commit comments

Comments
 (0)