tercul-backend/internal/jobs/analytics/worker_test.go
google-labs-jules[bot] 04878c7bec feat: Implement event-driven analytics system
This commit introduces a new event-driven analytics system to track user interactions with works and translations. The system is designed to be scalable and production-ready.

Key changes:
- An asynchronous event-driven architecture using `asynq` for handling analytics.
- A new background worker process (`cmd/worker`) to process analytics events from a Redis-backed queue.
- GraphQL resolvers now publish `AnalyticsEvent`s to the queue instead of directly calling the analytics service.
- New `popularTranslations` GraphQL query to leverage the new analytics data.
- Integration tests now use `miniredis` to mock Redis, making them self-contained.
- The `TODO.md` file has been updated to reflect the completed work.
2025-09-07 22:54:43 +00:00

86 lines
2.0 KiB
Go

package analytics_test
import (
"context"
"encoding/json"
"testing"
"tercul/internal/app/analytics"
analyticsjob "tercul/internal/jobs/analytics"
"tercul/internal/testutil"
"time"
"github.com/hibiken/asynq"
"github.com/stretchr/testify/suite"
)
type WorkerIntegrationTestSuite struct {
testutil.IntegrationTestSuite
}
func TestWorkerIntegrationTestSuite(t *testing.T) {
suite.Run(t, new(WorkerIntegrationTestSuite))
}
func (s *WorkerIntegrationTestSuite) SetupTest() {
s.IntegrationTestSuite.SetupSuite(nil)
s.IntegrationTestSuite.SetupTest()
}
func (s *WorkerIntegrationTestSuite) TestWorker_ProcessTask() {
// Create a new worker
worker := analyticsjob.NewWorker(s.App.AnalyticsService)
// Create a new asynq client
redisAddr := s.Config.RedisAddr
client := asynq.NewClient(asynq.RedisClientOpt{Addr: redisAddr})
defer client.Close()
// Create a new asynq server and register the handler
srv := asynq.NewServer(
asynq.RedisClientOpt{Addr: redisAddr},
asynq.Config{
Concurrency: 1,
Queues: map[string]int{
"analytics": 1,
},
},
)
mux := asynq.NewServeMux()
mux.HandleFunc("analytics:event", worker.ProcessTask)
// Enqueue a task
work := testutil.CreateWork(s.Ctx, s.DB, "Test Work", "Test Author")
event := analytics.AnalyticsEvent{
EventType: analytics.EventTypeWorkViewed,
WorkID: &work.ID,
}
payload, err := json.Marshal(event)
s.Require().NoError(err)
task := asynq.NewTask("analytics:event", payload)
_, err = client.Enqueue(task, asynq.Queue("analytics"))
s.Require().NoError(err)
// Process the task
go func() {
err := srv.Run(mux)
s.Require().NoError(err)
}()
defer srv.Stop()
// Verify
s.Eventually(func() bool {
popular, err := s.App.AnalyticsService.GetPopularWorks(context.Background(), 10)
if err != nil {
return false
}
for _, p := range popular {
if p.WorkID == work.ID {
return true
}
}
return false
}, 5*time.Second, 100*time.Millisecond, "work should be in popular list")
}