tercul-backend/internal/app/localization/service_test.go
google-labs-jules[bot] 49e2bdd9ac feat: Refactor localization, auth, copyright, and monetization domains
This change introduces a major architectural refactoring of the application, with a focus on improving testability, decoupling, and observability.

The following domains have been successfully refactored:
- `localization`: Wrote a full suite of unit tests and added logging.
- `auth`: Introduced a `JWTManager` interface, wrote comprehensive unit tests, and added logging.
- `copyright`: Separated integration tests, wrote a full suite of unit tests, and added logging.
- `monetization`: Wrote a full suite of unit tests and added logging.
- `search`: Refactored the Weaviate client usage by creating a wrapper to improve testability, and achieved 100% test coverage.

For each of these domains, 100% test coverage has been achieved for the refactored code.

The refactoring of the `work` domain is currently in progress. Unit tests have been written for the commands and queries, but there is a persistent build issue with the query tests that needs to be resolved. The error indicates that the query methods are undefined, despite appearing to be correctly defined and called.
2025-09-06 15:15:10 +00:00

232 lines
9.1 KiB
Go

package localization
import (
"context"
"errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"tercul/internal/domain"
"testing"
"gorm.io/gorm"
)
// mockTranslationRepository is a local mock for the TranslationRepository interface.
type mockTranslationRepository struct {
translations []domain.Translation
err error
}
func (m *mockTranslationRepository) ListByWorkID(ctx context.Context, workID uint) ([]domain.Translation, error) {
if m.err != nil {
return nil, m.err
}
var results []domain.Translation
for _, t := range m.translations {
if t.TranslatableType == "Work" && t.TranslatableID == workID {
results = append(results, t)
}
}
return results, nil
}
func (m *mockTranslationRepository) ListByEntity(ctx context.Context, entityType string, entityID uint) ([]domain.Translation, error) {
if m.err != nil {
return nil, m.err
}
var results []domain.Translation
for _, t := range m.translations {
if t.TranslatableType == entityType && t.TranslatableID == entityID {
results = append(results, t)
}
}
return results, nil
}
// Implement the rest of the TranslationRepository interface with empty methods.
func (m *mockTranslationRepository) Create(ctx context.Context, entity *domain.Translation) error {
m.translations = append(m.translations, *entity)
return nil
}
func (m *mockTranslationRepository) GetByID(ctx context.Context, id uint) (*domain.Translation, error) { return nil, nil }
func (m *mockTranslationRepository) Update(ctx context.Context, entity *domain.Translation) error { return nil }
func (m *mockTranslationRepository) Delete(ctx context.Context, id uint) error { return nil }
func (m *mockTranslationRepository) List(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Translation], error) {
return nil, nil
}
func (m *mockTranslationRepository) ListAll(ctx context.Context) ([]domain.Translation, error) {
return nil, nil
}
func (m *mockTranslationRepository) Count(ctx context.Context) (int64, error) { return 0, nil }
func (m *mockTranslationRepository) ListByTranslatorID(ctx context.Context, translatorID uint) ([]domain.Translation, error) {
return nil, nil
}
func (m *mockTranslationRepository) ListByStatus(ctx context.Context, status domain.TranslationStatus) ([]domain.Translation, error) {
return nil, nil
}
func (m *mockTranslationRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Translation) error {
return nil
}
func (m *mockTranslationRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Translation, error) {
return nil, nil
}
func (m *mockTranslationRepository) UpdateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Translation) error {
return nil
}
func (m *mockTranslationRepository) DeleteInTx(ctx context.Context, tx *gorm.DB, id uint) error {
return nil
}
func (m *mockTranslationRepository) ListWithOptions(ctx context.Context, options *domain.QueryOptions) ([]domain.Translation, error) {
return nil, nil
}
func (m *mockTranslationRepository) CountWithOptions(ctx context.Context, options *domain.QueryOptions) (int64, error) {
return 0, nil
}
func (m *mockTranslationRepository) FindWithPreload(ctx context.Context, preloads []string, id uint) (*domain.Translation, error) {
return nil, nil
}
func (m *mockTranslationRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]domain.Translation, error) {
return nil, nil
}
func (m *mockTranslationRepository) Exists(ctx context.Context, id uint) (bool, error) {
return false, 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 nil
}
type LocalizationServiceSuite struct {
suite.Suite
repo *mockTranslationRepository
service Service
}
func (s *LocalizationServiceSuite) SetupTest() {
s.repo = &mockTranslationRepository{}
s.service = NewService(s.repo)
}
func TestLocalizationServiceSuite(t *testing.T) {
suite.Run(t, new(LocalizationServiceSuite))
}
func (s *LocalizationServiceSuite) TestGetWorkContent_ZeroWorkID() {
content, err := s.service.GetWorkContent(context.Background(), 0, "en")
assert.Error(s.T(), err)
assert.Equal(s.T(), "invalid work ID", err.Error())
assert.Empty(s.T(), content)
}
func (s *LocalizationServiceSuite) TestGetWorkContent_NoTranslations() {
content, err := s.service.GetWorkContent(context.Background(), 1, "en")
assert.NoError(s.T(), err)
assert.Empty(s.T(), content)
}
func (s *LocalizationServiceSuite) TestGetWorkContent_OriginalLanguage() {
s.repo.translations = []domain.Translation{
{TranslatableType: "Work", TranslatableID: 1, Language: "es", Content: "Contenido original", IsOriginalLanguage: true},
{TranslatableType: "Work", TranslatableID: 1, Language: "en", Content: "English content", IsOriginalLanguage: false},
}
content, err := s.service.GetWorkContent(context.Background(), 1, "fr")
assert.NoError(s.T(), err)
assert.Equal(s.T(), "Contenido original", content)
}
func (s *LocalizationServiceSuite) TestGetWorkContent_PreferredLanguage() {
s.repo.translations = []domain.Translation{
{TranslatableType: "Work", TranslatableID: 1, Language: "es", Content: "Contenido en español", IsOriginalLanguage: false},
{TranslatableType: "Work", TranslatableID: 1, Language: "en", Content: "English content", IsOriginalLanguage: false},
}
content, err := s.service.GetWorkContent(context.Background(), 1, "en")
assert.NoError(s.T(), err)
assert.Equal(s.T(), "English content", content)
}
func (s *LocalizationServiceSuite) TestGetWorkContent_Fallback() {
s.repo.translations = []domain.Translation{
{TranslatableType: "Work", TranslatableID: 1, Language: "es", Content: "Contenido en español", IsOriginalLanguage: false},
{TranslatableType: "Work", TranslatableID: 1, Language: "fr", Content: "Contenu en français", IsOriginalLanguage: false},
}
content, err := s.service.GetWorkContent(context.Background(), 1, "en")
assert.NoError(s.T(), err)
assert.Equal(s.T(), "Contenido en español", content)
}
func (s *LocalizationServiceSuite) TestGetWorkContent_RepoError() {
s.repo.err = errors.New("database error")
content, err := s.service.GetWorkContent(context.Background(), 1, "en")
assert.Error(s.T(), err)
assert.Equal(s.T(), "database error", err.Error())
assert.Empty(s.T(), content)
}
func (s *LocalizationServiceSuite) TestGetAuthorBiography_ZeroAuthorID() {
content, err := s.service.GetAuthorBiography(context.Background(), 0, "en")
assert.Error(s.T(), err)
assert.Equal(s.T(), "invalid author ID", err.Error())
assert.Empty(s.T(), content)
}
func (s *LocalizationServiceSuite) TestGetAuthorBiography_NoTranslations() {
content, err := s.service.GetAuthorBiography(context.Background(), 1, "en")
assert.NoError(s.T(), err)
assert.Empty(s.T(), content)
}
func (s *LocalizationServiceSuite) TestGetAuthorBiography_OriginalLanguage() {
s.repo.translations = []domain.Translation{
{TranslatableType: "Author", TranslatableID: 1, Language: "es", Description: "Biografía original", IsOriginalLanguage: true},
{TranslatableType: "Author", TranslatableID: 1, Language: "en", Description: "English biography", IsOriginalLanguage: false},
}
content, err := s.service.GetAuthorBiography(context.Background(), 1, "fr")
assert.NoError(s.T(), err)
assert.Equal(s.T(), "Biografía original", content)
}
func (s *LocalizationServiceSuite) TestGetAuthorBiography_PreferredLanguage() {
s.repo.translations = []domain.Translation{
{TranslatableType: "Author", TranslatableID: 1, Language: "es", Description: "Biografía en español", IsOriginalLanguage: false},
{TranslatableType: "Author", TranslatableID: 1, Language: "en", Description: "English biography", IsOriginalLanguage: false},
}
content, err := s.service.GetAuthorBiography(context.Background(), 1, "en")
assert.NoError(s.T(), err)
assert.Equal(s.T(), "English biography", content)
}
func (s *LocalizationServiceSuite) TestGetAuthorBiography_Fallback() {
s.repo.translations = []domain.Translation{
{TranslatableType: "Author", TranslatableID: 1, Language: "es", Description: "Biografía en español", IsOriginalLanguage: false},
{TranslatableType: "Author", TranslatableID: 1, Language: "fr", Description: "Biographie en français", IsOriginalLanguage: false},
}
content, err := s.service.GetAuthorBiography(context.Background(), 1, "en")
assert.NoError(s.T(), err)
assert.Equal(s.T(), "Biografía en español", content)
}
func (s *LocalizationServiceSuite) TestGetAuthorBiography_NoDescription() {
s.repo.translations = []domain.Translation{
{TranslatableType: "Author", TranslatableID: 1, Language: "es", Content: "Contenido sin descripción", IsOriginalLanguage: false},
}
content, err := s.service.GetAuthorBiography(context.Background(), 1, "en")
assert.NoError(s.T(), err)
assert.Empty(s.T(), content)
}
func (s *LocalizationServiceSuite) TestGetAuthorBiography_RepoError() {
s.repo.err = errors.New("database error")
content, err := s.service.GetAuthorBiography(context.Background(), 1, "en")
assert.Error(s.T(), err)
assert.Equal(s.T(), "database error", err.Error())
assert.Empty(s.T(), content)
}