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 != 4 { t.Errorf("Expected MaxConns=4 (pgxpool default), 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) } }