package repositories_test import ( "context" "encoding/json" "errors" "testing" "time" "tercul/internal/testutil" "tercul/models" "tercul/repositories" "github.com/stretchr/testify/suite" ) // ErrCacheMiss is returned when a key is not found in the cache var ErrCacheMiss = errors.New("cache miss") // MarshalValue marshals a value to JSON func MarshalValue(value interface{}) ([]byte, error) { return json.Marshal(value) } // UnmarshalValue unmarshals a value from JSON func UnmarshalValue(data []byte, value interface{}) error { return json.Unmarshal(data, value) } // testCache is a simple in-memory cache for testing type testCache struct { data map[string][]byte } func (c *testCache) Get(ctx context.Context, key string, value interface{}) error { data, ok := c.data[key] if !ok { return ErrCacheMiss } return UnmarshalValue(data, value) } func (c *testCache) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error { data, err := MarshalValue(value) if err != nil { return err } c.data[key] = data return nil } func (c *testCache) Delete(ctx context.Context, key string) error { delete(c.data, key) return nil } func (c *testCache) Clear(ctx context.Context) error { c.data = make(map[string][]byte) return nil } func (c *testCache) GetMulti(ctx context.Context, keys []string) (map[string][]byte, error) { result := make(map[string][]byte) for _, key := range keys { if data, ok := c.data[key]; ok { result[key] = data } } return result, nil } func (c *testCache) SetMulti(ctx context.Context, items map[string]interface{}, expiration time.Duration) error { for key, value := range items { data, err := MarshalValue(value) if err != nil { return err } c.data[key] = data } return nil } // MockWorkRepository for testing type MockWorkRepository struct { works []*models.Work } func NewMockWorkRepository() *MockWorkRepository { return &MockWorkRepository{works: []*models.Work{}} } func (m *MockWorkRepository) AddWork(work *models.Work) { work.ID = uint(len(m.works) + 1) m.works = append(m.works, work) } func (m *MockWorkRepository) GetByID(id uint) (*models.Work, error) { for _, w := range m.works { if w.ID == id { return w, nil } } return nil, errors.New("not found") } func (m *MockWorkRepository) FindByTitle(title string) ([]*models.Work, error) { var result []*models.Work for _, w := range m.works { if len(title) == 0 || (len(w.Title) >= len(title) && w.Title[:len(title)] == title) { result = append(result, w) } } return result, nil } func (m *MockWorkRepository) FindByLanguage(language string, page, pageSize int) (*repositories.PaginatedResult[*models.Work], error) { var filtered []*models.Work for _, w := range m.works { if w.Language == language { filtered = append(filtered, w) } } total := int64(len(filtered)) start := (page - 1) * pageSize end := start + pageSize if start > len(filtered) { return &repositories.PaginatedResult[*models.Work]{Items: []*models.Work{}, TotalCount: total}, nil } if end > len(filtered) { end = len(filtered) } return &repositories.PaginatedResult[*models.Work]{Items: filtered[start:end], TotalCount: total}, nil } func (m *MockWorkRepository) Count() (int64, error) { return int64(len(m.works)), nil } // CachedWorkRepositorySuite is a test suite for the CachedWorkRepository // Refactored to use MockWorkRepository and in-memory cache type CachedWorkRepositorySuite struct { suite.Suite baseRepo *testutil.UnifiedMockWorkRepository cache *testCache repo *repositories.CachedWorkRepository } func (s *CachedWorkRepositorySuite) SetupSuite() { // No DB setup required } func (s *CachedWorkRepositorySuite) SetupTest() { s.baseRepo = testutil.NewUnifiedMockWorkRepository() s.cache = &testCache{data: make(map[string][]byte)} s.repo = repositories.NewCachedWorkRepository( s.baseRepo, s.cache, nil, 30*time.Minute, ) } // createTestWork creates a test work and adds it to the mock repo func (s *CachedWorkRepositorySuite) createTestWork(title, language string) *models.Work { work := &models.Work{ TranslatableModel: models.TranslatableModel{BaseModel: models.BaseModel{ID: 0}, Language: language}, Title: title, Description: "Test description", Status: "published", } s.baseRepo.AddWork(work) return work } // TestGetByID tests the GetByID method with cache miss and hit func (s *CachedWorkRepositorySuite) TestGetByID() { work := s.createTestWork("Test Work", "en") result1, err := s.repo.GetByID(context.Background(), work.ID) s.Require().NoError(err) s.Require().NotNil(result1) s.Equal(work.ID, result1.ID) s.Equal(work.Title, result1.Title) result2, err := s.repo.GetByID(context.Background(), work.ID) s.Require().NoError(err) s.Require().NotNil(result2) s.Equal(work.ID, result2.ID) s.Equal(work.Title, result2.Title) s.Equal(result1.ID, result2.ID) s.Equal(result1.Title, result2.Title) } // TestFindByTitle tests the FindByTitle method func (s *CachedWorkRepositorySuite) TestFindByTitle() { work1 := s.createTestWork("Test Work 1", "en") work2 := s.createTestWork("Test Work 2", "en") _ = s.createTestWork("Another Work", "en") works1, err := s.repo.FindByTitle(context.Background(), "Test") s.Require().NoError(err) s.Require().Len(works1, 2) works2, err := s.repo.FindByTitle(context.Background(), "Test") s.Require().NoError(err) s.Require().Len(works2, 2) foundWork1 := false foundWork2 := false for _, work := range works2 { if work.ID == work1.ID { foundWork1 = true } if work.ID == work2.ID { foundWork2 = true } } s.True(foundWork1) s.True(foundWork2) } // TestFindByLanguage tests the FindByLanguage method func (s *CachedWorkRepositorySuite) TestFindByLanguage() { s.createTestWork("Work 1", "en") s.createTestWork("Work 2", "en") s.createTestWork("Work 3", "fr") s.createTestWork("Work 4", "fr") s.createTestWork("Work 5", "es") result1, err := s.repo.FindByLanguage(context.Background(), "en", 1, 10) s.Require().NoError(err) s.Require().NotNil(result1) s.Equal(int64(2), result1.TotalCount) s.Equal(2, len(result1.Items)) result2, err := s.repo.FindByLanguage(context.Background(), "en", 1, 10) s.Require().NoError(err) s.Require().NotNil(result2) s.Equal(int64(2), result2.TotalCount) s.Equal(2, len(result2.Items)) } // TestCachedWorkRepositorySuite runs the test suite func TestCachedWorkRepositorySuite(t *testing.T) { suite.Run(t, new(CachedWorkRepositorySuite)) }