feat: complete contract-driven business development and MySQL integration
This commit is contained in:
115
database/mysql/integration_test.go
Normal file
115
database/mysql/integration_test.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package mysql_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"git.yuntuo.hk/k2safe/go-omni/database"
|
||||
adapter "git.yuntuo.hk/k2safe/go-omni/database/mysql"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestStructuredMySQLAdapterTLS(t *testing.T) {
|
||||
if os.Getenv("GO_OMNI_CONNECTOR_MYSQL_TEST") != "1" {
|
||||
t.Skip("use scripts/verifyConnector.py with an isolated local MySQL")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
cfg := adapter.DefaultConfig()
|
||||
host, port, err := net.SplitHostPort(os.Getenv("GO_OMNI_CONNECTOR_ADDR"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cfg.Host = host
|
||||
cfg.Port, _ = strconv.Atoi(port)
|
||||
cfg.User = os.Getenv("GO_OMNI_CONNECTOR_USER")
|
||||
cfg.Password = os.Getenv("GO_OMNI_CONNECTOR_PASSWORD")
|
||||
cfg.Database = os.Getenv("GO_OMNI_CONNECTOR_DATABASE")
|
||||
cfg.TLS.CAFile = os.Getenv("GO_OMNI_CONNECTOR_CA_FILE")
|
||||
cfg.TLS.ServerName = "localhost"
|
||||
cfg.Pool.MaxOpenConns = 2
|
||||
cfg.Pool.MaxIdleConns = 2
|
||||
cfg.Pool.PingTimeout = 2 * time.Second
|
||||
cfg.Session = map[string]string{"time_zone": "'+00:00'"}
|
||||
checked := false
|
||||
first, err := adapter.Open(ctx, cfg, adapter.WithStartupCheck(func(ctx context.Context, db *database.DB) error {
|
||||
var one int
|
||||
err := db.QueryRowContext(ctx, "SELECT 1").Scan(&one)
|
||||
checked = one == 1
|
||||
return err
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer first.Close()
|
||||
if !checked {
|
||||
t.Fatal("startup check not called")
|
||||
}
|
||||
cfg.Session = map[string]string{"time_zone": "'+08:00'"}
|
||||
second, err := adapter.Open(ctx, cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer second.Close()
|
||||
for i, db := range []*database.DB{first, second} {
|
||||
var key, cipher, zone string
|
||||
if err = db.QueryRowContext(ctx, "SHOW SESSION STATUS LIKE 'Ssl_cipher'").Scan(&key, &cipher); err != nil || cipher == "" {
|
||||
t.Fatal("not encrypted", err)
|
||||
}
|
||||
if err = db.QueryRowContext(ctx, "SELECT @@session.time_zone").Scan(&zone); err != nil || zone != []string{"+00:00", "+08:00"}[i] {
|
||||
t.Fatal("session isolation failed", zone, err)
|
||||
}
|
||||
if db.Stats().MaxOpenConnections != 2 {
|
||||
t.Fatal("pool limit ignored")
|
||||
}
|
||||
}
|
||||
for _, bad := range []string{"untrusted", "hostname"} {
|
||||
invalid := cfg
|
||||
if bad == "untrusted" {
|
||||
invalid.TLS.CAFile = ""
|
||||
} else {
|
||||
invalid.TLS.ServerName = "wrong.invalid"
|
||||
}
|
||||
db, e := adapter.Open(ctx, invalid)
|
||||
if db != nil {
|
||||
_ = db.Close()
|
||||
t.Fatal("bad certificate accepted")
|
||||
}
|
||||
var verify *tls.CertificateVerificationError
|
||||
if !errors.As(e, &verify) {
|
||||
t.Fatal("lost TLS verification cause", e)
|
||||
}
|
||||
}
|
||||
cause := errors.New("project schema check failed")
|
||||
if db, e := adapter.Open(ctx, cfg, adapter.WithStartupCheck(func(context.Context, *database.DB) error { return cause })); db != nil || !errors.Is(e, cause) {
|
||||
t.Fatal("failed startup escaped", e)
|
||||
}
|
||||
if err = first.Health(ctx); err != nil {
|
||||
t.Fatal("failed pool affected healthy pool", err)
|
||||
}
|
||||
// The server requires a client certificate for this separate fixture user.
|
||||
cfg.User = "omni_mtls"
|
||||
cfg.Password = ""
|
||||
cfg.Database = ""
|
||||
cfg.Session = nil
|
||||
if db, e := adapter.Open(ctx, cfg); e == nil {
|
||||
db.Close()
|
||||
t.Fatal("missing required client certificate accepted")
|
||||
}
|
||||
cfg.TLS.CertFile = os.Getenv("GO_OMNI_CONNECTOR_CLIENT_CERT")
|
||||
cfg.TLS.KeyFile = os.Getenv("GO_OMNI_CONNECTOR_CLIENT_KEY")
|
||||
mtls, err := adapter.Open(ctx, cfg)
|
||||
if err != nil {
|
||||
t.Fatal("mTLS failed", err)
|
||||
}
|
||||
if err = mtls.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = mtls.Close(); err != nil {
|
||||
t.Fatal("pool close is not idempotent", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user