Files
upstream-ory-keto/internal/x/dbx/dsn_testutils.go
upstream-mirror 9456a4e922
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
snapshot of https://github.com/ory/keto.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:08:34 +05:00

189 lines
3.9 KiB
Go

// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package dbx
import (
"crypto/rand"
"fmt"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/go-sql-driver/mysql"
"github.com/stretchr/testify/require"
"github.com/tidwall/sjson"
"golang.org/x/sync/errgroup"
"github.com/ory/pop/v6"
)
type DsnT struct {
Name string
Conn string
MigrateUp, MigrateDown bool
}
const mySQLSchema = "mysql://"
func mySQLWithDbName(dsn string, db string) string {
cfg, err := mysql.ParseDSN(strings.TrimPrefix(dsn, mySQLSchema))
if err != nil {
return ""
}
cfg.DBName = db
return mySQLSchema + cfg.FormatDSN()
}
func withDbName(dsn string, db string) string {
// Special case for mysql, because their URLs are not parsable.
if strings.HasPrefix(dsn, mySQLSchema) {
return mySQLWithDbName(dsn, db)
}
u, err := url.Parse(dsn)
if err != nil {
return ""
}
u.Path = db
return u.String()
}
func dbName() string {
var buf [20]byte
_, _ = rand.Read(buf[:])
return fmt.Sprintf("testdb_%x", buf)
}
func openAndPing(url string) (*pop.Connection, error) {
conn, err := pop.NewConnection(&pop.ConnectionDetails{URL: url})
if err != nil {
return nil, fmt.Errorf("failed to connect to %q: %w", url, err)
}
for range 120 {
fmt.Println("trying to open connection to", url)
if err := conn.Open(); err != nil {
// return nil, fmt.Errorf("failed to open connection: %w", err)
time.Sleep(1 * time.Second)
continue
}
if err := Ping(conn); err == nil {
break
}
time.Sleep(1 * time.Second)
}
if err := Ping(conn); err != nil {
_ = conn.Close()
return nil, fmt.Errorf("failed to ping: %w", err)
}
return conn, nil
}
func createDB(t testing.TB, url string, dbName string) (err error) {
var conn *pop.Connection
if conn, err = openAndPing(url); err != nil {
return fmt.Errorf("failed to connect to %q: %w", url, err)
}
t.Cleanup(func() {
if err := conn.Close(); err != nil {
t.Logf("could not close connection for %q in %q: %v", dbName, url, err)
}
})
if err := conn.RawQuery("CREATE DATABASE " + dbName).Exec(); err != nil {
return fmt.Errorf("failed to create db %q in %q: %w", dbName, url, err)
}
t.Cleanup(func() {
if err := conn.RawQuery("DROP DATABASE " + dbName).Exec(); err != nil {
t.Logf("could not drop database %q in %q: %v", dbName, url, err)
}
})
return
}
func GetDSNs(t testing.TB) []*DsnT {
dsns := []*DsnT{GetSqlite(t)}
if !testing.Short() {
var mysql, postgres, cockroach string
testDB := dbName()
var eg errgroup.Group
eg.Go(func() (err error) {
postgres, err = RunPostgres(t, testDB)
return
})
eg.Go(func() (err error) {
mysql, err = RunMySQL(t, testDB)
return
})
eg.Go(func() (err error) {
cockroach, err = RunCockroach(t, testDB)
return
})
require.NoError(t, eg.Wait())
if mysql != "" {
dsns = append(dsns, &DsnT{
Name: "mysql",
Conn: mysql,
MigrateUp: true,
MigrateDown: true,
})
}
if postgres != "" {
dsns = append(dsns, &DsnT{
Name: "postgres",
Conn: postgres,
MigrateUp: true,
MigrateDown: true,
})
}
if cockroach != "" {
dsns = append(dsns, &DsnT{
Name: "cockroach",
Conn: cockroach,
MigrateUp: true,
MigrateDown: true,
})
}
}
require.NotZero(t, len(dsns), "expected to run against at least one database")
return dsns
}
func ConfigFile(t testing.TB, values map[string]interface{}) string {
dir := t.TempDir()
fn := filepath.Join(dir, "keto.json")
c := []byte("{}")
for key, val := range values {
var err error
c, err = sjson.SetBytes(c, key, val)
require.NoError(t, err)
}
require.NoError(t, os.WriteFile(fn, c, 0600))
return fn
}
type pinger interface{ Ping() error }
func Ping(conn *pop.Connection) error {
p, ok := conn.Store.(pinger)
if !ok {
return fmt.Errorf("connection not opened: Store is %T", conn.Store)
}
return p.Ping()
}