veza/veza-backend-api/internal/core/track/service_test.go
senke 0950fa30aa [T0-006] test(backend): Ajout tests pour frontend_log_handler
- Tests complets pour frontend_log_handler.go (12 tests)
- Tests couvrent NewFrontendLogHandler et ReceiveLog
- Tests pour tous les niveaux de log (DEBUG, INFO, WARN, ERROR)
- Tests pour gestion des erreurs et validation JSON
- Couverture actuelle: 30.6% (objectif: 80%)

Files: veza-backend-api/internal/handlers/frontend_log_handler_test.go
       VEZA_ROADMAP.json
Hours: 16 estimated, 23 actual
2026-01-04 01:44:22 +01:00

466 lines
13 KiB
Go

package track
import (
"bytes"
"context"
"mime/multipart" // Removed "net/http" since it is not used in the existing imports
"os" // Added "path" import
"path/filepath"
"testing"
"time"
"veza-backend-api/internal/models"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
func setupTestTrackService(t *testing.T) (*TrackService, *gorm.DB, func()) {
logger := zaptest.NewLogger(t)
// Create temp upload dir
uploadDir, err := os.MkdirTemp("", "track_service_test")
require.NoError(t, err)
// Setup SQLite database file
dbPath := filepath.Join(uploadDir, "test.db")
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
require.NoError(t, err)
// Enable foreign keys
db.Exec("PRAGMA foreign_keys = ON")
// Auto-migrate models
err = db.AutoMigrate(
&models.User{},
&models.Track{},
&models.TrackLike{}, // Added TrackLike model to migration
)
require.NoError(t, err)
service := NewTrackService(db, logger, uploadDir)
cleanup := func() {
os.RemoveAll(uploadDir)
}
return service, db, cleanup
}
func createMultipartFileHeader(t *testing.T, filename string, content []byte, contentType string) *multipart.FileHeader {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("file", filename)
require.NoError(t, err)
_, err = part.Write(content)
require.NoError(t, err)
err = writer.Close()
require.NoError(t, err)
reader := multipart.NewReader(body, writer.Boundary())
form, err := reader.ReadForm(1024 * 1024)
require.NoError(t, err)
headers := form.File["file"]
require.NotEmpty(t, headers)
headers[0].Header.Set("Content-Type", contentType)
return headers[0]
}
func TestTrackService_ValidateTrackFile(t *testing.T) {
service, _, cleanup := setupTestTrackService(t)
defer cleanup()
// Test case: valid MP3 (mock content with ID3 header)
mp3Content := append([]byte("ID3"), make([]byte, 100)...)
header := createMultipartFileHeader(t, "test.mp3", mp3Content, "audio/mpeg")
err := service.ValidateTrackFile(header)
assert.NoError(t, err)
// Test case: valid WAV (mock content with RIFF/WAVE header)
wavContent := append([]byte("RIFF"), make([]byte, 4)...)
wavContent = append(wavContent, []byte("WAVE")...)
wavContent = append(wavContent, make([]byte, 100)...)
headerWav := createMultipartFileHeader(t, "test.wav", wavContent, "audio/wav")
err = service.ValidateTrackFile(headerWav)
assert.NoError(t, err)
// Test case: invalid extension
headerInvalid := createMultipartFileHeader(t, "test.txt", []byte("some text"), "text/plain")
err = service.ValidateTrackFile(headerInvalid)
assert.ErrorIs(t, err, ErrInvalidTrackFormat)
// Test case: file too large (manually set size to mock large file without large content)
headerTooLarge := createMultipartFileHeader(t, "large.mp3", mp3Content, "audio/mpeg")
headerTooLarge.Size = 500 * 1024 * 1024 // 500MB
err = service.ValidateTrackFile(headerTooLarge)
assert.ErrorIs(t, err, ErrTrackTooLarge)
}
func TestTrackService_CheckUserQuota(t *testing.T) {
service, db, cleanup := setupTestTrackService(t)
defer cleanup()
// Create a user
userID := uuid.New()
user := &models.User{
ID: userID,
Username: "quotatest",
Email: "quota@example.com",
}
db.Create(user)
ctx := context.Background()
// Test: Empty user checks OK
err := service.CheckUserQuota(ctx, userID, 1024*1024)
assert.NoError(t, err)
// Create a track consuming storage
track := &models.Track{
ID: uuid.New(),
UserID: userID,
Title: "Big Track",
FileSize: MaxStoragePerUser - 100, // Almost full
Status: models.TrackStatusCompleted,
}
db.Create(track)
// Now try to upload something bigger than remaining
err = service.CheckUserQuota(ctx, userID, 200)
assert.ErrorIs(t, err, ErrStorageQuotaExceeded)
}
func TestTrackService_GetUserQuota(t *testing.T) {
service, db, cleanup := setupTestTrackService(t)
defer cleanup()
userID := uuid.New()
user := &models.User{ID: userID, Username: "quotauser", Email: "qu@example.com"}
db.Create(user)
// Add 2 tracks
db.Create(&models.Track{ID: uuid.New(), UserID: userID, FileSize: 1000, Status: models.TrackStatusCompleted})
db.Create(&models.Track{ID: uuid.New(), UserID: userID, FileSize: 2000, Status: models.TrackStatusCompleted})
ctx := context.Background()
quota, err := service.GetUserQuota(ctx, userID)
assert.NoError(t, err)
assert.NotNil(t, quota)
assert.Equal(t, int64(2), quota.TracksCount)
assert.Equal(t, int64(3000), quota.StorageUsed)
assert.Equal(t, int64(MaxTracksPerUser), quota.TracksLimit)
}
func TestTrackService_ListTracks(t *testing.T) {
service, db, cleanup := setupTestTrackService(t)
defer cleanup()
userID := uuid.New()
user := &models.User{ID: userID, Username: "listuser", Email: "list@example.com"}
db.Create(user)
// Create tracks
for i := 0; i < 5; i++ {
db.Create(&models.Track{
ID: uuid.New(),
UserID: userID,
Title: "Track " + string(rune('A'+i)),
Format: "mp3",
IsPublic: true,
Status: models.TrackStatusCompleted,
CreatedAt: time.Now().Add(time.Duration(i) * time.Minute),
})
}
// Private track
db.Create(&models.Track{
ID: uuid.New(),
UserID: userID,
Title: "Private Track",
Format: "wav",
IsPublic: false,
Status: models.TrackStatusCompleted,
})
ctx := context.Background()
// Test: List all
params := TrackListParams{
UserID: &userID,
Page: 1,
Limit: 10,
}
tracks, total, err := service.ListTracks(ctx, params)
assert.NoError(t, err)
assert.Equal(t, int64(6), total)
assert.Len(t, tracks, 6)
// Test: Filter by format
fmtMp3 := "mp3"
params.Format = &fmtMp3
tracks, total, err = service.ListTracks(ctx, params)
assert.NoError(t, err)
assert.Equal(t, int64(5), total)
}
func TestTrackService_CreateTrackFromPath_Success(t *testing.T) {
service, db, cleanup := setupTestTrackService(t)
defer cleanup()
userID := uuid.New()
user := &models.User{ID: userID, Username: "pathuser", Email: "path@example.com"}
db.Create(user)
ctx := context.Background()
filePath := "/tmp/some/file.mp3"
filename := "file.mp3"
fileSize := int64(12345)
format := "mp3"
track, err := service.CreateTrackFromPath(ctx, userID, filePath, filename, fileSize, format)
assert.NoError(t, err)
assert.NotNil(t, track)
assert.Equal(t, models.TrackStatusUploading, track.Status)
assert.Equal(t, filePath, track.FilePath)
// Verify in DB
var dbTrack models.Track
err = db.First(&dbTrack, "id = ?", track.ID).Error
assert.NoError(t, err)
assert.Equal(t, fileSize, dbTrack.FileSize)
}
func TestTrackService_UpdateStreamStatus(t *testing.T) {
service, db, cleanup := setupTestTrackService(t)
defer cleanup()
userID := uuid.New()
db.Create(&models.User{ID: userID})
trackID := uuid.New()
db.Create(&models.Track{ID: trackID, UserID: userID, Status: models.TrackStatusProcessing})
ctx := context.Background()
err := service.UpdateStreamStatus(ctx, trackID, "ready", "http://manifest.url")
assert.NoError(t, err)
var track models.Track
db.First(&track, "id = ?", trackID)
assert.Equal(t, models.TrackStatusCompleted, track.Status)
assert.Equal(t, "ready", track.StreamStatus)
assert.Equal(t, "http://manifest.url", track.StreamManifestURL)
// Test error status
err = service.UpdateStreamStatus(ctx, trackID, "error", "")
assert.NoError(t, err)
db.First(&track, "id = ?", trackID)
assert.Equal(t, models.TrackStatusFailed, track.Status)
assert.Equal(t, "error", track.StreamStatus)
}
func TestTrackService_BatchOperations(t *testing.T) {
service, db, cleanup := setupTestTrackService(t)
defer cleanup()
userID := uuid.New()
db.Create(&models.User{ID: userID})
ids := []uuid.UUID{uuid.New(), uuid.New(), uuid.New()}
for _, id := range ids {
db.Create(&models.Track{ID: id, UserID: userID, Title: "Original", Status: models.TrackStatusCompleted})
}
ctx := context.Background()
// Batch Update
updates := map[string]interface{}{
"title": "Batch Updated",
}
result, err := service.BatchUpdateTracks(ctx, ids, userID, updates)
assert.NoError(t, err)
assert.Equal(t, 3, len(result.Updated))
var tracks []models.Track
db.Find(&tracks, "id IN ?", ids)
for _, tr := range tracks {
assert.Equal(t, "Batch Updated", tr.Title)
}
// Batch Delete
deleteResult, err := service.BatchDeleteTracks(ctx, ids, userID)
assert.NoError(t, err)
assert.Equal(t, 3, len(deleteResult.Deleted))
var count int64
db.Model(&models.Track{}).Where("id IN ?", ids).Count(&count)
assert.Equal(t, int64(0), count)
}
func TestTrackService_GetTrackByID(t *testing.T) {
service, db, cleanup := setupTestTrackService(t)
defer cleanup()
userID := uuid.New()
db.Create(&models.User{ID: userID, Username: "getuser", Email: "get@example.com"})
trackID := uuid.New()
// Pre-create track
track := &models.Track{
ID: trackID,
UserID: userID,
Title: "Test Track",
Status: models.TrackStatusCompleted,
IsPublic: true,
}
db.Create(track)
ctx := context.Background()
// Test: Success
found, err := service.GetTrackByID(ctx, trackID)
assert.NoError(t, err)
assert.Equal(t, trackID, found.ID)
assert.Equal(t, "Test Track", found.Title)
// Test: NotFound
_, err = service.GetTrackByID(ctx, uuid.New())
assert.ErrorIs(t, err, ErrTrackNotFound)
}
func TestTrackService_UpdateTrack(t *testing.T) {
service, db, cleanup := setupTestTrackService(t)
defer cleanup()
ownerID := uuid.New()
otherID := uuid.New()
db.Create(&models.User{ID: ownerID, Username: "owner", Email: "owner@example.com"})
db.Create(&models.User{ID: otherID, Username: "other", Email: "other@example.com"})
trackID := uuid.New()
db.Create(&models.Track{
ID: trackID,
UserID: ownerID,
Title: "Original Title",
Genre: "Pop",
IsPublic: true,
})
ctx := context.Background()
// Test: Update Success (Owner)
newTitle := "Updated Title"
newGenre := "Rock"
isPublic := false
params := UpdateTrackParams{
Title: &newTitle,
Genre: &newGenre,
IsPublic: &isPublic,
}
updated, err := service.UpdateTrack(ctx, trackID, ownerID, params)
assert.NoError(t, err)
assert.Equal(t, "Updated Title", updated.Title)
assert.Equal(t, "Rock", updated.Genre)
assert.False(t, updated.IsPublic)
// Test: Forbidden (Other User)
params2 := UpdateTrackParams{Title: &newTitle}
_, err = service.UpdateTrack(ctx, trackID, otherID, params2)
assert.ErrorIs(t, err, ErrForbidden)
// Test: Admin Override
// (Assuming context key "is_admin" works as implemented in service)
adminCtx := context.WithValue(ctx, "is_admin", true)
adminTitle := "Admin Title"
params3 := UpdateTrackParams{Title: &adminTitle}
updatedAdmin, err := service.UpdateTrack(adminCtx, trackID, otherID, params3)
assert.NoError(t, err)
assert.Equal(t, "Admin Title", updatedAdmin.Title)
}
func TestTrackService_DeleteTrack(t *testing.T) {
service, db, cleanup := setupTestTrackService(t)
defer cleanup()
ownerID := uuid.New()
otherID := uuid.New()
db.Create(&models.User{ID: ownerID})
db.Create(&models.User{ID: otherID})
// Create file for deletion test
tmpFile, err := os.CreateTemp(service.uploadDir, "track_*.mp3")
require.NoError(t, err)
tmpFile.Close()
filePath := tmpFile.Name()
trackID := uuid.New()
db.Create(&models.Track{
ID: trackID,
UserID: ownerID,
FilePath: filePath,
})
ctx := context.Background()
// Test: Forbidden
err = service.DeleteTrack(ctx, trackID, otherID)
assert.ErrorIs(t, err, ErrForbidden)
// Check file still exists
_, err = os.Stat(filePath)
assert.NoError(t, err)
// Test: Success
err = service.DeleteTrack(ctx, trackID, ownerID)
assert.NoError(t, err)
// Verify DB deletion
var count int64
db.Model(&models.Track{}).Where("id = ?", trackID).Count(&count)
assert.Equal(t, int64(0), count)
// Verify File deletion
_, err = os.Stat(filePath)
assert.True(t, os.IsNotExist(err))
}
func TestTrackService_UploadTrack_Basic(t *testing.T) {
service, db, cleanup := setupTestTrackService(t)
defer cleanup()
userID := uuid.New()
db.Create(&models.User{ID: userID})
ctx := context.Background()
// Mock file header
content := []byte{0xFF, 0xFB, 0x00, 0x00} // Fake MP3 frame header
header := createMultipartFileHeader(t, "upload.mp3", content, "audio/mpeg")
metadata := TrackMetadata{
Title: "Uploaded Track",
IsPublic: true,
}
// Test Upload
track, err := service.UploadTrack(ctx, userID, header, metadata)
assert.NoError(t, err)
assert.NotNil(t, track)
assert.Equal(t, "Uploaded Track", track.Title)
assert.Equal(t, models.TrackStatusUploading, track.Status)
assert.NotEmpty(t, track.FilePath)
// Verify DB
var dbTrack models.Track
db.First(&dbTrack, "id = ?", track.ID)
assert.Equal(t, "Uploaded Track", dbTrack.Title)
// Wait for async processing to finish to avoid "Log in goroutine after Test has completed"
assert.Eventually(t, func() bool {
db.First(&dbTrack, "id = ?", track.ID)
return dbTrack.Status != models.TrackStatusUploading
}, 2*time.Second, 100*time.Millisecond)
}