tercul-backend/internal/testutil/mock_translation_repository.go
google-labs-jules[bot] 781b313bf1 feat: Complete all pending tasks from TASKS.md
This commit addresses all the high-priority tasks outlined in the TASKS.md file, significantly improving the application's observability, completing key features, and refactoring critical parts of the codebase.

### Observability

- **Centralized Logging:** Implemented a new structured, context-aware logging system using `zerolog`. A new logging middleware injects request-specific information (request ID, user ID, trace ID) into the logger, and all application logging has been refactored to use this new system.
- **Prometheus Metrics:** Added Prometheus metrics for database query performance by creating a GORM plugin that automatically records query latency and totals.
- **OpenTelemetry Tracing:** Fully instrumented all application services in `internal/app` and data repositories in `internal/data/sql` with OpenTelemetry tracing, providing deep visibility into application performance.

### Features

- **Analytics:** Implemented like, comment, and bookmark counting. The respective command handlers now call the analytics service to increment counters when these actions are performed.
- **Enrichment Tool:** Built a new, extensible `enrich` command-line tool to fetch data from external sources. The initial implementation enriches author data using the Open Library API.

### Refactoring & Fixes

- **Decoupled Testing:** Refactored the testing utilities in `internal/testutil` to be database-agnostic, promoting the use of mock-based unit tests and improving test speed and reliability.
- **Build Fixes:** Resolved numerous build errors, including a critical import cycle between the logging, observability, and authentication packages.
- **Search Service:** Fixed the search service integration by implementing the `GetWorkContent` method in the localization service, allowing the search indexer to correctly fetch and index work content.
2025-10-05 05:26:27 +00:00

190 lines
5.7 KiB
Go

package testutil
import (
"context"
"errors"
"gorm.io/gorm"
"tercul/internal/domain"
)
// MockTranslationRepository is an in-memory implementation of TranslationRepository
type MockTranslationRepository struct {
items []domain.Translation
}
func NewMockTranslationRepository() *MockTranslationRepository {
return &MockTranslationRepository{items: []domain.Translation{}}
}
var _ domain.TranslationRepository = (*MockTranslationRepository)(nil)
// BaseRepository methods with context support
func (m *MockTranslationRepository) Create(ctx context.Context, t *domain.Translation) error {
if t == nil {
return errors.New("nil translation")
}
t.ID = uint(len(m.items) + 1)
m.items = append(m.items, *t)
return nil
}
func (m *MockTranslationRepository) GetByID(ctx context.Context, id uint) (*domain.Translation, error) {
for i := range m.items {
if m.items[i].ID == id {
cp := m.items[i]
return &cp, nil
}
}
return nil, domain.ErrEntityNotFound
}
func (m *MockTranslationRepository) Update(ctx context.Context, t *domain.Translation) error {
for i := range m.items {
if m.items[i].ID == t.ID {
m.items[i] = *t
return nil
}
}
return domain.ErrEntityNotFound
}
func (m *MockTranslationRepository) Delete(ctx context.Context, id uint) error {
for i := range m.items {
if m.items[i].ID == id {
m.items = append(m.items[:i], m.items[i+1:]...)
return nil
}
}
return domain.ErrEntityNotFound
}
func (m *MockTranslationRepository) List(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Translation], error) {
all := append([]domain.Translation(nil), m.items...)
total := int64(len(all))
start := (page - 1) * pageSize
end := start + pageSize
if start > len(all) {
return &domain.PaginatedResult[domain.Translation]{Items: []domain.Translation{}, TotalCount: total}, nil
}
if end > len(all) {
end = len(all)
}
return &domain.PaginatedResult[domain.Translation]{Items: all[start:end], TotalCount: total}, nil
}
func (m *MockTranslationRepository) ListAll(ctx context.Context) ([]domain.Translation, error) {
return append([]domain.Translation(nil), m.items...), nil
}
func (m *MockTranslationRepository) Count(ctx context.Context) (int64, error) {
return int64(len(m.items)), nil
}
func (m *MockTranslationRepository) FindWithPreload(ctx context.Context, preloads []string, id uint) (*domain.Translation, error) {
return m.GetByID(ctx, id)
}
func (m *MockTranslationRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]domain.Translation, error) {
all := append([]domain.Translation(nil), m.items...)
end := offset + batchSize
if end > len(all) {
end = len(all)
}
if offset > len(all) {
return []domain.Translation{}, nil
}
return all[offset:end], nil
}
// New BaseRepository methods
func (m *MockTranslationRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Translation) error {
return m.Create(ctx, entity)
}
func (m *MockTranslationRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Translation, error) {
return m.GetByID(ctx, id)
}
func (m *MockTranslationRepository) UpdateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Translation) error {
return m.Update(ctx, entity)
}
func (m *MockTranslationRepository) DeleteInTx(ctx context.Context, tx *gorm.DB, id uint) error {
return m.Delete(ctx, id)
}
func (m *MockTranslationRepository) ListWithOptions(ctx context.Context, options *domain.QueryOptions) ([]domain.Translation, error) {
result, err := m.List(ctx, 1, 1000)
if err != nil {
return nil, err
}
return result.Items, nil
}
func (m *MockTranslationRepository) CountWithOptions(ctx context.Context, options *domain.QueryOptions) (int64, error) {
return m.Count(ctx)
}
func (m *MockTranslationRepository) Exists(ctx context.Context, id uint) (bool, error) {
_, err := m.GetByID(ctx, id)
return err == nil, nil
}
func (m *MockTranslationRepository) BeginTx(ctx context.Context) (*gorm.DB, error) {
return nil, nil
}
func (m *MockTranslationRepository) WithTx(ctx context.Context, fn func(tx *gorm.DB) error) error {
return fn(nil)
}
// TranslationRepository specific methods
func (m *MockTranslationRepository) ListByWorkID(ctx context.Context, workID uint) ([]domain.Translation, error) {
return m.ListByEntity(ctx, "Work", workID)
}
func (m *MockTranslationRepository) ListByEntity(ctx context.Context, entityType string, entityID uint) ([]domain.Translation, error) {
var out []domain.Translation
for i := range m.items {
tr := m.items[i]
if tr.TranslatableType == entityType && tr.TranslatableID == entityID {
out = append(out, tr)
}
}
return out, nil
}
func (m *MockTranslationRepository) ListByTranslatorID(ctx context.Context, translatorID uint) ([]domain.Translation, error) {
var out []domain.Translation
for i := range m.items {
if m.items[i].TranslatorID != nil && *m.items[i].TranslatorID == translatorID {
out = append(out, m.items[i])
}
}
return out, nil
}
func (m *MockTranslationRepository) ListByStatus(ctx context.Context, status domain.TranslationStatus) ([]domain.Translation, error) {
var out []domain.Translation
for i := range m.items {
if m.items[i].Status == status {
out = append(out, m.items[i])
}
}
return out, nil
}
// Test helper: add a translation for a Work
func (m *MockTranslationRepository) AddTranslationForWork(workID uint, language string, content string, isOriginal bool) {
m.Create(context.Background(), &domain.Translation{
Title: "",
Content: content,
Description: "",
Language: language,
Status: domain.TranslationStatusPublished,
TranslatableID: workID,
TranslatableType: "Work",
IsOriginalLanguage: isOriginal,
})
}