package test import ( "RenewCMS/internal/domain/user" entity "RenewCMS/internal/infrastructure/persistence/models" persistence "RenewCMS/internal/infrastructure/persistence/repositories" "errors" "testing" "gorm.io/gorm" ) func failAccountDeleteKeyRemoval(t *testing.T, db *gorm.DB, name string, failure error) { t.Helper() err := db.Callback().Delete().Before("gorm:delete").Register(name, func(tx *gorm.DB) { if tx.Statement.Table == "api_keys" { _ = tx.AddError(failure) } }) if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = db.Callback().Delete().Remove(name) }) } func TestAccountDeleteContentionMapping(t *testing.T) { cases := []struct { name string failure string guards []uint32 notDeletable bool }{ {"locked database with a guard role", "database is locked", []uint32{8}, true}, {"sqlite busy with a guard role", "SQLITE_BUSY: x", []uint32{8}, true}, {"deadlock with a guard role", "Deadlock found when trying to get lock", []uint32{8}, true}, {"serialization failure with a guard role", "could not serialize access", []uint32{8}, true}, {"locked database without a guard role", "database is locked", nil, false}, {"unrelated failure with a guard role", "disk exploded", []uint32{8}, false}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { db := openContractSQLite(t) seedDeleteAccountRows(t, db, true) failure := errors.New(tc.failure) failAccountDeleteKeyRemoval(t, db, "test:account_delete_contention", failure) err := persistence.NewAccountEraser(db).Erase(user.Erasure{UserID: 7, GuardRoleIDs: tc.guards}) if errors.Is(err, user.ErrAccountNotDeletable) != tc.notDeletable { t.Errorf("ErrAccountNotDeletable = %v, want %v (err %v)", !tc.notDeletable, tc.notDeletable, err) } if !tc.notDeletable && !errors.Is(err, failure) { t.Errorf("expected the original failure, got %v", err) } if deleteAccountRowCount(t, db, &entity.User{}, "id = ?", 7) != 1 { t.Error("the user must still exist after a failed erasure") } }) } } func TestAccountDeleteSQLiteTakesTheWriteLockBeforeCounting(t *testing.T) { db := openContractSQLite(t) seedDeleteAccountRows(t, db, true) roleWrites := 0 err := db.Callback().Update().Before("gorm:update").Register("test:account_delete_role_writes", func(tx *gorm.DB) { if tx.Statement.Table == "roles" { roleWrites++ } }) if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = db.Callback().Update().Remove("test:account_delete_role_writes") }) err = persistence.NewAccountEraser(db).Erase(user.Erasure{UserID: 7, GuardRoleIDs: []uint32{8}}) if err != nil || roleWrites != 1 { t.Errorf("expected one lock write on the guard roles and no error, got %d writes and %v", roleWrites, err) } }