feat: use postgres
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user