feat: complete contract-driven business development and MySQL integration

This commit is contained in:
2026-09-24 21:56:04 +08:00
parent f9afb8f8dd
commit f5478cdc76
108 changed files with 6557 additions and 550 deletions

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