Repo .git was damaged (refs/ missing, index truncated, object store near-empty). Repaired via git init (rebuilt refs), stale index preserved as .git/index.corrupt-20260910.bak, then full worktree committed as a rollback baseline.
146 lines
3.4 KiB
Go
146 lines
3.4 KiB
Go
// Copyright 2023 E99p1ant. All rights reserved.
|
|
// Use of this source code is governed by a MIT-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package dbutil
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"math/rand"
|
|
"net/url"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"gorm.io/driver/mysql"
|
|
"gorm.io/driver/postgres"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
var flagParseOnce sync.Once
|
|
|
|
func NewTestDB(t *testing.T, migrationTables ...interface{}) (testDB *gorm.DB, cleanup func(...string) error) {
|
|
dbType := os.Getenv("DB_TYPE")
|
|
|
|
var dsn string
|
|
var dialectFunc func(string) gorm.Dialector
|
|
|
|
switch dbType {
|
|
case "mysql":
|
|
dsn = os.ExpandEnv("$DB_USER:$DB_PASSWORD@tcp($DB_HOST:$DB_PORT)/$DB_DATABASE?charset=utf8mb4&parseTime=True&loc=Local")
|
|
dialectFunc = mysql.Open
|
|
case "postgres":
|
|
dsn = os.ExpandEnv("postgres://$DB_USER:$DB_PASSWORD@$DB_HOST:$DB_PORT?sslmode=disable")
|
|
dialectFunc = postgres.Open
|
|
default:
|
|
t.Fatalf("Unknown database type: %q", dbType)
|
|
}
|
|
|
|
db, err := gorm.Open(dialectFunc(dsn), &gorm.Config{
|
|
NowFunc: Now,
|
|
SkipDefaultTransaction: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to open connection: %v", err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
dbname := "tamabox-test-" + strconv.FormatUint(rng.Uint64(), 10)
|
|
|
|
err = db.WithContext(ctx).Exec(`CREATE DATABASE ` + QuoteIdentifier(dbType, dbname)).Error
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test database: %v", err)
|
|
}
|
|
|
|
switch dbType {
|
|
case "mysql":
|
|
dsn = strings.ReplaceAll(dsn, os.Getenv("DB_DATABASE"), dbname)
|
|
case "postgres":
|
|
cfg, err := url.Parse(dsn)
|
|
if err != nil {
|
|
t.Fatalf("Failed to parse DSN")
|
|
}
|
|
cfg.Path = "/" + dbname
|
|
dsn = cfg.String()
|
|
}
|
|
|
|
flagParseOnce.Do(flag.Parse)
|
|
|
|
testDB, err = gorm.Open(dialectFunc(dsn), &gorm.Config{
|
|
NowFunc: Now,
|
|
SkipDefaultTransaction: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to open test connection: %v", err)
|
|
}
|
|
|
|
err = testDB.AutoMigrate(migrationTables...)
|
|
if err != nil {
|
|
t.Fatalf("Failed to auto migrate tables: %v", err)
|
|
}
|
|
|
|
t.Cleanup(func() {
|
|
defer func() {
|
|
if database, err := db.DB(); err == nil {
|
|
_ = database.Close()
|
|
}
|
|
}()
|
|
|
|
if t.Failed() {
|
|
t.Logf("DATABASE %s left intact for inspection", dbname)
|
|
return
|
|
}
|
|
|
|
database, err := testDB.DB()
|
|
if err != nil {
|
|
t.Fatalf("Failed to get currently open database: %v", err)
|
|
}
|
|
|
|
err = database.Close()
|
|
if err != nil {
|
|
t.Fatalf("Failed to close currently open database: %v", err)
|
|
}
|
|
|
|
err = db.WithContext(ctx).Exec(`DROP DATABASE ` + QuoteIdentifier(dbType, dbname)).Error
|
|
if err != nil {
|
|
t.Fatalf("Failed to drop test database: %v", err)
|
|
}
|
|
})
|
|
|
|
return testDB, func(tables ...string) error {
|
|
if t.Failed() {
|
|
return nil
|
|
}
|
|
|
|
for _, table := range tables {
|
|
var query string
|
|
switch dbType {
|
|
case "mysql":
|
|
query = `TRUNCATE TABLE ` + QuoteIdentifier(dbType, table)
|
|
case "postgres":
|
|
query = `TRUNCATE TABLE ` + QuoteIdentifier(dbType, table) + ` RESTART IDENTITY CASCADE`
|
|
}
|
|
|
|
err := testDB.WithContext(ctx).Exec(query).Error
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// QuoteIdentifier quotes an "identifier" (e.g. a table or a column name) to be
|
|
// used as part of an SQL statement.
|
|
func QuoteIdentifier(typ, s string) string {
|
|
if typ == "postgres" {
|
|
return `"` + strings.ReplaceAll(s, `"`, `""`) + `"`
|
|
}
|
|
return "`" + strings.ReplaceAll(s, "`", "``") + "`"
|
|
}
|