Files
common/database/pgdb/pool_test.go
Ask Bjørn Hansen 283d3936f6 feat(pgdb): add DATABASE_URI environment variable support
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).
2025-11-29 11:52:45 -08:00

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