package services import ( "context" "github.com/google/uuid" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "gorm.io/driver/sqlite" "gorm.io/gorm" "veza-backend-api/internal/models" ) func setupTestTrackHistoryService(t *testing.T) (*TrackHistoryService, *gorm.DB, func()) { db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) err = db.AutoMigrate(&models.User{}, &models.Track{}, &models.TrackHistory{}) require.NoError(t, err) logger := zap.NewNop() service := NewTrackHistoryService(db, logger) cleanup := func() { // No cleanup needed for in-memory database } return service, db, cleanup } func TestTrackHistoryService_RecordHistory(t *testing.T) { service, db, cleanup := setupTestTrackHistoryService(t) defer cleanup() ctx := context.Background() // Create user user := &models.User{ ID: uuid.New(), Username: "testuser", Email: "test@example.com", IsActive: true, } db.Create(user) // Create track track := &models.Track{ UserID: user.ID, Title: "Test Track", FilePath: "/path/to/track.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } db.Create(track) // Record history params := RecordHistoryParams{ TrackID: track.ID, UserID: user.ID, Action: models.TrackHistoryActionCreated, OldValue: nil, NewValue: map[string]interface{}{"title": "Test Track"}, } history, err := service.RecordHistory(ctx, params) assert.NoError(t, err) assert.NotNil(t, history) assert.Equal(t, track.ID, history.TrackID) assert.Equal(t, user.ID, history.UserID) assert.Equal(t, models.TrackHistoryActionCreated, history.Action) assert.NotEmpty(t, history.NewValue) } func TestTrackHistoryService_RecordHistory_TrackNotFound(t *testing.T) { service, db, cleanup := setupTestTrackHistoryService(t) defer cleanup() ctx := context.Background() // Create user user := &models.User{ ID: uuid.New(), Username: "testuser", Email: "test@example.com", IsActive: true, } db.Create(user) // Record history with non-existent track params := RecordHistoryParams{ TrackID: uuid.New(), UserID: user.ID, Action: models.TrackHistoryActionCreated, OldValue: nil, NewValue: map[string]interface{}{"title": "Test Track"}, } _, err := service.RecordHistory(ctx, params) assert.Error(t, err) assert.ErrorIs(t, err, ErrTrackNotFound) } func TestTrackHistoryService_RecordHistory_WithStringValues(t *testing.T) { service, db, cleanup := setupTestTrackHistoryService(t) defer cleanup() ctx := context.Background() // Create user user := &models.User{ ID: uuid.New(), Username: "testuser", Email: "test@example.com", IsActive: true, } db.Create(user) // Create track track := &models.Track{ UserID: user.ID, Title: "Test Track", FilePath: "/path/to/track.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } db.Create(track) // Record history with string values params := RecordHistoryParams{ TrackID: track.ID, UserID: user.ID, Action: models.TrackHistoryActionUpdated, OldValue: "Old Title", NewValue: "New Title", } history, err := service.RecordHistory(ctx, params) assert.NoError(t, err) assert.NotNil(t, history) assert.Contains(t, history.OldValue, "Old Title") assert.Contains(t, history.NewValue, "New Title") } func TestTrackHistoryService_GetHistory(t *testing.T) { service, db, cleanup := setupTestTrackHistoryService(t) defer cleanup() ctx := context.Background() // Create user user := &models.User{ ID: uuid.New(), Username: "testuser", Email: "test@example.com", IsActive: true, } db.Create(user) // Create track track := &models.Track{ UserID: user.ID, Title: "Test Track", FilePath: "/path/to/track.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } db.Create(track) // Record multiple history entries for i := 0; i < 5; i++ { params := RecordHistoryParams{ TrackID: track.ID, UserID: user.ID, Action: models.TrackHistoryActionUpdated, OldValue: map[string]interface{}{"iteration": i}, NewValue: map[string]interface{}{"iteration": i + 1}, } _, err := service.RecordHistory(ctx, params) require.NoError(t, err) } // Get history histories, total, err := service.GetHistory(ctx, track.ID, 10, 0) assert.NoError(t, err) assert.Equal(t, int64(5), total) assert.Len(t, histories, 5) // Verify ordering (should be DESC by created_at) for i := 0; i < len(histories)-1; i++ { assert.True(t, histories[i].CreatedAt.After(histories[i+1].CreatedAt) || histories[i].CreatedAt.Equal(histories[i+1].CreatedAt)) } } func TestTrackHistoryService_GetHistory_WithPagination(t *testing.T) { service, db, cleanup := setupTestTrackHistoryService(t) defer cleanup() ctx := context.Background() // Create user user := &models.User{ ID: uuid.New(), Username: "testuser", Email: "test@example.com", IsActive: true, } db.Create(user) // Create track track := &models.Track{ UserID: user.ID, Title: "Test Track", FilePath: "/path/to/track.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } db.Create(track) // Record multiple history entries for i := 0; i < 10; i++ { params := RecordHistoryParams{ TrackID: track.ID, UserID: user.ID, Action: models.TrackHistoryActionUpdated, OldValue: map[string]interface{}{"iteration": i}, NewValue: map[string]interface{}{"iteration": i + 1}, } _, err := service.RecordHistory(ctx, params) require.NoError(t, err) } // Get first page histories, total, err := service.GetHistory(ctx, track.ID, 5, 0) assert.NoError(t, err) assert.Equal(t, int64(10), total) assert.Len(t, histories, 5) // Get second page histories2, total2, err := service.GetHistory(ctx, track.ID, 5, 5) assert.NoError(t, err) assert.Equal(t, int64(10), total2) assert.Len(t, histories2, 5) // Verify no overlap assert.NotEqual(t, histories[0].ID, histories2[0].ID) } func TestTrackHistoryService_GetHistory_TrackNotFound(t *testing.T) { service, _, cleanup := setupTestTrackHistoryService(t) defer cleanup() ctx := context.Background() _, _, err := service.GetHistory(ctx, uuid.New(), 10, 0) assert.Error(t, err) assert.ErrorIs(t, err, ErrTrackNotFound) } func TestTrackHistoryService_GetHistoryByUser(t *testing.T) { service, db, cleanup := setupTestTrackHistoryService(t) defer cleanup() ctx := context.Background() // Create users user1 := &models.User{ ID: uuid.New(), Username: "user1", Email: "user1@example.com", IsActive: true, } user2 := &models.User{ ID: uuid.New(), Username: "user2", Email: "user2@example.com", IsActive: true, } db.Create(user1) db.Create(user2) // Create tracks track1 := &models.Track{ UserID: user1.ID, Title: "Track 1", FilePath: "/path/to/track1.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } track2 := &models.Track{ UserID: user2.ID, Title: "Track 2", FilePath: "/path/to/track2.mp3", FileSize: 2048, Format: "MP3", Duration: 240, IsPublic: true, Status: models.TrackStatusCompleted, } db.Create(track1) db.Create(track2) // Record history for user1 for i := 0; i < 3; i++ { params := RecordHistoryParams{ TrackID: track1.ID, UserID: user1.ID, Action: models.TrackHistoryActionUpdated, OldValue: map[string]interface{}{"iteration": i}, NewValue: map[string]interface{}{"iteration": i + 1}, } _, err := service.RecordHistory(ctx, params) require.NoError(t, err) } // Record history for user2 for i := 0; i < 2; i++ { params := RecordHistoryParams{ TrackID: track2.ID, UserID: user2.ID, Action: models.TrackHistoryActionUpdated, OldValue: map[string]interface{}{"iteration": i}, NewValue: map[string]interface{}{"iteration": i + 1}, } _, err := service.RecordHistory(ctx, params) require.NoError(t, err) } // Get history for user1 histories, total, err := service.GetHistoryByUser(ctx, user1.ID, 10, 0) assert.NoError(t, err) assert.Equal(t, int64(3), total) assert.Len(t, histories, 3) // Verify all entries belong to user1 for _, h := range histories { assert.Equal(t, user1.ID, h.UserID) } } func TestTrackHistoryService_GetHistoryByAction(t *testing.T) { service, db, cleanup := setupTestTrackHistoryService(t) defer cleanup() ctx := context.Background() // Create user user := &models.User{ ID: uuid.New(), Username: "testuser", Email: "test@example.com", IsActive: true, } db.Create(user) // Create track track := &models.Track{ UserID: user.ID, Title: "Test Track", FilePath: "/path/to/track.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } db.Create(track) // Record different actions actions := []models.TrackHistoryAction{ models.TrackHistoryActionCreated, models.TrackHistoryActionUpdated, models.TrackHistoryActionUpdated, models.TrackHistoryActionPublished, models.TrackHistoryActionUpdated, } for _, action := range actions { params := RecordHistoryParams{ TrackID: track.ID, UserID: user.ID, Action: action, OldValue: nil, NewValue: map[string]interface{}{"action": string(action)}, } _, err := service.RecordHistory(ctx, params) require.NoError(t, err) } // Get history for "updated" action only histories, total, err := service.GetHistoryByAction(ctx, track.ID, models.TrackHistoryActionUpdated, 10, 0) assert.NoError(t, err) assert.Equal(t, int64(3), total) assert.Len(t, histories, 3) // Verify all entries have "updated" action for _, h := range histories { assert.Equal(t, models.TrackHistoryActionUpdated, h.Action) } } func TestTrackHistoryService_GetHistoryByAction_TrackNotFound(t *testing.T) { service, _, cleanup := setupTestTrackHistoryService(t) defer cleanup() ctx := context.Background() _, _, err := service.GetHistoryByAction(ctx, uuid.New(), models.TrackHistoryActionUpdated, 10, 0) assert.Error(t, err) assert.ErrorIs(t, err, ErrTrackNotFound) }