tercul-backend/internal/testutil/testutil.go
2025-09-05 21:37:42 +00:00

159 lines
4.2 KiB
Go

package testutil
import (
"database/sql"
"errors"
"fmt"
"log"
"os"
"testing"
"time"
"github.com/stretchr/testify/suite"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"tercul/internal/platform/config"
)
var ErrEntityNotFound = errors.New("entity not found")
// TestDB holds the test database connection
var TestDB *gorm.DB
// SetupTestDB sets up a test database connection
func SetupTestDB() (*gorm.DB, error) {
// Load configuration
config.LoadConfig()
// Use test-specific environment variables if available, otherwise fall back to main config
host := getEnv("TEST_DB_HOST", config.Cfg.DBHost)
port := getEnv("TEST_DB_PORT", config.Cfg.DBPort)
user := getEnv("TEST_DB_USER", config.Cfg.DBUser)
password := getEnv("TEST_DB_PASSWORD", config.Cfg.DBPassword)
dbname := getEnv("TEST_DB_NAME", "tercul_test") // Always use test database
sslmode := getEnv("TEST_DB_SSLMODE", config.Cfg.DBSSLMode)
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s",
host, port, user, password, dbname, sslmode)
// Custom logger for tests
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags),
logger.Config{
SlowThreshold: time.Second,
LogLevel: logger.Silent, // Silent during tests
IgnoreRecordNotFoundError: true,
Colorful: false,
},
)
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
Logger: newLogger,
})
if err != nil {
return nil, fmt.Errorf("failed to connect to test database: %w", err)
}
// Set connection pool settings
sqlDB, err := db.DB()
if err != nil {
return nil, fmt.Errorf("failed to get SQL DB instance: %w", err)
}
sqlDB.SetMaxOpenConns(5)
sqlDB.SetMaxIdleConns(2)
sqlDB.SetConnMaxLifetime(time.Hour)
return db, nil
}
// TruncateTables truncates all tables in the test database
func TruncateTables(db *gorm.DB, tables ...string) error {
for _, table := range tables {
if err := db.Exec(fmt.Sprintf("TRUNCATE TABLE %s CASCADE", table)).Error; err != nil {
return err
}
}
return nil
}
// CloseDB closes the test database connection
func CloseDB(db *gorm.DB) error {
sqlDB, err := db.DB()
if err != nil {
return err
}
return sqlDB.Close()
}
// getEnv gets an environment variable or returns a default value
func getEnv(key, defaultValue string) string {
value, exists := os.LookupEnv(key)
if !exists {
return defaultValue
}
return value
}
// BaseSuite is a base test suite with common functionality
// For integration tests using mocks, DB is not used
// TODO: Remove DB logic for mock-based integration tests (priority: high, effort: medium)
type BaseSuite struct {
suite.Suite
// DB *gorm.DB // Removed for mock-based integration tests
}
// SetupSuite sets up the test suite
func (s *BaseSuite) SetupSuite() {
// No DB setup for mock-based integration tests
}
// TearDownSuite tears down the test suite
func (s *BaseSuite) TearDownSuite() {
// No DB teardown for mock-based integration tests
}
// SetupTest sets up each test
func (s *BaseSuite) SetupTest() {
// Can be overridden by specific test suites
}
// TearDownTest tears down each test
func (s *BaseSuite) TearDownTest() {
// Can be overridden by specific test suites
}
// RunTransactional runs a test function in a transaction
// TODO: Remove or refactor for mock-based tests (priority: low, effort: low)
func (s *BaseSuite) RunTransactional(testFunc func(tx interface{})) {
// No-op for mock-based tests
}
// MockDB creates a mock database for testing
func MockDB() (*sql.DB, error) {
// Use environment variables for test database connection
host := getEnv("TEST_DB_HOST", "localhost")
port := getEnv("TEST_DB_PORT", "5432")
user := getEnv("TEST_DB_USER", "postgres")
password := getEnv("TEST_DB_PASSWORD", "postgres")
dbname := getEnv("TEST_DB_NAME", "tercul_test")
sslmode := getEnv("TEST_DB_SSLMODE", "disable")
dsn := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=%s",
user, password, host, port, dbname, sslmode)
db, err := sql.Open("postgres", dsn)
if err != nil {
return nil, err
}
return db, nil
}
// SkipIfShort skips a test if the -short flag is provided
func SkipIfShort(t *testing.T) {
if testing.Short() {
t.Skip("Skipping test in short mode")
}
}