tercul-backend/internal/data/cache/cached_translation_repository_test.go
Damir Mukimov d50722dad5
Some checks failed
Test / Integration Tests (push) Successful in 4s
Build / Build Binary (push) Failing after 2m9s
Docker Build / Build Docker Image (push) Failing after 2m32s
Test / Unit Tests (push) Failing after 3m12s
Lint / Go Lint (push) Failing after 1m0s
Refactor ID handling to use UUIDs across the application
- Updated database models and repositories to replace uint IDs with UUIDs.
- Modified test fixtures to generate and use UUIDs for authors, translations, users, and works.
- Adjusted mock implementations to align with the new UUID structure.
- Ensured all relevant functions and methods are updated to handle UUIDs correctly.
- Added necessary imports for UUID handling in various files.
2025-12-27 00:33:34 +01:00

257 lines
7.9 KiB
Go

package cache
import (
"context"
"encoding/json"
"errors"
"sync"
"testing"
"time"
"tercul/internal/domain"
"gorm.io/gorm"
"github.com/stretchr/testify/require"
)
// memCacheT - small in-memory cache for translation tests
type memCacheT struct {
m map[string][]byte
mu sync.RWMutex
}
func newMemCacheT() *memCacheT { return &memCacheT{m: map[string][]byte{}} }
func (c *memCacheT) Get(_ context.Context, key string, value interface{}) error {
c.mu.RLock()
defer c.mu.RUnlock()
b, ok := c.m[key]
if !ok {
return errors.New("cache miss")
}
return json.Unmarshal(b, value)
}
func (c *memCacheT) Set(_ context.Context, key string, value interface{}, expiration time.Duration) error {
b, err := json.Marshal(value)
if err != nil {
return err
}
c.mu.Lock()
defer c.mu.Unlock()
c.m[key] = b
return nil
}
func (c *memCacheT) Delete(_ context.Context, key string) error {
c.mu.Lock()
delete(c.m, key)
c.mu.Unlock()
return nil
}
func (c *memCacheT) Clear(_ context.Context) error {
c.mu.Lock()
c.m = map[string][]byte{}
c.mu.Unlock()
return nil
}
func (c *memCacheT) GetMulti(_ context.Context, keys []string) (map[string][]byte, error) {
res := map[string][]byte{}
c.mu.RLock()
defer c.mu.RUnlock()
for _, k := range keys {
if v, ok := c.m[k]; ok {
res[k] = v
}
}
return res, nil
}
func (c *memCacheT) SetMulti(_ context.Context, items map[string]interface{}, expiration time.Duration) error {
for k, v := range items {
b, _ := json.Marshal(v)
c.m[k] = b
}
return nil
}
// dummyTranslationRepo implements domain.TranslationRepository minimal functionality for tests
type dummyTranslationRepo struct {
store map[uint]*domain.Translation
calls int
mu sync.Mutex
}
func newDummyTranslationRepo() *dummyTranslationRepo {
return &dummyTranslationRepo{store: map[uint]*domain.Translation{}}
}
func (d *dummyTranslationRepo) Create(_ context.Context, entity *domain.Translation) error {
d.mu.Lock()
defer d.mu.Unlock()
d.calls++
d.store[entity.ID] = entity
return nil
}
func (d *dummyTranslationRepo) CreateInTx(_ context.Context, tx *gorm.DB, entity *domain.Translation) error {
return errors.New("not implemented")
}
func (d *dummyTranslationRepo) GetByID(_ context.Context, id uuid.UUID) (*domain.Translation, error) {
d.mu.Lock()
defer d.mu.Unlock()
d.calls++
tr, ok := d.store[id]
if !ok {
return nil, errors.New("not found")
}
cp := *tr
return &cp, nil
}
func (d *dummyTranslationRepo) GetByIDWithOptions(_ context.Context, id uint, options *domain.QueryOptions) (*domain.Translation, error) {
return d.GetByID(context.Background(), id)
}
func (d *dummyTranslationRepo) Update(_ context.Context, entity *domain.Translation) error {
d.mu.Lock()
defer d.mu.Unlock()
d.calls++
if _, ok := d.store[entity.ID]; !ok {
return errors.New("not found")
}
d.store[entity.ID] = entity
return nil
}
func (d *dummyTranslationRepo) UpdateInTx(_ context.Context, tx *gorm.DB, entity *domain.Translation) error {
return errors.New("not implemented")
}
func (d *dummyTranslationRepo) Delete(_ context.Context, id uint) error {
d.mu.Lock()
defer d.mu.Unlock()
d.calls++
if _, ok := d.store[id]; !ok {
return errors.New("not found")
}
delete(d.store, id)
return nil
}
func (d *dummyTranslationRepo) DeleteInTx(_ context.Context, tx *gorm.DB, id uint) error {
return errors.New("not implemented")
}
func (d *dummyTranslationRepo) List(_ context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Translation], error) {
return &domain.PaginatedResult[domain.Translation]{Items: []domain.Translation{}}, nil
}
func (d *dummyTranslationRepo) ListWithOptions(_ context.Context, options *domain.QueryOptions) ([]domain.Translation, error) {
return nil, nil
}
func (d *dummyTranslationRepo) ListAll(_ context.Context) ([]domain.Translation, error) {
return nil, nil
}
func (d *dummyTranslationRepo) Count(_ context.Context) (int64, error) { return 0, nil }
func (d *dummyTranslationRepo) CountWithOptions(_ context.Context, options *domain.QueryOptions) (int64, error) {
return 0, nil
}
func (d *dummyTranslationRepo) FindWithPreload(_ context.Context, preloads []string, id uint) (*domain.Translation, error) {
return d.GetByID(context.Background(), id)
}
func (d *dummyTranslationRepo) GetAllForSync(_ context.Context, batchSize, offset int) ([]domain.Translation, error) {
return nil, nil
}
func (d *dummyTranslationRepo) Exists(_ context.Context, id uint) (bool, error) {
_, ok := d.store[id]
return ok, nil
}
func (d *dummyTranslationRepo) BeginTx(_ context.Context) (*gorm.DB, error) { return nil, nil }
func (d *dummyTranslationRepo) WithTx(_ context.Context, fn func(tx *gorm.DB) error) error {
return fn(nil)
}
func (d *dummyTranslationRepo) ListByWorkID(_ context.Context, workID uint) ([]domain.Translation, error) {
d.mu.Lock()
defer d.mu.Unlock()
d.calls++
res := []domain.Translation{}
for _, v := range d.store {
if v.TranslatableID == workID {
res = append(res, *v)
}
}
return res, nil
}
func (d *dummyTranslationRepo) ListByWorkIDPaginated(_ context.Context, workID uint, language *string, page, pageSize int) (*domain.PaginatedResult[domain.Translation], error) {
all, _ := d.ListByWorkID(context.Background(), workID)
return &domain.PaginatedResult[domain.Translation]{Items: all, TotalCount: int64(len(all)), Page: page, PageSize: pageSize}, nil
}
func (d *dummyTranslationRepo) ListByEntity(_ context.Context, entityType string, entityID uint) ([]domain.Translation, error) {
return nil, nil
}
func (d *dummyTranslationRepo) ListByTranslatorID(_ context.Context, translatorID uint) ([]domain.Translation, error) {
return nil, nil
}
func (d *dummyTranslationRepo) ListByStatus(_ context.Context, status domain.TranslationStatus) ([]domain.Translation, error) {
return nil, nil
}
func (d *dummyTranslationRepo) Upsert(_ context.Context, translation *domain.Translation) error {
d.mu.Lock()
defer d.mu.Unlock()
if translation.ID == 0 {
translation.ID = uint(len(d.store) + 1)
}
d.store[translation.ID] = translation
d.calls++
return nil
}
func TestCachedTranslation_GetByID_Caches(t *testing.T) {
ctx := context.Background()
d := newDummyTranslationRepo()
d.store[1] = &domain.Translation{BaseModel: domain.BaseModel{ID: 1}, TranslatableID: 10, Language: "en", Title: "T1"}
mc := newMemCacheT()
ct := NewCachedTranslationRepository(d, mc, nil)
tr, err := ct.GetByID(ctx, 1)
require.NoError(t, err)
require.Equal(t, "T1", tr.Title)
require.Equal(t, 1, d.calls)
// second read should hit cache
tr2, err := ct.GetByID(ctx, 1)
require.NoError(t, err)
require.Equal(t, "T1", tr2.Title)
require.Equal(t, 1, d.calls)
}
func TestCachedTranslation_ListByWorkID_Caches(t *testing.T) {
ctx := context.Background()
d := newDummyTranslationRepo()
d.store[11] = &domain.Translation{BaseModel: domain.BaseModel{ID: 11}, TranslatableID: 100, Language: "en", Title: "A"}
d.store[12] = &domain.Translation{BaseModel: domain.BaseModel{ID: 12}, TranslatableID: 100, Language: "fr", Title: "B"}
mc := newMemCacheT()
ct := NewCachedTranslationRepository(d, mc, nil)
list, err := ct.ListByWorkID(ctx, 100)
require.NoError(t, err)
require.Len(t, list, 2)
require.Equal(t, 1, d.calls)
// second call should hit cache
list2, err := ct.ListByWorkID(ctx, 100)
require.NoError(t, err)
require.Len(t, list2, 2)
require.Equal(t, 1, d.calls)
}
func TestCachedTranslation_Update_Invalidates(t *testing.T) {
ctx := context.Background()
d := newDummyTranslationRepo()
d.store[1] = &domain.Translation{BaseModel: domain.BaseModel{ID: 1}, TranslatableID: 10, Language: "en", Title: "Old"}
mc := newMemCacheT()
ct := NewCachedTranslationRepository(d, mc, nil)
_, err := ct.GetByID(ctx, 1)
require.NoError(t, err)
// update underlying
d.store[1] = &domain.Translation{BaseModel: domain.BaseModel{ID: 1}, TranslatableID: 10, Language: "en", Title: "New"}
err = ct.Update(ctx, d.store[1])
require.NoError(t, err)
tr, err := ct.GetByID(ctx, 1)
require.NoError(t, err)
require.Equal(t, "New", tr.Title)
require.Equal(t, 3, d.calls)
}