Files
2026-09-18 13:05:49 +08:00

112 lines
3.3 KiB
Go

package db
import (
"context"
"fmt"
"net/url"
"os"
"testing"
"time"
"github.com/jackc/pgx/v5"
)
// TestOpenCreatesMissingDatabase 验证「配置里写了不存在的库,启动时自动建出来」。
// 需要一个可连的库:用 ROND_DSN 指过来(会临时建一个库并在结束时删掉),未设置时跳过。
func TestOpenCreatesMissingDatabase(t *testing.T) {
dsn := os.Getenv("ROND_DSN")
if dsn == "" {
t.Skip("设置 ROND_DSN 后运行自动建库检查")
}
name := fmt.Sprintf("rond_test_autocreate_%d", time.Now().UnixNano())
u, err := url.Parse(dsn)
if err != nil {
t.Fatalf("解析 ROND_DSN: %v", err)
}
u.Path = "/" + name
target := u.String()
d, err := Open(target)
if err != nil {
t.Fatalf("库不存在时应自动建出来再连,实际报错: %v", err)
}
defer func() {
d.Close()
// DROP DATABASE 要求没有连接挂着,所以先关掉上面的句柄
dropDatabase(t, dsn, name)
}()
// 库真的建出来了
var n int
if err := d.QueryRow(`SELECT count(*) FROM pg_database WHERE datname=$1`, name).Scan(&n); err != nil {
t.Fatalf("查 pg_database: %v", err)
}
if n != 1 {
t.Fatalf("库 %s 未被创建(匹配到 %d 条)", name, n)
}
// 建表也能正常跑(否则「建了个空库」没有意义)
if err := Migrate(context.Background(), d); err != nil {
t.Fatalf("初始化表结构: %v", err)
}
var tables int
if err := d.QueryRow(`SELECT count(*) FROM information_schema.tables WHERE table_schema='public'`).Scan(&tables); err != nil {
t.Fatal(err)
}
if tables < 10 {
t.Errorf("表结构不完整,只有 %d 张表", tables)
}
}
// TestOpenExistingDatabase 守住常规路径:库已存在时不该多做事(也不该报错)。
func TestOpenExistingDatabase(t *testing.T) {
dsn := os.Getenv("ROND_DSN")
if dsn == "" {
t.Skip("设置 ROND_DSN 后运行连接检查")
}
d, err := Open(dsn)
if err != nil {
t.Fatalf("连接既有库失败: %v", err)
}
d.Close()
}
// TestDatabaseMissingDetection 确认只有「库不存在」会被当成需要建库的信号,
// 否则连错服务器、口令错也会被当成「那就建个新的」。
func TestDatabaseMissingDetection(t *testing.T) {
if isDatabaseMissing(nil) {
t.Error("nil 不该判为库不存在")
}
if !isDatabaseMissing(fmt.Errorf(`failed to connect: database "rond_x" does not exist (SQLSTATE 3D000)`)) {
t.Error("库不存在的错误应被识别")
}
for _, msg := range []string{
"failed SASL auth: 用户 \"postgres\" Password 认证失败 (SQLSTATE 28P01)",
"dial tcp 127.0.0.1:5499: connectex: No connection could be made",
} {
if isDatabaseMissing(fmt.Errorf("%s", msg)) {
t.Errorf("不该判为库不存在: %s", msg)
}
}
}
func dropDatabase(t *testing.T, dsn, name string) {
t.Helper()
cc, err := pgx.ParseConfig(dsn)
if err != nil {
t.Logf("清理时解析 dsn 失败: %v", err)
return
}
cc.Database = "postgres"
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
conn, err := pgx.ConnectConfig(ctx, cc)
if err != nil {
t.Logf("清理时连维护库失败: %v", err)
return
}
defer conn.Close(ctx)
if _, err := conn.Exec(ctx, "DROP DATABASE IF EXISTS "+pgx.Identifier{name}.Sanitize()); err != nil {
t.Logf("删除测试库失败: %v", err)
}
}