package testutil import ( "context" "log" "os" "path/filepath" "time" "github.com/stretchr/testify/suite" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" graph "tercul/internal/adapters/graphql" "tercul/internal/app/auth" auth_platform "tercul/internal/platform/auth" "tercul/internal/app/localization" "tercul/internal/app/work" "tercul/internal/data/sql" "tercul/internal/domain" ) // IntegrationTestSuite provides a comprehensive test environment with either in-memory SQLite or mock repositories type IntegrationTestSuite struct { suite.Suite DB *gorm.DB WorkRepo domain.WorkRepository UserRepo domain.UserRepository AuthorRepo domain.AuthorRepository TranslationRepo domain.TranslationRepository CommentRepo domain.CommentRepository LikeRepo domain.LikeRepository BookmarkRepo domain.BookmarkRepository CollectionRepo domain.CollectionRepository TagRepo domain.TagRepository CategoryRepo domain.CategoryRepository // Services WorkCommands *work.WorkCommands WorkQueries *work.WorkQueries Localization localization.Service AuthCommands *auth.AuthCommands AuthQueries *auth.AuthQueries // Test data TestWorks []*domain.Work TestUsers []*domain.User TestAuthors []*domain.Author TestTranslations []*domain.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( &domain.Work{}, &domain.User{}, &domain.Author{}, &domain.Translation{}, &domain.Comment{}, &domain.Like{}, &domain.Bookmark{}, &domain.Collection{}, &domain.Tag{}, &domain.Category{}, &domain.Country{}, &domain.City{}, &domain.Place{}, &domain.Address{}, &domain.Copyright{}, &domain.CopyrightClaim{}, &domain.Monetization{}, &domain.Book{}, &domain.Publisher{}, &domain.Source{}, // &domain.WorkAnalytics{}, // Commented out as it's not in models package &domain.ReadabilityScore{}, &domain.WritingStyle{}, &domain.Emotion{}, &domain.TopicCluster{}, &domain.Mood{}, &domain.Concept{}, &domain.LinguisticLayer{}, &domain.WorkStats{}, &domain.TextMetadata{}, &domain.PoeticAnalysis{}, &domain.TranslationField{}, ); err != nil { s.T().Fatalf("Failed to run migrations: %v", err) } // Create repository instances s.WorkRepo = sql.NewWorkRepository(db) s.UserRepo = sql.NewUserRepository(db) s.AuthorRepo = sql.NewAuthorRepository(db) s.TranslationRepo = sql.NewTranslationRepository(db) s.CommentRepo = sql.NewCommentRepository(db) s.LikeRepo = sql.NewLikeRepository(db) s.BookmarkRepo = sql.NewBookmarkRepository(db) s.CollectionRepo = sql.NewCollectionRepository(db) s.TagRepo = sql.NewTagRepository(db) s.CategoryRepo = sql.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() { mockAnalyzer := &MockAnalyzer{} s.WorkCommands = work.NewWorkCommands(s.WorkRepo, mockAnalyzer) s.WorkQueries = work.NewWorkQueries(s.WorkRepo) s.Localization = localization.NewService(s.TranslationRepo) jwtManager := auth_platform.NewJWTManager() s.AuthCommands = auth.NewAuthCommands(s.UserRepo, jwtManager) s.AuthQueries = auth.NewAuthQueries(s.UserRepo, jwtManager) } // setupTestData creates initial test data func (s *IntegrationTestSuite) setupTestData() { // Create test users s.TestUsers = []*domain.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 = []*domain.Author{ {Name: "Test Author 1", TranslatableModel: domain.TranslatableModel{Language: "en"}}, {Name: "Test Author 2", TranslatableModel: domain.TranslatableModel{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 = []*domain.Work{ {Title: "Test Work 1", TranslatableModel: domain.TranslatableModel{Language: "en"}}, {Title: "Test Work 2", TranslatableModel: domain.TranslatableModel{Language: "en"}}, {Title: "Test Work 3", TranslatableModel: domain.TranslatableModel{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 = []*domain.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{ // This needs to be updated to reflect the new resolver structure } } // CreateTestWork creates a test work with optional content func (s *IntegrationTestSuite) CreateTestWork(title, language string, content string) *domain.Work { work := &domain.Work{ Title: title, TranslatableModel: domain.TranslatableModel{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 := &domain.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") } }