package bundb_test import ( "context" "database/sql" "errors" "fmt" "net" "net/url" "os" "strings" "testing" "time" "unicode" "gitea.dwysokinski.me/twhelp/sessions/internal/bundb/migrations" "github.com/cenkalti/backoff/v4" "github.com/google/uuid" "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/uptrace/bun" "github.com/uptrace/bun/dialect/pgdialect" "github.com/uptrace/bun/driver/pgdriver" ) func newDB(tb testing.TB) *bun.DB { tb.Helper() if dsn, ok := os.LookupEnv("TESTS_DB_DSN"); ok { return newDBWithDSN(tb, dsn) } q := url.Values{} q.Add("sslmode", "disable") dsn := &url.URL{ Scheme: "postgres", User: url.UserPassword("postgres", "postgres"), Path: "twhelp", RawQuery: q.Encode(), } pool, err := dockertest.NewPool("") require.NoError(tb, err, "couldn't connect to docker") pool.MaxWait = 20 * time.Second pw, _ := dsn.User.Password() resource, err := pool.RunWithOptions(&dockertest.RunOptions{ Repository: "postgres", Tag: "14.5", Env: []string{ fmt.Sprintf("POSTGRES_USER=%s", dsn.User.Username()), fmt.Sprintf("POSTGRES_PASSWORD=%s", pw), fmt.Sprintf("POSTGRES_DB=%s", dsn.Path), }, }, func(config *docker.HostConfig) { config.AutoRemove = true config.RestartPolicy = docker.RestartPolicy{ Name: "no", } }) require.NoError(tb, err, "couldn't start resource") tb.Cleanup(func() { _ = pool.Purge(resource) }) assert.NoError(tb, resource.Expire(60)) dsn.Host = getHostPort(tb, resource, "5432/tcp") return newDBWithDSN(tb, dsn.String()) } func newDBWithDSN(tb testing.TB, dsn string) *bun.DB { tb.Helper() schema := generateSchema() sqldb := sql.OpenDB( pgdriver.NewConnector( pgdriver.WithDSN(dsn), pgdriver.WithConnParams(map[string]any{ "search_path": schema, }), ), ) bunDB := bun.NewDB(sqldb, pgdialect.New()) tb.Cleanup(func() { _ = bunDB.Close() }) require.NoError(tb, retry(bunDB.Ping), "couldn't ping DB") _, err := bunDB.Exec("CREATE SCHEMA ?", bun.Safe(schema)) require.NoError(tb, err, "couldn't create schema") runMigrations(tb, bunDB) return bunDB } func getHostPort(tb testing.TB, resource *dockertest.Resource, id string) string { tb.Helper() dockerURL := os.Getenv("DOCKER_HOST") if dockerURL == "" { return resource.GetHostPort(id) } u, err := url.Parse(dockerURL) require.NoError(tb, err) return net.JoinHostPort(u.Hostname(), resource.GetPort(id)) } func runMigrations(tb testing.TB, db *bun.DB) { tb.Helper() migrator := migrations.NewMigrator(db) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() require.NoError(tb, migrator.Init(ctx), "couldn't migrate (1)") _, err := migrator.Migrate(ctx) require.NoError(tb, err, "couldn't migrate (2)") } func generateSchema() string { return strings.TrimFunc(strings.ReplaceAll(uuid.NewString(), "-", "_"), unicode.IsNumber) } func retry(op func() error) error { bo := backoff.NewExponentialBackOff() bo.MaxInterval = time.Second * 5 bo.MaxElapsedTime = 30 * time.Second if err := backoff.Retry(op, bo); err != nil { if bo.NextBackOff() == backoff.Stop { return errors.New("reached retry deadline") } return err } return nil }