import pytest import zipfile import io from unittest.mock import MagicMock, patch from src.core.batch_processor import BatchProcessor from src.core.workout_data import WorkoutData, WorkoutMetadata from datetime import datetime, timedelta import pandas as pd @pytest.fixture def mock_workout_data(): metadata = WorkoutMetadata( start_time=datetime(2025, 1, 1, 10, 0, 0), duration=timedelta(minutes=10), device="Garmin", file_type="FIT" ) time_series_data = pd.DataFrame({ "power": [100, 110, 120], "heart_rate": [150, 155, 160] }) return WorkoutData(metadata=metadata, time_series_data=time_series_data) @pytest.fixture def mock_file_parser(): parser = MagicMock() parser.parse.return_value = MagicMock(spec=WorkoutData) return parser @pytest.fixture def mock_workout_analyzer(): analyzer = MagicMock() analyzer.calculate_summary_metrics.return_value = {"avg_power": 100} return analyzer @pytest.fixture def mock_report_generator(): generator = MagicMock() generator.generate_html_report.return_value = "report" return generator @pytest.fixture def mock_db_session(): session = MagicMock() return session def create_zip_file(file_names_and_content): zip_buffer = io.BytesIO() with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zf: for name, content in file_names_and_content.items(): zf.writestr(name, content) zip_buffer.seek(0) return zip_buffer def test_batch_processor_initialization(mock_db_session): processor = BatchProcessor(db_session=mock_db_session) assert processor.db_session == mock_db_session @patch('src.core.file_parser.FitParser') @patch('src.core.file_parser.TcxParser') @patch('src.core.file_parser.GpxParser') @patch('src.core.workout_analyzer.WorkoutAnalyzer') @patch('src.core.report_generator.ReportGenerator') def test_process_zip_file_single_fit(mock_report_generator_cls, mock_workout_analyzer_cls, mock_gpx_parser_cls, mock_tcx_parser_cls, mock_fit_parser_cls, mock_db_session, mock_workout_data): # Mock parsers to return mock_workout_data mock_fit_parser_cls.return_value.parse.return_value = mock_workout_data mock_workout_analyzer_cls.return_value.calculate_summary_metrics.return_value = {"avg_power": 100} mock_report_generator_cls.return_value.generate_html_report.return_value = "report" zip_content = create_zip_file({"workout.fit": b"dummy_fit_content"}) processor = BatchProcessor(db_session=mock_db_session) results = processor.process_zip_file(zip_content, user_id=None, ftp_value=None) assert len(results) == 1 assert results[0]["file_name"] == "workout.fit" assert results[0]["status"] == "completed" mock_fit_parser_cls.return_value.parse.assert_called_once() mock_workout_analyzer_cls.assert_called_once() mock_db_session.add.assert_called_once() mock_db_session.commit.assert_called_once() @patch('src.core.file_parser.FitParser') @patch('src.core.file_parser.TcxParser') @patch('src.core.file_parser.GpxParser') @patch('src.core.workout_analyzer.WorkoutAnalyzer') @patch('src.core.report_generator.ReportGenerator') def test_process_zip_file_multiple_files(mock_report_generator_cls, mock_workout_analyzer_cls, mock_gpx_parser_cls, mock_tcx_parser_cls, mock_fit_parser_cls, mock_db_session, mock_workout_data): mock_fit_parser_cls.return_value.parse.return_value = mock_workout_data mock_tcx_parser_cls.return_value.parse.return_value = mock_workout_data mock_workout_analyzer_cls.return_value.calculate_summary_metrics.return_value = {"avg_power": 100} mock_report_generator_cls.return_value.generate_html_report.return_value = "report" zip_content = create_zip_file({"workout1.fit": b"dummy_fit_content", "workout2.tcx": b"dummy_tcx_content"}) processor = BatchProcessor(db_session=mock_db_session) results = processor.process_zip_file(zip_content, user_id=None, ftp_value=None) assert len(results) == 2 assert any(r["file_name"] == "workout1.fit" for r in results) assert any(r["file_name"] == "workout2.tcx" for r in results) assert all(r["status"] == "completed" for r in results) assert mock_fit_parser_cls.return_value.parse.call_count == 1 assert mock_tcx_parser_cls.return_value.parse.call_count == 1 assert mock_workout_analyzer_cls.call_count == 2 assert mock_db_session.add.call_count == 2 assert mock_db_session.commit.call_count == 2 @patch('src.core.file_parser.FitParser') @patch('src.core.workout_analyzer.WorkoutAnalyzer') def test_process_zip_file_unsupported_file_type(mock_workout_analyzer_cls, mock_fit_parser_cls, mock_db_session): zip_content = create_zip_file({"document.txt": b"some text"}) processor = BatchProcessor(db_session=mock_db_session) results = processor.process_zip_file(zip_content, user_id=None, ftp_value=None) assert len(results) == 1 assert results[0]["file_name"] == "document.txt" assert results[0]["status"] == "failed" assert "Unsupported file type" in results[0]["error_message"] mock_fit_parser_cls.return_value.parse.assert_not_called() mock_workout_analyzer_cls.assert_not_called() mock_db_session.add.assert_not_called() mock_db_session.commit.assert_not_called() @patch('src.core.file_parser.FitParser') @patch('src.core.workout_analyzer.WorkoutAnalyzer') def test_process_zip_file_parsing_error(mock_workout_analyzer_cls, mock_fit_parser_cls, mock_db_session): mock_fit_parser_cls.return_value.parse.side_effect = Exception("Corrupted file") zip_content = create_zip_file({"corrupted.fit": b"bad content"}) processor = BatchProcessor(db_session=mock_db_session) results = processor.process_zip_file(zip_content, user_id=None, ftp_value=None) assert len(results) == 1 assert results[0]["file_name"] == "corrupted.fit" assert results[0]["status"] == "failed" assert "Corrupted file" in results[0]["error_message"] mock_fit_parser_cls.return_value.parse.assert_called_once() mock_workout_analyzer_cls.assert_not_called() mock_db_session.add.assert_not_called() mock_db_session.commit.assert_not_called()