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
197 lines
4.8 KiB
Go
197 lines
4.8 KiB
Go
package database
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
)
|
|
|
|
func TestDefaultConfigOptions(t *testing.T) {
|
|
opts := DefaultConfigOptions()
|
|
|
|
// Verify expected defaults for API package
|
|
if opts.MaxOpenConns != 25 {
|
|
t.Errorf("Expected MaxOpenConns=25, got %d", opts.MaxOpenConns)
|
|
}
|
|
if opts.MaxIdleConns != 10 {
|
|
t.Errorf("Expected MaxIdleConns=10, got %d", opts.MaxIdleConns)
|
|
}
|
|
if opts.ConnMaxLifetime != 3*time.Minute {
|
|
t.Errorf("Expected ConnMaxLifetime=3m, got %v", opts.ConnMaxLifetime)
|
|
}
|
|
if !opts.EnablePoolMonitoring {
|
|
t.Error("Expected EnablePoolMonitoring=true")
|
|
}
|
|
if opts.PrometheusRegisterer != prometheus.DefaultRegisterer {
|
|
t.Error("Expected PrometheusRegisterer to be DefaultRegisterer")
|
|
}
|
|
if len(opts.ConfigFiles) == 0 {
|
|
t.Error("Expected ConfigFiles to be non-empty")
|
|
}
|
|
}
|
|
|
|
func TestMonitorConfigOptions(t *testing.T) {
|
|
opts := MonitorConfigOptions()
|
|
|
|
// Verify expected defaults for Monitor package
|
|
if opts.MaxOpenConns != 10 {
|
|
t.Errorf("Expected MaxOpenConns=10, got %d", opts.MaxOpenConns)
|
|
}
|
|
if opts.MaxIdleConns != 5 {
|
|
t.Errorf("Expected MaxIdleConns=5, got %d", opts.MaxIdleConns)
|
|
}
|
|
if opts.ConnMaxLifetime != 3*time.Minute {
|
|
t.Errorf("Expected ConnMaxLifetime=3m, got %v", opts.ConnMaxLifetime)
|
|
}
|
|
if opts.EnablePoolMonitoring {
|
|
t.Error("Expected EnablePoolMonitoring=false")
|
|
}
|
|
if opts.PrometheusRegisterer != nil {
|
|
t.Error("Expected PrometheusRegisterer to be nil")
|
|
}
|
|
if len(opts.ConfigFiles) == 0 {
|
|
t.Error("Expected ConfigFiles to be non-empty")
|
|
}
|
|
}
|
|
|
|
func TestConfigStructures(t *testing.T) {
|
|
// Test that MySQL configuration structures can be created and populated
|
|
config := Config{
|
|
MySQL: &MySQLConfig{
|
|
DSN: "user:pass@tcp(localhost:3306)/dbname",
|
|
User: "testuser",
|
|
Pass: "testpass",
|
|
DBName: "testdb",
|
|
},
|
|
}
|
|
|
|
if config.MySQL.DSN == "" {
|
|
t.Error("Expected DSN to be set")
|
|
}
|
|
if config.MySQL.User != "testuser" {
|
|
t.Errorf("Expected User='testuser', got '%s'", config.MySQL.User)
|
|
}
|
|
if config.MySQL.Pass != "testpass" {
|
|
t.Errorf("Expected Pass='testpass', got '%s'", config.MySQL.Pass)
|
|
}
|
|
if config.MySQL.DBName != "testdb" {
|
|
t.Errorf("Expected DBName='testdb', got '%s'", config.MySQL.DBName)
|
|
}
|
|
}
|
|
|
|
func TestPostgresConfigStructures(t *testing.T) {
|
|
// Test that PostgreSQL configuration structures can be created and populated
|
|
config := Config{
|
|
Postgres: &PostgresConfig{
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
User: "testuser",
|
|
Pass: "testpass",
|
|
Name: "testdb",
|
|
SSLMode: "require",
|
|
},
|
|
}
|
|
|
|
if config.Postgres.Host != "localhost" {
|
|
t.Errorf("Expected Host='localhost', got '%s'", config.Postgres.Host)
|
|
}
|
|
if config.Postgres.Port != 5432 {
|
|
t.Errorf("Expected Port=5432, got %d", config.Postgres.Port)
|
|
}
|
|
if config.Postgres.User != "testuser" {
|
|
t.Errorf("Expected User='testuser', got '%s'", config.Postgres.User)
|
|
}
|
|
if config.Postgres.Pass != "testpass" {
|
|
t.Errorf("Expected Pass='testpass', got '%s'", config.Postgres.Pass)
|
|
}
|
|
if config.Postgres.Name != "testdb" {
|
|
t.Errorf("Expected Name='testdb', got '%s'", config.Postgres.Name)
|
|
}
|
|
if config.Postgres.SSLMode != "require" {
|
|
t.Errorf("Expected SSLMode='require', got '%s'", config.Postgres.SSLMode)
|
|
}
|
|
}
|
|
|
|
func TestLegacyPostgresConfig(t *testing.T) {
|
|
// Test that legacy flat PostgreSQL format can be created
|
|
config := Config{
|
|
User: "testuser",
|
|
Pass: "testpass",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Name: "testdb",
|
|
SSLMode: "require",
|
|
}
|
|
|
|
if config.User != "testuser" {
|
|
t.Errorf("Expected User='testuser', got '%s'", config.User)
|
|
}
|
|
if config.Name != "testdb" {
|
|
t.Errorf("Expected Name='testdb', got '%s'", config.Name)
|
|
}
|
|
}
|
|
|
|
func TestConfigValidation(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
config Config
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "valid mysql config",
|
|
config: Config{
|
|
MySQL: &MySQLConfig{DSN: "test"},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid postgres config",
|
|
config: Config{
|
|
Postgres: &PostgresConfig{User: "test", Host: "localhost", Name: "test"},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid legacy postgres config",
|
|
config: Config{
|
|
User: "test",
|
|
Host: "localhost",
|
|
Name: "testdb",
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "both mysql and postgres set",
|
|
config: Config{
|
|
MySQL: &MySQLConfig{DSN: "test"},
|
|
Postgres: &PostgresConfig{User: "test"},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "mysql and legacy postgres set",
|
|
config: Config{
|
|
MySQL: &MySQLConfig{DSN: "test"},
|
|
User: "test",
|
|
Name: "testdb",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "no config set",
|
|
config: Config{},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := tt.config.Validate()
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|