package testutil import ( "context" "database/sql" "errors" "fmt" "log" "os" "testing" "tercul/internal/domain" "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") } } func CreateWork(ctx context.Context, db *gorm.DB, title, authorName string) *domain.Work { author := &domain.Author{Name: authorName} db.Create(author) work := &domain.Work{ Title: title, Authors: []*domain.Author{author}, TranslatableModel: domain.TranslatableModel{ Language: "en", }, } db.Create(work) return work }