//go:build integration package storage import ( "context" "fmt" "testing" "time" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/postgres" "github.com/testcontainers/testcontainers-go/wait" ) // testDB holds the shared database connection for integration tests type testDB struct { *DB container *postgres.PostgresContainer } // setupTestDB creates a PostgreSQL container and returns a connected DB instance func setupTestDB(t *testing.T) *testDB { t.Helper() ctx := context.Background() container, err := postgres.Run(ctx, "postgres:16-alpine", postgres.WithDatabase("training_tracker_test"), postgres.WithUsername("test"), postgres.WithPassword("test"), testcontainers.WithWaitStrategy( wait.ForLog("database system is ready to accept connections"). WithOccurrence(2). WithStartupTimeout(30*time.Second), ), ) if err != nil { t.Fatalf("failed to start postgres container: %v", err) } connStr, err := container.ConnectionString(ctx, "sslmode=disable") if err != nil { t.Fatalf("failed to get connection string: %v", err) } db, err := NewDBWithConnString(ctx, connStr) if err != nil { t.Fatalf("failed to connect to database: %v", err) } if err := db.Migrate(ctx); err != nil { t.Fatalf("failed to run migrations: %v", err) } return &testDB{ DB: db, container: container, } } // cleanup terminates the container and closes the database connection func (tdb *testDB) cleanup(t *testing.T) { t.Helper() ctx := context.Background() if tdb.DB != nil { tdb.DB.Close() } if tdb.container != nil { if err := tdb.container.Terminate(ctx); err != nil { t.Logf("failed to terminate container: %v", err) } } } // truncateTables clears all data from tables for test isolation func (tdb *testDB) truncateTables(t *testing.T) { t.Helper() ctx := context.Background() tables := []string{"session_entries", "sessions", "plan_exercises", "training_plans", "exercises"} for _, table := range tables { _, err := tdb.ExecContext(ctx, fmt.Sprintf("TRUNCATE TABLE %s CASCADE", table)) if err != nil { t.Fatalf("failed to truncate %s: %v", table, err) } } } // NewDBWithConnString creates a DB instance with a custom connection string func NewDBWithConnString(ctx context.Context, connStr string) (*DB, error) { db, err := openDB(connStr) if err != nil { return nil, err } if err := db.PingContext(ctx); err != nil { return nil, fmt.Errorf("failed to ping database: %w", err) } return &DB{db}, nil }