package mysql import ( "bytes" "context" "crypto/tls" "database/sql/driver" "errors" "net" "os" "path/filepath" "strings" "sync/atomic" "testing" "time" "git.yuntuo.hk/k2safe/go-omni/database" "git.yuntuo.hk/k2safe/go-omni/logx" mysqldriver "github.com/go-sql-driver/mysql" ) func TestConfigurationTLSLoggingAndIsolation(t *testing.T) { cfg := DefaultConfig() cfg.User = "test" cfg.Password = "do-not-print" cfg.Database = "mixed-name" cfg.Session = map[string]string{"time_zone": "'+00:00'"} cfg.Collation = "utf8mb4_bin" var out bytes.Buffer logger, _ := logx.New(logx.Config{Writer: &out}) d, err := driverConfig(cfg, settings([]Option{WithLogger(logger)})) if err != nil { t.Fatal(err) } if d.TLS == nil || d.TLS.MinVersion != tls.VersionTLS12 || d.TLS.ServerName != cfg.Host || d.MultiStatements || d.Params["time_zone"] != "'+00:00'" { t.Fatal("driver policy differs") } cfg.Session["time_zone"] = "'+08:00'" if d.Params["time_zone"] != "'+00:00'" { t.Fatal("session map alias") } d.Logger.Print("connection ", cfg.Password) if strings.Contains(out.String(), cfg.Password) || !strings.Contains(out.String(), "[REDACTED]") { t.Fatal("secret leaked") } cfg.TLS.Mode = "disabled" d2, err := driverConfig(cfg, options{}) if err != nil || d2.TLS != nil || d.TLS == nil { t.Fatal("TLS configuration crossed instances", err) } cfg.TLS.CAFile = "unused" if err = cfg.Validate(); err == nil { t.Fatal("disabled TLS accepted CA") } cfg = DefaultConfig() cfg.TLS.CAFile = filepath.Join(t.TempDir(), "missing") if err = cfg.Validate(); err != nil { t.Fatal("Validate reads certificates", err) } if _, err = NewConnector(cfg); err == nil { t.Fatal("missing CA accepted") } if err = os.WriteFile(cfg.TLS.CAFile, []byte("do-not-print"), 0600); err != nil { t.Fatal(err) } if _, err = NewConnector(cfg); err == nil || strings.Contains(err.Error(), "do-not-print") { t.Fatal(err) } if _, err = NewConnector(DefaultConfig(), WithStartupCheck(func(context.Context, *database.DB) error { return nil })); err == nil { t.Fatal("unused startup check accepted") } } func TestDriverHandshakeErrorUsesIndependentLogger(t *testing.T) { var a, b bytes.Buffer la, _ := logx.New(logx.Config{Writer: &a}) lb, _ := logx.New(logx.Config{Writer: &b}) cfg := DefaultConfig() cfg.TLS.Mode = "disabled" cfg.Pool.PingTimeout = time.Second for i, l := range []*logx.Logger{la, lb} { before := []int{a.Len(), b.Len()} db, err := Open(context.Background(), cfg, WithLogger(l), WithDriverConfig(func(d *mysqldriver.Config) error { d.DialFunc = func(context.Context, string, string) (net.Conn, error) { reader, writer := net.Pipe() _ = writer.Close() return reader, nil } return nil })) if db != nil || err == nil { t.Fatal("broken handshake succeeded") } sizes := []int{a.Len(), b.Len()} if sizes[i] <= before[i] || sizes[1-i] != before[1-i] { t.Fatal("driver log isolation failed") } } } type testConnector struct{ closed atomic.Int32 } func (c *testConnector) Connect(context.Context) (driver.Conn, error) { return &testConn{c}, nil } func (c *testConnector) Driver() driver.Driver { return testDriver{} } type testDriver struct{} func (testDriver) Open(string) (driver.Conn, error) { return nil, errors.New("unexpected DSN") } type testConn struct{ owner *testConnector } func (c *testConn) Prepare(string) (driver.Stmt, error) { return nil, errors.New("unused") } func (c *testConn) Begin() (driver.Tx, error) { return nil, errors.New("unused") } func (c *testConn) Ping(context.Context) error { return nil } func (c *testConn) Close() error { c.owner.closed.Add(1); return nil } func TestStartupCheckRollbackAndPanic(t *testing.T) { for _, panics := range []bool{false, true} { c := &testConnector{} cause := errors.New("schema failed") func() { if panics { defer func() { if recover() == nil { t.Error("panic lost") } }() } db, err := openPool(context.Background(), c, database.DefaultPoolConfig(), []Check{func(ctx context.Context, db *database.DB) error { if _, ok := ctx.Deadline(); !ok { t.Error("unbounded check") } if panics { panic("failed check") } return cause }}) if db != nil || !errors.Is(err, cause) { t.Error("failed pool escaped or cause lost", err) } }() if c.closed.Load() != 1 { t.Fatal("failed initialization leaked pool", c.closed.Load()) } } }