Files
summercms/modules/conga/postgres_test.go
Jakub Zych 037dc53030 feat(lagoon): per-query collation for OrderBy, drop the database locale check
- lagoon.OrderBy takes variadic lagoon.OrderOption values; lagoon.Collate(name)
  emits a validated, double-quoted COLLATE clause (e.g. "pl-x-icu")
- remove the exported CheckLocale and the ICU pl-PL check from Open and Use
- framework test containers and per-test databases are plain PostgreSQL
- lagoon README, root README and docs pages drop the locale requirement;
  queries-and-pagination gains a "Sorting with a collation" section
  backed by ExampleCollate
2026-10-01 09:51:03 +02:00

176 lines
3.8 KiB
Go

package conga
import (
"context"
"database/sql"
"fmt"
"net/url"
"os"
"strings"
"sync/atomic"
"testing"
"time"
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/compass"
"git.golem15.com/golem15/summercms/modules/lagoon"
_ "github.com/jackc/pgx/v5/stdlib"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/postgres"
"gorm.io/gorm"
)
var (
congaPG *postgres.PostgresContainer
congaSQL *sql.DB
congaDSN string
congaPGErr error
dbSeq atomic.Int64
)
func TestMain(m *testing.M) {
code := 1
if !testShort() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
congaPGErr = startCongaPostgres(ctx)
cancel()
if congaPGErr != nil {
fmt.Fprintf(os.Stderr, "conga: testcontainers postgres: %v\n", congaPGErr)
stopCongaPostgres()
os.Exit(1)
}
}
code = m.Run()
stopCongaPostgres()
os.Exit(code)
}
func testShort() bool {
for _, a := range os.Args {
if a == "-test.short" {
return true
}
}
return false
}
func startCongaPostgres(ctx context.Context) error {
ctr, err := postgres.Run(ctx,
"postgres:16-alpine",
postgres.WithDatabase("conga"),
postgres.WithUsername("conga"),
postgres.WithPassword("conga"),
postgres.BasicWaitStrategies(),
)
if err != nil {
return err
}
congaPG = ctr
dsn, err := ctr.ConnectionString(ctx, "sslmode=disable")
if err != nil {
return err
}
db, err := sql.Open("pgx", dsn)
if err != nil {
return err
}
if err := db.PingContext(ctx); err != nil {
_ = db.Close()
return err
}
congaSQL = db
congaDSN = dsn
return nil
}
func stopCongaPostgres() {
if congaSQL != nil {
_ = congaSQL.Close()
}
if congaPG != nil {
_ = testcontainers.TerminateContainer(congaPG)
}
}
func adminDB(t *testing.T) *sql.DB {
t.Helper()
if testing.Short() {
t.Skip("requires testcontainers postgres")
}
if congaPGErr != nil {
t.Fatalf("postgres unavailable: %v", congaPGErr)
}
if congaSQL == nil {
t.Fatal("postgres unavailable: container was not started")
}
return congaSQL
}
// migratedDB returns a dedicated database migrated with
// lagoon.Migrate (River v7 and summer_jobs) plus its DSN.
func migratedDB(t *testing.T) (*sql.DB, string) {
t.Helper()
admin := adminDB(t)
ctx := t.Context()
name := fmt.Sprintf("conga_%d", dbSeq.Add(1))
if _, err := admin.ExecContext(ctx, `CREATE DATABASE `+name+` TEMPLATE template0 ENCODING 'UTF8'`); err != nil && !strings.Contains(err.Error(), "already exists") {
t.Fatalf("create %s: %v", name, err)
}
dsn, err := dsnWithDB(congaDSN, name)
if err != nil {
t.Fatal(err)
}
db, err := sql.Open("pgx", dsn)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
_ = db.Close()
_, _ = admin.ExecContext(context.Background(), `DROP DATABASE IF EXISTS `+name+` WITH (FORCE)`)
})
gdb, err := lagoon.Use(ctx, db)
if err != nil {
t.Fatal(err)
}
if err := lagoon.Migrate(gdb, nil); err != nil {
t.Fatal(err)
}
return db, dsn
}
func dsnWithDB(dsn, name string) (string, error) {
u, err := url.Parse(dsn)
if err != nil {
return "", err
}
u.Path = "/" + name
return u.String(), nil
}
// testApp returns an app whose config names dsn and which has published db
// and a GORM handle on it. Extra config keys are set as overrides.
func testApp(t *testing.T, db *sql.DB, dsn string, extra map[string]any) (*backpack.App, *gorm.DB) {
t.Helper()
cfg, err := compass.Open(compass.Options{Dir: t.TempDir(), Env: "testing", Environ: []string{}})
if err != nil {
t.Fatal(err)
}
if err := cfg.Set("database.dsn", dsn); err != nil {
t.Fatal(err)
}
for k, v := range extra {
if err := cfg.Set(k, v); err != nil {
t.Fatal(err)
}
}
app := backpack.New(cfg)
gdb, err := lagoon.Use(t.Context(), db)
if err != nil {
t.Fatal(err)
}
if err := lagoon.Publish(app, db, gdb); err != nil {
t.Fatal(err)
}
return app, gdb
}