tercul-backend/internal/testutil/integration_test_utils.go
google-labs-jules[bot] 1c4dcbcf99 Refactor: Introduce service layer for application logic
This change introduces a service layer to encapsulate the business logic
for each domain aggregate. This will make the code more modular,
testable, and easier to maintain.

The following services have been created:
- author
- bookmark
- category
- collection
- comment
- like
- tag
- translation
- user

The main Application struct has been updated to use these new services.
The integration test suite has also been updated to use the new
Application struct and services.

This is a work in progress. The next step is to fix the compilation
errors and then refactor the resolvers to use the new services.
2025-09-09 02:28:25 +00:00

142 lines
3.7 KiB
Go

package testutil
import (
"context"
"log"
"os"
"path/filepath"
"runtime"
"tercul/internal/app"
"tercul/internal/data/sql"
"tercul/internal/domain"
"tercul/internal/platform/config"
"tercul/internal/platform/search"
"testing"
"time"
"github.com/stretchr/testify/suite"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// IntegrationTestSuite provides a comprehensive test environment with either in-memory SQLite or mock repositories
type IntegrationTestSuite struct {
suite.Suite
App *app.Application
DB *gorm.DB
}
// 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()
}
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
db.AutoMigrate(
&domain.Work{}, &domain.User{}, &domain.Author{}, &domain.Translation{},
&domain.Comment{}, &domain.Like{}, &domain.Bookmark{}, &domain.Collection{},
&domain.Tag{}, &domain.Category{}, &domain.Book{}, &domain.Publisher{},
&domain.Source{}, &domain.Copyright{}, &domain.Monetization{},
&domain.WorkStats{}, &domain.Trending{}, &domain.UserSession{}, &domain.Localization{},
)
repos := sql.NewRepositories(s.DB)
searchClient := search.NewClient("http://testhost", "testkey")
s.App = app.NewApplication(repos, searchClient)
}
// 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.DB.Exec("DELETE FROM trendings")
s.DB.Exec("DELETE FROM work_stats")
s.DB.Exec("DELETE FROM translation_stats")
}
}
// CreateTestWork creates a test work with optional content
func (s *IntegrationTestSuite) CreateTestWork(title, language string, content string) *domain.Work {
work := &domain.Work{
Title: title,
Language: language,
}
err := s.App.Repos.Work.Create(context.Background(), work)
s.Require().NoError(err)
if content != "" {
translation := &domain.Translation{
Title: title,
Content: content,
Language: language,
TranslatableID: work.ID,
TranslatableType: "Work",
}
err = s.App.Repos.Translation.Create(context.Background(), translation)
s.Require().NoError(err)
}
return work
}