Files
go-omni/database/mysql/mysql_test.go

147 lines
4.4 KiB
Go

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())
}
}
}