// Package testutil 为 Admin 测试创建彼此隔离的 MySQL 8 数据库。 package testutil import ( "crypto/rand" "database/sql" "encoding/hex" "fmt" "os" "strings" "cmautobuy/admin/config" "cmautobuy/admin/repository" ) // TestingT 是本包需要的 testing.T 最小接口,避免生产包依赖 testing。 type TestingT interface { Helper() Cleanup(func()) Fatalf(format string, args ...any) Errorf(format string, args ...any) Skipf(format string, args ...any) } // OpenMySQL 为一个测试创建独立数据库,测试结束后只删除该数据库。 func OpenMySQL(t TestingT) *sql.DB { t.Helper() if os.Getenv("CMAUTOBUY_MYSQL_TEST") != "1" { t.Skipf("未设置 CMAUTOBUY_MYSQL_TEST=1,跳过真实 MySQL 8 测试") } baseConfig, err := config.LoadDatabaseFromEnv() if err != nil { t.Fatalf("读取 MySQL 测试配置失败: %v", err) } if !strings.HasSuffix(baseConfig.Name, "_test") { t.Fatalf("MySQL 测试基础库名必须以 _test 结尾,实际 %q", baseConfig.Name) } adminDB, err := repository.OpenMySQL(baseConfig) if err != nil { t.Fatalf("连接 MySQL 测试基础库失败: %v", err) } databaseName := testDatabaseName(baseConfig.Name) quotedName := quoteIdentifier(databaseName) if _, err := adminDB.Exec("CREATE DATABASE " + quotedName + " CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci"); err != nil { adminDB.Close() t.Fatalf("创建隔离 MySQL 测试库失败: %v", err) } testConfig := baseConfig testConfig.Name = databaseName db, err := repository.OpenMySQL(testConfig) if err != nil { adminDB.Exec("DROP DATABASE " + quotedName) adminDB.Close() t.Fatalf("连接隔离 MySQL 测试库失败: %v", err) } if err := repository.MigrateMySQL(db); err != nil { db.Close() adminDB.Exec("DROP DATABASE " + quotedName) adminDB.Close() t.Fatalf("初始化隔离 MySQL 测试库失败: %v", err) } t.Cleanup(func() { db.Close() if _, err := adminDB.Exec("DROP DATABASE " + quotedName); err != nil { t.Errorf("清理隔离 MySQL 测试库 %s 失败: %v", databaseName, err) } adminDB.Close() }) return db } func testDatabaseName(base string) string { random := make([]byte, 8) if _, err := rand.Read(random); err != nil { panic(fmt.Sprintf("生成 MySQL 测试库随机名失败: %v", err)) } if len(base) > 40 { base = base[:40] } return base + "_" + hex.EncodeToString(random) } func quoteIdentifier(name string) string { return "`" + strings.ReplaceAll(name, "`", "``") + "`" }