feat(database): add PostgreSQL support with native pgx pool
Add PostgreSQL support to database package alongside existing MySQL support. Both databases share common infrastructure (pool management, metrics, transactions) while using database-specific connectors. database/ changes: - Add PostgresConfig struct and PostgreSQL connector using pgx/stdlib - Change MySQL config from DBConfig to *MySQLConfig (pointer) - Add Config.Validate() to prevent multiple database configs - Add PostgreSQL connector with secure config building (no password in DSN) - Add field validation and secure defaults (SSLMode="prefer") - Support legacy flat PostgreSQL config format for backward compatibility - Add tests for PostgreSQL configs and validation New database/pgdb/ package: - Native pgx connection pool support (*pgxpool.Pool) - OpenPool() and OpenPoolWithConfigFile() APIs - CreatePoolConfig() for secure config conversion - PoolOptions for fine-grained pool control - Full test coverage and documentation Security: - Passwords never exposed in DSN strings - Set passwords separately in pgx config objects - Validate all configuration before connection Architecture: - Shared code in database/ for both MySQL and PostgreSQL (sql.DB) - database/pgdb/ for PostgreSQL-specific native pool support
This commit is contained in:
151
database/pgdb/pool_test.go
Normal file
151
database/pgdb/pool_test.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package pgdb
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.ntppool.org/common/database"
|
||||
)
|
||||
|
||||
func TestCreatePoolConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg *database.PostgresConfig
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid config",
|
||||
cfg: &database.PostgresConfig{
|
||||
Host: "localhost",
|
||||
Port: 5432,
|
||||
User: "testuser",
|
||||
Pass: "testpass",
|
||||
Name: "testdb",
|
||||
SSLMode: "require",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid config with defaults",
|
||||
cfg: &database.PostgresConfig{
|
||||
Host: "localhost",
|
||||
User: "testuser",
|
||||
Pass: "testpass",
|
||||
Name: "testdb",
|
||||
// Port and SSLMode will use defaults
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "missing host",
|
||||
cfg: &database.PostgresConfig{
|
||||
User: "testuser",
|
||||
Pass: "testpass",
|
||||
Name: "testdb",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing user",
|
||||
cfg: &database.PostgresConfig{
|
||||
Host: "localhost",
|
||||
Pass: "testpass",
|
||||
Name: "testdb",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing database name",
|
||||
cfg: &database.PostgresConfig{
|
||||
Host: "localhost",
|
||||
User: "testuser",
|
||||
Pass: "testpass",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid sslmode",
|
||||
cfg: &database.PostgresConfig{
|
||||
Host: "localhost",
|
||||
User: "testuser",
|
||||
Pass: "testpass",
|
||||
Name: "testdb",
|
||||
SSLMode: "invalid",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
poolCfg, err := CreatePoolConfig(tt.cfg)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("CreatePoolConfig() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !tt.wantErr && poolCfg == nil {
|
||||
t.Error("CreatePoolConfig() returned nil config without error")
|
||||
}
|
||||
if !tt.wantErr && poolCfg != nil {
|
||||
// Verify config fields are set correctly
|
||||
if poolCfg.ConnConfig.Host != tt.cfg.Host && tt.cfg.Host != "" {
|
||||
t.Errorf("Expected Host=%s, got %s", tt.cfg.Host, poolCfg.ConnConfig.Host)
|
||||
}
|
||||
if poolCfg.ConnConfig.User != tt.cfg.User {
|
||||
t.Errorf("Expected User=%s, got %s", tt.cfg.User, poolCfg.ConnConfig.User)
|
||||
}
|
||||
if poolCfg.ConnConfig.Password != tt.cfg.Pass {
|
||||
t.Errorf("Expected Password to be set correctly")
|
||||
}
|
||||
if poolCfg.ConnConfig.Database != tt.cfg.Name {
|
||||
t.Errorf("Expected Database=%s, got %s", tt.cfg.Name, poolCfg.ConnConfig.Database)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultPoolOptions(t *testing.T) {
|
||||
opts := DefaultPoolOptions()
|
||||
|
||||
// Verify expected defaults
|
||||
if opts.MinConns != 0 {
|
||||
t.Errorf("Expected MinConns=0, got %d", opts.MinConns)
|
||||
}
|
||||
if opts.MaxConns != 25 {
|
||||
t.Errorf("Expected MaxConns=25, got %d", opts.MaxConns)
|
||||
}
|
||||
if opts.MaxConnLifetime != time.Hour {
|
||||
t.Errorf("Expected MaxConnLifetime=1h, got %v", opts.MaxConnLifetime)
|
||||
}
|
||||
if opts.MaxConnIdleTime != 30*time.Minute {
|
||||
t.Errorf("Expected MaxConnIdleTime=30m, got %v", opts.MaxConnIdleTime)
|
||||
}
|
||||
if opts.HealthCheckPeriod != time.Minute {
|
||||
t.Errorf("Expected HealthCheckPeriod=1m, got %v", opts.HealthCheckPeriod)
|
||||
}
|
||||
if len(opts.ConfigFiles) == 0 {
|
||||
t.Error("Expected ConfigFiles to be non-empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreatePoolConfigDefaults(t *testing.T) {
|
||||
// Test that defaults are applied correctly
|
||||
cfg := &database.PostgresConfig{
|
||||
Host: "localhost",
|
||||
User: "testuser",
|
||||
Pass: "testpass",
|
||||
Name: "testdb",
|
||||
// Port and SSLMode not set
|
||||
}
|
||||
|
||||
poolCfg, err := CreatePoolConfig(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("CreatePoolConfig() failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify defaults were applied
|
||||
if poolCfg.ConnConfig.Port != 5432 {
|
||||
t.Errorf("Expected default Port=5432, got %d", poolCfg.ConnConfig.Port)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user