package postgres_test import ( "context" "embed" "io/fs" "testing" "time" "galaxy/postgres" testcontainers "github.com/testcontainers/testcontainers-go" tcpostgres "github.com/testcontainers/testcontainers-go/modules/postgres" "github.com/testcontainers/testcontainers-go/wait" ) const smokeImage = "postgres:16-alpine" //go:embed testdata/migrations/*.sql var smokeMigrationsFS embed.FS func TestPostgresPackageRoundTrip(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) t.Cleanup(cancel) pgContainer, err := tcpostgres.Run(ctx, smokeImage, tcpostgres.WithDatabase("galaxy_smoke"), tcpostgres.WithUsername("galaxy_smoke"), tcpostgres.WithPassword("galaxy_smoke"), // The Postgres image emits "ready to accept connections" twice during // startup: once for the temporary bootstrap instance, once for the real // listener on the mapped port. Waiting for the second occurrence // prevents racing the bootstrap. testcontainers.WithWaitStrategy( wait.ForLog("database system is ready to accept connections"). WithOccurrence(2). WithStartupTimeout(60*time.Second), ), ) if err != nil { t.Fatalf("start postgres container: %v", err) } t.Cleanup(func() { if err := testcontainers.TerminateContainer(pgContainer); err != nil { t.Errorf("terminate postgres container: %v", err) } }) dsn, err := pgContainer.ConnectionString(ctx, "sslmode=disable") if err != nil { t.Fatalf("postgres connection string: %v", err) } cfg := postgres.DefaultConfig() cfg.PrimaryDSN = dsn cfg.OperationTimeout = 5 * time.Second db, err := postgres.OpenPrimary(ctx, cfg) if err != nil { t.Fatalf("open primary: %v", err) } t.Cleanup(func() { if err := db.Close(); err != nil { t.Errorf("close db: %v", err) } }) if err := postgres.Ping(ctx, db, cfg.OperationTimeout); err != nil { t.Fatalf("ping: %v", err) } migrationsDir, err := fs.Sub(smokeMigrationsFS, "testdata/migrations") if err != nil { t.Fatalf("sub migrations FS: %v", err) } if err := postgres.RunMigrations(ctx, db, migrationsDir, "."); err != nil { t.Fatalf("run migrations: %v", err) } var insertedID int64 if err := db.QueryRowContext(ctx, "INSERT INTO smoke (note) VALUES ($1) RETURNING id", "hello", ).Scan(&insertedID); err != nil { t.Fatalf("insert returning id: %v", err) } if insertedID <= 0 { t.Fatalf("inserted id = %d, want > 0", insertedID) } var note string if err := db.QueryRowContext(ctx, "SELECT note FROM smoke WHERE id = $1", insertedID, ).Scan(¬e); err != nil { t.Fatalf("select note: %v", err) } if note != "hello" { t.Fatalf("note = %q, want %q", note, "hello") } } func TestOpenReplicasReturnsNilWhenUnconfigured(t *testing.T) { t.Parallel() cfg := postgres.DefaultConfig() cfg.PrimaryDSN = "postgres://localhost:5432/galaxy?sslmode=disable" dbs, err := postgres.OpenReplicas(context.Background(), cfg) if err != nil { t.Fatalf("open replicas: %v", err) } if dbs != nil { t.Fatalf("replicas = %v, want nil", dbs) } }