feat: complete contract-driven business development and MySQL integration
This commit is contained in:
146
database/mysql/mysql_test.go
Normal file
146
database/mysql/mysql_test.go
Normal file
@@ -0,0 +1,146 @@
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user