Files
kandev/apps/backend/internal/backendapp/postgres_boot_test.go
T

179 lines
4.2 KiB
Go

package backendapp
import (
"context"
"fmt"
"net/url"
"os"
"strings"
"testing"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgconn"
_ "github.com/jackc/pgx/v5/stdlib"
"github.com/jmoiron/sqlx"
"github.com/kandev/kandev/internal/agent/registry"
"github.com/kandev/kandev/internal/common/config"
"github.com/kandev/kandev/internal/events/bus"
"github.com/kandev/kandev/internal/testutil"
)
func TestPostgresBootInitializesRepositories(t *testing.T) {
adminDSN := testutil.PostgresDSNFromEnv(t)
adminInfo, err := parsePostgresConnInfo(adminDSN)
if err != nil {
t.Fatalf("parse postgres dsn: %v", err)
}
admin := openPostgresAdmin(t, adminDSN)
dbName := "kandev_boot_" + strings.ReplaceAll(uuid.NewString(), "-", "")
if _, err := admin.Exec("CREATE DATABASE " + dbName); err != nil {
t.Fatalf("create postgres database %s: %v", dbName, err)
}
t.Cleanup(func() {
_, _ = admin.Exec("DROP DATABASE IF EXISTS " + dbName + " WITH (FORCE)")
})
cfg := &config.Config{
HomeDir: t.TempDir(),
Database: config.DatabaseConfig{
Driver: "postgres",
Host: adminInfo.Host,
Port: adminInfo.Port,
User: adminInfo.User,
Password: adminInfo.Password,
DBName: dbName,
SSLMode: adminInfo.SSLMode,
MaxConns: 5,
MinConns: 1,
},
}
log := newTestLogger()
pool, repos, cleanups, err := provideRepositories(context.Background(), cfg, log, "test-postgres-boot")
if err != nil {
t.Fatalf("provide repositories with postgres: %v", err)
}
if pool == nil || repos == nil {
t.Fatal("provideRepositories returned nil pool or repositories")
}
t.Cleanup(func() {
for i := len(cleanups) - 1; i >= 0; i-- {
if cleanups[i] != nil {
_ = cleanups[i]()
}
}
})
agentRegistry, registryCleanup, err := registry.Provide(log)
if err != nil {
t.Fatalf("provide agent registry: %v", err)
}
t.Cleanup(func() {
if registryCleanup != nil {
_ = registryCleanup()
}
})
services, agentSettingsController, err := provideServices(
cfg,
log,
repos,
pool,
bus.NewMemoryEventBus(log),
agentRegistry,
"test-postgres-boot",
)
if err != nil {
t.Fatalf("provide services with postgres: %v", err)
}
if err := runInitialAgentSetup(context.Background(), services.User, agentSettingsController, log); err != nil {
t.Fatalf("run initial agent setup with postgres: %v", err)
}
}
type postgresConnInfo struct {
Host string
Port int
User string
Password string
SSLMode string
}
func openPostgresAdmin(t *testing.T, dsn string) *sqlx.DB {
t.Helper()
db, err := sqlx.Open("pgx", dsn)
if err != nil {
t.Fatalf("open postgres admin connection: %v", err)
}
t.Cleanup(func() {
_ = db.Close()
})
if err := db.Ping(); err != nil {
t.Fatalf("ping postgres admin connection: %v", err)
}
return db
}
func parsePostgresConnInfo(dsn string) (postgresConnInfo, error) {
cfg, err := pgconn.ParseConfig(dsn)
if err != nil {
return postgresConnInfo{}, err
}
info := postgresConnInfo{
Host: cfg.Host,
Port: int(cfg.Port),
User: cfg.User,
Password: cfg.Password,
SSLMode: postgresSSLMode(dsn),
}
if info.Host == "" {
info.Host = "localhost"
}
if info.Port == 0 {
info.Port = 5432
}
return info, nil
}
func postgresSSLMode(dsn string) string {
if parsed, err := url.Parse(dsn); err == nil && parsed.Scheme != "" {
if mode := parsed.Query().Get("sslmode"); mode != "" {
return mode
}
}
for _, field := range strings.Fields(dsn) {
key, value, ok := strings.Cut(field, "=")
if ok && strings.EqualFold(key, "sslmode") {
return strings.Trim(value, "'\"")
}
}
if mode := os.Getenv("PGSSLMODE"); mode != "" {
return mode
}
return "disable"
}
func TestParsePostgresConnInfo(t *testing.T) {
info, err := parsePostgresConnInfo(
"host=postgres port=5433 user=kandev password=secret dbname=kandev_test sslmode=require",
)
if err != nil {
t.Fatalf("parse conn info: %v", err)
}
want := postgresConnInfo{
Host: "postgres",
Port: 5433,
User: kandevName,
Password: "secret",
SSLMode: "require",
}
if info != want {
t.Fatalf("conn info = %s, want %s", formatConnInfo(info), formatConnInfo(want))
}
}
func formatConnInfo(info postgresConnInfo) string {
return fmt.Sprintf("{host:%s port:%d user:%s sslmode:%s}", info.Host, info.Port, info.User, info.SSLMode)
}