tercul-backend/internal/app/analytics/service_test.go
google-labs-jules[bot] a8dfb727a1 feat: Implement critical features and fix build
This commit addresses several high-priority tasks from the TASKS.md file, including:

- **Fix Background Job Panic:** Replaced `log.Fatalf` with `log.Printf` in the `asynq` server to prevent crashes.
- **Refactor API Server Setup:** Consolidated the GraphQL Playground and Prometheus metrics endpoints into the main API server.
- **Implement `DeleteUser` Mutation:** Implemented the `DeleteUser` resolver.
- **Implement `CreateContribution` Mutation:** Implemented the `CreateContribution` resolver and its required application service.

Additionally, this commit includes a major refactoring of the configuration management system to fix a broken build. The global `config.Cfg` variable has been removed and replaced with a dependency injection approach, where the configuration object is passed to all components that require it. This change has been applied across the entire codebase, including the test suite, to ensure a stable and testable application.
2025-10-05 18:29:18 +00:00

264 lines
8.1 KiB
Go

package analytics_test
import (
"context"
"strings"
"testing"
"tercul/internal/app/analytics"
"tercul/internal/data/sql"
"tercul/internal/domain"
"tercul/internal/domain/work"
"tercul/internal/jobs/linguistics"
"tercul/internal/platform/config"
"tercul/internal/testutil"
"github.com/stretchr/testify/suite"
)
type AnalyticsServiceTestSuite struct {
testutil.IntegrationTestSuite
service analytics.Service
}
func (s *AnalyticsServiceTestSuite) SetupSuite() {
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
cfg, err := config.LoadConfig()
s.Require().NoError(err)
analyticsRepo := sql.NewAnalyticsRepository(s.DB, cfg)
analysisRepo := linguistics.NewGORMAnalysisRepository(s.DB)
translationRepo := sql.NewTranslationRepository(s.DB, cfg)
workRepo := sql.NewWorkRepository(s.DB, cfg)
sentimentProvider, _ := linguistics.NewGoVADERSentimentProvider()
s.service = analytics.NewService(analyticsRepo, analysisRepo, translationRepo, workRepo, sentimentProvider)
}
func (s *AnalyticsServiceTestSuite) SetupTest() {
s.IntegrationTestSuite.SetupTest()
s.DB.Exec("DELETE FROM trendings")
}
func (s *AnalyticsServiceTestSuite) TestIncrementWorkViews() {
s.Run("should increment the view count for a work", func() {
// Arrange
work := s.CreateTestWork("Test Work", "en", "Test content")
// Act
err := s.service.IncrementWorkViews(context.Background(), work.ID)
s.Require().NoError(err)
// Assert
stats, err := s.service.GetOrCreateWorkStats(context.Background(), work.ID)
s.Require().NoError(err)
s.Equal(int64(1), stats.Views)
})
}
func (s *AnalyticsServiceTestSuite) TestIncrementWorkLikes() {
s.Run("should increment the like count for a work", func() {
// Arrange
work := s.CreateTestWork("Test Work", "en", "Test content")
// Act
err := s.service.IncrementWorkLikes(context.Background(), work.ID)
s.Require().NoError(err)
// Assert
stats, err := s.service.GetOrCreateWorkStats(context.Background(), work.ID)
s.Require().NoError(err)
s.Equal(int64(1), stats.Likes)
})
}
func (s *AnalyticsServiceTestSuite) TestIncrementWorkComments() {
s.Run("should increment the comment count for a work", func() {
// Arrange
work := s.CreateTestWork("Test Work", "en", "Test content")
// Act
err := s.service.IncrementWorkComments(context.Background(), work.ID)
s.Require().NoError(err)
// Assert
stats, err := s.service.GetOrCreateWorkStats(context.Background(), work.ID)
s.Require().NoError(err)
s.Equal(int64(1), stats.Comments)
})
}
func (s *AnalyticsServiceTestSuite) TestIncrementWorkBookmarks() {
s.Run("should increment the bookmark count for a work", func() {
// Arrange
work := s.CreateTestWork("Test Work", "en", "Test content")
// Act
err := s.service.IncrementWorkBookmarks(context.Background(), work.ID)
s.Require().NoError(err)
// Assert
stats, err := s.service.GetOrCreateWorkStats(context.Background(), work.ID)
s.Require().NoError(err)
s.Equal(int64(1), stats.Bookmarks)
})
}
func (s *AnalyticsServiceTestSuite) TestIncrementWorkShares() {
s.Run("should increment the share count for a work", func() {
// Arrange
work := s.CreateTestWork("Test Work", "en", "Test content")
// Act
err := s.service.IncrementWorkShares(context.Background(), work.ID)
s.Require().NoError(err)
// Assert
stats, err := s.service.GetOrCreateWorkStats(context.Background(), work.ID)
s.Require().NoError(err)
s.Equal(int64(1), stats.Shares)
})
}
func (s *AnalyticsServiceTestSuite) TestIncrementWorkTranslationCount() {
s.Run("should increment the translation count for a work", func() {
// Arrange
work := s.CreateTestWork("Test Work", "en", "Test content")
// Act
err := s.service.IncrementWorkTranslationCount(context.Background(), work.ID)
s.Require().NoError(err)
// Assert
stats, err := s.service.GetOrCreateWorkStats(context.Background(), work.ID)
s.Require().NoError(err)
s.Equal(int64(1), stats.TranslationCount)
})
}
func (s *AnalyticsServiceTestSuite) TestUpdateWorkReadingTime() {
s.Run("should update the reading time for a work", func() {
// Arrange
work := s.CreateTestWork("Test Work", "en", "Test content")
s.DB.Create(&domain.ReadabilityScore{WorkID: work.ID})
s.DB.Create(&domain.LanguageAnalysis{WorkID: work.ID, Analysis: domain.JSONB{}})
textMetadata := &domain.TextMetadata{
WorkID: work.ID,
WordCount: 1000,
}
s.DB.Create(textMetadata)
// Act
err := s.service.UpdateWorkReadingTime(context.Background(), work.ID)
s.Require().NoError(err)
// Assert
stats, err := s.service.GetOrCreateWorkStats(context.Background(), work.ID)
s.Require().NoError(err)
s.Equal(5, stats.ReadingTime) // 1000 words / 200 wpm = 5 minutes
})
}
func (s *AnalyticsServiceTestSuite) TestUpdateTranslationReadingTime() {
s.Run("should update the reading time for a translation", func() {
// Arrange
work := s.CreateTestWork("Test Work", "en", "Test content")
translation := s.CreateTestTranslation(work.ID, "es", strings.Repeat("Contenido de prueba con quinientas palabras. ", 100))
// Act
err := s.service.UpdateTranslationReadingTime(context.Background(), translation.ID)
s.Require().NoError(err)
// Assert
stats, err := s.service.GetOrCreateTranslationStats(context.Background(), translation.ID)
s.Require().NoError(err)
s.Equal(3, stats.ReadingTime) // 500 words / 200 wpm = 2.5 -> 3 minutes
})
}
func (s *AnalyticsServiceTestSuite) TestUpdateWorkComplexity() {
s.Run("should update the complexity for a work", func() {
// Arrange
work := s.CreateTestWork("Test Work", "en", "Test content")
s.DB.Create(&domain.TextMetadata{WorkID: work.ID})
s.DB.Create(&domain.LanguageAnalysis{WorkID: work.ID, Analysis: domain.JSONB{}})
readabilityScore := &domain.ReadabilityScore{
WorkID: work.ID,
Score: 12.34,
}
s.DB.Create(readabilityScore)
// Act
err := s.service.UpdateWorkComplexity(context.Background(), work.ID)
s.Require().NoError(err)
// Assert
stats, err := s.service.GetOrCreateWorkStats(context.Background(), work.ID)
s.Require().NoError(err)
s.Equal(12.34, stats.Complexity)
})
}
func (s *AnalyticsServiceTestSuite) TestUpdateWorkSentiment() {
s.Run("should update the sentiment for a work", func() {
// Arrange
work := s.CreateTestWork("Test Work", "en", "Test content")
s.DB.Create(&domain.TextMetadata{WorkID: work.ID})
s.DB.Create(&domain.ReadabilityScore{WorkID: work.ID})
languageAnalysis := &domain.LanguageAnalysis{
WorkID: work.ID,
Analysis: domain.JSONB{
"sentiment": 0.5678,
},
}
s.DB.Create(languageAnalysis)
// Act
err := s.service.UpdateWorkSentiment(context.Background(), work.ID)
s.Require().NoError(err)
// Assert
stats, err := s.service.GetOrCreateWorkStats(context.Background(), work.ID)
s.Require().NoError(err)
s.Equal(0.5678, stats.Sentiment)
})
}
func (s *AnalyticsServiceTestSuite) TestUpdateTranslationSentiment() {
s.Run("should update the sentiment for a translation", func() {
// Arrange
work := s.CreateTestWork("Test Work", "en", "Test content")
translation := s.CreateTestTranslation(work.ID, "en", "This is a wonderfully positive and uplifting sentence.")
// Act
err := s.service.UpdateTranslationSentiment(context.Background(), translation.ID)
s.Require().NoError(err)
// Assert
stats, err := s.service.GetOrCreateTranslationStats(context.Background(), translation.ID)
s.Require().NoError(err)
s.True(stats.Sentiment > 0.5)
})
}
func (s *AnalyticsServiceTestSuite) TestUpdateTrending() {
s.Run("should update the trending works", func() {
// Arrange
work1 := s.CreateTestWork("Work 1", "en", "content")
work2 := s.CreateTestWork("Work 2", "en", "content")
s.DB.Create(&work.WorkStats{WorkID: work1.ID, Views: 100, Likes: 10, Comments: 1})
s.DB.Create(&work.WorkStats{WorkID: work2.ID, Views: 10, Likes: 100, Comments: 10})
// Act
err := s.service.UpdateTrending(context.Background())
s.Require().NoError(err)
// Assert
var trendingWorks []*domain.Trending
s.DB.Order("rank asc").Find(&trendingWorks)
s.Require().Len(trendingWorks, 2)
s.Equal(work2.ID, trendingWorks[0].EntityID)
s.Equal(work1.ID, trendingWorks[1].EntityID)
})
}
func TestAnalyticsService(t *testing.T) {
suite.Run(t, new(AnalyticsServiceTestSuite))
}