mirror of
https://github.com/SamyRai/tercul-backend.git
synced 2025-12-27 04:01:34 +00:00
This commit implements a robust, production-ready analytics system using an event-driven architecture with Redis and `asynq`. Key changes: - Event-Driven Architecture: Instead of synchronous database updates, analytics events (e.g., views, likes, comments) are now published to a Redis queue. This improves API response times and decouples the analytics system from the main application flow. - Background Worker: A new worker process (`cmd/worker`) has been created to consume events from the queue and update the analytics counters in the database. - View Counting: Implemented the missing view counting feature for both works and translations. - New Analytics Query: Added a `popularTranslations` GraphQL query to demonstrate how to use the collected analytics data. - Testing: Added unit tests for the new event publisher and integration tests for the analytics worker. Known Issue: The integration tests for the analytics worker (`AnalyticsWorkerSuite`) and the GraphQL API (`GraphQLIntegrationSuite`) are currently failing due to the lack of a Redis service in the test environment. The tests are written and are expected to pass in an environment where Redis is available on `localhost:6379`, as configured in the CI pipeline.
86 lines
2.2 KiB
Go
86 lines
2.2 KiB
Go
package analytics_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"testing"
|
|
"tercul/internal/app/analytics"
|
|
analytics_job "tercul/internal/jobs/analytics"
|
|
"tercul/internal/testutil"
|
|
"time"
|
|
|
|
"github.com/hibiken/asynq"
|
|
"github.com/stretchr/testify/suite"
|
|
)
|
|
|
|
type AnalyticsWorkerSuite struct {
|
|
testutil.IntegrationTestSuite
|
|
asynqClient *asynq.Client
|
|
asynqServer *asynq.Server
|
|
}
|
|
|
|
func (s *AnalyticsWorkerSuite) SetupSuite() {
|
|
config := testutil.DefaultTestConfig()
|
|
s.IntegrationTestSuite.SetupSuite(config)
|
|
s.asynqClient = s.AsynqClient
|
|
s.asynqServer = asynq.NewServer(
|
|
asynq.RedisClientOpt{
|
|
Addr: config.RedisAddr,
|
|
},
|
|
asynq.Config{
|
|
Queues: map[string]int{
|
|
analytics.QueueAnalytics: 10,
|
|
},
|
|
},
|
|
)
|
|
}
|
|
|
|
func (s *AnalyticsWorkerSuite) TearDownSuite() {
|
|
s.asynqClient.Close()
|
|
s.asynqServer.Shutdown()
|
|
s.IntegrationTestSuite.TearDownSuite()
|
|
}
|
|
|
|
func (s *AnalyticsWorkerSuite) TestAnalyticsWorker_ProcessTask() {
|
|
// Create worker and register handler
|
|
analyticsService := analytics.NewService(s.AnalyticsRepo, nil, nil, nil, nil)
|
|
worker := analytics_job.NewWorker(analyticsService)
|
|
mux := asynq.NewServeMux()
|
|
mux.HandleFunc(string(analytics.EventTypeWorkViewed), worker.ProcessTask)
|
|
|
|
// Start the server in a goroutine
|
|
go func() {
|
|
if err := s.asynqServer.Run(mux); err != nil {
|
|
s.T().Logf("asynq server error: %v", err)
|
|
}
|
|
}()
|
|
time.Sleep(200 * time.Millisecond) // Give the server time to start
|
|
|
|
// Create a test work
|
|
work := s.CreateTestWork("Test Work", "en", "content")
|
|
|
|
// Enqueue a task
|
|
event := analytics.AnalyticsEvent{
|
|
EventType: analytics.EventTypeWorkViewed,
|
|
WorkID: &work.ID,
|
|
}
|
|
payload, err := json.Marshal(event)
|
|
s.Require().NoError(err)
|
|
task := asynq.NewTask(string(event.EventType), payload)
|
|
_, err = s.asynqClient.Enqueue(task, asynq.Queue(analytics.QueueAnalytics))
|
|
s.Require().NoError(err)
|
|
|
|
// Wait for the worker to process the task
|
|
time.Sleep(500 * time.Millisecond)
|
|
|
|
// Check the database
|
|
stats, err := s.AnalyticsRepo.GetOrCreateWorkStats(context.Background(), work.ID)
|
|
s.Require().NoError(err)
|
|
s.Equal(int64(1), stats.Views)
|
|
}
|
|
|
|
func TestAnalyticsWorker(t *testing.T) {
|
|
testutil.SkipIfShort(t)
|
|
suite.Run(t, new(AnalyticsWorkerSuite))
|
|
}
|