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  }