You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

132 lines
3.2 KiB
Go

package containers
import (
"context"
"fmt"
"strconv"
"strings"
"testing"
"time"
"github.com/testcontainers/testcontainers-go/modules/postgres"
sharedpostgres "knowfoolery/backend/shared/infra/database/postgres"
)
const (
testPostgresUser = "knowfoolery"
testPostgresPassword = "knowfoolery"
testPostgresDB = "knowfoolery_test"
)
// PostgresInstance contains connection details for a disposable Postgres test container.
type PostgresInstance struct {
Host string
Port int
User string
Password string
Database string
}
// URL returns a connection URL for pgx/stdlib clients.
func (p PostgresInstance) URL() string {
return fmt.Sprintf(
"postgresql://%s:%s@%s:%d/%s?sslmode=disable",
p.User,
p.Password,
p.Host,
p.Port,
p.Database,
)
}
// Config converts container details to the shared postgres config type.
func (p PostgresInstance) Config() sharedpostgres.Config {
cfg := sharedpostgres.DefaultConfig()
cfg.Host = p.Host
cfg.Port = p.Port
cfg.User = p.User
cfg.Password = p.Password
cfg.Database = p.Database
cfg.SSLMode = "disable"
return cfg
}
// StartPostgres boots a disposable Postgres container and registers cleanup with t.Cleanup.
func StartPostgres(t *testing.T) PostgresInstance {
t.Helper()
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
t.Cleanup(cancel)
var (
container *postgres.PostgresContainer
err error
panicErr error
)
func() {
defer func() {
if recovered := recover(); recovered != nil {
panicErr = fmt.Errorf("%v", recovered)
}
}()
container, err = postgres.Run(
ctx,
"postgres:16-alpine",
postgres.WithDatabase(testPostgresDB),
postgres.WithUsername(testPostgresUser),
postgres.WithPassword(testPostgresPassword),
postgres.BasicWaitStrategies(),
)
}()
if panicErr != nil {
if isDockerUnavailable(panicErr) {
t.Skipf("docker unavailable for testcontainers: %v", panicErr)
}
t.Fatalf("start postgres testcontainer panic: %v", panicErr)
}
if err != nil {
if isDockerUnavailable(err) {
t.Skipf("docker unavailable for testcontainers: %v", err)
}
t.Fatalf("start postgres testcontainer: %v", err)
}
t.Cleanup(func() {
stopCtx, stopCancel := context.WithTimeout(context.Background(), 30*time.Second)
defer stopCancel()
if termErr := container.Terminate(stopCtx); termErr != nil {
t.Fatalf("terminate postgres testcontainer: %v", termErr)
}
})
host, err := container.Host(ctx)
if err != nil {
t.Fatalf("resolve postgres host: %v", err)
}
mappedPort, err := container.MappedPort(ctx, "5432/tcp")
if err != nil {
t.Fatalf("resolve postgres mapped port: %v", err)
}
port, err := strconv.Atoi(mappedPort.Port())
if err != nil {
t.Fatalf("parse postgres mapped port: %v", err)
}
return PostgresInstance{
Host: host,
Port: port,
User: testPostgresUser,
Password: testPostgresPassword,
Database: testPostgresDB,
}
}
func isDockerUnavailable(err error) bool {
msg := strings.ToLower(err.Error())
return strings.Contains(msg, "permission denied") ||
strings.Contains(msg, "cannot connect to the docker daemon") ||
strings.Contains(msg, "docker.sock") ||
strings.Contains(msg, "connection refused")
}