- CI: workflows updates (cd, ci), remove playwright.yml - E2E: global-setup, auth/playlists/profile specs - Remove playwright-report and test-results artifacts from tracking - Backend: auth, handlers, services, workers, migrations - Frontend: components, features, vite config - Add e2e-results.json to gitignore - Docs: REMEDIATION_PROGRESS, audit archive - Rust: chat-server, stream-server updates
220 lines
6.3 KiB
Go
220 lines
6.3 KiB
Go
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)
|
|
}
|