Add support for PostgreSQL connection URIs via DATABASE_URI env var. When set, it takes precedence over config files and PoolOptions are ignored (pool settings can be specified in URI query string).
215 lines
5.3 KiB
Go
215 lines
5.3 KiB
Go
package pgdb
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"strings"
|
|
"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)
|
|
}
|
|
}
|
|
|
|
func TestOpenPoolWithDatabaseURI(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
|
|
// This test requires a running PostgreSQL instance
|
|
uri := os.Getenv("TEST_DATABASE_URI")
|
|
if uri == "" {
|
|
t.Skip("TEST_DATABASE_URI not set")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
t.Setenv("DATABASE_URI", uri)
|
|
|
|
pool, err := OpenPool(ctx, DefaultPoolOptions())
|
|
if err != nil {
|
|
t.Fatalf("OpenPool failed: %v", err)
|
|
}
|
|
defer pool.Close()
|
|
|
|
// Verify connection works
|
|
var result int
|
|
err = pool.QueryRow(ctx, "SELECT 1").Scan(&result)
|
|
if err != nil {
|
|
t.Fatalf("query failed: %v", err)
|
|
}
|
|
if result != 1 {
|
|
t.Errorf("expected 1, got %d", result)
|
|
}
|
|
}
|
|
|
|
func TestDatabaseURIPrecedence(t *testing.T) {
|
|
// Test that DATABASE_URI takes precedence over config files
|
|
// We use localhost with a port that's unlikely to have postgres running
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
|
|
// Set DATABASE_URI to a parseable URI pointing to a non-listening port
|
|
t.Setenv("DATABASE_URI", "postgres://testuser:testpass@127.0.0.1:59999/testdb?connect_timeout=1")
|
|
|
|
// Set config files to a nonexistent path - if this were used, we'd get
|
|
// "config file not found" error instead of connection refused
|
|
opts := DefaultPoolOptions()
|
|
opts.ConfigFiles = []string{"/nonexistent/path/database.yaml"}
|
|
|
|
_, err := OpenPool(ctx, opts)
|
|
|
|
// Should fail with connection error (not config file error)
|
|
// This proves DATABASE_URI was used instead of config files
|
|
if err == nil {
|
|
t.Fatal("expected error, got nil")
|
|
}
|
|
|
|
// The error should be about connection failure, not about missing config file
|
|
errStr := err.Error()
|
|
if strings.Contains(errStr, "config file") || strings.Contains(errStr, "no such file") {
|
|
t.Errorf("expected connection error, got config file error: %v", err)
|
|
}
|
|
}
|