feat: add embedded testing server for postgres (#9955)

# Which Problems Are Solved

1. there was no embedded database to run tests against
2. there were no tests for postgres/migrate
3. there was no test setup for repository which starts a client for the
embedded database

# How the Problems Are Solved

1. postgres/embedded package was added
2. tests were added
3. TestMain was added incl. an example test

# Additional Changes

none

# Additional Context

closes #9934

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Silvan
2025-05-26 09:31:45 +02:00
committed by GitHub
parent 362420f62b
commit 01180d2a63
11 changed files with 274 additions and 19 deletions

View File

@@ -13,8 +13,9 @@ import (
)
var (
_ database.Connector = (*Config)(nil)
Name = "postgres"
_ database.Connector = (*Config)(nil)
Name = "postgres"
isMigrated bool
)
type Config struct {
@@ -45,7 +46,7 @@ func (c *Config) Connect(ctx context.Context) (database.Pool, error) {
if err = pool.Ping(ctx); err != nil {
return nil, err
}
return &pgxPool{pool}, nil
return &pgxPool{Pool: pool}, nil
}
func (c *Config) getPool(ctx context.Context) (*pgxpool.Pool, error) {

View File

@@ -9,11 +9,12 @@ import (
"github.com/zitadel/zitadel/backend/v3/storage/database/dialect/postgres/migration"
)
type pgxConn struct{ *pgxpool.Conn }
type pgxConn struct {
*pgxpool.Conn
}
var (
_ database.Client = (*pgxConn)(nil)
_ database.Migrator = (*pgxConn)(nil)
_ database.Client = (*pgxConn)(nil)
)
// Release implements [database.Client].
@@ -53,5 +54,10 @@ func (c *pgxConn) Exec(ctx context.Context, sql string, args ...any) error {
// Migrate implements [database.Migrator].
func (c *pgxConn) Migrate(ctx context.Context) error {
return migration.Migrate(ctx, c.Conn.Conn())
if isMigrated {
return nil
}
err := migration.Migrate(ctx, c.Conn.Conn())
isMigrated = err == nil
return err
}

View File

@@ -0,0 +1,50 @@
// embedded is used for testing purposes
package embedded
import (
"net"
"os"
embeddedpostgres "github.com/fergusstrange/embedded-postgres"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/backend/v3/storage/database"
"github.com/zitadel/zitadel/backend/v3/storage/database/dialect/postgres"
)
// StartEmbedded starts an embedded postgres v16 instance and returns a database connector and a stop function
// the database is started on a random port and data are stored in a temporary directory
// its used for testing purposes only
func StartEmbedded() (connector database.Connector, stop func(), err error) {
path, err := os.MkdirTemp("", "zitadel-embedded-postgres-*")
logging.OnError(err).Fatal("unable to create temp dir")
port, close := getPort()
config := embeddedpostgres.DefaultConfig().Version(embeddedpostgres.V16).Port(uint32(port)).RuntimePath(path)
embedded := embeddedpostgres.NewDatabase(config)
close()
err = embedded.Start()
logging.OnError(err).Fatal("unable to start db")
connector, err = postgres.DecodeConfig(config.GetConnectionURL())
if err != nil {
return nil, nil, err
}
return connector, func() {
logging.OnError(embedded.Stop()).Error("unable to stop db")
}, nil
}
// getPort returns a free port and locks it until close is called
func getPort() (port uint16, close func()) {
l, err := net.Listen("tcp", ":0")
logging.OnError(err).Fatal("unable to get port")
port = uint16(l.Addr().(*net.TCPAddr).Port)
logging.WithFields("port", port).Info("Port is available")
return port, func() {
logging.OnError(l.Close()).Error("unable to close port listener")
}
}

View File

@@ -0,0 +1,60 @@
package migration_test
import (
"context"
"testing"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/backend/v3/storage/database"
"github.com/zitadel/zitadel/backend/v3/storage/database/dialect/postgres/embedded"
)
func TestMigrate(t *testing.T) {
tests := []struct {
name string
stmt string
args []any
res []any
}{
{
name: "schema",
stmt: "SELECT EXISTS(SELECT 1 FROM information_schema.schemata where schema_name = 'zitadel') ;",
res: []any{true},
},
{
name: "001",
stmt: "SELECT EXISTS(SELECT 1 FROM pg_catalog.pg_tables WHERE schemaname = 'zitadel' and tablename=$1)",
args: []any{"instances"},
res: []any{true},
},
}
ctx := context.Background()
connector, stop, err := embedded.StartEmbedded()
require.NoError(t, err, "failed to start embedded postgres")
defer stop()
client, err := connector.Connect(ctx)
require.NoError(t, err, "failed to connect to embedded postgres")
err = client.(database.Migrator).Migrate(ctx)
require.NoError(t, err, "failed to execute migration steps")
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := make([]any, len(tt.res))
for i := range got {
got[i] = new(any)
tt.res[i] = gu.Ptr(tt.res[i])
}
require.NoError(t, client.QueryRow(ctx, tt.stmt, tt.args...).Scan(got...), "failed to execute check query")
assert.Equal(t, tt.res, got, "query result does not match")
})
}
}

View File

@@ -9,11 +9,12 @@ import (
"github.com/zitadel/zitadel/backend/v3/storage/database/dialect/postgres/migration"
)
type pgxPool struct{ *pgxpool.Pool }
type pgxPool struct {
*pgxpool.Pool
}
var (
_ database.Pool = (*pgxPool)(nil)
_ database.Migrator = (*pgxPool)(nil)
_ database.Pool = (*pgxPool)(nil)
)
// Acquire implements [database.Pool].
@@ -22,7 +23,7 @@ func (c *pgxPool) Acquire(ctx context.Context) (database.Client, error) {
if err != nil {
return nil, err
}
return &pgxConn{conn}, nil
return &pgxConn{Conn: conn}, nil
}
// Query implements [database.Pool].
@@ -62,9 +63,16 @@ func (c *pgxPool) Close(_ context.Context) error {
// Migrate implements [database.Migrator].
func (c *pgxPool) Migrate(ctx context.Context) error {
if isMigrated {
return nil
}
client, err := c.Pool.Acquire(ctx)
if err != nil {
return err
}
return migration.Migrate(ctx, client.Conn())
err = migration.Migrate(ctx, client.Conn())
isMigrated = err == nil
return err
}