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
152 lines
3.5 KiB
Go
152 lines
3.5 KiB
Go
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)
|
|
}
|
|
}
|