tercul-backend/internal/data/sql/analytics_repository_test.go
google-labs-jules[bot] 057d6ea6bb test: Increase test coverage for sql repositories
This commit improves the test coverage for the `internal/data/sql`
package by adding comprehensive tests for several repositories and
refactoring the testing strategy to be more robust.

The following changes were made:
- Refactored the `analytics_repository_test.go` to use an in-memory
  SQLite database instead of `sqlmock`. This makes the tests more
  reliable and less brittle against GORM's SQL generation.
- Added new tests for `auth_repository.go` and
  `copyright_claim_repository.go` using the same in-memory SQLite
  database strategy.
- Added the missing `WorkID` field to the `CopyrightClaim` domain
  entity to align it with the repository's logic.

This effort increased the test coverage for the `internal/data/sql`
package from 30.5% to 37.1%.
2025-10-08 21:58:20 +00:00

204 lines
6.2 KiB
Go

package sql_test
import (
"context"
"testing"
"tercul/internal/app/analytics"
"tercul/internal/data/sql"
"tercul/internal/domain"
"tercul/internal/platform/config"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
// newTestAnalyticsRepoWithSQLite sets up an in-memory SQLite database for testing.
func newTestAnalyticsRepoWithSQLite(t *testing.T) (analytics.Repository, *gorm.DB) {
// Using "file::memory:?cache=shared" to ensure the in-memory database is shared across connections in the same process.
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
require.NoError(t, err)
// Auto-migrate the necessary schemas
err = db.AutoMigrate(
&domain.Work{},
&domain.WorkStats{},
&domain.Translation{},
&domain.TranslationStats{},
&domain.Trending{},
&domain.UserEngagement{},
&domain.User{},
)
require.NoError(t, err)
cfg := &config.Config{}
repo := sql.NewAnalyticsRepository(db, cfg)
// Clean up the database after the test
t.Cleanup(func() {
sqlDB, err := db.DB()
require.NoError(t, err)
err = sqlDB.Close()
require.NoError(t, err)
})
return repo, db
}
func TestAnalyticsRepository_IncrementWorkCounter(t *testing.T) {
repo, db := newTestAnalyticsRepoWithSQLite(t)
ctx := context.Background()
// Setup: Create a work to associate stats with
work := domain.Work{Title: "Test Work"}
require.NoError(t, db.Create(&work).Error)
t.Run("creates_new_stats_if_not_exist", func(t *testing.T) {
err := repo.IncrementWorkCounter(ctx, work.ID, "views", 5)
require.NoError(t, err)
var stats domain.WorkStats
err = db.Where("work_id = ?", work.ID).First(&stats).Error
require.NoError(t, err)
assert.Equal(t, int64(5), stats.Views)
})
t.Run("increments_existing_stats", func(t *testing.T) {
// Increment again
err := repo.IncrementWorkCounter(ctx, work.ID, "views", 3)
require.NoError(t, err)
var stats domain.WorkStats
err = db.Where("work_id = ?", work.ID).First(&stats).Error
require.NoError(t, err)
assert.Equal(t, int64(8), stats.Views) // 5 + 3
})
t.Run("invalid_field", func(t *testing.T) {
err := repo.IncrementWorkCounter(ctx, work.ID, "invalid_field", 1)
assert.Error(t, err)
})
}
func TestAnalyticsRepository_GetTrendingWorks(t *testing.T) {
repo, db := newTestAnalyticsRepoWithSQLite(t)
ctx := context.Background()
// Setup: Create some works and trending data
work1 := domain.Work{Title: "Trending Work 1"}
work2 := domain.Work{Title: "Trending Work 2"}
require.NoError(t, db.Create(&work1).Error)
require.NoError(t, db.Create(&work2).Error)
trendingData := []*domain.Trending{
{EntityID: work1.ID, EntityType: "Work", Rank: 1, TimePeriod: "daily"},
{EntityID: work2.ID, EntityType: "Work", Rank: 2, TimePeriod: "daily"},
}
require.NoError(t, db.Create(&trendingData).Error)
t.Run("success", func(t *testing.T) {
works, err := repo.GetTrendingWorks(ctx, "daily", 5)
require.NoError(t, err)
require.Len(t, works, 2)
assert.Equal(t, work1.ID, works[0].ID)
assert.Equal(t, work2.ID, works[1].ID)
})
}
func TestAnalyticsRepository_UpdateTrendingWorks(t *testing.T) {
repo, db := newTestAnalyticsRepoWithSQLite(t)
ctx := context.Background()
// Setup: Create an old trending record
oldTrending := domain.Trending{EntityID: 99, EntityType: "Work", Rank: 1, TimePeriod: "daily"}
require.NoError(t, db.Create(&oldTrending).Error)
newTrendingData := []*domain.Trending{
{EntityID: 1, EntityType: "Work", Rank: 1, Score: 100, TimePeriod: "daily"},
{EntityID: 2, EntityType: "Work", Rank: 2, Score: 90, TimePeriod: "daily"},
}
err := repo.UpdateTrendingWorks(ctx, "daily", newTrendingData)
require.NoError(t, err)
var trendingResult []domain.Trending
db.Where("time_period = ?", "daily").Order("rank asc").Find(&trendingResult)
require.Len(t, trendingResult, 2)
assert.Equal(t, uint(1), trendingResult[0].EntityID)
assert.Equal(t, uint(2), trendingResult[1].EntityID)
}
func TestAnalyticsRepository_GetOrCreate(t *testing.T) {
repo, db := newTestAnalyticsRepoWithSQLite(t)
ctx := context.Background()
work := domain.Work{Title: "Test Work"}
require.NoError(t, db.Create(&work).Error)
translation := domain.Translation{Title: "Test Translation", TranslatableID: work.ID, TranslatableType: "Work"}
require.NoError(t, db.Create(&translation).Error)
user := domain.User{Username: "testuser", Email: "test@test.com"}
require.NoError(t, db.Create(&user).Error)
t.Run("GetOrCreateWorkStats", func(t *testing.T) {
// Create
stats, err := repo.GetOrCreateWorkStats(ctx, work.ID)
require.NoError(t, err)
assert.Equal(t, work.ID, stats.WorkID)
// Get
stats2, err := repo.GetOrCreateWorkStats(ctx, work.ID)
require.NoError(t, err)
assert.Equal(t, stats.ID, stats2.ID)
})
t.Run("GetOrCreateTranslationStats", func(t *testing.T) {
// Create
stats, err := repo.GetOrCreateTranslationStats(ctx, translation.ID)
require.NoError(t, err)
assert.Equal(t, translation.ID, stats.TranslationID)
// Get
stats2, err := repo.GetOrCreateTranslationStats(ctx, translation.ID)
require.NoError(t, err)
assert.Equal(t, stats.ID, stats2.ID)
})
t.Run("GetOrCreateUserEngagement", func(t *testing.T) {
date := time.Now().Truncate(24 * time.Hour)
// Create
eng, err := repo.GetOrCreateUserEngagement(ctx, user.ID, date)
require.NoError(t, err)
assert.Equal(t, user.ID, eng.UserID)
// Get
eng2, err := repo.GetOrCreateUserEngagement(ctx, user.ID, date)
require.NoError(t, err)
assert.Equal(t, eng.ID, eng2.ID)
})
}
func TestAnalyticsRepository_UpdateUserEngagement(t *testing.T) {
repo, db := newTestAnalyticsRepoWithSQLite(t)
ctx := context.Background()
user := domain.User{Username: "testuser", Email: "test@test.com"}
require.NoError(t, db.Create(&user).Error)
date := time.Now().Truncate(24 * time.Hour)
engagement, err := repo.GetOrCreateUserEngagement(ctx, user.ID, date)
require.NoError(t, err)
engagement.LikesGiven = 15
engagement.WorksRead = 10
err = repo.UpdateUserEngagement(ctx, engagement)
require.NoError(t, err)
var updatedEngagement domain.UserEngagement
db.First(&updatedEngagement, engagement.ID)
assert.Equal(t, 15, updatedEngagement.LikesGiven)
assert.Equal(t, 10, updatedEngagement.WorksRead)
}