package handlers import ( "bytes" "context" "encoding/json" "errors" "mime/multipart" "net/http" "net/http/httptest" "testing" "veza-backend-api/internal/services" "veza-backend-api/internal/upload" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "go.uber.org/zap" ) // MockUploadValidator implements UploadValidatorInterface type MockUploadValidator struct { mock.Mock } func (m *MockUploadValidator) ValidateFile(ctx context.Context, fileHeader *multipart.FileHeader, fileType string) (*services.ValidationResult, error) { args := m.Called(ctx, fileHeader, fileType) if args.Get(0) == nil { return nil, args.Error(1) } return args.Get(0).(*services.ValidationResult), args.Error(1) } func (m *MockUploadValidator) GetFileTypeFromPath(filename string) string { args := m.Called(filename) return args.String(0) } // MockUploadAuditService implements UploadAuditServiceInterface type MockUploadAuditService struct { mock.Mock } func (m *MockUploadAuditService) LogUpload(ctx context.Context, userID uuid.UUID, resourceID uuid.UUID, fileName string, fileSize int64, ipAddress, userAgent string) error { args := m.Called(ctx, userID, resourceID, fileName, fileSize, ipAddress, userAgent) return args.Error(0) } func (m *MockUploadAuditService) LogDeletion(ctx context.Context, userID uuid.UUID, resource string, resourceID uuid.UUID, ipAddress, userAgent string) error { args := m.Called(ctx, userID, resource, resourceID, ipAddress, userAgent) return args.Error(0) } // MockTrackUploadService implements TrackUploadServiceInterface type MockTrackUploadService struct { mock.Mock } func (m *MockTrackUploadService) GetUploadStats(ctx context.Context, userID uuid.UUID) (map[string]interface{}, error) { args := m.Called(ctx, userID) if args.Get(0) == nil { return nil, args.Error(1) } return args.Get(0).(map[string]interface{}), args.Error(1) } // Setup helper func setupTestUploadHandler(t *testing.T) (*UploadHandler, *MockUploadValidator, *MockUploadAuditService, *MockTrackUploadService) { mockValidator := new(MockUploadValidator) mockAudit := new(MockUploadAuditService) mockTrackUpload := new(MockTrackUploadService) logger := zap.NewNop() handler := NewUploadHandlerWithInterface(mockValidator, mockAudit, mockTrackUpload, logger, 10) return handler, mockValidator, mockAudit, mockTrackUpload } func TestUploadFile_Success(t *testing.T) { handler, mockValidator, mockAudit, _ := setupTestUploadHandler(t) gin.SetMode(gin.TestMode) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) // Create multipart request body := new(bytes.Buffer) writer := multipart.NewWriter(body) part, _ := writer.CreateFormFile("file", "test.mp3") part.Write([]byte("dummy content")) writer.WriteField("track_id", uuid.New().String()) writer.WriteField("file_type", "audio") writer.WriteField("title", "Test Title") writer.WriteField("artist", "Test Artist") writer.WriteField("duration", "120") writer.Close() req, _ := http.NewRequest("POST", "/upload", body) req.Header.Set("Content-Type", writer.FormDataContentType()) c.Request = req userID := uuid.New() c.Set("user_id", userID) // Mocks expectedResult := &services.ValidationResult{ Valid: true, FileType: "audio", FileSize: 100, Checksum: "abc", } mockValidator.On("ValidateFile", mock.Anything, mock.Anything, "audio").Return(expectedResult, nil) mockAudit.On("LogUpload", mock.Anything, userID, mock.Anything, "test.mp3", int64(100), mock.Anything, mock.Anything).Return(nil) // Execute handler.UploadFile()(c) // Assert if w.Code != http.StatusCreated { t.Logf("Response Body: %s", w.Body.String()) } assert.Equal(t, http.StatusCreated, w.Code) var respWrapper struct { Success bool `json:"success"` Data upload.StandardUploadResponse `json:"data"` } err := json.Unmarshal(w.Body.Bytes(), &respWrapper) assert.NoError(t, err) assert.True(t, respWrapper.Success) assert.Equal(t, "test.mp3", respWrapper.Data.FileName) assert.Equal(t, int64(100), respWrapper.Data.FileSize) mockValidator.AssertExpectations(t) mockAudit.AssertExpectations(t) } func TestUploadFile_ValidationFailed(t *testing.T) { handler, mockValidator, _, _ := setupTestUploadHandler(t) gin.SetMode(gin.TestMode) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) // Create multipart request body := new(bytes.Buffer) writer := multipart.NewWriter(body) part, _ := writer.CreateFormFile("file", "test.mp3") part.Write([]byte("dummy content")) writer.WriteField("track_id", uuid.New().String()) writer.WriteField("file_type", "audio") writer.WriteField("title", "Test Title") writer.WriteField("artist", "Test Artist") writer.Close() req, _ := http.NewRequest("POST", "/upload", body) req.Header.Set("Content-Type", writer.FormDataContentType()) c.Request = req userID := uuid.New() c.Set("user_id", userID) // Mocks expectedResult := &services.ValidationResult{ Valid: false, Error: "Invalid format", } mockValidator.On("ValidateFile", mock.Anything, mock.Anything, "audio").Return(expectedResult, nil) // Execute handler.UploadFile()(c) // Assert assert.Equal(t, http.StatusBadRequest, w.Code) // AppError converts ErrCodeValidation to 400 } func TestUploadFile_ClamAVUnavailable(t *testing.T) { handler, mockValidator, _, _ := setupTestUploadHandler(t) gin.SetMode(gin.TestMode) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) // Create multipart request body := new(bytes.Buffer) writer := multipart.NewWriter(body) part, _ := writer.CreateFormFile("file", "test.mp3") part.Write([]byte("dummy content")) writer.WriteField("track_id", uuid.New().String()) writer.WriteField("file_type", "audio") writer.WriteField("title", "Test Title") writer.WriteField("artist", "Test Artist") writer.Close() req, _ := http.NewRequest("POST", "/upload", body) req.Header.Set("Content-Type", writer.FormDataContentType()) c.Request = req userID := uuid.New() c.Set("user_id", userID) // Mocks mockValidator.On("ValidateFile", mock.Anything, mock.Anything, "audio").Return(nil, errors.New("clamav_unavailable")) // Execute handler.UploadFile()(c) // Assert assert.Equal(t, http.StatusServiceUnavailable, w.Code) } func TestUploadFile_VirusDetected(t *testing.T) { handler, mockValidator, _, _ := setupTestUploadHandler(t) gin.SetMode(gin.TestMode) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) // Create multipart request body := new(bytes.Buffer) writer := multipart.NewWriter(body) part, _ := writer.CreateFormFile("file", "virus.mp3") part.Write([]byte("virus signature")) writer.WriteField("track_id", uuid.New().String()) writer.WriteField("file_type", "audio") writer.WriteField("title", "Test Title") writer.WriteField("artist", "Test Artist") writer.Close() req, _ := http.NewRequest("POST", "/upload", body) req.Header.Set("Content-Type", writer.FormDataContentType()) c.Request = req userID := uuid.New() c.Set("user_id", userID) // Mocks expectedResult := &services.ValidationResult{ Valid: false, Quarantined: true, Error: "Virus detected", } mockValidator.On("ValidateFile", mock.Anything, mock.Anything, "audio").Return(expectedResult, nil) // Execute handler.UploadFile()(c) // Assert assert.Equal(t, http.StatusUnprocessableEntity, w.Code) } func TestDeleteUpload_Success(t *testing.T) { handler, _, mockAudit, _ := setupTestUploadHandler(t) gin.SetMode(gin.TestMode) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) uploadID := uuid.New() c.Params = []gin.Param{{Key: "id", Value: uploadID.String()}} userID := uuid.New() c.Set("user_id", userID) req, _ := http.NewRequest("DELETE", "/upload/"+uploadID.String(), nil) c.Request = req // Mocks mockAudit.On("LogDeletion", mock.Anything, userID, "upload", uploadID, mock.Anything, mock.Anything).Return(nil) // Execute handler.DeleteUpload()(c) // Assert assert.Equal(t, http.StatusOK, w.Code) mockAudit.AssertExpectations(t) } func TestGetUploadStats_Success(t *testing.T) { handler, _, _, mockTrackUpload := setupTestUploadHandler(t) gin.SetMode(gin.TestMode) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) userID := uuid.New() c.Set("user_id", userID) // GetUserIDUUID looks for "user_id" in context which is UUID req, _ := http.NewRequest("GET", "/stats", nil) c.Request = req // Mocks stats := map[string]interface{}{"total_uploads": int64(5)} mockTrackUpload.On("GetUploadStats", mock.Anything, userID).Return(stats, nil) // Execute handler.GetUploadStats()(c) // Assert assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) assert.Equal(t, float64(5), resp["stats"].(map[string]interface{})["total_uploads"]) }