github.com/letsencrypt/boulder@v0.20251208.0/test/db.go (about) 1 package test 2 3 import ( 4 "context" 5 "database/sql" 6 "fmt" 7 "io" 8 "testing" 9 10 "github.com/letsencrypt/boulder/test/vars" 11 ) 12 13 var _ CleanUpDB = &sql.DB{} 14 15 // CleanUpDB is an interface with only what is needed to delete all 16 // rows in all tables in a database plus close the database 17 // connection. It is satisfied by *sql.DB. 18 type CleanUpDB interface { 19 BeginTx(context.Context, *sql.TxOptions) (*sql.Tx, error) 20 ExecContext(context.Context, string, ...any) (sql.Result, error) 21 QueryContext(context.Context, string, ...any) (*sql.Rows, error) 22 io.Closer 23 } 24 25 // ResetBoulderTestDatabase returns a cleanup function which deletes all rows in 26 // all tables of the 'boulder_sa_test' database. Omits the 'gorp_migrations' 27 // table as this is used by sql-migrate (https://github.com/rubenv/sql-migrate) 28 // to track migrations. If it encounters an error it fails the tests. 29 func ResetBoulderTestDatabase(t testing.TB) func() { 30 return resetTestDatabase(t, context.Background(), vars.DBConnSAFullPerms) 31 } 32 33 // ResetIncidentsTestDatabase returns a cleanup function which deletes all rows 34 // in all tables of the 'incidents_sa_test' database. Omits the 35 // 'gorp_migrations' table as this is used by sql-migrate 36 // (https://github.com/rubenv/sql-migrate) to track migrations. If it encounters 37 // an error it fails the tests. 38 func ResetIncidentsTestDatabase(t testing.TB) func() { 39 return resetTestDatabase(t, context.Background(), vars.DBConnIncidentsFullPerms) 40 } 41 42 func resetTestDatabase(t testing.TB, ctx context.Context, dsn string) func() { 43 db, err := sql.Open("mysql", dsn) 44 if err != nil { 45 t.Fatalf("Couldn't create db: %s", err) 46 } 47 err = deleteEverythingInAllTables(ctx, db) 48 if err != nil { 49 t.Fatalf("Failed to delete everything: %s", err) 50 } 51 return func() { 52 err := deleteEverythingInAllTables(ctx, db) 53 if err != nil { 54 t.Fatalf("Failed to truncate tables after the test: %s", err) 55 } 56 _ = db.Close() 57 } 58 } 59 60 // clearEverythingInAllTables deletes all rows in the tables available to the 61 // CleanUpDB passed. See allTableNamesInDB for what is meant by "all tables 62 // available". To be used only in test code. 63 func deleteEverythingInAllTables(ctx context.Context, db CleanUpDB) error { 64 ts, err := allTableNamesInDB(ctx, db) 65 if err != nil { 66 return err 67 } 68 69 // We do this in a transaction to make sure that the foreign 70 // key checks remain disabled even if the db object chooses 71 // another connection to make the deletion on. Note that 72 // `alter table` statements will silently cause transactions 73 // to commit, so we do them outside of the transaction. 74 tx, err := db.BeginTx(ctx, nil) 75 if err != nil { 76 return fmt.Errorf("unable to start transaction to delete all rows: %s", err) 77 } 78 79 _, err = tx.ExecContext(ctx, "SET FOREIGN_KEY_CHECKS = 0") 80 if err != nil { 81 _ = tx.Rollback() 82 return fmt.Errorf("unable to disable FOREIGN_KEY_CHECKS: %s", err) 83 } 84 85 for _, tn := range ts { 86 // 1 = 1 here prevents the i_am_a_dummy setting from rejecting the 87 // DELETE for not having a WHERE clause. 88 _, err = tx.ExecContext(ctx, "DELETE FROM `"+tn+"` WHERE 1 = 1") 89 if err != nil { 90 _, _ = tx.ExecContext(ctx, "SET FOREIGN_KEY_CHECKS = 1") 91 _ = tx.Rollback() 92 return fmt.Errorf("unable to delete all rows from table %#v: %s", tn, err) 93 } 94 } 95 _, err = tx.ExecContext(ctx, "SET FOREIGN_KEY_CHECKS = 1") 96 if err != nil { 97 _ = tx.Rollback() 98 return fmt.Errorf("unable to re-enable FOREIGN_KEY_CHECKS: %s", err) 99 } 100 101 err = tx.Commit() 102 if err != nil { 103 return fmt.Errorf("unable to commit transaction to delete all rows: %s", err) 104 } 105 return nil 106 } 107 108 // allTableNamesInDB returns the names of the tables available to the passed 109 // CleanUpDB. Omits the 'gorp_migrations' table as this is used by sql-migrate 110 // (https://github.com/rubenv/sql-migrate) to track migrations. 111 func allTableNamesInDB(ctx context.Context, db CleanUpDB) ([]string, error) { 112 r, err := db.QueryContext(ctx, "select table_name from information_schema.tables t where t.table_schema = DATABASE() and t.table_name != 'gorp_migrations';") 113 if err != nil { 114 return nil, err 115 } 116 defer r.Close() 117 var ts []string 118 for r.Next() { 119 tableName := "" 120 err = r.Scan(&tableName) 121 if err != nil { 122 return nil, err 123 } 124 ts = append(ts, tableName) 125 } 126 return ts, r.Err() 127 }