package services import ( "context" "fmt" "testing" "time" "github.com/google/uuid" "github.com/stretchr/testify/assert" "go.uber.org/zap" ) // MockStore for testing type MockStore struct { data map[string]*ChunkUploadInfo } func NewMockStore() *MockStore { return &MockStore{data: make(map[string]*ChunkUploadInfo)} } func (m *MockStore) SetState(ctx context.Context, info *ChunkUploadInfo) error { m.data[info.UploadID] = info return nil } func (m *MockStore) GetState(ctx context.Context, uploadID string) (*ChunkUploadInfo, error) { info, ok := m.data[uploadID] if !ok { return nil, fmt.Errorf("upload not found") } return info, nil } func (m *MockStore) DeleteState(ctx context.Context, uploadID string) error { delete(m.data, uploadID) return nil } func setupTestTrackChunkServiceForResume(_ *testing.T) (*TrackChunkService, func()) { logger := zap.NewNop() chunksDir := "test_uploads/tracks/chunks" // Create service with MockStore service := &TrackChunkService{ chunksDir: chunksDir, store: NewMockStore(), logger: logger, // other fields defaults } cleanup := func() { // Cleanup will be handled by the service // os.RemoveAll(chunksDir) if needed } return service, cleanup } func TestTrackChunkService_GetUploadState_Success(t *testing.T) { service, cleanup := setupTestTrackChunkServiceForResume(t) defer cleanup() // Initialiser un upload userID := uuid.New() uploadID, err := service.InitiateChunkedUpload(userID, 5, 1024*1024*50, "test.mp3") assert.NoError(t, err) assert.NotEmpty(t, uploadID) // Récupérer l'état initial (aucun chunk reçu) state, err := service.GetUploadState(uploadID) assert.NoError(t, err) assert.NotNil(t, state) assert.Equal(t, uploadID, state.UploadID) assert.Equal(t, userID, state.UserID) assert.Equal(t, 5, state.TotalChunks) assert.Equal(t, int64(1024*1024*50), state.TotalSize) assert.Equal(t, "test.mp3", state.Filename) assert.Empty(t, state.ChunksReceived) assert.Equal(t, 0, state.LastChunk) assert.Equal(t, 0, state.ReceivedCount) assert.Equal(t, 0, state.Progress) } func TestTrackChunkService_GetUploadState_NotFound(t *testing.T) { service, cleanup := setupTestTrackChunkServiceForResume(t) defer cleanup() // Essayer de récupérer l'état d'un upload inexistant state, err := service.GetUploadState("non-existent-upload-id") assert.Error(t, err) assert.Nil(t, state) assert.Contains(t, err.Error(), "upload not found") } func TestTrackChunkService_GetUploadState_WithChunks(t *testing.T) { service, cleanup := setupTestTrackChunkServiceForResume(t) defer cleanup() // Initialiser un upload userID := uuid.New() uploadID, err := service.InitiateChunkedUpload(userID, 5, 1024*1024*50, "test.mp3") assert.NoError(t, err) // Simuler l'ajout de quelques chunks via l'interface // Note: In real Redis implementation we can't lock keys like this. // For testing, we update the MockStore directly. mockStore := service.store.(*MockStore) uploadInfo := mockStore.data[uploadID] assert.NotNil(t, uploadInfo) // Modifying the struct directly for test setup // SHA256 checksums (64 hex chars) - SEC-007 uploadInfo.Chunks[1] = ChunkInfo{ ChunkNumber: 1, Size: 1024 * 1024 * 10, MD5: "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456", FilePath: "test/chunk_1", Received: true, } uploadInfo.Chunks[2] = ChunkInfo{ ChunkNumber: 2, Size: 1024 * 1024 * 10, MD5: "b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef12345678", FilePath: "test/chunk_2", Received: true, } uploadInfo.Chunks[4] = ChunkInfo{ ChunkNumber: 4, Size: 1024 * 1024 * 10, MD5: "c3d4e5f6789012345678901234567890abcdef1234567890abcdef1234567890", FilePath: "test/chunk_4", Received: true, } // Update store manually mockStore.data[uploadID] = uploadInfo // Récupérer l'état state, err := service.GetUploadState(uploadID) assert.NoError(t, err) assert.NotNil(t, state) // Vérifier les chunks reçus assert.Equal(t, 3, state.ReceivedCount) assert.Equal(t, 4, state.LastChunk) // Le dernier chunk reçu est le 4 assert.Equal(t, 60, state.Progress) // 3/5 = 60% assert.Contains(t, state.ChunksReceived, 1) assert.Contains(t, state.ChunksReceived, 2) assert.Contains(t, state.ChunksReceived, 4) assert.NotContains(t, state.ChunksReceived, 3) assert.NotContains(t, state.ChunksReceived, 5) } func TestTrackChunkService_GetUploadState_Complete(t *testing.T) { service, cleanup := setupTestTrackChunkServiceForResume(t) defer cleanup() // Initialiser un upload userID := uuid.New() uploadID, err := service.InitiateChunkedUpload(userID, 3, 1024*1024*30, "complete.mp3") assert.NoError(t, err) // Simuler tous les chunks reçus mockStore := service.store.(*MockStore) uploadInfo := mockStore.data[uploadID] assert.NotNil(t, uploadInfo) chunkChecksum := "d4e5f6789012345678901234567890abcdef1234567890abcdef1234567890ab" // SHA256 format for i := 1; i <= 3; i++ { uploadInfo.Chunks[i] = ChunkInfo{ ChunkNumber: i, Size: 1024 * 1024 * 10, MD5: chunkChecksum, FilePath: "test/chunk_" + string(rune(i)), Received: true, } } uploadInfo.UpdatedAt = time.Now() // Update store manually mockStore.data[uploadID] = uploadInfo // Récupérer l'état state, err := service.GetUploadState(uploadID) assert.NoError(t, err) assert.NotNil(t, state) assert.Equal(t, 3, state.ReceivedCount) assert.Equal(t, 3, state.LastChunk) assert.Equal(t, 100, state.Progress) assert.Equal(t, 3, len(state.ChunksReceived)) } func TestTrackChunkService_GetUploadState_MultipleUsers(t *testing.T) { service, cleanup := setupTestTrackChunkServiceForResume(t) defer cleanup() // Créer deux uploads pour deux utilisateurs différents userID1 := uuid.New() uploadID1, err := service.InitiateChunkedUpload(userID1, 5, 1024*1024*50, "user1.mp3") assert.NoError(t, err) userID2 := uuid.New() uploadID2, err := service.InitiateChunkedUpload(userID2, 3, 1024*1024*30, "user2.mp3") assert.NoError(t, err) // Récupérer les états state1, err := service.GetUploadState(uploadID1) assert.NoError(t, err) assert.Equal(t, userID1, state1.UserID) state2, err := service.GetUploadState(uploadID2) assert.NoError(t, err) assert.Equal(t, userID2, state2.UserID) // Vérifier que les états sont isolés assert.NotEqual(t, state1.UploadID, state2.UploadID) }