ci: add postgres backend boot smoke test (#1398)
This commit is contained in:
@@ -152,6 +152,37 @@ jobs:
|
||||
apps/backend/test-results.json
|
||||
retention-days: 30
|
||||
|
||||
postgres-boot:
|
||||
name: Backend Postgres boot
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
container: ghcr.io/kdlbs/kandev-ci:build-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16
|
||||
env:
|
||||
POSTGRES_USER: kandev
|
||||
POSTGRES_PASSWORD: kandev
|
||||
POSTGRES_DB: kandev_admin
|
||||
options: >-
|
||||
--health-cmd "pg_isready -U kandev -d kandev_admin"
|
||||
--health-interval 5s
|
||||
--health-timeout 5s
|
||||
--health-retries 12
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: apps/backend
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Run Postgres boot smoke test
|
||||
env:
|
||||
KANDEV_TEST_POSTGRES_DSN: host=postgres port=5432 user=kandev password=kandev dbname=kandev_admin sslmode=disable
|
||||
run: go test -v -race -run TestPostgresBootInitializesRepositories ./cmd/kandev
|
||||
|
||||
test-windows:
|
||||
name: Backend (windows)
|
||||
runs-on: windows-latest
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"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/common/config"
|
||||
"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(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]()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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: "kandev",
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user