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(), testcontainers.WithEnv(map[string]string{ "POSTGRES_INITDB_ARGS": "--locale-provider=icu --icu-locale=pl-PL --encoding=UTF8", }), ) 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 ICU pl-PL 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' LOCALE_PROVIDER icu ICU_LOCALE 'pl-PL'`); 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 }