mirror of
https://github.com/SamyRai/tercul-backend.git
synced 2025-12-27 05:11:34 +00:00
- Core Go application with GraphQL API using gqlgen - Comprehensive data models for literary works, authors, translations - Repository pattern with caching layer - Authentication and authorization system - Linguistics analysis capabilities with multiple adapters - Vector search integration with Weaviate - Docker containerization support - Python data migration and analysis scripts - Clean architecture with proper separation of concerns - Production-ready configuration and middleware - Proper .gitignore excluding vendor/, database files, and build artifacts
252 lines
6.4 KiB
Go
252 lines
6.4 KiB
Go
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))
|
|
}
|