147 lines
4.4 KiB
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())
|
|
}
|
|
}
|
|
}
|