package testutil import ( "context" "fmt" "log" "os" "path/filepath" "testing" "time" "github.com/stretchr/testify/suite" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" "tercul/internal/models" "tercul/internal/repositories" "tercul/services" "tercul/graph" ) // IntegrationTestSuite provides a comprehensive test environment with either in-memory SQLite or mock repositories type IntegrationTestSuite struct { suite.Suite DB *gorm.DB WorkRepo repositories.WorkRepository UserRepo repositories.UserRepository AuthorRepo repositories.AuthorRepository TranslationRepo repositories.TranslationRepository CommentRepo repositories.CommentRepository LikeRepo repositories.LikeRepository BookmarkRepo repositories.BookmarkRepository CollectionRepo repositories.CollectionRepository TagRepo repositories.TagRepository CategoryRepo repositories.CategoryRepository // Services WorkService services.WorkService Localization services.LocalizationService AuthService services.AuthService // Test data TestWorks []*models.Work TestUsers []*models.User TestAuthors []*models.Author TestTranslations []*models.Translation } // TestConfig holds configuration for the test environment type TestConfig struct { UseInMemoryDB bool // If true, use SQLite in-memory, otherwise use mock repositories DBPath string // Path for SQLite file (only used if UseInMemoryDB is false) LogLevel logger.LogLevel } // DefaultTestConfig returns a default test configuration func DefaultTestConfig() *TestConfig { return &TestConfig{ UseInMemoryDB: true, DBPath: "", LogLevel: logger.Silent, } } // SetupSuite sets up the test suite with the specified configuration func (s *IntegrationTestSuite) SetupSuite(config *TestConfig) { if config == nil { config = DefaultTestConfig() } if config.UseInMemoryDB { s.setupInMemoryDB(config) } else { s.setupMockRepositories() } s.setupServices() s.setupTestData() } // setupInMemoryDB sets up an in-memory SQLite database for testing func (s *IntegrationTestSuite) setupInMemoryDB(config *TestConfig) { var dbPath string if config.DBPath != "" { // Ensure directory exists dir := filepath.Dir(config.DBPath) if err := os.MkdirAll(dir, 0755); err != nil { s.T().Fatalf("Failed to create database directory: %v", err) } dbPath = config.DBPath } else { // Use in-memory database dbPath = ":memory:" } // Custom logger for tests newLogger := logger.New( log.New(os.Stdout, "\r\n", log.LstdFlags), logger.Config{ SlowThreshold: time.Second, LogLevel: config.LogLevel, IgnoreRecordNotFoundError: true, Colorful: false, }, ) db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{ Logger: newLogger, }) if err != nil { s.T().Fatalf("Failed to connect to test database: %v", err) } s.DB = db // Run migrations if err := db.AutoMigrate( &models.Work{}, &models.User{}, &models.Author{}, &models.Translation{}, &models.Comment{}, &models.Like{}, &models.Bookmark{}, &models.Collection{}, &models.Tag{}, &models.Category{}, &models.Country{}, &models.City{}, &models.Place{}, &models.Address{}, &models.Copyright{}, &models.CopyrightClaim{}, &models.Monetization{}, &models.Book{}, &models.Publisher{}, &models.Source{}, // &models.WorkAnalytics{}, // Commented out as it's not in models package &models.ReadabilityScore{}, &models.WritingStyle{}, &models.Emotion{}, &models.TopicCluster{}, &models.Mood{}, &models.Concept{}, &models.LinguisticLayer{}, &models.WorkStats{}, &models.TextMetadata{}, &models.PoeticAnalysis{}, &models.TranslationField{}, ); err != nil { s.T().Fatalf("Failed to run migrations: %v", err) } // Create repository instances s.WorkRepo = repositories.NewWorkRepository(db) s.UserRepo = repositories.NewUserRepository(db) s.AuthorRepo = repositories.NewAuthorRepository(db) s.TranslationRepo = repositories.NewTranslationRepository(db) s.CommentRepo = repositories.NewCommentRepository(db) s.LikeRepo = repositories.NewLikeRepository(db) s.BookmarkRepo = repositories.NewBookmarkRepository(db) s.CollectionRepo = repositories.NewCollectionRepository(db) s.TagRepo = repositories.NewTagRepository(db) s.CategoryRepo = repositories.NewCategoryRepository(db) } // setupMockRepositories sets up mock repositories for testing func (s *IntegrationTestSuite) setupMockRepositories() { s.WorkRepo = NewUnifiedMockWorkRepository() // Temporarily comment out problematic repositories until we fix the interface implementations // s.UserRepo = NewMockUserRepository() // s.AuthorRepo = NewMockAuthorRepository() // s.TranslationRepo = NewMockTranslationRepository() // s.CommentRepo = NewMockCommentRepository() // s.LikeRepo = NewMockLikeRepository() // s.BookmarkRepo = NewMockBookmarkRepository() // s.CollectionRepo = NewMockCollectionRepository() // s.TagRepo = NewMockTagRepository() // s.CategoryRepo = NewMockCategoryRepository() } // setupServices sets up service instances func (s *IntegrationTestSuite) setupServices() { s.WorkService = services.NewWorkService(s.WorkRepo, nil) // Temporarily comment out services that depend on problematic repositories // s.Localization = services.NewLocalizationService(s.TranslationRepo) // s.AuthService = services.NewAuthService(s.UserRepo, "test-secret-key") } // setupTestData creates initial test data func (s *IntegrationTestSuite) setupTestData() { // Create test users s.TestUsers = []*models.User{ {Username: "testuser1", Email: "test1@example.com", FirstName: "Test", LastName: "User1"}, {Username: "testuser2", Email: "test2@example.com", FirstName: "Test", LastName: "User2"}, } for _, user := range s.TestUsers { if err := s.UserRepo.Create(context.Background(), user); err != nil { s.T().Logf("Warning: Failed to create test user: %v", err) } } // Create test authors s.TestAuthors = []*models.Author{ {Name: "Test Author 1", Language: "en"}, {Name: "Test Author 2", Language: "fr"}, } for _, author := range s.TestAuthors { if err := s.AuthorRepo.Create(context.Background(), author); err != nil { s.T().Logf("Warning: Failed to create test author: %v", err) } } // Create test works s.TestWorks = []*models.Work{ {Title: "Test Work 1", Language: "en"}, {Title: "Test Work 2", Language: "en"}, {Title: "Test Work 3", Language: "fr"}, } for _, work := range s.TestWorks { if err := s.WorkRepo.Create(context.Background(), work); err != nil { s.T().Logf("Warning: Failed to create test work: %v", err) } } // Create test translations s.TestTranslations = []*models.Translation{ { Title: "Test Work 1", Content: "Test content for work 1", Language: "en", TranslatableID: s.TestWorks[0].ID, TranslatableType: "Work", IsOriginalLanguage: true, }, { Title: "Test Work 2", Content: "Test content for work 2", Language: "en", TranslatableID: s.TestWorks[1].ID, TranslatableType: "Work", IsOriginalLanguage: true, }, { Title: "Test Work 3", Content: "Test content for work 3", Language: "fr", TranslatableID: s.TestWorks[2].ID, TranslatableType: "Work", IsOriginalLanguage: true, }, } for _, translation := range s.TestTranslations { if err := s.TranslationRepo.Create(context.Background(), translation); err != nil { s.T().Logf("Warning: Failed to create test translation: %v", err) } } } // TearDownSuite cleans up the test suite func (s *IntegrationTestSuite) TearDownSuite() { if s.DB != nil { sqlDB, err := s.DB.DB() if err == nil { sqlDB.Close() } } } // SetupTest resets test data for each test func (s *IntegrationTestSuite) SetupTest() { if s.DB != nil { // Reset database for each test s.DB.Exec("DELETE FROM translations") s.DB.Exec("DELETE FROM works") s.DB.Exec("DELETE FROM authors") s.DB.Exec("DELETE FROM users") s.setupTestData() } else { // Reset mock repositories if mockRepo, ok := s.WorkRepo.(*UnifiedMockWorkRepository); ok { mockRepo.Reset() } // Add similar reset logic for other mock repositories } } // GetResolver returns a properly configured GraphQL resolver for testing func (s *IntegrationTestSuite) GetResolver() *graph.Resolver { return &graph.Resolver{ WorkRepo: s.WorkRepo, UserRepo: s.UserRepo, AuthorRepo: s.AuthorRepo, TranslationRepo: s.TranslationRepo, CommentRepo: s.CommentRepo, LikeRepo: s.LikeRepo, BookmarkRepo: s.BookmarkRepo, CollectionRepo: s.CollectionRepo, TagRepo: s.TagRepo, CategoryRepo: s.CategoryRepo, WorkService: s.WorkService, Localization: s.Localization, AuthService: s.AuthService, } } // CreateTestWork creates a test work with optional content func (s *IntegrationTestSuite) CreateTestWork(title, language string, content string) *models.Work { work := &models.Work{ Title: title, } work.Language = language if err := s.WorkRepo.Create(context.Background(), work); err != nil { s.T().Fatalf("Failed to create test work: %v", err) } if content != "" { translation := &models.Translation{ Title: title, Content: content, Language: language, TranslatableID: work.ID, TranslatableType: "Work", IsOriginalLanguage: true, } if err := s.TranslationRepo.Create(context.Background(), translation); err != nil { s.T().Logf("Warning: Failed to create test translation: %v", err) } } return work } // CleanupTestData removes all test data func (s *IntegrationTestSuite) CleanupTestData() { if s.DB != nil { s.DB.Exec("DELETE FROM translations") s.DB.Exec("DELETE FROM works") s.DB.Exec("DELETE FROM authors") s.DB.Exec("DELETE FROM users") } }